From b04d1b7d4a581e1b3d59653f98a624ebc77fca9b Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Wed, 21 Oct 2020 15:36:07 -0700 Subject: [PATCH] Intel AMT configuration now automated thru agent. --- agents/meshcore-bad.js | 3924 ++++++++++++++++++++++++++++++++++++++ agents/meshcore-old.js | 3903 +++++++++++++++++++++++++++++++++++++ agents/meshcore.js | 80 +- amtmanager.js | 68 +- meshagent.js | 44 +- translate/translate.json | 1485 +++++++-------- views/default.handlebars | 2 +- 7 files changed, 8652 insertions(+), 854 deletions(-) create mode 100644 agents/meshcore-bad.js create mode 100644 agents/meshcore-old.js diff --git a/agents/meshcore-bad.js b/agents/meshcore-bad.js new file mode 100644 index 00000000..e7907d0b --- /dev/null +++ b/agents/meshcore-bad.js @@ -0,0 +1,3924 @@ +/* +Copyright 2018-2020 Intel Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +process.on('uncaughtException', function (ex) { + require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: "uncaughtException1: " + ex }); +}); + +// NOTE: This seems to cause big problems, don't enable the debugger in the server's meshcore. +//attachDebugger({ webport: 9999, wait: 1 }).then(function (prt) { console.log('Point Browser for Debug to port: ' + prt); }); + +// Mesh Rights +var MNG_ERROR = 65; +var MESHRIGHT_EDITMESH = 1; +var MESHRIGHT_MANAGEUSERS = 2; +var MESHRIGHT_MANAGECOMPUTERS = 4; +var MESHRIGHT_REMOTECONTROL = 8; +var MESHRIGHT_AGENTCONSOLE = 16; +var MESHRIGHT_SERVERFILES = 32; +var MESHRIGHT_WAKEDEVICE = 64; +var MESHRIGHT_SETNOTES = 128; +var MESHRIGHT_REMOTEVIEW = 256; +var MESHRIGHT_NOTERMINAL = 512; +var MESHRIGHT_NOFILES = 1024; +var MESHRIGHT_NOAMT = 2048; +var MESHRIGHT_LIMITEDINPUT = 4096; +var MESHRIGHT_LIMITEVENTS = 8192; +var MESHRIGHT_CHATNOTIFY = 16384; +var MESHRIGHT_UNINSTALL = 32768; +var MESHRIGHT_NODESKTOP = 65536; + +function createMeshCore(agent) { + var obj = {}; + if (process.platform == 'win32' && require('user-sessions').isRoot()) { + // Check the Agent Uninstall MetaData for correctness, as the installer may have written an incorrect value + try { + var writtenSize = 0, actualSize = Math.floor(require('fs').statSync(process.execPath).size / 1024); + try { writtenSize = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MeshCentralAgent', 'EstimatedSize'); } catch (e) { } + if (writtenSize != actualSize) { try { require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MeshCentralAgent', 'EstimatedSize', actualSize); } catch (e) { } } + } catch (x) { } + + // Check to see if we are the Installed Mesh Agent Service, if we are, make sure we can run in Safe Mode + try { + var meshCheck = false; + try { meshCheck = require('service-manager').manager.getService('Mesh Agent').isMe(); } catch (e) { } + if (meshCheck && require('win-bcd').isSafeModeService && !require('win-bcd').isSafeModeService('Mesh Agent')) { require('win-bcd').enableSafeModeService('Mesh Agent'); } + } catch (e) { } + } + + if (process.platform == 'darwin' && !process.versions) { + // This is an older MacOS Agent, so we'll need to check the service definition so that Auto-Update will function correctly + var child = require('child_process').execFile('/bin/sh', ['sh']); + child.stdout.str = ''; + child.stdout.on('data', function (chunk) { this.str += chunk.toString(); }); + child.stdin.write("cat /Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist | tr '\n' '\.' | awk '{split($0, a, \"KeepAlive\"); split(a[2], b, \"<\"); split(b[2], c, \">\"); "); + child.stdin.write(" if(c[1]==\"dict\"){ split(a[2], d, \"\"); if(split(d[1], truval, \"\")>1) { split(truval[1], kn1, \"\"); split(kn1[2], kn2, \"\"); print kn2[1]; } }"); + child.stdin.write(" else { split(c[1], ka, \"/\"); if(ka[1]==\"true\") {print \"ALWAYS\";} } }'\nexit\n"); + child.waitExit(); + if (child.stdout.str.trim() == 'Crashed') { + child = require('child_process').execFile('/bin/sh', ['sh']); + child.stdout.str = ''; + child.stdout.on('data', function (chunk) { this.str += chunk.toString(); }); + child.stdin.write("launchctl list | grep 'meshagent' | awk '{ if($3==\"meshagent\"){print $1;}}'\nexit\n"); + child.waitExit(); + + if (parseInt(child.stdout.str.trim()) == process.pid) { + // The currently running MeshAgent is us, so we can continue with the update + var plist = require('fs').readFileSync('/Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist').toString(); + var tokens = plist.split('KeepAlive'); + if (tokens[1].split('>')[0].split('<')[1] == 'dict') { + var tmp = tokens[1].split(''); + tmp.shift(); + tokens[1] = '\n ' + tmp.join(''); + tokens = tokens.join('KeepAlive'); + + require('fs').writeFileSync('/Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist', tokens); + + var fix = ''; + fix += ("function macosRepair()\n"); + fix += ("{\n"); + fix += (" var child = require('child_process').execFile('/bin/sh', ['sh']);\n"); + fix += (" child.stdout.str = '';\n"); + fix += (" child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });\n"); + fix += (" child.stderr.on('data', function (chunk) { });\n"); + fix += (" child.stdin.write('launchctl unload /Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist\\n');\n"); + fix += (" child.stdin.write('launchctl load /Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist\\n');\n"); + fix += (" child.stdin.write('rm /Library/LaunchDaemons/meshagentRepair.plist\\n');\n"); + fix += (" child.stdin.write('rm " + process.cwd() + "/macosRepair.js\\n');\n"); + fix += (" child.stdin.write('launchctl stop meshagentRepair\\nexit\\n');\n"); + fix += (" child.waitExit();\n"); + fix += ("}\n"); + fix += ("macosRepair();\n"); + fix += ("process.exit();\n"); + require('fs').writeFileSync(process.cwd() + '/macosRepair.js', fix); + + var plist = '\n'; + plist += '\n'; + plist += '\n'; + plist += ' \n'; + plist += ' Label\n'; + plist += (' meshagentRepair\n'); + plist += ' ProgramArguments\n'; + plist += ' \n'; + plist += (' ' + process.execPath + '\n'); + plist += ' macosRepair.js\n'; + plist += ' \n'; + plist += ' WorkingDirectory\n'; + plist += (' ' + process.cwd() + '\n'); + plist += ' RunAtLoad\n'; + plist += ' \n'; + plist += ' \n'; + plist += ''; + require('fs').writeFileSync('/Library/LaunchDaemons/meshagentRepair.plist', plist); + + child = require('child_process').execFile('/bin/sh', ['sh']); + child.stdout.str = ''; + child.stdout.on('data', function (chunk) { this.str += chunk.toString(); }); + child.stdin.write("launchctl load /Library/LaunchDaemons/meshagentRepair.plist\nexit\n"); + child.waitExit(); + } + } + } + } + + // Create Secure IPC for Diagnostic Agent Communications + obj.DAIPC = require('net').createServer(); + if (process.platform != 'win32') { try { require('fs').unlinkSync(process.cwd() + '/DAIPC'); } catch (e) { } } + obj.DAIPC.IPCPATH = process.platform == 'win32' ? ('\\\\.\\pipe\\' + require('_agentNodeId')() + '-DAIPC') : (process.cwd() + '/DAIPC'); + try { obj.DAIPC.listen({ path: obj.DAIPC.IPCPATH, writableAll: true, maxConnections: 5 }); } catch (e) { } + obj.DAIPC._daipc = []; + obj.DAIPC.on('connection', function (c) { + c._send = function (j) { + var data = JSON.stringify(j); + var packet = Buffer.alloc(data.length + 4); + packet.writeUInt32LE(data.length + 4, 0); + Buffer.from(data).copy(packet, 4); + this.write(packet); + }; + this._daipc.push(c); + c.parent = this; + c.on('end', function () { removeRegisteredApp(this); }); + c.on('data', function (chunk) { + if (chunk.length < 4) { this.unshift(chunk); return; } + var len = chunk.readUInt32LE(0); + if (len > 8192) { removeRegisteredApp(this); this.end(); return; } + if (chunk.length < len) { this.unshift(chunk); return; } + + var data = chunk.slice(4, len); + try { data = JSON.parse(data.toString()); } catch (e) { } + if ((data == null) || (typeof data.cmd != 'string')) return; + + try { + switch (data.cmd) { + case 'requesthelp': + if (this._registered == null) return; + sendConsoleText('Request Help (' + this._registered + '): ' + data.value); + var help = {}; + help[this._registered] = data.value; + try { mesh.SendCommand({ action: 'sessions', type: 'help', value: help }); } catch (e) { } + MeshServerLogEx(98, [this._registered, data.value], "Help Requested, user: " + this._registered + ", details: " + data.value, null); + break; + case 'cancelhelp': + if (this._registered == null) return; + sendConsoleText('Cancel Help (' + this._registered + ')'); + try { mesh.SendCommand({ action: 'sessions', type: 'help', value: {} }); } catch (e) { } + break; + case 'register': + if (typeof data.value == 'string') { + this._registered = data.value; + var apps = {}; + apps[data.value] = 1; + try { mesh.SendCommand({ action: 'sessions', type: 'app', value: apps }); } catch (e) { } + this._send({ cmd: 'serverstate', value: meshServerConnectionState, url: require('MeshAgent').ConnectedServer, amt: (amt != null) }); + } + break; + case 'query': + switch (data.value) { + case 'connection': + data.result = require('MeshAgent').ConnectedServer; + this._send(data); + break; + case 'descriptors': + require('ChainViewer').getSnapshot().then(function (f) { + this.tag.payload.result = f; + this.tag.ipc._send(this.tag.payload); + }).parentPromise.tag = { ipc: this, payload: data }; + break; + } + break; + case 'amtstate': + if (amt == null) return; + var func = function amtStateFunc(state) { if (state != null) { amtStateFunc.pipe._send({ cmd: 'amtstate', value: state }); } } + func.pipe = this; + amt.getAmtInfo(func); + break; + case 'sessions': + this._send({ cmd: 'sessions', sessions: tunnelUserCount }); + break; + } + } + catch (e) { removeRegisteredApp(this); this.end(); return; } + }); + }); + + // Send current sessions to registered apps + function broadcastSessionsToRegisteredApps(x) { + broadcastToRegisteredApps({ cmd: 'sessions', sessions: tunnelUserCount }); + } + + // Send this object to all registered local applications + function broadcastToRegisteredApps(x) { + if ((obj.DAIPC == null) || (obj.DAIPC._daipc == null)) return; + for (var i in obj.DAIPC._daipc) { if (obj.DAIPC._daipc[i]._registered != null) { obj.DAIPC._daipc[i]._send(x); } } + } + + // Send list of registered apps to the server + function updateRegisteredAppsToServer() { + if ((obj.DAIPC == null) || (obj.DAIPC._daipc == null)) return; + var apps = {}; + for (var i in obj.DAIPC._daipc) { if (apps[obj.DAIPC._daipc[i]._registered] == null) { apps[obj.DAIPC._daipc[i]._registered] = 1; } else { apps[obj.DAIPC._daipc[i]._registered]++; } } + try { mesh.SendCommand({ action: 'sessions', type: 'app', value: apps }); } catch (e) { } + } + + // Remove a registered app + function removeRegisteredApp(pipe) { + for (var i = obj.DAIPC._daipc.length - 1; i >= 0; i--) { if (obj.DAIPC._daipc[i] === pipe) { obj.DAIPC._daipc.splice(i, 1); } } + if (pipe._registered != null) updateRegisteredAppsToServer(); + } + + function diagnosticAgent_uninstall() { + require('service-manager').manager.uninstallService('meshagentDiagnostic'); + require('task-scheduler').delete('meshagentDiagnostic/periodicStart'); + }; + function diagnosticAgent_installCheck(install) { + try { + var diag = require('service-manager').manager.getService('meshagentDiagnostic'); + return (diag); + } + catch (e) { + } + if (!install) { return (null); } + + var svc = null; + try { + require('service-manager').manager.installService( + { + name: 'meshagentDiagnostic', + displayName: "Mesh Agent Diagnostic Service", + description: "Mesh Agent Diagnostic Service", + servicePath: process.execPath, + parameters: ['-recovery'] + //files: [{ newName: 'diagnostic.js', _buffer: Buffer.from('LyoNCkNvcHlyaWdodCAyMDE5IEludGVsIENvcnBvcmF0aW9uDQoNCkxpY2Vuc2VkIHVuZGVyIHRoZSBBcGFjaGUgTGljZW5zZSwgVmVyc2lvbiAyLjAgKHRoZSAiTGljZW5zZSIpOw0KeW91IG1heSBub3QgdXNlIHRoaXMgZmlsZSBleGNlcHQgaW4gY29tcGxpYW5jZSB3aXRoIHRoZSBMaWNlbnNlLg0KWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0DQoNCiAgICBodHRwOi8vd3d3LmFwYWNoZS5vcmcvbGljZW5zZXMvTElDRU5TRS0yLjANCg0KVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQ0KZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywNCldJVEhPVVQgV0FSUkFOVElFUyBPUiBDT05ESVRJT05TIE9GIEFOWSBLSU5ELCBlaXRoZXIgZXhwcmVzcyBvciBpbXBsaWVkLg0KU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZA0KbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuDQoqLw0KDQp2YXIgaG9zdCA9IHJlcXVpcmUoJ3NlcnZpY2UtaG9zdCcpLmNyZWF0ZSgnbWVzaGFnZW50RGlhZ25vc3RpYycpOw0KdmFyIFJlY292ZXJ5QWdlbnQgPSByZXF1aXJlKCdNZXNoQWdlbnQnKTsNCg0KaG9zdC5vbignc2VydmljZVN0YXJ0JywgZnVuY3Rpb24gKCkNCnsNCiAgICBjb25zb2xlLnNldERlc3RpbmF0aW9uKGNvbnNvbGUuRGVzdGluYXRpb25zLkxPR0ZJTEUpOw0KICAgIGhvc3Quc3RvcCA9IGZ1bmN0aW9uKCkNCiAgICB7DQogICAgICAgIHJlcXVpcmUoJ3NlcnZpY2UtbWFuYWdlcicpLm1hbmFnZXIuZ2V0U2VydmljZSgnbWVzaGFnZW50RGlhZ25vc3RpYycpLnN0b3AoKTsNCiAgICB9DQogICAgUmVjb3ZlcnlBZ2VudC5vbignQ29ubmVjdGVkJywgZnVuY3Rpb24gKHN0YXR1cykNCiAgICB7DQogICAgICAgIGlmIChzdGF0dXMgPT0gMCkNCiAgICAgICAgew0KICAgICAgICAgICAgY29uc29sZS5sb2coJ0RpYWdub3N0aWMgQWdlbnQ6IFNlcnZlciBjb25uZWN0aW9uIGxvc3QuLi4nKTsNCiAgICAgICAgICAgIHJldHVybjsNCiAgICAgICAgfQ0KICAgICAgICBjb25zb2xlLmxvZygnRGlhZ25vc3RpYyBBZ2VudDogQ29ubmVjdGlvbiBFc3RhYmxpc2hlZCB3aXRoIFNlcnZlcicpOw0KICAgICAgICBzdGFydCgpOw0KICAgIH0pOw0KfSk7DQpob3N0Lm9uKCdub3JtYWxTdGFydCcsIGZ1bmN0aW9uICgpDQp7DQogICAgaG9zdC5zdG9wID0gZnVuY3Rpb24gKCkNCiAgICB7DQogICAgICAgIHByb2Nlc3MuZXhpdCgpOw0KICAgIH0NCiAgICBjb25zb2xlLmxvZygnTm9uIFNlcnZpY2UgTW9kZScpOw0KICAgIFJlY292ZXJ5QWdlbnQub24oJ0Nvbm5lY3RlZCcsIGZ1bmN0aW9uIChzdGF0dXMpDQogICAgew0KICAgICAgICBpZiAoc3RhdHVzID09IDApDQogICAgICAgIHsNCiAgICAgICAgICAgIGNvbnNvbGUubG9nKCdEaWFnbm9zdGljIEFnZW50OiBTZXJ2ZXIgY29ubmVjdGlvbiBsb3N0Li4uJyk7DQogICAgICAgICAgICByZXR1cm47DQogICAgICAgIH0NCiAgICAgICAgY29uc29sZS5sb2coJ0RpYWdub3N0aWMgQWdlbnQ6IENvbm5lY3Rpb24gRXN0YWJsaXNoZWQgd2l0aCBTZXJ2ZXInKTsNCiAgICAgICAgc3RhcnQoKTsNCiAgICB9KTsNCn0pOw0KaG9zdC5vbignc2VydmljZVN0b3AnLCBmdW5jdGlvbiAoKSB7IHByb2Nlc3MuZXhpdCgpOyB9KTsNCmhvc3QucnVuKCk7DQoNCg0KZnVuY3Rpb24gc3RhcnQoKQ0Kew0KDQp9Ow0K', 'base64') }] + }); + svc = require('service-manager').manager.getService('meshagentDiagnostic'); + } + catch (e) { + return (null); + } + var proxyConfig = require('global-tunnel').proxyConfig; + var cert = require('MeshAgent').GenerateAgentCertificate('CN=MeshNodeDiagnosticCertificate'); + var nodeid = require('tls').loadCertificate(cert.root).getKeyHash().toString('base64'); + ddb = require('SimpleDataStore').Create(svc.appWorkingDirectory().replace('\\', '/') + '/meshagentDiagnostic.db'); + ddb.Put('disableUpdate', '1'); + ddb.Put('MeshID', Buffer.from(require('MeshAgent').ServerInfo.MeshID, 'hex')); + ddb.Put('ServerID', require('MeshAgent').ServerInfo.ServerID); + ddb.Put('MeshServer', require('MeshAgent').ServerInfo.ServerUri); + if (cert.root.pfx) { ddb.Put('SelfNodeCert', cert.root.pfx); } + if (cert.tls) { ddb.Put('SelfNodeTlsCert', cert.tls.pfx); } + if (proxyConfig) { + ddb.Put('WebProxy', proxyConfig.host + ':' + proxyConfig.port); + } else { + ddb.Put('ignoreProxyFile', '1'); + } + + require('MeshAgent').SendCommand({ action: 'diagnostic', value: { command: 'register', value: nodeid } }); + require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: "Diagnostic Agent Registered [" + nodeid.length + "/" + nodeid + "]" }); + + delete ddb; + + // Set a recurrent task, to run the Diagnostic Agent every 2 days + require('task-scheduler').create({ name: 'meshagentDiagnostic/periodicStart', daily: 2, time: require('tls').generateRandomInteger('0', '23') + ':' + require('tls').generateRandomInteger('0', '59').padStart(2, '0'), service: 'meshagentDiagnostic' }); + //require('task-scheduler').create({ name: 'meshagentDiagnostic/periodicStart', daily: '1', time: '17:16', service: 'meshagentDiagnostic' }); + + return (svc); + } + + // Monitor the file 'batterystate.txt' in the agent's folder and sends battery update when this file is changed. + if ((require('fs').existsSync(process.cwd() + 'batterystate.txt')) && (require('fs').watch != null)) { + // Setup manual battery monitoring + require('MeshAgent')._batteryFileWatcher = require('fs').watch(process.cwd(), function () { + if (require('MeshAgent')._batteryFileTimer != null) return; + require('MeshAgent')._batteryFileTimer = setTimeout(function () { + try { + require('MeshAgent')._batteryFileTimer = null; + var data = null; + try { data = require('fs').readFileSync(process.cwd() + 'batterystate.txt').toString(); } catch (e) { } + if ((data != null) && (data.length < 10)) { + data = data.split(','); + if ((data.length == 2) && ((data[0] == 'ac') || (data[0] == 'dc'))) { + var level = parseInt(data[1]); + if ((level >= 0) && (level <= 100)) { require('MeshAgent').SendCommand({ action: 'battery', state: data[0], level: level }); } + } + } + } catch (e) { } + }, 1000); + }); + } else { + // Setup normal battery monitoring + if (require('identifiers').isBatteryPowered && require('identifiers').isBatteryPowered()) { + require('MeshAgent')._battLevelChanged = function _battLevelChanged(val) { + _battLevelChanged.self._currentBatteryLevel = val; + _battLevelChanged.self.SendCommand({ action: 'battery', state: _battLevelChanged.self._currentPowerState, level: val }); + }; + require('MeshAgent')._battLevelChanged.self = require('MeshAgent'); + require('MeshAgent')._powerChanged = function _powerChanged(val) { + _powerChanged.self._currentPowerState = (val == 'AC' ? 'ac' : 'dc'); + _powerChanged.self.SendCommand({ action: 'battery', state: (val == 'AC' ? 'ac' : 'dc'), level: _powerChanged.self._currentBatteryLevel }); + }; + require('MeshAgent')._powerChanged.self = require('MeshAgent'); + require('MeshAgent').on('Connected', function (status) { + if (status == 0) { + require('power-monitor').removeListener('acdc', this._powerChanged); + require('power-monitor').removeListener('batteryLevel', this._battLevelChanged); + } else { + require('power-monitor').on('acdc', this._powerChanged); + require('power-monitor').on('batteryLevel', this._battLevelChanged); + } + }); + } + } + + + /* + function borderController() { + this.container = null; + this.Start = function Start(user) { + if (this.container == null) { + if (process.platform == 'win32') { + try { + this.container = require('ScriptContainer').Create({ processIsolation: 1, sessionId: user.SessionId }); + } catch (e) { + this.container = require('ScriptContainer').Create({ processIsolation: 1 }); + } + } else { + this.container = require('ScriptContainer').Create({ processIsolation: 1, sessionId: user.uid }); + } + this.container.parent = this; + this.container.addModule('monitor-info', getJSModule('monitor-info')); + this.container.addModule('monitor-border', getJSModule('monitor-border')); + this.container.addModule('promise', getJSModule('promise')); + this.container.once('exit', function (code) { sendConsoleText('Border Process Exited with code: ' + code); this.parent.container = this.parent._container = null; }); + this.container.ExecuteString("var border = require('monitor-border'); border.Start();"); + } + } + this.Stop = function Stop() { + if (this.container != null) { + this._container = this.container; + this._container.parent = this; + this.container = null; + this._container.exit(); + } + } + } + obj.borderManager = new borderController(); + */ + + // MeshAgent JavaScript Core Module. This code is sent to and running on the mesh agent. + var meshCoreObj = { action: 'coreinfo', value: (require('MeshAgent').coreHash ? ('MeshCore CRC-' + crc32c(require('MeshAgent').coreHash)) : ('MeshCore v6')), caps: 14, root: require('user-sessions').isRoot() }; // Capability bitmask: 1 = Desktop, 2 = Terminal, 4 = Files, 8 = Console, 16 = JavaScript, 32 = Temporary Agent, 64 = Recovery Agent + + + // Get the operating system description string + try { require('os').name().then(function (v) { meshCoreObj.osdesc = v; }); } catch (e) { } + + var meshServerConnectionState = 0; + var tunnels = {}; + var lastMeInfo = null; + var lastNetworkInfo = null; + var lastPublicLocationInfo = null; + var selfInfoUpdateTimer = null; + var http = require('http'); + var net = require('net'); + var fs = require('fs'); + var rtc = require('ILibWebRTC'); + var amt = null; + var processManager = require('process-manager'); + var wifiScannerLib = null; + var wifiScanner = null; + var networkMonitor = null; + var amtscanner = null; + var nextTunnelIndex = 1; + var apftunnel = null; + var tunnelUserCount = { terminal: {}, files: {}, tcp: {}, udp: {}, msg: {} }; // List of userid->count sessions for terminal, files and TCP/UDP routing + + // Add to the server event log + function MeshServerLog(msg, state) { + if (typeof msg == 'string') { msg = { action: 'log', msg: msg }; } else { msg.action = 'log'; } + if (state) { + if (state.userid) { msg.userid = state.userid; } + if (state.username) { msg.username = state.username; } + if (state.sessionid) { msg.sessionid = state.sessionid; } + if (state.remoteaddr) { msg.remoteaddr = state.remoteaddr; } + } + mesh.SendCommand(msg); + } + + // Add to the server event log, use internationalized events + function MeshServerLogEx(id, args, msg, state) { + var msg = { action: 'log', msgid: id, msgArgs: args, msg: msg }; + if (state) { + if (state.userid) { msg.userid = state.userid; } + if (state.username) { msg.username = state.username; } + if (state.sessionid) { msg.sessionid = state.sessionid; } + if (state.remoteaddr) { msg.remoteaddr = state.remoteaddr; } + } + mesh.SendCommand(msg); + } + + // If we are running in Duktape, agent will be null + if (agent == null) { + // Running in native agent, Import libraries + db = require('SimpleDataStore').Shared(); + sha = require('SHA256Stream'); + mesh = require('MeshAgent'); + childProcess = require('child_process'); + if (mesh.hasKVM == 1) { // if the agent is compiled with KVM support + // Check if this computer supports a desktop + try + { + if ((process.platform == 'win32') || (process.platform == 'darwin') || (require('monitor-info').kvm_x11_support)) + { + meshCoreObj.caps |= 1; + } + else if(process.platform == 'linux' || process.platform == 'freebsd') + { + require('monitor-info').on('kvmSupportDetected', function (value) + { + meshCoreObj.caps |= 1; + mesh.SendCommand(meshCoreObj); + }); + } + } catch (e) { } + } + } else { + // Running in nodejs + meshCoreObj.value += '-NodeJS'; + meshCoreObj.caps = 8; + mesh = agent.getMeshApi(); + } + + mesh.DAIPC = obj.DAIPC; + + /* + var AMTScanner = require("AMTScanner"); + var scan = new AMTScanner(); + + scan.on("found", function (data) { + if (typeof data === 'string') { + console.log(data); + } else { + console.log(JSON.stringify(data, null, " ")); + } + }); + scan.scan("10.2.55.140", 1000); + scan.scan("10.2.55.139-10.2.55.145", 1000); + scan.scan("10.2.55.128/25", 2000); + */ + + /* + // Try to load up the network monitor + try { + networkMonitor = require('NetworkMonitor'); + networkMonitor.on('change', function () { sendNetworkUpdateNagle(); }); + networkMonitor.on('add', function (addr) { sendNetworkUpdateNagle(); }); + networkMonitor.on('remove', function (addr) { sendNetworkUpdateNagle(); }); + } catch (e) { networkMonitor = null; } + */ + + // Try to load up the Intel AMT scanner + try { + var AMTScannerModule = require('amt-scanner'); + amtscanner = new AMTScannerModule(); + //amtscanner.on('found', function (data) { if (typeof data != 'string') { data = JSON.stringify(data, null, " "); } sendConsoleText(data); }); + } catch (e) { amtscanner = null; } + + // Fetch the SMBios Tables + var SMBiosTables = null; + var SMBiosTablesRaw = null; + try { + var SMBiosModule = null; + try { SMBiosModule = require('smbios'); } catch (e) { } + if (SMBiosModule != null) { + SMBiosModule.get(function (data) { + if (data != null) { + SMBiosTablesRaw = data; + SMBiosTables = require('smbios').parse(data) + if (mesh.isControlChannelConnected) { mesh.SendCommand({ action: 'smbios', value: SMBiosTablesRaw }); } + + // If SMBios tables say that Intel AMT is present, try to connect MEI + if (SMBiosTables.amtInfo && (SMBiosTables.amtInfo.AMT == true)) + { + var amtmodule = require('amt-manage'); + amt = new amtmodule(mesh, db, false); + amt.on('portBinding_LMS', function (map) + { + var j = { action: 'lmsinfo', value: { ports: map.keys() } }; + mesh.SendCommand(j); + }); + amt.on('stateChange_LMS', function (v) + { + if (!meshCoreObj.intelamt) { meshCoreObj.intelamt = {}; } + switch(v) + { + case 0: + meshCoreObj.intelamt.microlms = 'DISABLED'; + break; + case 1: + meshCoreObj.intelamt.microlms = 'CONNECTING'; + break; + case 2: + meshCoreObj.intelamt.microlms = 'CONNECTED'; + break; + default: + break; + } + mesh.SendCommand(meshCoreObj); + }); + amt.onStateChange = function (state) { if (state == 2) { sendPeriodicServerUpdate(1); } } + amt.start(); + } + } + }); + } + } catch (e) { sendConsoleText("ex1: " + e); } + + // Try to load up the WIFI scanner + try { + var wifiScannerLib = require('wifi-scanner'); + wifiScanner = new wifiScannerLib(); + wifiScanner.on('accessPoint', function (data) { sendConsoleText("wifiScanner: " + data); }); + } catch (e) { wifiScannerLib = null; wifiScanner = null; } + + // Get our location (lat/long) using our public IP address + var getIpLocationDataExInProgress = false; + var getIpLocationDataExCounts = [0, 0]; + function getIpLocationDataEx(func) { + if (getIpLocationDataExInProgress == true) { return false; } + try { + getIpLocationDataExInProgress = true; + getIpLocationDataExCounts[0]++; + var options = http.parseUri("http://ipinfo.io/json"); + options.method = 'GET'; + http.request(options, function (resp) { + if (resp.statusCode == 200) { + var geoData = ''; + resp.data = function (geoipdata) { geoData += geoipdata; }; + resp.end = function () { + var location = null; + try { + if (typeof geoData == 'string') { + var result = JSON.parse(geoData); + if (result.ip && result.loc) { location = result; } + } + } catch (e) { } + if (func) { getIpLocationDataExCounts[1]++; func(location); } + } + } else { func(null); } + getIpLocationDataExInProgress = false; + }).end(); + return true; + } + catch (e) { return false; } + } + + // Remove all Gateway MAC addresses for interface list. This is useful because the gateway MAC is not always populated reliably. + function clearGatewayMac(str) { + if (str == null) return null; + var x = JSON.parse(str); + for (var i in x.netif) { if (x.netif[i].gatewaymac) { delete x.netif[i].gatewaymac } } + return JSON.stringify(x); + } + + function getIpLocationData(func) { + // Get the location information for the cache if possible + var publicLocationInfo = db.Get('publicLocationInfo'); + if (publicLocationInfo != null) { publicLocationInfo = JSON.parse(publicLocationInfo); } + if (publicLocationInfo == null) { + // Nothing in the cache, fetch the data + getIpLocationDataEx(function (locationData) { + if (locationData != null) { + publicLocationInfo = {}; + publicLocationInfo.netInfoStr = lastNetworkInfo; + publicLocationInfo.locationData = locationData; + var x = db.Put('publicLocationInfo', JSON.stringify(publicLocationInfo)); // Save to database + if (func) func(locationData); // Report the new location + } else { + if (func) func(null); // Report no location + } + }); + } else { + // Check the cache + if (clearGatewayMac(publicLocationInfo.netInfoStr) == clearGatewayMac(lastNetworkInfo)) { + // Cache match + if (func) func(publicLocationInfo.locationData); + } else { + // Cache mismatch + getIpLocationDataEx(function (locationData) { + if (locationData != null) { + publicLocationInfo = {}; + publicLocationInfo.netInfoStr = lastNetworkInfo; + publicLocationInfo.locationData = locationData; + var x = db.Put('publicLocationInfo', JSON.stringify(publicLocationInfo)); // Save to database + if (func) func(locationData); // Report the new location + } else { + if (func) func(publicLocationInfo.locationData); // Can't get new location, report the old location + } + }); + } + } + } + + // Polyfill String.endsWith + if (!String.prototype.endsWith) { + String.prototype.endsWith = function (searchString, position) { + var subjectString = this.toString(); + if (typeof position !== 'number' || !isFinite(position) || Math.floor(position) !== position || position > subjectString.length) { position = subjectString.length; } + position -= searchString.length; + var lastIndex = subjectString.lastIndexOf(searchString, position); + return lastIndex !== -1 && lastIndex === position; + }; + } + + // Polyfill path.join + obj.path = { + join: function () { + var x = []; + for (var i in arguments) { + var w = arguments[i]; + if (w != null) { + while (w.endsWith('/') || w.endsWith('\\')) { w = w.substring(0, w.length - 1); } + if (i != 0) { + while (w.startsWith('/') || w.startsWith('\\')) { w = w.substring(1); } + } + x.push(w); + } + } + if (x.length == 0) return '/'; + return x.join('/'); + } + }; + + // Replace a string with a number if the string is an exact number + function toNumberIfNumber(x) { if ((typeof x == 'string') && (+parseInt(x) === x)) { x = parseInt(x); } return x; } + + // Convert decimal to hex + function char2hex(i) { return (i + 0x100).toString(16).substr(-2).toUpperCase(); } + + // Convert a raw string to a hex string + function rstr2hex(input) { var r = '', i; for (i = 0; i < input.length; i++) { r += char2hex(input.charCodeAt(i)); } return r; } + + // Convert a buffer into a string + function buf2rstr(buf) { var r = ''; for (var i = 0; i < buf.length; i++) { r += String.fromCharCode(buf[i]); } return r; } + + // Convert a hex string to a raw string // TODO: Do this using Buffer(), will be MUCH faster + function hex2rstr(d) { + if (typeof d != "string" || d.length == 0) return ''; + var r = '', m = ('' + d).match(/../g), t; + while (t = m.shift()) r += String.fromCharCode('0x' + t); + return r + } + + // Convert an object to string with all functions + function objToString(x, p, pad, ret) { + if (ret == undefined) ret = ''; + if (p == undefined) p = 0; + if (x == null) { return '[null]'; } + if (p > 8) { return '[...]'; } + if (x == undefined) { return '[undefined]'; } + if (typeof x == 'string') { if (p == 0) return x; return '"' + x + '"'; } + if (typeof x == 'buffer') { return '[buffer]'; } + if (typeof x != 'object') { return x; } + var r = '{' + (ret ? '\r\n' : ' '); + for (var i in x) { if (i != '_ObjectID') { r += (addPad(p + 2, pad) + i + ': ' + objToString(x[i], p + 2, pad, ret) + (ret ? '\r\n' : ' ')); } } + return r + addPad(p, pad) + '}'; + } + + // Return p number of spaces + function addPad(p, ret) { var r = ''; for (var i = 0; i < p; i++) { r += ret; } return r; } + + // Split a string taking into account the quoats. Used for command line parsing + function splitArgs(str) { + var myArray = [], myRegexp = /[^\s"]+|"([^"]*)"/gi; + do { var match = myRegexp.exec(str); if (match != null) { myArray.push(match[1] ? match[1] : match[0]); } } while (match != null); + return myArray; + } + + // Parse arguments string array into an object + function parseArgs(argv) { + var results = { '_': [] }, current = null; + for (var i = 1, len = argv.length; i < len; i++) { + var x = argv[i]; + if (x.length > 2 && x[0] == '-' && x[1] == '-') { + if (current != null) { results[current] = true; } + current = x.substring(2); + } else { + if (current != null) { results[current] = toNumberIfNumber(x); current = null; } else { results['_'].push(toNumberIfNumber(x)); } + } + } + if (current != null) { results[current] = true; } + return results; + } + + // Get server target url with a custom path + function getServerTargetUrl(path) { + var x = mesh.ServerUrl; + //sendConsoleText("mesh.ServerUrl: " + mesh.ServerUrl); + if (x == null) { return null; } + if (path == null) { path = ''; } + x = http.parseUri(x); + if (x == null) return null; + return x.protocol + '//' + x.host + ':' + x.port + '/' + path; + } + + // Get server url. If the url starts with "*/..." change it, it not use the url as is. + function getServerTargetUrlEx(url) { + if (url.substring(0, 2) == '*/') { return getServerTargetUrl(url.substring(2)); } + return url; + } + + // Send a wake-on-lan packet + function sendWakeOnLan(hexMac) { + hexMac = hexMac.split(':').join(''); + var count = 0; + try { + var interfaces = require('os').networkInterfaces(); + var magic = 'FFFFFFFFFFFF'; + for (var x = 1; x <= 16; ++x) { magic += hexMac; } + var magicbin = Buffer.from(magic, 'hex'); + + for (var adapter in interfaces) { + if (interfaces.hasOwnProperty(adapter)) { + for (var i = 0; i < interfaces[adapter].length; ++i) { + var addr = interfaces[adapter][i]; + if ((addr.family == 'IPv4') && (addr.mac != '00:00:00:00:00:00')) { + try { + var socket = require('dgram').createSocket({ type: 'udp4' }); + socket.bind({ address: addr.address }); + socket.setBroadcast(true); + socket.setMulticastInterface(addr.address); + socket.setMulticastTTL(1); + socket.send(magicbin, 7, '255.255.255.255'); + socket.descriptorMetadata = 'WoL (' + addr.address + ' => ' + hexMac + ')'; + count++; + } + catch (e) { } + } + } + } + } + } catch (e) { } + return count; + } + + // Handle a mesh agent command + function handleServerCommand(data) { + if (typeof data == 'object') { + // If this is a console command, parse it and call the console handler + switch (data.action) { + case 'msg': { + switch (data.type) { + case 'console': { // Process a console command + if (data.value && data.sessionid) { + MeshServerLogEx(17, [data.value], "Processing console command: " + data.value, data); + var args = splitArgs(data.value); + processConsoleCommand(args[0].toLowerCase(), parseArgs(args), data.rights, data.sessionid); + } + break; + } + case 'tunnel': { + if (data.value != null) { // Process a new tunnel connection request + // Create a new tunnel object + var xurl = getServerTargetUrlEx(data.value); + if (xurl != null) { + xurl = xurl.split('$').join('%24').split('@').join('%40'); // Escape the $ and @ characters + var woptions = http.parseUri(xurl); + woptions.perMessageDeflate = false; + if (typeof data.perMessageDeflate == 'boolean') { woptions.perMessageDeflate = data.perMessageDeflate; } + woptions.rejectUnauthorized = 0; + //sendConsoleText(JSON.stringify(woptions)); + //sendConsoleText('TUNNEL: ' + JSON.stringify(data)); + var tunnel = http.request(woptions); + tunnel.upgrade = onTunnelUpgrade; + tunnel.on('error', function (e) { sendConsoleText("ERROR: Unable to connect relay tunnel to: " + this.url + ", " + JSON.stringify(e)); }); + tunnel.sessionid = data.sessionid; + tunnel.rights = data.rights; + tunnel.consent = data.consent; + tunnel.privacybartext = data.privacybartext ? data.privacybartext : "Sharing desktop with: {0}"; + tunnel.username = data.username + (data.guestname ? (' - ' + data.guestname) : ''); + tunnel.realname = (data.realname ? data.realname : data.username) + (data.guestname ? (' - ' + data.guestname) : ''); + tunnel.userid = data.userid; + tunnel.remoteaddr = data.remoteaddr; + tunnel.state = 0; + tunnel.url = xurl; + tunnel.protocol = 0; + tunnel.soptions = data.soptions; + tunnel.tcpaddr = data.tcpaddr; + tunnel.tcpport = data.tcpport; + tunnel.udpaddr = data.udpaddr; + tunnel.udpport = data.udpport; + tunnel.end(); + // Put the tunnel in the tunnels list + var index = nextTunnelIndex++; + tunnel.index = index; + tunnels[index] = tunnel; + + //sendConsoleText('New tunnel connection #' + index + ': ' + tunnel.url + ', rights: ' + tunnel.rights, data.sessionid); + } + } + break; + } + case 'messagebox': { + // Display a message box + if (data.title && data.msg) { + MeshServerLogEx(18, [data.title, data.msg], "Displaying message box, title=" + data.title + ", message=" + data.msg, data); + data.msg = data.msg.split('\r').join('\\r').split('\n').join('\\n'); + try { require('message-box').create(data.title, data.msg, 120); } catch (e) { } + } + break; + } + case 'ps': { + // Return the list of running processes + if (data.sessionid) { + processManager.getProcesses(function (plist) { + mesh.SendCommand({ action: 'msg', type: 'ps', value: JSON.stringify(plist), sessionid: data.sessionid }); + }); + } + break; + } + case 'pskill': { + // Kill a process + if (data.value) { + MeshServerLogEx(19, [data.value], "Killing process " + data.value, data); + try { process.kill(data.value); } catch (e) { sendConsoleText("pskill: " + JSON.stringify(e)); } + } + break; + } + case 'services': { + // Return the list of installed services + var services = null; + try { services = require('service-manager').manager.enumerateService(); } catch (e) { } + if (services != null) { mesh.SendCommand({ action: 'msg', type: 'services', value: JSON.stringify(services), sessionid: data.sessionid }); } + break; + } + case 'serviceStop': { + // Stop a service + try { + var service = require('service-manager').manager.getService(data.serviceName); + if (service != null) { service.stop(); } + } catch (e) { } + break; + } + case 'serviceStart': { + // Start a service + try { + var service = require('service-manager').manager.getService(data.serviceName); + if (service != null) { service.start(); } + } catch (e) { } + break; + } + case 'serviceRestart': { + // Restart a service + try { + var service = require('service-manager').manager.getService(data.serviceName); + if (service != null) { service.restart(); } + } catch (e) { } + break; + } + case 'deskBackground': + { + // Toggle desktop background + try { + if (process.platform == 'win32') { + var stype = require('user-sessions').getProcessOwnerName(process.pid).tsid == 0 ? 1 : 0; + var sid = undefined; + if (stype == 1) { + if (require('MeshAgent')._tsid != null) { + stype = 5; + sid = require('MeshAgent')._tsid; + } + } + var id = require('user-sessions').getProcessOwnerName(process.pid).tsid == 0 ? 1 : 0; + var child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0='], { type: stype, uid: sid }); + child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); + child.stderr.on('data', function () { }); + child.waitExit(); + var current = child.stdout.str.trim(); + if (current != '') { require('MeshAgent')._wallpaper = current; } + child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0=', current != '' ? '""' : require('MeshAgent')._wallpaper], { type: stype, uid: sid }); + child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); + child.stderr.on('data', function () { }); + child.waitExit(); + } else { + var id = require('user-sessions').consoleUid(); + var current = require('linux-gnome-helpers').getDesktopWallpaper(id); + if (current != '/dev/null') { require('MeshAgent')._wallpaper = current; } + require('linux-gnome-helpers').setDesktopWallpaper(id, current != '/dev/null' ? undefined : require('MeshAgent')._wallpaper); + } + } catch (e) { + sendConsoleText(e); + } + break; + } + case 'openUrl': { + // Open a local web browser and return success/fail + MeshServerLogEx(20, [data.url], "Opening: " + data.url, data); + sendConsoleText("OpenURL: " + data.url); + if (data.url) { mesh.SendCommand({ action: 'msg', type: 'openUrl', url: data.url, sessionid: data.sessionid, success: (openUserDesktopUrl(data.url) != null) }); } + break; + } + case 'getclip': { + // Send the load clipboard back to the user + //sendConsoleText('getClip: ' + JSON.stringify(data)); + if (require('MeshAgent').isService) { + require('clipboard').dispatchRead().then(function (str) { + if (str) { + MeshServerLogEx(21, [str.length], "Getting clipboard content, " + str.length + " byte(s)", data); + mesh.SendCommand({ action: 'msg', type: 'getclip', sessionid: data.sessionid, data: str, tag: data.tag }); + } + }); + } else { + require("clipboard").read().then(function (str) { + if (str) { + MeshServerLogEx(21, [str.length], "Getting clipboard content, " + str.length + " byte(s)", data); + mesh.SendCommand({ action: 'msg', type: 'getclip', sessionid: data.sessionid, data: str, tag: data.tag }); + } + }); + } + break; + } + case 'setclip': { + // Set the load clipboard to a user value + //sendConsoleText('setClip: ' + JSON.stringify(data)); + if (typeof data.data == 'string') { + MeshServerLogEx(22, [data.data.length], "Setting clipboard content, " + data.data.length + " byte(s)", data); + if (require('MeshAgent').isService) { require('clipboard').dispatchWrite(data.data); } else { require("clipboard")(data.data); } // Set the clipboard + mesh.SendCommand({ action: 'msg', type: 'setclip', sessionid: data.sessionid, success: true }); + } + break; + } + case 'userSessions': { + // Send back current user sessions list, this is Windows only. + //sendConsoleText('userSessions: ' + JSON.stringify(data)); + if (process.platform != 'win32') break; + var p = require('user-sessions').enumerateUsers(); + p.sessionid = data.sessionid; + p.then(function (u) { mesh.SendCommand({ action: 'msg', type: 'userSessions', sessionid: data.sessionid, data: u, tag: data.tag }); }); + break; + } + default: + // Unknown action, ignore it. + break; + } + break; + } + case 'acmactivate': { + if (amt != null) { + MeshServerLogEx(23, null, "Attempting Intel AMT ACM mode activation", data); + amt.setAcmResponse(data); + } + break; + } + case 'wakeonlan': { + // Send wake-on-lan on all interfaces for all MAC addresses in data.macs array. The array is a list of HEX MAC addresses. + sendConsoleText("Server requesting wake-on-lan for: " + data.macs.join(', ')); + for (var i in data.macs) { sendWakeOnLan(data.macs[i]); } + break; + } + case 'runcommands': { + if (mesh.cmdchild != null) { sendConsoleText("Run commands can't execute, already busy."); break; } + sendConsoleText("Run commands (" + data.runAsUser + "): " + data.cmds); + + // data.runAsUser: 0=Agent,1=UserOrAgent,2=UserOnly + var options = {}; + if (data.runAsUser > 0) { + try { options.uid = require('user-sessions').consoleUid(); } catch (e) { } + options.type = require('child_process').SpawnTypes.TERM; + } + if (data.runAsUser == 2) { + if (options.uid == null) break; + if (((require('user-sessions').minUid != null) && (options.uid < require('user-sessions').minUid()))) break; // This command can only run as user. + } + + if (process.platform == 'win32') { + if (data.type == 1) { + // Windows command shell + mesh.cmdchild = require('child_process').execFile(process.env['windir'] + '\\system32\\cmd.exe', ['cmd'], options); + mesh.cmdchild.descriptorMetadata = 'UserCommandsShell'; + mesh.cmdchild.stdout.on('data', function (c) { sendConsoleText(c.toString()); }); + mesh.cmdchild.stderr.on('data', function (c) { sendConsoleText(c.toString()); }); + mesh.cmdchild.stdin.write(data.cmds + '\r\nexit\r\n'); + mesh.cmdchild.on('exit', function () { sendConsoleText("Run commands completed."); delete mesh.cmdchild; }); + } else if (data.type == 2) { + // Windows Powershell + mesh.cmdchild = require('child_process').execFile(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', ['powershell', '-noprofile', '-nologo', '-command', '-'], options); + mesh.cmdchild.descriptorMetadata = 'UserCommandsPowerShell'; + mesh.cmdchild.stdout.on('data', function (c) { sendConsoleText(c.toString()); }); + mesh.cmdchild.stderr.on('data', function (c) { sendConsoleText(c.toString()); }); + mesh.cmdchild.stdin.write(data.cmds + '\r\nexit\r\n'); + mesh.cmdchild.on('exit', function () { sendConsoleText("Run commands completed."); delete mesh.cmdchild; }); + } + } else if (data.type == 3) { + // Linux shell + mesh.cmdchild = require('child_process').execFile('/bin/sh', ['sh'], options); + mesh.cmdchild.descriptorMetadata = 'UserCommandsShell'; + mesh.cmdchild.stdout.on('data', function (c) { sendConsoleText(c.toString()); }); + mesh.cmdchild.stderr.on('data', function (c) { sendConsoleText(c.toString()); }); + mesh.cmdchild.stdin.write(data.cmds.split('\r').join('') + '\nexit\n'); + mesh.cmdchild.on('exit', function () { sendConsoleText("Run commands completed."); delete mesh.cmdchild; }); + } + break; + } + case 'uninstallagent': + // Uninstall this agent + var agentName = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'; + if (require('service-manager').manager.getService(agentName).isMe()) { + try { diagnosticAgent_uninstall(); } catch (e) { } + var js = "require('service-manager').manager.getService('" + agentName + "').stop(); require('service-manager').manager.uninstallService('" + agentName + "'); process.exit();"; + this.child = require('child_process').execFile(process.execPath, [process.platform == 'win32' ? (process.execPath.split('\\').pop()) : (process.execPath.split('/').pop()), '-b64exec', Buffer.from(js).toString('base64')], { type: 4, detached: true }); + } + break; + case 'poweraction': { + // Server telling us to execute a power action + if ((mesh.ExecPowerState != undefined) && (data.actiontype)) { + var forced = 0; + if (data.forced == 1) { forced = 1; } + data.actiontype = parseInt(data.actiontype); + MeshServerLogEx(25, [data.actiontype, forced], "Performing power action=" + data.actiontype + ", forced=" + forced, data); + sendConsoleText("Performing power action=" + data.actiontype + ", forced=" + forced + '.'); + var r = mesh.ExecPowerState(data.actiontype, forced); + sendConsoleText("ExecPowerState returned code: " + r); + } + break; + } + case 'iplocation': { + // Update the IP location information of this node. Only do this when requested by the server since we have a limited amount of time we can call this per day + getIpLocationData(function (location) { mesh.SendCommand({ action: 'iplocation', type: 'publicip', value: location }); }); + break; + } + case 'toast': { + // Display a toast message + if (data.title && data.msg) { + MeshServerLogEx(26, [data.title, data.msg], "Displaying toast message, title=" + data.title + ", message=" + data.msg, data); + data.msg = data.msg.split('\r').join('\\r').split('\n').join('\\n'); + try { require('toaster').Toast(data.title, data.msg); } catch (e) { } + } + break; + } + case 'openUrl': { + // Open a local web browser and return success/fail + //sendConsoleText('OpenURL: ' + data.url); + MeshServerLogEx(20, [data.url], "Opening: " + data.url, data); + if (data.url) { mesh.SendCommand({ action: 'openUrl', url: data.url, sessionid: data.sessionid, success: (openUserDesktopUrl(data.url) != null) }); } + break; + } + case 'amtconfig': { + // Perform Intel AMT activation and/or configuration + require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: 'amtconfig' }); + if ((apftunnel != null) || (amt == null)) break; + getMeiState(15, function (state) { + if ((state == null) || (state.ProvisioningState == null)) return; + if ((state.UUID == null) || (state.UUID.length != 36)) return; // Bad UUID + var apfarg = { + mpsurl: mesh.ServerUrl.replace('agent.ashx', 'apf.ashx'), + mpsuser: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16), // TODO: User a server provided encrypted cookie for CIRA-LMS login + mpspass: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16), + mpskeepalive: 60000, + clientname: state.OsHostname, + clientaddress: '127.0.0.1', + clientuuid: state.UUID, + conntype: 2, // 0 = CIRA, 1 = Relay, 2 = LMS. The correct value is 2 since we are performing an LMS relay, other values for testing. + meiState: state // MEI state will be passed to MPS server + }; + require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: 'apf-on' }); + apftunnel = require('apfclient')({ debug: false }, apfarg); + apftunnel.onJsonControl = function (data) { + if (data.action == 'console') { require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: data.msg }); } // Display a console message (DEBUG) + if (data.action == 'mestate') { getMeiState(15, function (state) { apftunnel.updateMeiState(state); }); } // Update the MEI state + if (data.action == 'deactivate') { // Request CCM deactivation + var amtMeiModule, amtMei; + try { amtMeiModule = require('amt-mei'); amtMei = new amtMeiModule(); } catch (ex) { apftunnel.sendMeiDeactivationState(1); return; } + amtMei.on('error', function (e) { apftunnel.sendMeiDeactivationState(1); }); + amtMei.unprovision(1, function (status) { apftunnel.sendMeiDeactivationState(status); }); // 0 = Success + } + if (data.action == 'close') { try { apftunnel.disconnect(); } catch (e) { } apftunnel = null; } // Close the CIRA-LMS connection + } + apftunnel.onChannelClosed = function () { apftunnel = null; require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: 'apf-off' }); } + try { apftunnel.connect(); } catch (ex) { } + }); + break; + } + case 'getScript': { + // Received a configuration script from the server + sendConsoleText('getScript: ' + JSON.stringify(data)); + break; + } + case 'sysinfo': { + // Fetch system information + getSystemInformation(function (results) { + if ((results != null) && (data.hash != results.hash)) { mesh.SendCommand({ action: 'sysinfo', sessionid: this.sessionid, data: results }); } + }); + break; + } + case 'ping': { mesh.SendCommand('{"action":"pong"}'); break; } + case 'pong': { break; } + case 'plugin': { + try { require(data.plugin).consoleaction(data, data.rights, data.sessionid, this); } catch (e) { throw e; } + break; + } + case 'coredump': + // Set the current agent coredump situation. + if (data.value === true) { + // TODO: This replace() below is not ideal, would be better to remove the .exe at the end instead of replace. + process.coreDumpLocation = (process.platform == 'win32') ? (process.execPath.replace('.exe', '.dmp')) : (process.execPath + '.dmp'); + } else if (data.value === false) { + process.coreDumpLocation = null; + } + break; + case 'getcoredump': + // Ask the agent if a core dump is currently available, if yes, also return the hash of the agent. + var r = { action: 'getcoredump', value: (process.coreDumpLocation != null) }; + var coreDumpPath = null; + if (process.platform == 'win32') { coreDumpPath = process.coreDumpLocation; } else { coreDumpPath = (process.cwd() != '//') ? fs.existsSync(process.cwd() + 'core') : null; } + if ((coreDumpPath != null) && (fs.existsSync(coreDumpPath))) { r.exists = (db.Get('CoreDumpTime') != require('fs').statSync(coreDumpPath).mtime); } + if (r.exists == true) { r.agenthashhex = getSHA384FileHash(process.execPath).toString('hex'); } + mesh.SendCommand(JSON.stringify(r)); + default: + // Unknown action, ignore it. + break; + } + } + } + + // Called when a file changed in the file system + /* + function onFileWatcher(a, b) { + console.log('onFileWatcher', a, b, this.path); + var response = getDirectoryInfo(this.path); + if ((response != undefined) && (response != null)) { this.tunnel.s.write(JSON.stringify(response)); } + } + */ + + function getSystemInformation(func) { + try { + var results = { hardware: require('identifiers').get() }; // Hardware info + if (results.hardware && results.hardware.windows) { + // Remove extra entries and things that change quickly + var x = results.hardware.windows.osinfo; + try { delete x.FreePhysicalMemory; } catch (e) { } + try { delete x.FreeSpaceInPagingFiles; } catch (e) { } + try { delete x.FreeVirtualMemory; } catch (e) { } + try { delete x.LocalDateTime; } catch (e) { } + try { delete x.MaxProcessMemorySize; } catch (e) { } + try { delete x.TotalVirtualMemorySize; } catch (e) { } + try { delete x.TotalVisibleMemorySize; } catch (e) { } + try { + if (results.hardware.windows.memory) { for (var i in results.hardware.windows.memory) { delete results.hardware.windows.memory[i].Node; } } + if (results.hardware.windows.osinfo) { delete results.hardware.windows.osinfo.Node; } + if (results.hardware.windows.partitions) { for (var i in results.hardware.windows.partitions) { delete results.hardware.windows.partitions[i].Node; } } + } catch (e) { } + } + if (process.platform == 'win32') { results.pendingReboot = require('win-info').pendingReboot(); } // Pending reboot + /* + if (process.platform == 'win32') { + var defragResult = function (r) { + if (typeof r == 'object') { results[this.callname] = r; } + if (this.callname == 'defrag') { + var pr = require('win-info').installedApps(); // Installed apps + pr.callname = 'installedApps'; + pr.sessionid = data.sessionid; + pr.then(defragResult, defragResult); + } + else { + results.winpatches = require('win-info').qfe(); // Windows patches + results.hash = require('SHA384Stream').create().syncHash(JSON.stringify(results)).toString('hex'); + func(results); + } + } + var pr = require('win-info').defrag({ volume: 'C:' }); // Defrag TODO + pr.callname = 'defrag'; + pr.sessionid = data.sessionid; + pr.then(defragResult, defragResult); + } else { + */ + results.hash = require('SHA384Stream').create().syncHash(JSON.stringify(results)).toString('hex'); + func(results); + //} + } catch (e) { func(null, e); } + } + + // Get a formated response for a given directory path + function getDirectoryInfo(reqpath) { + var response = { path: reqpath, dir: [] }; + if (((reqpath == undefined) || (reqpath == '')) && (process.platform == 'win32')) { + // List all the drives in the root, or the root itself + var results = null; + try { results = fs.readDrivesSync(); } catch (e) { } // TODO: Anyway to get drive total size and free space? Could draw a progress bar. + if (results != null) { + for (var i = 0; i < results.length; ++i) { + var drive = { n: results[i].name, t: 1 }; + if (results[i].type == 'REMOVABLE') { drive.dt = 'removable'; } // TODO: See if this is USB/CDROM or something else, we can draw icons. + response.dir.push(drive); + } + } + } else { + // List all the files and folders in this path + if (reqpath == '') { reqpath = '/'; } + var results = null, xpath = obj.path.join(reqpath, '*'); + //if (process.platform == "win32") { xpath = xpath.split('/').join('\\'); } + try { results = fs.readdirSync(xpath); } catch (e) { } + if (results != null) { + for (var i = 0; i < results.length; ++i) { + if ((results[i] != '.') && (results[i] != '..')) { + var stat = null, p = obj.path.join(reqpath, results[i]); + //if (process.platform == "win32") { p = p.split('/').join('\\'); } + try { stat = fs.statSync(p); } catch (e) { } // TODO: Get file size/date + if ((stat != null) && (stat != undefined)) { + if (stat.isDirectory() == true) { + response.dir.push({ n: results[i], t: 2, d: stat.mtime }); + } else { + response.dir.push({ n: results[i], t: 3, s: stat.size, d: stat.mtime }); + } + } + } + } + } + } + return response; + } + + // Tunnel callback operations + function onTunnelUpgrade(response, s, head) { + this.s = s; + s.httprequest = this; + s.end = onTunnelClosed; + s.tunnel = this; + s.descriptorMetadata = "MeshAgent_relayTunnel"; + + if (require('MeshAgent').idleTimeout != null) + { + s.setTimeout(require('MeshAgent').idleTimeout * 1000); + s.on('timeout', function () + { + this.ping(); + this.setTimeout(require('MeshAgent').idleTimeout * 1000); + }); + } + + //sendConsoleText('onTunnelUpgrade - ' + this.tcpport + ' - ' + this.udpport); + + if (this.tcpport != null) { + // This is a TCP relay connection, pause now and try to connect to the target. + s.pause(); + s.data = onTcpRelayServerTunnelData; + var connectionOptions = { port: parseInt(this.tcpport) }; + if (this.tcpaddr != null) { connectionOptions.host = this.tcpaddr; } else { connectionOptions.host = '127.0.0.1'; } + s.tcprelay = net.createConnection(connectionOptions, onTcpRelayTargetTunnelConnect); + s.tcprelay.peerindex = this.index; + + // Add the TCP session to the count and update the server + if (s.httprequest.userid != null) { + if (tunnelUserCount.tcp[s.httprequest.userid] == null) { tunnelUserCount.tcp[s.httprequest.userid] = 1; } else { tunnelUserCount.tcp[s.httprequest.userid]++; } + try { mesh.SendCommand({ action: 'sessions', type: 'tcp', value: tunnelUserCount.tcp }); } catch (e) { } + broadcastSessionsToRegisteredApps(); + } + } if (this.udpport != null) { + // This is a UDP relay connection, get the UDP socket setup. // TODO: *************** + s.data = onUdpRelayServerTunnelData; + s.udprelay = require('dgram').createSocket({ type: 'udp4' }); + s.udprelay.bind({ port: 0 }); + s.udprelay.peerindex = this.index; + s.udprelay.on('message', onUdpRelayTargetTunnelConnect); + s.udprelay.udpport = this.udpport; + s.udprelay.udpaddr = this.udpaddr; + s.udprelay.first = true; + + // Add the UDP session to the count and update the server + if (s.httprequest.userid != null) { + if (tunnelUserCount.udp[s.httprequest.userid] == null) { tunnelUserCount.udp[s.httprequest.userid] = 1; } else { tunnelUserCount.udp[s.httprequest.userid]++; } + try { mesh.SendCommand({ action: 'sessions', type: 'udp', value: tunnelUserCount.tcp }); } catch (e) { } + broadcastSessionsToRegisteredApps(); + } + } else { + // This is a normal connect for KVM/Terminal/Files + s.data = onTunnelData; + } + } + + // Called when UDP relay data is received // TODO**** + function onUdpRelayTargetTunnelConnect(data) { + var peerTunnel = tunnels[this.peerindex]; + peerTunnel.s.write(data); + } + + // Called when we get data from the server for a TCP relay (We have to skip the first received 'c' and pipe the rest) + function onUdpRelayServerTunnelData(data) { + if (this.udprelay.first === true) { + delete this.udprelay.first; // Skip the first 'c' that is received. + } else { + this.udprelay.send(data, parseInt(this.udprelay.udpport), this.udprelay.udpaddr ? this.udprelay.udpaddr : '127.0.0.1'); + } + } + + // Called when the TCP relay target is connected + function onTcpRelayTargetTunnelConnect() { + var peerTunnel = tunnels[this.peerindex]; + this.pipe(peerTunnel.s); // Pipe Target --> Server + peerTunnel.s.first = true; + peerTunnel.s.resume(); + } + + // Called when we get data from the server for a TCP relay (We have to skip the first received 'c' and pipe the rest) + function onTcpRelayServerTunnelData(data) { + if (this.first == true) { + this.first = false; + this.pipe(this.tcprelay, { dataTypeSkip: 1 }); // Pipe Server --> Target (don't pipe text type websocket frames) + } + } + + function onTunnelClosed() { + var tunnel = tunnels[this.httprequest.index]; + if (tunnel == null) return; // Stop duplicate calls. + + // If this is a routing session, clean up and send the new session counts. + if (this.httprequest.userid != null) { + if (this.httprequest.tcpport != null) { + if (tunnelUserCount.tcp[this.httprequest.userid] != null) { tunnelUserCount.tcp[this.httprequest.userid]--; if (tunnelUserCount.tcp[this.httprequest.userid] <= 0) { delete tunnelUserCount.tcp[this.httprequest.userid]; } } + try { mesh.SendCommand({ action: 'sessions', type: 'tcp', value: tunnelUserCount.tcp }); } catch (e) { } + broadcastSessionsToRegisteredApps(); + } else if (this.httprequest.udpport != null) { + if (tunnelUserCount.udp[this.httprequest.userid] != null) { tunnelUserCount.udp[this.httprequest.userid]--; if (tunnelUserCount.udp[this.httprequest.userid] <= 0) { delete tunnelUserCount.udp[this.httprequest.userid]; } } + try { mesh.SendCommand({ action: 'sessions', type: 'udp', value: tunnelUserCount.udp }); } catch (e) { } + broadcastSessionsToRegisteredApps(); + } + } + + // Sent tunnel statistics to the server, only send this if compression was used. + if ((this.bytesSent_uncompressed) && (this.bytesSent_uncompressed.toString() != this.bytesSent_actual.toString())) { + mesh.SendCommand({ + action: 'tunnelCloseStats', + url: tunnel.url, + userid: tunnel.userid, + protocol: tunnel.protocol, + sessionid: tunnel.sessionid, + sent: this.bytesSent_uncompressed.toString(), + sentActual: this.bytesSent_actual.toString(), + sentRatio: this.bytesSent_ratio, + received: this.bytesReceived_uncompressed.toString(), + receivedActual: this.bytesReceived_actual.toString(), + receivedRatio: this.bytesReceived_ratio + }); + } + + //sendConsoleText("Tunnel #" + this.httprequest.index + " closed. Sent -> " + this.bytesSent_uncompressed + ' bytes (uncompressed), ' + this.bytesSent_actual + ' bytes (actual), ' + this.bytesSent_ratio + '% compression', this.httprequest.sessionid); + if (this.httprequest.index) { delete tunnels[this.httprequest.index]; } + + /* + // Close the watcher if required + if (this.httprequest.watcher != undefined) { + //console.log('Closing watcher: ' + this.httprequest.watcher.path); + //this.httprequest.watcher.close(); // TODO: This line causes the agent to crash!!!! + delete this.httprequest.watcher; + } + */ + + // If there is a upload or download active on this connection, close the file + if (this.httprequest.uploadFile) { fs.closeSync(this.httprequest.uploadFile); delete this.httprequest.uploadFile; delete this.httprequest.uploadFileid; delete this.httprequest.uploadFilePath; } + if (this.httprequest.downloadFile) { delete this.httprequest.downloadFile; } + + // Clean up WebRTC + if (this.webrtc != null) { + if (this.webrtc.rtcchannel) { try { this.webrtc.rtcchannel.close(); } catch (e) { } this.webrtc.rtcchannel.removeAllListeners('data'); this.webrtc.rtcchannel.removeAllListeners('end'); delete this.webrtc.rtcchannel; } + if (this.webrtc.websocket) { delete this.webrtc.websocket; } + try { this.webrtc.close(); } catch (e) { } + this.webrtc.removeAllListeners('connected'); + this.webrtc.removeAllListeners('disconnected'); + this.webrtc.removeAllListeners('dataChannel'); + delete this.webrtc; + } + + // Clean up WebSocket + this.removeAllListeners('data'); + } + function onTunnelSendOk() { /*sendConsoleText("Tunnel #" + this.index + " SendOK.", this.sessionid);*/ } + function onTunnelData(data) { + //console.log("OnTunnelData"); + //sendConsoleText('OnTunnelData, ' + data.length + ', ' + typeof data + ', ' + data); + + // If this is upload data, save it to file + if ((this.httprequest.uploadFile) && (typeof data == 'object') && (data[0] != 123)) { + // Save the data to file being uploaded. + if (data[0] == 0) { + // If data starts with zero, skip the first byte. This is used to escape binary file data from JSON. + try { fs.writeSync(this.httprequest.uploadFile, data, 1, data.length - 1); } catch (e) { sendConsoleText('FileUpload Error'); this.write(Buffer.from(JSON.stringify({ action: 'uploaderror' }))); return; } // Write to the file, if there is a problem, error out. + } else { + // If data does not start with zero, save as-is. + try { fs.writeSync(this.httprequest.uploadFile, data); } catch (e) { sendConsoleText('FileUpload Error'); this.write(Buffer.from(JSON.stringify({ action: 'uploaderror' }))); return; } // Write to the file, if there is a problem, error out. + } + this.write(Buffer.from(JSON.stringify({ action: 'uploadack', reqid: this.httprequest.uploadFileid }))); // Ask for more data. + return; + } + + if (this.httprequest.state == 0) { + // Check if this is a relay connection + if ((data == 'c') || (data == 'cr')) { this.httprequest.state = 1; /*sendConsoleText("Tunnel #" + this.httprequest.index + " now active", this.httprequest.sessionid);*/ } + } + else + { + // Handle tunnel data + if (this.httprequest.protocol == 0) { // 1 = Terminal (admin), 2 = Desktop, 5 = Files, 6 = PowerShell (admin), 7 = Plugin Data Exchange, 8 = Terminal (user), 9 = PowerShell (user), 10 = FileTransfer + // Take a look at the protocol + if ((data.length > 3) && (data[0] == '{')) { onTunnelControlData(data, this); return; } + this.httprequest.protocol = parseInt(data); + if (typeof this.httprequest.protocol != 'number') { this.httprequest.protocol = 0; } + if (this.httprequest.protocol == 10) { + // + // Basic file transfer + // + var stats = null; + if ((process.platform != 'win32') && (this.httprequest.xoptions.file.startsWith('/') == false)) { this.httprequest.xoptions.file = '/' + this.httprequest.xoptions.file; } + try { stats = require('fs').statSync(this.httprequest.xoptions.file) } catch (e) { } + try { if (stats) { this.httprequest.downloadFile = fs.createReadStream(this.httprequest.xoptions.file, { flags: 'rbN' }); } } catch (e) { } + if (this.httprequest.downloadFile) { + //sendConsoleText('BasicFileTransfer, ok, ' + this.httprequest.xoptions.file + ', ' + JSON.stringify(stats)); + this.write(JSON.stringify({ op: 'ok', size: stats.size })); + this.httprequest.downloadFile.pipe(this); + this.httprequest.downloadFile.end = function () { } + } else { + //sendConsoleText('BasicFileTransfer, cancel, ' + this.httprequest.xoptions.file); + this.write(JSON.stringify({ op: 'cancel' })); + } + } + else if ((this.httprequest.protocol == 1) || (this.httprequest.protocol == 6) || (this.httprequest.protocol == 8) || (this.httprequest.protocol == 9)) + { + // + // Remote Terminal + // + + // Check user access rights for terminal + if (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) || ((this.httprequest.rights != 0xFFFFFFFF) && ((this.httprequest.rights & MESHRIGHT_NOTERMINAL) != 0))) { + // Disengage this tunnel, user does not have the rights to do this!! + this.httprequest.protocol = 999999; + this.httprequest.s.end(); + sendConsoleText("Error: No Terminal Control Rights."); + return; + } + + this.descriptorMetadata = "Remote Terminal"; + + if (process.platform == 'win32') + { + if (!require('win-terminal').PowerShellCapable() && (this.httprequest.protocol == 6 || this.httprequest.protocol == 9)) + { + this.httprequest.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: 'PowerShell is not supported on this version of windows', msgid: 1 })); + this.httprequest.s.end(); + return; + } + } + + var prom = require('promise'); + this.httprequest.tpromise = new prom(function (res, rej) { this._res = res; this._rej = rej; }); + this.httprequest.tpromise.that = this; + this.httprequest.tpromise.httprequest = this.httprequest; + + this.end = function () + { + if (this.httprequest.tpromise._consent) { this.httprequest.tpromise._consent.close(); } + if (this.httprequest.connectionPromise) { this.httprequest.connectionPromise._rej('Closed'); } + + // Remove the terminal session to the count to update the server + if (this.httprequest.userid != null) + { + if (tunnelUserCount.terminal[this.httprequest.userid] != null) { tunnelUserCount.terminal[this.httprequest.userid]--; if (tunnelUserCount.terminal[this.httprequest.userid] <= 0) { delete tunnelUserCount.terminal[this.httprequest.userid]; } } + try { mesh.SendCommand({ action: 'sessions', type: 'terminal', value: tunnelUserCount.terminal }); } catch (e) { } + broadcastSessionsToRegisteredApps(); + } + + if (process.platform == 'win32') + { + // Unpipe the web socket + this.unpipe(this.httprequest._term); + if (this.httprequest._term) { this.httprequest._term.unpipe(this); } + + // Unpipe the WebRTC channel if needed (This will also be done when the WebRTC channel ends). + if (this.rtcchannel) + { + this.rtcchannel.unpipe(this.httprequest._term); + if (this.httprequest._term) { this.httprequest._term.unpipe(this.rtcchannel); } + } + + // Clean up + if (this.httprequest._term) { this.httprequest._term.end(); } + this.httprequest._term = null; + } + }; + + // Perform User-Consent if needed. + if (this.httprequest.consent && (this.httprequest.consent & 16)) + { + this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 })); + var consentMessage = this.httprequest.username + " requesting remote terminal access. Grant access?", consentTitle = 'MeshCentral'; + if (this.httprequest.soptions != null) { + if (this.httprequest.soptions.consentTitle != null) { consentTitle = this.httprequest.soptions.consentTitle; } + if (this.httprequest.soptions.consentMsgTerminal != null) { consentMessage = this.httprequest.soptions.consentMsgTerminal.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); } + } + this.httprequest.tpromise._consent = require('message-box').create(consentTitle, consentMessage, 30); + this.httprequest.tpromise._consent.retPromise = this.httprequest.tpromise; + this.httprequest.tpromise._consent.then( + function () + { + // Success + MeshServerLogEx(27, null, "Local user accepted remote terminal request (" + this.retPromise.httprequest.remoteaddr + ")", this.retPromise.that.httprequest); + this.retPromise.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null, msgid: 0 })); + this.retPromise._consent = null; + this.retPromise._res(); + }, + function (e) + { + // Denied + MeshServerLogEx(28, null, "Local user rejected remote terminal request (" + this.retPromise.that.httprequest.remoteaddr + ")", this.retPromise.that.httprequest); + this.retPromise.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 })); + this.retPromise._rej(e.toString()); + }); + } + else + { + // User-Consent is not required, so just resolve this promise + this.httprequest.tpromise._res(); + } + + + this.httprequest.tpromise.then( + function () + { + this.httprequest.connectionPromise = new prom(function (res, rej) { this._res = res; this._rej = rej; }); + this.httprequest.connectionPromise.ws = this.that; + + // Start Terminal + if(process.platform == 'win32') + { + try + { + var cols = 80, rows = 25; + if (this.httprequest.xoptions) + { + if (this.httprequest.xoptions.rows) { rows = this.httprequest.xoptions.rows; } + if (this.httprequest.xoptions.cols) { cols = this.httprequest.xoptions.cols; } + } + + if ((this.httprequest.protocol == 1) || (this.httprequest.protocol == 6)) + { + // Admin Terminal + if (require('win-virtual-terminal').supported) + { + // ConPTY PseudoTerminal + // this.httprequest._term = require('win-virtual-terminal')[this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'](80, 25); + + // The above line is commented out, because there is a bug with ClosePseudoConsole() API, so this is the workaround + this.httprequest._dispatcher = require('win-dispatcher').dispatch({ modules: [{ name: 'win-virtual-terminal', script: getJSModule('win-virtual-terminal') }], launch: { module: 'win-virtual-terminal', method: (this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'), args: [cols, rows] } }); + this.httprequest._dispatcher.httprequest = this.httprequest; + this.httprequest._dispatcher.on('connection', function (c) + { + if (this.httprequest.connectionPromise.completed) + { + c.end(); + } + else + { + this.httprequest.connectionPromise._res(c); + } + }); + } + else + { + // Legacy Terminal + this.httprequest.connectionPromise._res(require('win-terminal')[this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'](cols, rows)); + } + } + else + { + // Logged in user + var userPromise = require('user-sessions').enumerateUsers(); + userPromise.that = this; + userPromise.then(function (u) + { + var that = this.that; + if (u.Active.length > 0) + { + var username = u.Active[0].Username; + if (require('win-virtual-terminal').supported) + { + // ConPTY PseudoTerminal + that.httprequest._dispatcher = require('win-dispatcher').dispatch({ user: username, modules: [{ name: 'win-virtual-terminal', script: getJSModule('win-virtual-terminal') }], launch: { module: 'win-virtual-terminal', method: (that.httprequest.protocol == 9 ? 'StartPowerShell' : 'Start'), args: [cols, rows] } }); + } + else + { + // Legacy Terminal + that.httprequest._dispatcher = require('win-dispatcher').dispatch({ user: username, modules: [{ name: 'win-terminal', script: getJSModule('win-terminal') }], launch: { module: 'win-terminal', method: (that.httprequest.protocol == 9 ? 'StartPowerShell' : 'Start'), args: [cols, rows] } }); + } + that.httprequest._dispatcher.ws = that; + that.httprequest._dispatcher.on('connection', function (c) + { + if (this.ws.httprequest.connectionPromise.completed) + { + c.end(); + } + else + { + this.ws.httprequest.connectionPromise._res(c); + } + }); + } + }); + } + } + catch (e) + { + this.httprequest.connectionPromise._rej('Failed to start remote terminal session, ' + e.toString()); + } + } + else + { + try + { + var bash = fs.existsSync('/bin/bash') ? '/bin/bash' : false; + var sh = fs.existsSync('/bin/sh') ? '/bin/sh' : false; + var login = process.platform == 'linux' ? '/bin/login' : '/usr/bin/login'; + + var env = { HISTCONTROL: 'ignoreboth' }; + if (this.httprequest.xoptions) + { + if (this.httprequest.xoptions.rows) { env.LINES = ('' + this.httprequest.xoptions.rows); } + if (this.httprequest.xoptions.cols) { env.COLUMNS = ('' + this.httprequest.xoptions.cols); } + } + var options = { type: childProcess.SpawnTypes.TERM, uid: (this.httprequest.protocol == 8) ? require('user-sessions').consoleUid() : null, env: env }; + if (this.httprequest.xoptions && this.httprequest.xoptions.requireLogin) + { + if (!require('fs').existsSync(login)) { throw ('Unable to spawn login process'); } + this.httprequest.connectionPromise._res(childProcess.execFile(login, ['login'], options)); // Start login shell + } + else if (bash) + { + var p = childProcess.execFile(bash, ['bash'], options); // Start bash + // Spaces at the beginning of lines are needed to hide commands from the command history + if (process.platform == 'linux') { p.stdin.write(' alias ls=\'ls --color=auto\';clear\n'); } + this.httprequest.connectionPromise._res(p); + } + else if (sh) + { + var p = childProcess.execFile(sh, ['sh'], options); // Start sh + // Spaces at the beginning of lines are needed to hide commands from the command history + if (process.platform == 'linux') { p.stdin.write(' alias ls=\'ls --color=auto\';clear\n'); } + this.httprequest.connectionPromise._res(p); + } + else + { + this.httprequest.connectionPromise._rej('Failed to start remote terminal session, no shell found'); + } + } + catch (e) + { + this.httprequest.connectionPromise._rej('Failed to start remote terminal session, ' + e.toString()); + } + } + + this.httprequest.connectionPromise.then( + function (term) + { + // SUCCESS + var stdoutstream; + var stdinstream; + if (process.platform == 'win32') + { + this.ws.httprequest._term = term; + this.ws.httprequest._term.tunnel = this.ws; + stdoutstream = stdinstream = term; + } + else + { + term.descriptorMetadata = 'Remote Terminal'; + this.ws.httprequest.process = term; + this.ws.httprequest.process.tunnel = this.ws; + term.stderr.stdout = term.stdout; + term.stderr.on('data', function (c) { this.stdout.write(c); }); + stdoutstream = term.stdout; + stdinstream = term.stdin; + this.ws.prependListener('end', function () { this.httprequest.process.kill(); }); + term.prependListener('exit', function () { this.tunnel.end(); }); + } + + this.ws.removeAllListeners('data'); + this.ws.on('data', onTunnelControlData); + + stdoutstream.pipe(this.ws, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text. + this.ws.pipe(stdinstream, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. + + // Add the terminal session to the count to update the server + if (this.ws.httprequest.userid != null) + { + if (tunnelUserCount.terminal[this.ws.httprequest.userid] == null) { tunnelUserCount.terminal[this.ws.httprequest.userid] = 1; } else { tunnelUserCount.terminal[this.ws.httprequest.userid]++; } + try { mesh.SendCommand({ action: 'sessions', type: 'terminal', value: tunnelUserCount.terminal }); } catch (e) { } + broadcastSessionsToRegisteredApps(); + } + + // Toast Notification, if required + if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 2)) + { + // User Notifications is required + var notifyMessage = this.ws.httprequest.username + " started a remote terminal session.", notifyTitle = "MeshCentral"; + if (this.ws.httprequest.soptions != null) { + if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; } + if (this.ws.httprequest.soptions.notifyMsgTerminal != null) { notifyMessage = this.ws.httprequest.soptions.notifyMsgTerminal.replace('{0}', this.ws.httprequest.realname).replace('{1}', this.ws.httprequest.username); } + } + try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (e) { } + } + }, + function (e) + { + // FAILED to connect terminal + this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 })); + this.ws.end(); + }); + }, + function (e) + { + // DO NOT start terminal + this.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 })); + this.that.end(); + }); + } + else if (this.httprequest.protocol == 2) + { + // + // Remote KVM + // + + // Check user access rights for desktop + if ((((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) && ((this.httprequest.rights & MESHRIGHT_REMOTEVIEW) == 0)) || ((this.httprequest.rights != 0xFFFFFFFF) && ((this.httprequest.rights & MESHRIGHT_NODESKTOP) != 0))) { + // Disengage this tunnel, user does not have the rights to do this!! + this.httprequest.protocol = 999999; + this.httprequest.s.end(); + sendConsoleText("Error: No Desktop Control Rights."); + return; + } + + this.descriptorMetadata = "Remote KVM"; + + // Look for a TSID + var tsid = null; + if ((this.httprequest.xoptions != null) && (typeof this.httprequest.xoptions.tsid == 'number')) { tsid = this.httprequest.xoptions.tsid; } + require('MeshAgent')._tsid = tsid; + + // Remote desktop using native pipes + this.httprequest.desktop = { state: 0, kvm: mesh.getRemoteDesktopStream(tsid), tunnel: this }; + this.httprequest.desktop.kvm.parent = this.httprequest.desktop; + this.desktop = this.httprequest.desktop; + + // Add ourself to the list of remote desktop sessions + if (this.httprequest.desktop.kvm.tunnels == null) { this.httprequest.desktop.kvm.tunnels = []; } + this.httprequest.desktop.kvm.tunnels.push(this); + + // Send a metadata update to all desktop sessions + var users = {}; + if (this.httprequest.desktop.kvm.tunnels != null) { + for (var i in this.httprequest.desktop.kvm.tunnels) { try { var userid = this.httprequest.desktop.kvm.tunnels[i].httprequest.userid; if (users[userid] == null) { users[userid] = 1; } else { users[userid]++; } } catch (e) { } } + for (var i in this.httprequest.desktop.kvm.tunnels) { try { this.httprequest.desktop.kvm.tunnels[i].write(JSON.stringify({ ctrlChannel: '102938', type: 'metadata', users: users })); } catch (e) { } } + tunnelUserCount.desktop = users; + try { mesh.SendCommand({ action: 'sessions', type: 'kvm', value: users }); } catch (e) { } + broadcastSessionsToRegisteredApps(); + } + + this.end = function () { + --this.desktop.kvm.connectionCount; + + // Remove ourself from the list of remote desktop session + var i = this.desktop.kvm.tunnels.indexOf(this); + if (i >= 0) { this.desktop.kvm.tunnels.splice(i, 1); } + + // Send a metadata update to all desktop sessions + var users = {}; + if (this.httprequest.desktop.kvm.tunnels != null) { + for (var i in this.httprequest.desktop.kvm.tunnels) { try { var userid = this.httprequest.desktop.kvm.tunnels[i].httprequest.userid; if (users[userid] == null) { users[userid] = 1; } else { users[userid]++; } } catch (e) { } } + for (var i in this.httprequest.desktop.kvm.tunnels) { try { this.httprequest.desktop.kvm.tunnels[i].write(JSON.stringify({ ctrlChannel: '102938', type: 'metadata', users: users })); } catch (e) { } } + tunnelUserCount.desktop = users; + try { mesh.SendCommand({ action: 'sessions', type: 'kvm', value: users }); } catch (e) { } + broadcastSessionsToRegisteredApps(); + } + + // Unpipe the web socket + try + { + this.unpipe(this.httprequest.desktop.kvm); + this.httprequest.desktop.kvm.unpipe(this); + } + catch(e) { } + + // Unpipe the WebRTC channel if needed (This will also be done when the WebRTC channel ends). + if (this.rtcchannel) + { + try + { + this.rtcchannel.unpipe(this.httprequest.desktop.kvm); + this.httprequest.desktop.kvm.unpipe(this.rtcchannel); + } + catch(e) { } + } + + // Place wallpaper back if needed + // TODO + + if (this.desktop.kvm.connectionCount == 0) { + // Display a toast message. This may not be supported on all platforms. + // try { require('toaster').Toast('MeshCentral', 'Remote Desktop Control Ended.'); } catch (e) { } + + this.httprequest.desktop.kvm.end(); + if (this.httprequest.desktop.kvm.connectionBar) { + this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close'); + this.httprequest.desktop.kvm.connectionBar.close(); + this.httprequest.desktop.kvm.connectionBar = null; + } + } else { + for (var i in this.httprequest.desktop.kvm.users) { + if ((this.httprequest.desktop.kvm.users[i] == this.httprequest.username) && this.httprequest.desktop.kvm.connectionBar) { + for (var j in this.httprequest.desktop.kvm.rusers) { if (this.httprequest.desktop.kvm.rusers[j] == this.httprequest.realname) { this.httprequest.desktop.kvm.rusers.splice(j, 1); break; } } + this.httprequest.desktop.kvm.users.splice(i, 1); + this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close'); + this.httprequest.desktop.kvm.connectionBar.close(); + this.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(this.httprequest.privacybartext.replace('{0}', this.httprequest.desktop.kvm.users.join(', ')).replace('{1}', this.httprequest.desktop.kvm.rusers.join(', ')), require('MeshAgent')._tsid); + this.httprequest.desktop.kvm.connectionBar.httprequest = this.httprequest; + this.httprequest.desktop.kvm.connectionBar.on('close', function () { + MeshServerLogEx(29, null, "Remote Desktop Connection forcefully closed by local user (" + this.httprequest.remoteaddr + ")", this.httprequest); + for (var i in this.httprequest.desktop.kvm._pipedStreams) { + this.httprequest.desktop.kvm._pipedStreams[i].end(); + } + this.httprequest.desktop.kvm.end(); + }); + break; + } + } + } + }; + if (this.httprequest.desktop.kvm.hasOwnProperty('connectionCount')) { + this.httprequest.desktop.kvm.connectionCount++; + this.httprequest.desktop.kvm.rusers.push(this.httprequest.realname); + this.httprequest.desktop.kvm.users.push(this.httprequest.username); + this.httprequest.desktop.kvm.rusers.sort(); + this.httprequest.desktop.kvm.users.sort(); + } else { + this.httprequest.desktop.kvm.connectionCount = 1; + this.httprequest.desktop.kvm.rusers = [this.httprequest.realname]; + this.httprequest.desktop.kvm.users = [this.httprequest.username]; + } + + if ((this.httprequest.rights == 0xFFFFFFFF) || (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) != 0) && ((this.httprequest.rights & MESHRIGHT_REMOTEVIEW) == 0))) { + // If we have remote control rights, pipe the KVM input + this.pipe(this.httprequest.desktop.kvm, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. Pipe the Browser --> KVM input. + } else { + // We need to only pipe non-mouse & non-keyboard inputs. + //sendConsoleText('Warning: No Remote Desktop Input Rights.'); + // TODO!!! + } + + // Perform notification if needed. Toast messages may not be supported on all platforms. + if (this.httprequest.consent && (this.httprequest.consent & 8)) + { + // User Consent Prompt is required + // Send a console message back using the console channel, "\n" is supported. + this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 })); + var consentMessage = this.httprequest.realname + " requesting remote desktop access. Grant access?", consentTitle = 'MeshCentral'; + if (this.httprequest.soptions != null) { + if (this.httprequest.soptions.consentTitle != null) { consentTitle = this.httprequest.soptions.consentTitle; } + if (this.httprequest.soptions.consentMsgDesktop != null) { consentMessage = this.httprequest.soptions.consentMsgDesktop.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); } + } + var pr = require('message-box').create(consentTitle, consentMessage, 30, null, tsid); + pr.ws = this; + this.pause(); + this._consentpromise = pr; + this.prependOnceListener('end', function () { if (this._consentpromise && this._consentpromise.close) { this._consentpromise.close(); }}); + pr.then( + function () + { + // Success + this.ws._consentpromise = null; + MeshServerLogEx(30, null, "Starting remote desktop after local user accepted (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest); + this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null, msgid: 0 })); + if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 1)) { + // User Notifications is required + var notifyMessage = this.ws.httprequest.realname + " started a remote desktop session.", notifyTitle = "MeshCentral"; + if (this.ws.httprequest.soptions != null) { + if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; } + if (this.ws.httprequest.soptions.notifyMsgDesktop != null) { notifyMessage = this.ws.httprequest.soptions.notifyMsgDesktop.replace('{0}', this.ws.httprequest.realname).replace('{1}', this.ws.httprequest.username); } + } + try { require('toaster').Toast(notifyTitle, notifyMessage, tsid); } catch (e) { } + } + if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 0x40)) { + // Connection Bar is required + if (this.ws.httprequest.desktop.kvm.connectionBar) { + this.ws.httprequest.desktop.kvm.connectionBar.removeAllListeners('close'); + this.ws.httprequest.desktop.kvm.connectionBar.close(); + } + try { + this.ws.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(this.ws.httprequest.privacybartext.replace('{0}', this.ws.httprequest.desktop.kvm.users.join(', ')).replace('{1}', this.ws.httprequest.desktop.kvm.rusers.join(', ')), require('MeshAgent')._tsid); + MeshServerLogEx(31, null, "Remote Desktop Connection Bar Activated/Updated (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest); + } + catch (e) { + if (process.platform != 'darwin') { + MeshServerLogEx(32, null, "Remote Desktop Connection Bar Failed or Not Supported (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest); + } + } + if (this.ws.httprequest.desktop.kvm.connectionBar) { + this.ws.httprequest.desktop.kvm.connectionBar.httprequest = this.ws.httprequest; + this.ws.httprequest.desktop.kvm.connectionBar.on('close', function () { + MeshServerLogEx(33, null, "Remote Desktop Connection forcefully closed by local user (" + this.httprequest.remoteaddr + ")", this.httprequest); + for (var i in this.httprequest.desktop.kvm._pipedStreams) { + this.httprequest.desktop.kvm._pipedStreams[i].end(); + } + this.httprequest.desktop.kvm.end(); + }); + } + } + this.ws.httprequest.desktop.kvm.pipe(this.ws, { dataTypeSkip: 1 }); + this.ws.resume(); + }, + function (e) + { + // User Consent Denied/Failed + this.ws._consentpromise = null; + MeshServerLogEx(34, null, "Failed to start remote desktop after local user rejected (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest); + this.ws.end(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 })); + }); + } else { + // User Consent Prompt is not required + if (this.httprequest.consent && (this.httprequest.consent & 1)) { + // User Notifications is required + MeshServerLogEx(35, null, "Started remote desktop with toast notification (" + this.httprequest.remoteaddr + ")", this.httprequest); + var notifyMessage = this.httprequest.realname + " started a remote desktop session.", notifyTitle = "MeshCentral"; + if (this.httprequest.soptions != null) { + if (this.httprequest.soptions.notifyTitle != null) { notifyTitle = this.httprequest.soptions.notifyTitle; } + if (this.httprequest.soptions.notifyMsgDesktop != null) { notifyMessage = this.httprequest.soptions.notifyMsgDesktop.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); } + } + try { require('toaster').Toast(notifyTitle, notifyMessage, tsid); } catch (e) { } + } else { + MeshServerLogEx(36, null, "Started remote desktop without notification (" + this.httprequest.remoteaddr + ")", this.httprequest); + } + if (this.httprequest.consent && (this.httprequest.consent & 0x40)) { + // Connection Bar is required + if (this.httprequest.desktop.kvm.connectionBar) { + this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close'); + this.httprequest.desktop.kvm.connectionBar.close(); + } + try { + this.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(this.httprequest.privacybartext.replace('{0}', this.httprequest.desktop.kvm.rusers.join(', ')).replace('{1}', this.httprequest.desktop.kvm.users.join(', ')), require('MeshAgent')._tsid); + MeshServerLogEx(37, null, "Remote Desktop Connection Bar Activated/Updated (" + this.httprequest.remoteaddr + ")", this.httprequest); + } + catch (e) { + MeshServerLogEx(38, null, "Remote Desktop Connection Bar Failed or not Supported (" + this.httprequest.remoteaddr + ")", this.httprequest); + } + if (this.httprequest.desktop.kvm.connectionBar) { + this.httprequest.desktop.kvm.connectionBar.httprequest = this.httprequest; + this.httprequest.desktop.kvm.connectionBar.on('close', function () { + MeshServerLogEx(39, null, "Remote Desktop Connection forcefully closed by local user (" + this.httprequest.remoteaddr + ")", this.httprequest); + for (var i in this.httprequest.desktop.kvm._pipedStreams) { + this.httprequest.desktop.kvm._pipedStreams[i].end(); + } + this.httprequest.desktop.kvm.end(); + }); + } + } + this.httprequest.desktop.kvm.pipe(this, { dataTypeSkip: 1 }); + } + + this.removeAllListeners('data'); + this.on('data', onTunnelControlData); + //this.write('MeshCore KVM Hello!1'); + + } else if (this.httprequest.protocol == 5) { + // + // Remote Files + // + + // Check user access rights for files + if (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) || ((this.httprequest.rights != 0xFFFFFFFF) && ((this.httprequest.rights & MESHRIGHT_NOFILES) != 0))) { + // Disengage this tunnel, user does not have the rights to do this!! + this.httprequest.protocol = 999999; + this.httprequest.s.end(); + sendConsoleText("Error: No files control rights."); + return; + } + + this.descriptorMetadata = "Remote Files"; + + // Add the files session to the count to update the server + if (this.httprequest.userid != null) { + if (tunnelUserCount.files[this.httprequest.userid] == null) { tunnelUserCount.files[this.httprequest.userid] = 1; } else { tunnelUserCount.files[this.httprequest.userid]++; } + try { mesh.SendCommand({ action: 'sessions', type: 'files', value: tunnelUserCount.files }); } catch (e) { } + broadcastSessionsToRegisteredApps(); + } + + this.end = function () { + // Remove the files session from the count to update the server + if (this.httprequest.userid != null) { + if (tunnelUserCount.files[this.httprequest.userid] != null) { tunnelUserCount.files[this.httprequest.userid]--; if (tunnelUserCount.files[this.httprequest.userid] <= 0) { delete tunnelUserCount.files[this.httprequest.userid]; } } + try { mesh.SendCommand({ action: 'sessions', type: 'files', value: tunnelUserCount.files }); } catch (e) { } + broadcastSessionsToRegisteredApps(); + } + }; + + // Perform notification if needed. Toast messages may not be supported on all platforms. + if (this.httprequest.consent && (this.httprequest.consent & 32)) { + // User Consent Prompt is required + // Send a console message back using the console channel, "\n" is supported. + this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 })); + var consentMessage = this.httprequest.realname + " requesting remote file Access. Grant access?", consentTitle = 'MeshCentral'; + if (this.httprequest.soptions != null) { + if (this.httprequest.soptions.consentTitle != null) { consentTitle = this.httprequest.soptions.consentTitle; } + if (this.httprequest.soptions.consentMsgFiles != null) { consentMessage = this.httprequest.soptions.consentMsgFiles.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); } + } + var pr = require('message-box').create(consentTitle, consentMessage, 30); + pr.ws = this; + this.pause(); + this._consentpromise = pr; + this.prependOnceListener('end', function () { if (this._consentpromise && this._consentpromise.close) { this._consentpromise.close(); } }); + pr.then( + function () + { + // Success + this.ws._consentpromise = null; + MeshServerLogEx(40, null, "Starting remote files after local user accepted (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest); + this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null })); + if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 4)) { + // User Notifications is required + var notifyMessage = this.ws.httprequest.realname + " started a remote file session.", notifyTitle = "MeshCentral"; + if (this.ws.httprequest.soptions != null) { + if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; } + if (this.ws.httprequest.soptions.notifyMsgFiles != null) { notifyMessage = this.ws.httprequest.soptions.notifyMsgFiles.replace('{0}', this.ws.httprequest.realname).replace('{1}', this.ws.httprequest.username); } + } + try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (e) { } + } + this.ws.resume(); + }, + function (e) + { + // User Consent Denied/Failed + this.ws._consentpromise = null; + MeshServerLogEx(41, null, "Failed to start remote files after local user rejected (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest); + this.ws.end(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 })); + }); + } else { + // User Consent Prompt is not required + if (this.httprequest.consent && (this.httprequest.consent & 4)) { + // User Notifications is required + MeshServerLogEx(42, null, "Started remote files with toast notification (" + this.httprequest.remoteaddr + ")", this.httprequest); + var notifyMessage = this.httprequest.realname + " started a remote file session.", notifyTitle = "MeshCentral"; + if (this.httprequest.soptions != null) { + if (this.httprequest.soptions.notifyTitle != null) { notifyTitle = this.httprequest.soptions.notifyTitle; } + if (this.httprequest.soptions.notifyMsgFiles != null) { notifyMessage = this.httprequest.soptions.notifyMsgFiles.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); } + } + try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (e) { } + } else { + MeshServerLogEx(43, null, "Started remote files without notification (" + this.httprequest.remoteaddr + ")", this.httprequest); + } + this.resume(); + } + + // Setup files + // NOP + } + } else if (this.httprequest.protocol == 1) { + // Send data into terminal stdin + //this.write(data); // Echo back the keys (Does not seem to be a good idea) + } else if (this.httprequest.protocol == 2) { + // Send data into remote desktop + if (this.httprequest.desktop.state == 0) { + this.write(Buffer.from(String.fromCharCode(0x11, 0xFE, 0x00, 0x00, 0x4D, 0x45, 0x53, 0x48, 0x00, 0x00, 0x00, 0x00, 0x02))); + this.httprequest.desktop.state = 1; + } else { + this.httprequest.desktop.write(data); + } + } else if (this.httprequest.protocol == 5) { + // Process files commands + var cmd = null; + try { cmd = JSON.parse(data); } catch (e) { }; + if (cmd == null) { return; } + if ((cmd.ctrlChannel == '102938') || ((cmd.type == 'offer') && (cmd.sdp != null))) { onTunnelControlData(cmd, this); return; } // If this is control data, handle it now. + if (cmd.action == undefined) { return; } + //sendConsoleText('CMD: ' + JSON.stringify(cmd)); + + if ((cmd.path != null) && (process.platform != 'win32') && (cmd.path[0] != '/')) { cmd.path = '/' + cmd.path; } // Add '/' to paths on non-windows + //console.log(objToString(cmd, 0, ' ')); + switch (cmd.action) { + case 'ls': { + /* + // Close the watcher if required + var samepath = ((this.httprequest.watcher != undefined) && (cmd.path == this.httprequest.watcher.path)); + if ((this.httprequest.watcher != undefined) && (samepath == false)) { + //console.log('Closing watcher: ' + this.httprequest.watcher.path); + //this.httprequest.watcher.close(); // TODO: This line causes the agent to crash!!!! + delete this.httprequest.watcher; + } + */ + + // Send the folder content to the browser + var response = getDirectoryInfo(cmd.path); + if (cmd.reqid != undefined) { response.reqid = cmd.reqid; } + this.write(Buffer.from(JSON.stringify(response))); + + /* + // Start the directory watcher + if ((cmd.path != '') && (samepath == false)) { + var watcher = fs.watch(cmd.path, onFileWatcher); + watcher.tunnel = this.httprequest; + watcher.path = cmd.path; + this.httprequest.watcher = watcher; + //console.log('Starting watcher: ' + this.httprequest.watcher.path); + } + */ + break; + } + case 'mkdir': { + // Create a new empty folder + fs.mkdirSync(cmd.path); + MeshServerLogEx(44, [cmd.path], "Create folder: \"" + cmd.path + "\"", this.httprequest); + break; + } + case 'rm': { + // Delete, possibly recursive delete + for (var i in cmd.delfiles) { + var p = obj.path.join(cmd.path, cmd.delfiles[i]), delcount = 0; + try { delcount = deleteFolderRecursive(p, cmd.rec); } catch (e) { } + if ((delcount == 1) && !cmd.rec) { + MeshServerLogEx(45, [p], "Delete: \"" + p + "\"", this.httprequest); + } else { + if (cmd.rec) { + MeshServerLogEx(46, [p, delcount], "Delete recursive: \"" + p + "\", " + delcount + " element(s) removed", this.httprequest); + } else { + MeshServerLogEx(47, [p, delcount], "Delete: \"" + p + "\", " + delcount + " element(s) removed", this.httprequest); + } + } + } + break; + } + case 'markcoredump': { + // If we are asking for the coredump file, set the right path. + var coreDumpPath = null; + if (process.platform == 'win32') { + if (fs.existsSync(process.coreDumpLocation)) { coreDumpPath = process.coreDumpLocation; } + } else { + if ((process.cwd() != '//') && fs.existsSync(process.cwd() + 'core')) { coreDumpPath = process.cwd() + 'core'; } + } + if (coreDumpPath != null) { db.Put('CoreDumpTime', require('fs').statSync(coreDumpPath).mtime); } + break; + } + case 'rename': { + // Rename a file or folder + var oldfullpath = obj.path.join(cmd.path, cmd.oldname); + var newfullpath = obj.path.join(cmd.path, cmd.newname); + MeshServerLogEx(48, [oldfullpath, cmd.newname], 'Rename: \"' + oldfullpath + '\" to \"' + cmd.newname + '\"', this.httprequest); + try { fs.renameSync(oldfullpath, newfullpath); } catch (e) { console.log(e); } + break; + } + case 'download': { + // Download a file + var sendNextBlock = 0; + if (cmd.sub == 'start') { // Setup the download + if ((cmd.path == null) && (cmd.ask == 'coredump')) { // If we are asking for the coredump file, set the right path. + if (process.platform == 'win32') { + if (fs.existsSync(process.coreDumpLocation)) { cmd.path = process.coreDumpLocation; } + } else { + if ((process.cwd() != '//') && fs.existsSync(process.cwd() + 'core')) { cmd.path = process.cwd() + 'core'; } + } + } + MeshServerLogEx(49, [cmd.path], 'Download: \"' + cmd.path + '\"', this.httprequest); + if ((cmd.path == null) || (this.filedownload != null)) { this.write({ action: 'download', sub: 'cancel', id: this.filedownload.id }); delete this.filedownload; } + this.filedownload = { id: cmd.id, path: cmd.path, ptr: 0 } + try { this.filedownload.f = fs.openSync(this.filedownload.path, 'rbN'); } catch (e) { this.write({ action: 'download', sub: 'cancel', id: this.filedownload.id }); delete this.filedownload; } + if (this.filedownload) { this.write({ action: 'download', sub: 'start', id: cmd.id }); } + } else if ((this.filedownload != null) && (cmd.id == this.filedownload.id)) { // Download commands + if (cmd.sub == 'startack') { sendNextBlock = ((typeof cmd.ack == 'number') ? cmd.ack : 8); } else if (cmd.sub == 'stop') { delete this.filedownload; } else if (cmd.sub == 'ack') { sendNextBlock = 1; } + } + // Send the next download block(s) + while (sendNextBlock > 0) { + sendNextBlock--; + var buf = Buffer.alloc(16384); + var len = fs.readSync(this.filedownload.f, buf, 4, 16380, null); + this.filedownload.ptr += len; + if (len < 16380) { buf.writeInt32BE(0x01000001, 0); fs.closeSync(this.filedownload.f); delete this.filedownload; sendNextBlock = 0; } else { buf.writeInt32BE(0x01000000, 0); } + this.write(buf.slice(0, len + 4)); // Write as binary + } + break; + } + /* + case 'download': { + // Packet download of a file, agent to browser + if (cmd.path == undefined) break; + var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path; + //console.log('Download: ' + filepath); + try { this.httprequest.downloadFile = fs.openSync(filepath, 'rbN'); } catch (e) { this.write(Buffer.from(JSON.stringify({ action: 'downloaderror', reqid: cmd.reqid }))); break; } + this.httprequest.downloadFileId = cmd.reqid; + this.httprequest.downloadFilePtr = 0; + if (this.httprequest.downloadFile) { this.write(Buffer.from(JSON.stringify({ action: 'downloadstart', reqid: this.httprequest.downloadFileId }))); } + break; + } + case 'download2': { + // Stream download of a file, agent to browser + if (cmd.path == undefined) break; + var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path; + try { this.httprequest.downloadFile = fs.createReadStream(filepath, { flags: 'rbN' }); } catch (e) { console.log(e); } + this.httprequest.downloadFile.pipe(this); + this.httprequest.downloadFile.end = function () { } + break; + } + */ + case 'upload': { + // Upload a file, browser to agent + if (this.httprequest.uploadFile != null) { fs.closeSync(this.httprequest.uploadFile); delete this.httprequest.uploadFile; } + if (cmd.path == undefined) break; + var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path; + this.httprequest.uploadFilePath = filepath; + MeshServerLogEx(50, [filepath], 'Upload: \"' + filepath + '\"', this.httprequest); + try { this.httprequest.uploadFile = fs.openSync(filepath, 'wbN'); } catch (e) { this.write(Buffer.from(JSON.stringify({ action: 'uploaderror', reqid: cmd.reqid }))); break; } + this.httprequest.uploadFileid = cmd.reqid; + if (this.httprequest.uploadFile) { this.write(Buffer.from(JSON.stringify({ action: 'uploadstart', reqid: this.httprequest.uploadFileid }))); } + break; + } + case 'uploaddone': { + // Indicates that an upload is done + if (this.httprequest.uploadFile) { + fs.closeSync(this.httprequest.uploadFile); + this.write(Buffer.from(JSON.stringify({ action: 'uploaddone', reqid: this.httprequest.uploadFileid }))); // Indicate that we closed the file. + delete this.httprequest.uploadFile; + delete this.httprequest.uploadFileid; + delete this.httprequest.uploadFilePath; + } + break; + } + case 'uploadcancel': { + // Indicates that an upload is canceled + if (this.httprequest.uploadFile) { + fs.closeSync(this.httprequest.uploadFile); + fs.unlinkSync(this.httprequest.uploadFilePath); + this.write(Buffer.from(JSON.stringify({ action: 'uploadcancel', reqid: this.httprequest.uploadFileid }))); // Indicate that we closed the file. + delete this.httprequest.uploadFile; + delete this.httprequest.uploadFileid; + delete this.httprequest.uploadFilePath; + } + break; + } + case 'copy': { + // Copy a bunch of files from scpath to dspath + for (var i in cmd.names) { + var sc = obj.path.join(cmd.scpath, cmd.names[i]), ds = obj.path.join(cmd.dspath, cmd.names[i]); + MeshServerLogEx(51, [sc, ds], 'Copy: \"' + sc + '\" to \"' + ds + '\"', this.httprequest); + if (sc != ds) { try { fs.copyFileSync(sc, ds); } catch (e) { } } + } + break; + } + case 'move': { + // Move a bunch of files from scpath to dspath + for (var i in cmd.names) { + var sc = obj.path.join(cmd.scpath, cmd.names[i]), ds = obj.path.join(cmd.dspath, cmd.names[i]); + MeshServerLogEx(52, [sc, ds], 'Move: \"' + sc + '\" to \"' + ds + '\"', this.httprequest); + if (sc != ds) { try { fs.copyFileSync(sc, ds); fs.unlinkSync(sc); } catch (e) { } } + } + break; + } + case 'zip': + // Zip a bunch of files + if (this.zip != null) return; // Zip operating is currently running, exit now. + + // Check that the specified files exist & build full paths + var fp, stat, p = []; + for (var i in cmd.files) { fp = cmd.path + '/' + cmd.files[i]; stat = null; try { stat = fs.statSync(fp); } catch (e) { } if (stat != null) { p.push(fp); } } + if (p.length == 0) return; // No files, quit now. + + // Setup file compression + var ofile = cmd.path + '/' + cmd.output; + this.write(Buffer.from(JSON.stringify({ action: 'dialogmessage', msg: 'zipping' }))); + this.zipfile = ofile; + delete this.zipcancel; + var out = require('fs').createWriteStream(ofile, { flags: 'wb' }); + out.xws = this; + out.on('close', function () { + this.xws.write(Buffer.from(JSON.stringify({ action: 'dialogmessage', msg: null }))); + this.xws.write(Buffer.from(JSON.stringify({ action: 'refresh' }))); + if (this.xws.zipcancel === true) { fs.unlinkSync(this.xws.zipfile); } // Delete the complete file. + delete this.xws.zipcancel; + delete this.xws.zipfile; + delete this.xws.zip; + }); + this.zip = require('zip-writer').write({ files: p, basePath: cmd.path }); + this.zip.xws = this; + this.zip.on('progress', require('events').moderated(function (name, p) { this.xws.write(Buffer.from(JSON.stringify({ action: 'dialogmessage', msg: 'zippingFile', file: ((process.platform == 'win32') ? (name.split('/').join('\\')) : name), progress: p }))); }, 1000)); + this.zip.pipe(out); + break; + case 'cancel': + // Cancel zip operation if present + try { this.zipcancel = true; this.zip.cancel(function () { }); } catch (e) { } + this.zip = null; + break; + default: + // Unknown action, ignore it. + break; + } + } else if (this.httprequest.protocol == 7) { // Plugin data exchange + var cmd = null; + try { cmd = JSON.parse(data); } catch (e) { }; + if (cmd == null) { return; } + if ((cmd.ctrlChannel == '102938') || ((cmd.type == 'offer') && (cmd.sdp != null))) { onTunnelControlData(cmd, this); return; } // If this is control data, handle it now. + if (cmd.action == undefined) return; + + switch (cmd.action) { + case 'plugin': { + try { require(cmd.plugin).consoleaction(cmd, null, null, this); } catch (e) { throw e; } + break; + } + default: { + // probably shouldn't happen, but just in case this feature is expanded + } + } + + } + //sendConsoleText("Got tunnel #" + this.httprequest.index + " data: " + data, this.httprequest.sessionid); + } + } + + // Delete a directory with a files and directories within it + function deleteFolderRecursive(path, rec) { + var count = 0; + if (fs.existsSync(path)) { + if (rec == true) { + fs.readdirSync(obj.path.join(path, '*')).forEach(function (file, index) { + var curPath = obj.path.join(path, file); + if (fs.statSync(curPath).isDirectory()) { // recurse + count += deleteFolderRecursive(curPath, true); + } else { // delete file + fs.unlinkSync(curPath); + count++; + } + }); + } + fs.unlinkSync(path); + count++; + } + return count; + }; + + // Called when receiving control data on WebRTC + function onTunnelWebRTCControlData(data) { + if (typeof data != 'string') return; + var obj; + try { obj = JSON.parse(data); } catch (e) { sendConsoleText('Invalid control JSON on WebRTC: ' + data); return; } + if (obj.type == 'close') { + //sendConsoleText('Tunnel #' + this.xrtc.websocket.tunnel.index + ' WebRTC control close'); + try { this.close(); } catch (e) { } + try { this.xrtc.close(); } catch (e) { } + } + } + + // Called when receiving control data on websocket + function onTunnelControlData(data, ws) { + var obj; + if (ws == null) { ws = this; } + if (typeof data == 'string') { try { obj = JSON.parse(data); } catch (e) { sendConsoleText('Invalid control JSON: ' + data); return; } } + else if (typeof data == 'object') { obj = data; } else { return; } + //sendConsoleText('onTunnelControlData(' + ws.httprequest.protocol + '): ' + JSON.stringify(data)); + //console.log('onTunnelControlData: ' + JSON.stringify(data)); + + if (obj.action) { + switch (obj.action) { + case 'lock': { + // Lock the current user out of the desktop + try { + if (process.platform == 'win32') { + MeshServerLogEx(53, null, "Locking remote user out of desktop", ws.httprequest); + var child = require('child_process'); + child.execFile(process.env['windir'] + '\\system32\\cmd.exe', ['/c', 'RunDll32.exe user32.dll,LockWorkStation'], { type: 1 }); + } + } catch (e) { } + break; + } + default: + // Unknown action, ignore it. + break; + } + return; + } + + switch (obj.type) { + case 'options': { + // These are additional connection options passed in the control channel. + //sendConsoleText('options: ' + JSON.stringify(obj)); + delete obj.type; + ws.httprequest.xoptions = obj; + + // Set additional user consent options if present + if ((obj != null) && (typeof obj.consent == 'number')) { ws.httprequest.consent |= obj.consent; } + + break; + } + case 'close': { + // We received the close on the websocket + //sendConsoleText('Tunnel #' + ws.tunnel.index + ' WebSocket control close'); + try { ws.close(); } catch (e) { } + break; + } + case 'termsize': { + // Indicates a change in terminal size + if (process.platform == 'win32') + { + if (ws.httprequest._dispatcher == null) return; + //sendConsoleText('Win32-TermSize: ' + obj.cols + 'x' + obj.rows); + if (ws.httprequest._dispatcher.invoke) { ws.httprequest._dispatcher.invoke('resizeTerminal', [obj.cols, obj.rows]); } + } else + { + if (ws.httprequest.process == null || ws.httprequest.process.pty == 0) return; + //sendConsoleText('Linux Resize: ' + obj.cols + 'x' + obj.rows); + + if (ws.httprequest.process.tcsetsize) { ws.httprequest.process.tcsetsize(obj.rows, obj.cols); } + } + break; + } + case 'webrtc0': { // Browser indicates we can start WebRTC switch-over. + if (ws.httprequest.protocol == 1) { // Terminal + // This is a terminal data stream, unpipe the terminal now and indicate to the other side that terminal data will no longer be received over WebSocket + if (process.platform == 'win32') { + ws.httprequest._term.unpipe(ws); + } else { + ws.httprequest.process.stdout.unpipe(ws); + ws.httprequest.process.stderr.unpipe(ws); + } + } else if (ws.httprequest.protocol == 2) { // Desktop + // This is a KVM data stream, unpipe the KVM now and indicate to the other side that KVM data will no longer be received over WebSocket + ws.httprequest.desktop.kvm.unpipe(ws); + } else { + // Switch things around so all WebRTC data goes to onTunnelData(). + ws.rtcchannel.httprequest = ws.httprequest; + ws.rtcchannel.removeAllListeners('data'); + ws.rtcchannel.on('data', onTunnelData); + } + ws.write("{\"ctrlChannel\":\"102938\",\"type\":\"webrtc1\"}"); // End of data marker + break; + } + case 'webrtc1': { + if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) { // Terminal + // Switch the user input from websocket to webrtc at this point. + if (process.platform == 'win32') { + ws.unpipe(ws.httprequest._term); + ws.rtcchannel.pipe(ws.httprequest._term, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text. + } else { + ws.unpipe(ws.httprequest.process.stdin); + ws.rtcchannel.pipe(ws.httprequest.process.stdin, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text. + } + ws.resume(); // Resume the websocket to keep receiving control data + } else if (ws.httprequest.protocol == 2) { // Desktop + // Switch the user input from websocket to webrtc at this point. + ws.unpipe(ws.httprequest.desktop.kvm); + try { ws.webrtc.rtcchannel.pipe(ws.httprequest.desktop.kvm, { dataTypeSkip: 1, end: false }); } catch (e) { sendConsoleText('EX2'); } // 0 = Binary, 1 = Text. + ws.resume(); // Resume the websocket to keep receiving control data + } + ws.write('{\"ctrlChannel\":\"102938\",\"type\":\"webrtc2\"}'); // Indicates we will no longer get any data on websocket, switching to WebRTC at this point. + break; + } + case 'webrtc2': { + // Other side received websocket end of data marker, start sending data on WebRTC channel + if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) { // Terminal + if (process.platform == 'win32') { + ws.httprequest._term.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. + } else { + ws.httprequest.process.stdout.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. + ws.httprequest.process.stderr.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. + } + } else if (ws.httprequest.protocol == 2) { // Desktop + ws.httprequest.desktop.kvm.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text. + } + break; + } + case 'offer': { + // This is a WebRTC offer. + if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) return; // TODO: Terminal is currently broken with WebRTC. Reject WebRTC upgrade for now. + ws.webrtc = rtc.createConnection(); + ws.webrtc.websocket = ws; + ws.webrtc.on('connected', function () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC connected');*/ }); + ws.webrtc.on('disconnected', function () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC disconnected');*/ }); + ws.webrtc.on('dataChannel', function (rtcchannel) { + //sendConsoleText('WebRTC Datachannel open, protocol: ' + this.websocket.httprequest.protocol); + rtcchannel.maxFragmentSize = 32768; + rtcchannel.xrtc = this; + rtcchannel.websocket = this.websocket; + this.rtcchannel = rtcchannel; + this.websocket.rtcchannel = rtcchannel; + this.websocket.rtcchannel.on('data', onTunnelWebRTCControlData); + this.websocket.rtcchannel.on('end', function () { + // The WebRTC channel closed, unpipe the KVM now. This is also done when the web socket closes. + //sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC data channel closed'); + if (this.websocket.desktop && this.websocket.desktop.kvm) + { + try + { + this.unpipe(this.websocket.desktop.kvm); + this.websocket.httprequest.desktop.kvm.unpipe(this); + } + catch (e) { } + } + }); + this.websocket.write('{\"ctrlChannel\":\"102938\",\"type\":\"webrtc0\"}'); // Indicate we are ready for WebRTC switch-over. + }); + var sdp = null; + try { sdp = ws.webrtc.setOffer(obj.sdp); } catch (e) { } + if (sdp != null) { ws.write({ type: 'answer', ctrlChannel: '102938', sdp: sdp }); } + break; + } + case 'ping': { + ws.write("{\"ctrlChannel\":\"102938\",\"type\":\"pong\"}"); // Send pong response + break; + } + case 'pong': { // NOP + break; + } + case 'rtt': { + ws.write({ type: 'rtt', ctrlChannel: '102938', time: obj.time }); + break; + } + } + } + + // Console state + var consoleWebSockets = {}; + var consoleHttpRequest = null; + + // Console HTTP response + function consoleHttpResponse(response) { + response.data = function (data) { sendConsoleText(rstr2hex(buf2rstr(data)), this.sessionid); consoleHttpRequest = null; } + response.close = function () { sendConsoleText('httprequest.response.close', this.sessionid); consoleHttpRequest = null; } + }; + + // Open a web browser to a specified URL on current user's desktop + function openUserDesktopUrl(url) { + if ((url.toLowerCase().startsWith('http://') == false) && (url.toLowerCase().startsWith('https://') == false)) { return null; } + var child = null; + try { + switch (process.platform) { + case 'win32': + var user = require('user-sessions').getUsername(require('user-sessions').consoleUid()); + child = require('child_process').execFile(process.env['windir'] + '\\system32\\cmd.exe', ['cmd']); + child.stderr.on('data', function () { }); + child.stdout.on('data', function () { }); + child.stdin.write('SCHTASKS /CREATE /F /TN MeshChatTask /SC ONCE /ST 00:00 /RU ' + user + ' /TR "' + process.env['windir'] + '\\system32\\cmd.exe /C START ' + url + '"\r\n'); + child.stdin.write('SCHTASKS /RUN /TN MeshChatTask\r\n'); + child.stdin.write('SCHTASKS /DELETE /F /TN MeshChatTask\r\n'); + child.stdin.write('exit\r\n'); + child.waitExit(); + break; + case 'linux': + child = require('child_process').execFile('/usr/bin/xdg-open', ['xdg-open', url], { uid: require('user-sessions').consoleUid() }); + break; + case 'darwin': + child = require('child_process').execFile('/usr/bin/open', ['open', url], { uid: require('user-sessions').consoleUid() }); + break; + default: + // Unknown platform, ignore this command. + break; + } + } catch (e) { } + return child; + } + + // Process a mesh agent console command + function processConsoleCommand(cmd, args, rights, sessionid) { + try { + var response = null; + switch (cmd) { + case 'help': { // Displays available commands + var fin = '', f = '', availcommands = 'amtconfig,coredump,service,fdsnapshot,fdcount,startupoptions,alert,agentsize,versions,help,info,osinfo,args,print,type,dbkeys,dbget,dbset,dbcompact,eval,parseuri,httpget,nwslist,plugin,wsconnect,wssend,wsclose,notify,ls,ps,kill,amt,netinfo,location,power,wakeonlan,setdebug,smbios,rawsmbios,toast,lock,users,sendcaps,openurl,getscript,getclip,setclip,log,av,cpuinfo,sysinfo,apf,scanwifi,scanamt,wallpaper,agentmsg'; + if (process.platform == 'win32') { availcommands += ',safemode,wpfhwacceleration,uac'; } + if (process.platform != 'freebsd') { availcommands += ',vm';} + if (require('MeshAgent').maxKvmTileSize != null) { availcommands += ',kvmmode'; } + try { require('zip-reader'); availcommands += ',zip,unzip'; } catch (e) { } + + availcommands = availcommands.split(',').sort(); + while (availcommands.length > 0) { + if (f.length > 90) { fin += (f + ',\r\n'); f = ''; } + f += (((f != '') ? ', ' : ' ') + availcommands.shift()); + } + if (f != '') { fin += f; } + response = "Available commands: \r\n" + fin + "."; + break; + } + case 'agentmsg': { + if (args['_'].length == 0) { + response = "Proper usage:\r\n agentmsg add \"[message]\" [iconIndex]\r\n agentmsg remove [index]\r\n agentmsg list"; // Display usage + } else { + if ((args['_'][0] == 'add') && (args['_'].length > 1)) { + var msgIndex = 1, iconIndex = 0; + while (tunnelUserCount.msg[msgIndex] != null) { msgIndex++; } + if (args['_'].length >= 3) { try { iconIndex = parseInt(args['_'][2]); } catch (e) { } } + if (typeof iconIndex != 'number') { iconIndex = 0; } + tunnelUserCount.msg[msgIndex] = { msg: args['_'][1], icon: iconIndex }; + response = 'Agent message ' + msgIndex + ' added.'; + } else if ((args['_'][0] == 'remove') && (args['_'].length > 1)) { + var msgIndex = 0; + try { msgIndex = parseInt(args['_'][1]); } catch (x) { } + if (tunnelUserCount.msg[msgIndex] == null) { response = "Message not found."; } else { delete tunnelUserCount.msg[msgIndex]; response = "Message removed."; } + } else if (args['_'][0] == 'list') { + response = JSON.stringify(tunnelUserCount.msg, null, 2); + } + try { mesh.SendCommand({ action: 'sessions', type: 'msg', value: tunnelUserCount.msg }); } catch (x) { } + broadcastSessionsToRegisteredApps(); + } + break; + } + case 'clearagentmsg': { + tunnelUserCount.msg = {}; + try { mesh.SendCommand({ action: 'sessions', type: 'msg', value: tunnelUserCount.msg }); } catch (x) { } + broadcastSessionsToRegisteredApps(); + break; + } + case 'coredump': + if (args['_'].length != 1) { + response = "Proper usage: coredump on|off|status|clear"; // Display usage + } else { + switch (args['_'][0].toLowerCase()) + { + case 'on': + process.coreDumpLocation = (process.platform == 'win32') ? (process.execPath.replace('.exe', '.dmp')) : (process.execPath + '.dmp'); + response = 'coredump is now on'; + break; + case 'off': + process.coreDumpLocation = null; + response = 'coredump is now off'; + break; + case 'status': + response = 'coredump is: ' + ((process.coreDumpLocation == null) ? 'off' : 'on'); + if (process.coreDumpLocation != null) { + if (process.platform == 'win32') { + if (fs.existsSync(process.coreDumpLocation)) { response += '\r\n CoreDump present at: ' + process.coreDumpLocation; } + } else { + if ((process.cwd() != '//') && fs.existsSync(process.cwd() + 'core')) { response += '\r\n CoreDump present at: ' + process.cwd() + 'core'; } + } + } + break; + case 'clear': + db.Put('CoreDumpTime', null); + response = 'coredump db cleared'; + break; + default: + response = "Proper usage: coredump on|off|status"; // Display usage + break; + } + } + break; + case 'service': + if (args['_'].length != 1) + { + response = "Proper usage: service status|restart"; // Display usage + } + else + { + var s = require('service-manager').manager.getService(process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'); + switch(args['_'][0].toLowerCase()) + { + case 'status': + response = 'Service ' + (s.isRunning() ? (s.isMe() ? '[SELF]' : '[RUNNING]') : ('[NOT RUNNING]')); + break; + case 'restart': + if (s.isMe()) + { + s.restart(); + } + else + { + response = 'Restarting another agent instance is not allowed'; + } + break; + default: + response = "Proper usage: service status|restart"; // Display usage + break; + } + if (process.platform == 'win32') { s.close(); } + } + break; + case 'zip': + if (args['_'].length == 0) { + response = "Proper usage: zip (output file name), input1 [, input n]"; // Display usage + } else { + var p = args['_'].join(' ').split(','); + var ofile = p.shift(); + sendConsoleText('Writing ' + ofile + '...'); + var out = require('fs').createWriteStream(ofile, { flags: 'wb' }); + out.fname = ofile; + out.sessionid = sessionid; + out.on('close', function () { sendConsoleText('DONE writing ' + this.fname, this.sessionid); }); + var zip = require('zip-writer').write({ files: p }); + zip.pipe(out); + } + break; + case 'unzip': + if (args['_'].length == 0) { + response = "Proper usage: unzip input, destination"; // Display usage + } else { + var p = args['_'].join(' ').split(','); + if (p.length != 2) { response = "Proper usage: unzip input, destination"; break; } // Display usage + var prom = require('zip-reader').read(p[0]); + prom._dest = p[1]; + prom.self = this; + prom.sessionid = sessionid; + prom.then(function (zipped) { + sendConsoleText('Extracting to ' + this._dest + '...', this.sessionid); + zipped.extractAll(this._dest).then(function () { sendConsoleText('finished unzipping', this.sessionid); }, function (e) { sendConsoleText('Error unzipping: ' + e, this.sessionid); }).parentPromise.sessionid = this.sessionid; + }, function (e) { sendConsoleText('Error unzipping: ' + e, this.sessionid); }); + } + break; + case 'setbattery': + // require('MeshAgent').SendCommand({ action: 'battery', state: 'dc', level: 55 }); + if ((args['_'].length > 0) && ((args['_'][0] == 'ac') || (args['_'][0] == 'dc'))) { + var b = { action: 'battery', state: args['_'][0] }; + if (args['_'].length == 2) { b.level = parseInt(args['_'][1]); } + require('MeshAgent').SendCommand(b); + } else { + require('MeshAgent').SendCommand({ action: 'battery' }); + } + break; + case 'fdsnapshot': + require('ChainViewer').getSnapshot().then(function (c) { sendConsoleText(c, this.sessionid); }).parentPromise.sessionid = sessionid; + break; + case 'fdcount': + require('DescriptorEvents').getDescriptorCount().then( + function (c) + { + sendConsoleText('Descriptor Count: ' + c, this.sessionid); + }, function (e) + { + sendConsoleText('Error fetching descriptor count: ' + e, this.sessionid); + }).parentPromise.sessionid = sessionid; + break; + case 'uac': + if (process.platform != 'win32') + { + response = 'Unknown command "uac", type "help" for list of avaialble commands.'; + break; + } + if (args['_'].length != 1) + { + response = 'Proper usage: uac [get|interactive|secure]'; + } + else + { + switch(args['_'][0].toUpperCase()) + { + case 'GET': + var secd = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System', 'PromptOnSecureDesktop'); + response = "UAC mode: " + (secd == 0 ? "Interactive Desktop" : "Secure Desktop"); + break; + case 'INTERACTIVE': + try + { + require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System', 'PromptOnSecureDesktop', 0); + response = 'UAC mode changed to: Interactive Desktop'; + } + catch (e) + { + response = "Unable to change UAC Mode"; + } + break; + case 'SECURE': + try + { + require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System', 'PromptOnSecureDesktop', 1); + response = 'UAC mode changed to: Secure Desktop'; + } + catch(e) + { + response = "Unable to change UAC Mode"; + } + break; + default: + response = 'Proper usage: uac [get|interactive|secure]'; + break; + } + } + break; + case 'vm': + response = 'Virtual Machine = ' + require('identifiers').isVM(); + break; + case 'startupoptions': + response = JSON.stringify(require('MeshAgent').getStartupOptions()); + break; + case 'kvmmode': + if (require('MeshAgent').maxKvmTileSize == null) + { + response = "Unknown command \"kvmmode\", type \"help\" for list of avaialble commands."; + } + else + { + if(require('MeshAgent').maxKvmTileSize == 0) + { + response = 'KVM Mode: Full JUMBO'; + } + else + { + response = 'KVM Mode: ' + (require('MeshAgent').maxKvmTileSize <= 65500 ? 'NO JUMBO' : 'Partial JUMBO'); + response += (', TileLimit: ' + (require('MeshAgent').maxKvmTileSize < 1024 ? (require('MeshAgent').maxKvmTileSize + ' bytes') : (Math.round(require('MeshAgent').maxKvmTileSize/1024) + ' Kbytes'))); + } + } + break; + case 'alert': + if (args['_'].length == 0) + { + response = "Proper usage: alert TITLE, CAPTION [, TIMEOUT]"; // Display usage + } + else + { + var p = args['_'].join(' ').split(','); + if(p.length<2) + { + response = "Proper usage: alert TITLE, CAPTION [, TIMEOUT]"; // Display usage + } + else + { + this._alert = require('message-box').create(p[0], p[1], p.length==3?parseInt(p[2]):9999,1); + } + } + break; + case 'agentsize': + var actualSize = Math.floor(require('fs').statSync(process.execPath).size / 1024); + if (process.platform == 'win32') { + // Check the Agent Uninstall MetaData for correctness, as the installer may have written an incorrect value + var writtenSize = 0; + try { writtenSize = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MeshCentralAgent', 'EstimatedSize'); } catch (e) { response = e; } + if (writtenSize != actualSize) { + response = "Size updated from: " + writtenSize + " to: " + actualSize; + try { require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MeshCentralAgent', 'EstimatedSize', actualSize); } catch (e) { response = e; } + } else { response = "Agent Size: " + actualSize + " kb"; } + } else { response = "Agent Size: " + actualSize + " kb"; } + break; + case 'versions': + response = JSON.stringify(process.versions, null, ' '); + break; + case 'wpfhwacceleration': + if (process.platform != 'win32') { throw ("wpfhwacceleration setting is only supported on Windows"); } + if (args['_'].length != 1) { + response = "Proper usage: wpfhwacceleration (ON|OFF|STATUS)"; // Display usage + } + else { + var reg = require('win-registry'); + var uname = require('user-sessions').getUsername(require('user-sessions').consoleUid()); + var key = reg.usernameToUserKey(uname); + + switch (args['_'][0].toUpperCase()) { + default: + response = "Proper usage: wpfhwacceleration (ON|OFF|STATUS|DEFAULT)"; // Display usage + break; + case 'ON': + try { + reg.WriteKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration', 0); + response = "OK"; + } catch (e) { response = "FAILED"; } + break; + case 'OFF': + try { + reg.WriteKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration', 1); + response = 'OK'; + } catch (e) { response = 'FAILED'; } + break; + case 'STATUS': + var s; + try { s = reg.QueryKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration') == 1 ? 'DISABLED' : 'ENABLED'; } catch (e) { s = 'DEFAULT'; } + response = "WPF Hardware Acceleration: " + s; + break; + case 'DEFAULT': + try { reg.DeleteKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration'); } catch (e) { } + response = 'OK'; + break; + } + } + break; + case 'tsid': + if (process.platform == 'win32') { + if (args['_'].length != 1) { + response = "TSID: " + (require('MeshAgent')._tsid == null ? "console" : require('MeshAgent')._tsid); + } else { + var i = parseInt(args['_'][0]); + require('MeshAgent')._tsid = (isNaN(i) ? null : i); + response = "TSID set to: " + (require('MeshAgent')._tsid == null ? "console" : require('MeshAgent')._tsid); + } + } else { response = "TSID command only supported on Windows"; } + break; + case 'activeusers': + if (process.platform == 'win32') { + var p = require('user-sessions').enumerateUsers(); + p.sessionid = sessionid; + p.then(function (u) { + var v = []; + for (var i in u) { + if (u[i].State == 'Active') { v.push({ tsid: i, type: u[i].StationName, user: u[i].Username, domain: u[i].Domain }); } + } + sendConsoleText(JSON.stringify(v, null, 1), this.sessionid); + }); + } else { response = "activeusers command only supported on Windows"; } + break; + case 'wallpaper': + if (process.platform != 'win32' && !(process.platform == 'linux' && require('linux-gnome-helpers').available)) { + response = "wallpaper command not supported on this platform"; + } + else { + if (args['_'].length != 1) { + response = 'Proper usage: wallpaper (GET|TOGGLE)'; // Display usage + } + else { + switch (args['_'][0].toUpperCase()) { + default: + response = 'Proper usage: wallpaper (GET|TOGGLE)'; // Display usage + break; + case 'GET': + case 'TOGGLE': + if (process.platform == 'win32') { + var id = require('user-sessions').getProcessOwnerName(process.pid).tsid == 0 ? 1 : 0; + var child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0='], { type: id }); + child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); + child.stderr.on('data', function () { }); + child.waitExit(); + var current = child.stdout.str.trim(); + if (args['_'][0].toUpperCase() == 'GET') { + response = current; + break; + } + if (current != '') { + require('MeshAgent')._wallpaper = current; + response = 'Wallpaper cleared'; + } else { + response = 'Wallpaper restored'; + } + child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0=', current != '' ? '""' : require('MeshAgent')._wallpaper], { type: id }); + child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); + child.stderr.on('data', function () { }); + child.waitExit(); + } + else { + var id = require('user-sessions').consoleUid(); + var current = require('linux-gnome-helpers').getDesktopWallpaper(id); + if (args['_'][0].toUpperCase() == 'GET') { + response = current; + break; + } + if (current != '/dev/null') { + require('MeshAgent')._wallpaper = current; + response = 'Wallpaper cleared'; + } else { + response = 'Wallpaper restored'; + } + require('linux-gnome-helpers').setDesktopWallpaper(id, current != '/dev/null' ? undefined : require('MeshAgent')._wallpaper); + } + break; + } + } + } + break; + case 'safemode': + if (process.platform != 'win32') { + response = 'safemode only supported on Windows Platforms' + } + else { + if (args['_'].length != 1) { + response = 'Proper usage: safemode (ON|OFF|STATUS)'; // Display usage + } + else { + switch (args['_'][0].toUpperCase()) { + default: + response = 'Proper usage: safemode (ON|OFF|STATUS)'; // Display usage + break; + case 'ON': + require('win-bcd').setKey('safeboot', 'Network'); + require('win-bcd').enableSafeModeService('Mesh Agent'); + break; + case 'OFF': + require('win-bcd').deleteKey('safeboot'); + break; + case 'STATUS': + var nextboot = require('win-bcd').getKey('safeboot'); + if (nextboot) { + switch (nextboot) { + case 'Network': + case 'network': + nextboot = 'SAFE_MODE_NETWORK'; + break; + default: + nextboot = 'SAFE_MODE'; + break; + } + } + response = 'Current: ' + require('win-bcd').bootMode + ', NextBoot: ' + (nextboot ? nextboot : 'NORMAL'); + break; + } + } + } + break; + /* + case 'border': + { + if ((args['_'].length == 1) && (args['_'][0] == 'on')) { + if (meshCoreObj.users.length > 0) { + obj.borderManager.Start(meshCoreObj.users[0]); + response = 'Border blinking is on.'; + } else { + response = 'Cannot turn on border blinking, no logged in users.'; + } + } else if ((args['_'].length == 1) && (args['_'][0] == 'off')) { + obj.borderManager.Stop(); + response = 'Border blinking is off.'; + } else { + response = 'Proper usage: border "on|off"'; // Display correct command usage + } + } + break; + */ + case 'av': + if (process.platform == 'win32') { + // Windows Command: "wmic /Namespace:\\root\SecurityCenter2 Path AntiVirusProduct get /FORMAT:CSV" + response = JSON.stringify(require('win-info').av(), null, 1); + } else { + response = 'Not supported on the platform'; + } + break; + case 'log': + if (args['_'].length != 1) { response = 'Proper usage: log "sample text"'; } else { MeshServerLog(args['_'][0]); response = 'ok'; } + break; + case 'getclip': + if (require('MeshAgent').isService) { + require('clipboard').dispatchRead().then(function (str) { sendConsoleText(str, sessionid); }); + } else { + require("clipboard").read().then(function (str) { sendConsoleText(str, sessionid); }); + } + break; + case 'setclip': { + if (args['_'].length != 1) { + response = 'Proper usage: setclip "sample text"'; + } else { + if (require('MeshAgent').isService) { + require('clipboard').dispatchWrite(args['_'][0]); + response = 'Setting clipboard to: "' + args['_'][0] + '"'; + } + else { + require("clipboard")(args['_'][0]); response = 'Setting clipboard to: "' + args['_'][0] + '"'; + } + } + break; + } + /* + case 'amtreset': { + if (amt != null) { amt.reset(); response = 'Done.'; } + break; + } + case 'amtlmsreset': { + if (amt != null) { amt.lmsreset(); response = 'Done.'; } + break; + } + case 'amtccm': { + if (amt == null) { response = 'Intel AMT not supported.'; } else { + if (args['_'].length != 1) { response = 'Proper usage: amtccm (adminPassword)'; } // Display usage + else { amt.setPolicy({ type: 0 }); amt.activeToCCM(args['_'][0]); } + } + break; + } + case 'amtacm': { + if (amt == null) { response = 'Intel AMT not supported.'; } else { + amt.setPolicy({ type: 0 }); + amt.getAmtInfo(function (meinfo) { amt.activeToACM(meinfo); }); + } + break; + } + case 'amtdeactivate': { + if (amt == null) { response = 'Intel AMT not supported.'; } else { amt.setPolicy({ type: 0 }); amt.deactivateCCM(); } + break; + } + */ + case 'openurl': { + if (args['_'].length != 1) { response = 'Proper usage: openurl (url)'; } // Display usage + else { if (openUserDesktopUrl(args['_'][0]) == null) { response = 'Failed.'; } else { response = 'Success.'; } } + break; + } + case 'users': { + if (meshCoreObj.users == null) { response = 'Active users are unknown.'; } else { response = 'Active Users: ' + meshCoreObj.users.join(', ') + '.'; } + require('user-sessions').enumerateUsers().then(function (u) { for (var i in u) { sendConsoleText(u[i]); } }); + break; + } + case 'toast': { + if (args['_'].length < 1) { response = 'Proper usage: toast "message"'; } else { + if (require('MeshAgent')._tsid == null) { + require('toaster').Toast('MeshCentral', args['_'][0]).then(sendConsoleText, sendConsoleText); + } + else { + require('toaster').Toast('MeshCentral', args['_'][0], require('MeshAgent')._tsid).then(sendConsoleText, sendConsoleText); + } + } + break; + } + case 'setdebug': { + if (args['_'].length < 1) { response = 'Proper usage: setdebug (target), 0 = Disabled, 1 = StdOut, 2 = This Console, * = All Consoles, 4 = WebLog, 8 = Logfile'; } // Display usage + else { if (args['_'][0] == '*') { console.setDestination(2); } else { console.setDestination(parseInt(args['_'][0]), sessionid); } } + break; + } + case 'ps': { + processManager.getProcesses(function (plist) { + var x = ''; + for (var i in plist) { x += i + ((plist[i].user) ? (', ' + plist[i].user) : '') + ', ' + plist[i].cmd + '\r\n'; } + sendConsoleText(x, sessionid); + }); + break; + } + case 'kill': { + if ((args['_'].length < 1)) { + response = 'Proper usage: kill [pid]'; // Display correct command usage + } else { + process.kill(parseInt(args['_'][0])); + response = 'Killed process ' + args['_'][0] + '.'; + } + break; + } + case 'smbios': { + if (SMBiosTables == null) { response = 'SMBios tables not available.'; } else { response = objToString(SMBiosTables, 0, ' ', true); } + break; + } + case 'rawsmbios': { + if (SMBiosTablesRaw == null) { response = 'SMBios tables not available.'; } else { + response = ''; + for (var i in SMBiosTablesRaw) { + var header = false; + for (var j in SMBiosTablesRaw[i]) { + if (SMBiosTablesRaw[i][j].length > 0) { + if (header == false) { response += ('Table type #' + i + ((require('smbios').smTableTypes[i] == null) ? '' : (', ' + require('smbios').smTableTypes[i]))) + '\r\n'; header = true; } + response += (' ' + SMBiosTablesRaw[i][j].toString('hex')) + '\r\n'; + } + } + } + } + break; + } + case 'eval': { // Eval JavaScript + if (args['_'].length < 1) { + response = 'Proper usage: eval "JavaScript code"'; // Display correct command usage + } else { + response = JSON.stringify(mesh.eval(args['_'][0])); // This can only be run by trusted administrator. + } + break; + } + case 'uninstallagent': // Uninstall this agent + var agentName = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'; + if (!require('service-manager').manager.getService(agentName).isMe()) { + response = 'Uininstall failed, this instance is not the service instance'; + } else { + try { diagnosticAgent_uninstall(); } catch (e) { } + var js = "require('service-manager').manager.getService('" + agentName + "').stop(); require('service-manager').manager.uninstallService('" + agentName + "'); process.exit();"; + this.child = require('child_process').execFile(process.execPath, [process.platform == 'win32' ? (process.execPath.split('\\').pop()) : (process.execPath.split('/').pop()), '-b64exec', Buffer.from(js).toString('base64')], { type: 4, detached: true }); + } + break; + case 'notify': { // Send a notification message to the mesh + if (args['_'].length != 1) { + response = 'Proper usage: notify "message" [--session]'; // Display correct command usage + } else { + var notification = { action: 'msg', type: 'notify', value: args['_'][0], tag: 'console' }; + if (args.session) { notification.sessionid = sessionid; } // If "--session" is specified, notify only this session, if not, the server will notify the mesh + mesh.SendCommand(notification); // no sessionid or userid specified, notification will go to the entire mesh + response = "ok"; + } + break; + } + case 'cpuinfo': { // Return system information + // CPU & memory utilization + pr = require('sysinfo').cpuUtilization(); + pr.sessionid = sessionid; + pr.then(function (data) { + sendConsoleText(JSON.stringify({ cpu: data, memory: require('sysinfo').memUtilization() }, null, 1), this.sessionid); + }, function (e) { + sendConsoleText(e); + }); + break; + } + case 'sysinfo': { // Return system information + getSystemInformation(function (results, err) { + if (results == null) { sendConsoleText(err, this.sessionid); } else { + sendConsoleText(JSON.stringify(results, null, 1), this.sessionid); + } + }); + break; + } + case 'info': { // Return information about the agent and agent core module + response = 'Current Core: ' + meshCoreObj.value + '\r\nAgent Time: ' + Date() + '.\r\nUser Rights: 0x' + rights.toString(16) + '.\r\nPlatform: ' + process.platform + '.\r\nCapabilities: ' + meshCoreObj.caps + '.\r\nServer URL: ' + mesh.ServerUrl + '.'; + if (amt != null) { response += '\r\nBuilt-in LMS: ' + ['Disabled', 'Connecting..', 'Connected'][amt.lmsstate] + '.'; } + if (meshCoreObj.osdesc) { response += '\r\nOS: ' + meshCoreObj.osdesc + '.'; } + response += '\r\nModules: ' + addedModules.join(', ') + '.'; + response += '\r\nServer Connection: ' + mesh.isControlChannelConnected + ', State: ' + meshServerConnectionState + '.'; + response += '\r\lastMeInfo: ' + lastMeInfo + '.'; + var oldNodeId = db.Get('OldNodeId'); + if (oldNodeId != null) { response += '\r\nOldNodeID: ' + oldNodeId + '.'; } + if (process.platform == 'linux' || process.platform == 'freebsd') { response += '\r\nX11 support: ' + require('monitor-info').kvm_x11_support + '.'; } + break; + } + case 'osinfo': { // Return the operating system information + var i = 1; + if (args['_'].length > 0) { i = parseInt(args['_'][0]); if (i > 8) { i = 8; } response = 'Calling ' + i + ' times.'; } + for (var j = 0; j < i; j++) { + var pr = require('os').name(); + pr.sessionid = sessionid; + pr.then(function (v) { + sendConsoleText("OS: " + v + (process.platform == 'win32' ? (require('win-virtual-terminal').supported ? ' [ConPTY: YES]' : ' [ConPTY: NO]') : ''), this.sessionid); + }); + } + break; + } + case 'sendcaps': { // Send capability flags to the server + if (args['_'].length == 0) { + response = 'Proper usage: sendcaps (number)'; // Display correct command usage + } else { + meshCoreObj.caps = parseInt(args['_'][0]); + mesh.SendCommand(meshCoreObj); + response = JSON.stringify(meshCoreObj); + } + break; + } + case 'sendosdesc': { // Send OS description + if (args['_'].length > 0) { + meshCoreObj.osdesc = args['_'][0]; + mesh.SendCommand(meshCoreObj); + response = JSON.stringify(meshCoreObj); + } else { + response = 'Proper usage: sendosdesc [os description]'; // Display correct command usage + } + break; + } + case 'args': { // Displays parsed command arguments + response = 'args ' + objToString(args, 0, ' ', true); + break; + } + case 'print': { // Print a message on the mesh agent console, does nothing when running in the background + var r = []; + for (var i in args['_']) { r.push(args['_'][i]); } + console.log(r.join(' ')); + response = 'Message printed on agent console.'; + break; + } + case 'type': { // Returns the content of a file + if (args['_'].length == 0) { + response = 'Proper usage: type (filepath) [maxlength]'; // Display correct command usage + } else { + var max = 4096; + if ((args['_'].length > 1) && (typeof args['_'][1] == 'number')) { max = args['_'][1]; } + if (max > 4096) max = 4096; + var buf = Buffer.alloc(max), fd = fs.openSync(args['_'][0], "r"), r = fs.readSync(fd, buf, 0, max); // Read the file content + response = buf.toString(); + var i = response.indexOf('\n'); + if ((i > 0) && (response[i - 1] != '\r')) { response = response.split('\n').join('\r\n'); } + if (r == max) response += '...'; + fs.closeSync(fd); + } + break; + } + case 'dbkeys': { // Return all data store keys + response = JSON.stringify(db.Keys); + break; + } + case 'dbget': { // Return the data store value for a given key + if (db == null) { response = 'Database not accessible.'; break; } + if (args['_'].length != 1) { + response = 'Proper usage: dbget (key)'; // Display the value for a given database key + } else { + response = db.Get(args['_'][0]); + } + break; + } + case 'dbset': { // Set a data store key and value pair + if (db == null) { response = 'Database not accessible.'; break; } + if (args['_'].length != 2) { + response = 'Proper usage: dbset (key) (value)'; // Set a database key + } else { + var r = db.Put(args['_'][0], args['_'][1]); + response = 'Key set: ' + r; + } + break; + } + case 'dbcompact': { // Compact the data store + if (db == null) { response = 'Database not accessible.'; break; } + var r = db.Compact(); + response = 'Database compacted: ' + r; + break; + } + case 'httpget': { + if (consoleHttpRequest != null) { + response = 'HTTP operation already in progress.'; + } else { + if (args['_'].length != 1) { + response = 'Proper usage: httpget (url)'; + } else { + var options = http.parseUri(args['_'][0]); + options.method = 'GET'; + if (options == null) { + response = 'Invalid url.'; + } else { + try { consoleHttpRequest = http.request(options, consoleHttpResponse); } catch (e) { response = 'Invalid HTTP GET request'; } + consoleHttpRequest.sessionid = sessionid; + if (consoleHttpRequest != null) { + consoleHttpRequest.end(); + response = 'HTTPGET ' + options.protocol + '//' + options.host + ':' + options.port + options.path; + } + } + } + } + break; + } + case 'wslist': { // List all web sockets + response = ''; + for (var i in consoleWebSockets) { + var httprequest = consoleWebSockets[i]; + response += 'Websocket #' + i + ', ' + httprequest.url + '\r\n'; + } + if (response == '') { response = 'no websocket sessions.'; } + break; + } + case 'wsconnect': { // Setup a web socket + if (args['_'].length == 0) { + response = 'Proper usage: wsconnect (url)\r\nFor example: wsconnect wss://localhost:443/meshrelay.ashx?id=abc'; // Display correct command usage + } else { + var httprequest = null; + try { + var options = http.parseUri(args['_'][0].split('$').join('%24').split('@').join('%40')); // Escape the $ and @ characters in the URL + options.rejectUnauthorized = 0; + httprequest = http.request(options); + } catch (e) { response = 'Invalid HTTP websocket request'; } + if (httprequest != null) { + httprequest.upgrade = onWebSocketUpgrade; + httprequest.on('error', function (e) { sendConsoleText("ERROR: Unable to connect to: " + this.url + ", " + JSON.stringify(e)); }); + + var index = 1; + while (consoleWebSockets[index]) { index++; } + httprequest.sessionid = sessionid; + httprequest.index = index; + httprequest.url = args['_'][0]; + consoleWebSockets[index] = httprequest; + response = 'New websocket session #' + index; + } + } + break; + } + case 'wssend': { // Send data on a web socket + if (args['_'].length == 0) { + response = 'Proper usage: wssend (socketnumber)\r\n'; // Display correct command usage + for (var i in consoleWebSockets) { + var httprequest = consoleWebSockets[i]; + response += 'Websocket #' + i + ', ' + httprequest.url + '\r\n'; + } + } else { + var i = parseInt(args['_'][0]); + var httprequest = consoleWebSockets[i]; + if (httprequest != undefined) { + httprequest.s.write(args['_'][1]); + response = 'ok'; + } else { + response = 'Invalid web socket number'; + } + } + break; + } + case 'wsclose': { // Close a websocket + if (args['_'].length == 0) { + response = 'Proper usage: wsclose (socketnumber)'; // Display correct command usage + } else { + var i = parseInt(args['_'][0]); + var httprequest = consoleWebSockets[i]; + if (httprequest != undefined) { + if (httprequest.s != null) { httprequest.s.end(); } else { httprequest.end(); } + response = 'ok'; + } else { + response = 'Invalid web socket number'; + } + } + break; + } + case 'tunnels': { // Show the list of current tunnels + response = ''; + for (var i in tunnels) { response += 'Tunnel #' + i + ', ' + tunnels[i].url + '\r\n'; } + if (response == '') { response = 'No websocket sessions.'; } + break; + } + case 'ls': { // Show list of files and folders + response = ''; + var xpath = '*'; + if (args['_'].length > 0) { xpath = obj.path.join(args['_'][0], '*'); } + response = 'List of ' + xpath + '\r\n'; + var results = fs.readdirSync(xpath); + for (var i = 0; i < results.length; ++i) { + var stat = null, p = obj.path.join(args['_'][0], results[i]); + try { stat = fs.statSync(p); } catch (e) { } + if ((stat == null) || (stat == undefined)) { + response += (results[i] + "\r\n"); + } else { + response += (results[i] + " " + ((stat.isDirectory()) ? "(Folder)" : "(File)") + "\r\n"); + } + } + break; + } + case 'lsx': { // Show list of files and folders + response = objToString(getDirectoryInfo(args['_'][0]), 0, ' ', true); + break; + } + case 'lock': { // Lock the current user out of the desktop + if (process.platform == 'win32') { var child = require('child_process'); child.execFile(process.env['windir'] + '\\system32\\cmd.exe', ['/c', 'RunDll32.exe user32.dll,LockWorkStation'], { type: 1 }); response = 'Ok'; } + else { response = 'Not supported on the platform'; } + break; + } + case 'amt': { // Show Intel AMT status + if (amt != null) { + amt.getAmtInfo(function (state) { + var resp = 'Intel AMT not detected.'; + if (state != null) { resp = objToString(state, 0, ' ', true); } + sendConsoleText(resp, sessionid); + }); + } else { + response = 'Intel AMT not detected.'; + } + break; + } + case 'netinfo': { // Show network interface information + var interfaces = require('os').networkInterfaces(); + response = objToString(interfaces, 0, ' ', true); + break; + } + case 'wakeonlan': { // Send wake-on-lan + if ((args['_'].length != 1) || (args['_'][0].length != 12)) { + response = 'Proper usage: wakeonlan [mac], for example "wakeonlan 010203040506".'; + } else { + var count = sendWakeOnLan(args['_'][0]); + response = 'Sent wake-on-lan on ' + count + ' interface(s).'; + } + break; + } + case 'sendall': { // Send a message to all consoles on this mesh + sendConsoleText(args['_'].join(' ')); + break; + } + case 'power': { // Execute a power action on this computer + if (mesh.ExecPowerState == undefined) { + response = 'Power command not supported on this agent.'; + } else { + if ((args['_'].length == 0) || isNaN(Number(args['_'][0]))) { + response = 'Proper usage: power (actionNumber), where actionNumber is:\r\n LOGOFF = 1\r\n SHUTDOWN = 2\r\n REBOOT = 3\r\n SLEEP = 4\r\n HIBERNATE = 5\r\n DISPLAYON = 6\r\n KEEPAWAKE = 7\r\n BEEP = 8\r\n CTRLALTDEL = 9\r\n VIBRATE = 13\r\n FLASH = 14'; // Display correct command usage + } else { + var r = mesh.ExecPowerState(Number(args['_'][0]), Number(args['_'][1])); + response = 'Power action executed with return code: ' + r + '.'; + } + } + break; + } + case 'location': { + getIpLocationData(function (location) { + sendConsoleText(objToString({ action: 'iplocation', type: 'publicip', value: location }, 0, ' ')); + }); + break; + } + case 'parseuri': { + response = JSON.stringify(http.parseUri(args['_'][0])); + break; + } + case 'scanwifi': { + if (wifiScanner != null) { + var wifiPresent = wifiScanner.hasWireless; + if (wifiPresent) { response = "Perfoming Wifi scan..."; wifiScanner.Scan(); } else { response = "Wifi absent."; } + } else { response = "Wifi module not present."; } + break; + } + case 'scanamt': { + if (amtscanner != null) { + if (args['_'].length != 1) { + response = 'Usage examples:\r\n scanamt 1.2.3.4\r\n scanamt 1.2.3.0-1.2.3.255\r\n scanamt 1.2.3.0/24\r\n'; // Display correct command usage + } else { + response = 'Scanning: ' + args['_'][0] + '...'; + amtscanner.scan(args['_'][0], 2000, function (data) { + if (data.length > 0) { + var r = '', pstates = ['NotActivated', 'InActivation', 'Activated']; + for (var i in data) { + var x = data[i]; + if (r != '') { r += '\r\n'; } + r += x.address + ' - Intel AMT v' + x.majorVersion + '.' + x.minorVersion; + if (x.provisioningState < 3) { r += (', ' + pstates[x.provisioningState]); } + if (x.provisioningState == 2) { r += (', ' + x.openPorts.join(', ')); } + r += '.'; + } + } else { + r = 'No Intel AMT found.'; + } + sendConsoleText(r); + }); + } + } else { response = "Intel AMT scanner module not present."; } + break; + } + case 'modules': { + response = JSON.stringify(addedModules); + break; + } + case 'listservices': { + var services = require('service-manager').manager.enumerateService(); + response = JSON.stringify(services, null, 1); + break; + } + case 'getscript': { + if (args['_'].length != 1) { + response = "Proper usage: getscript [scriptNumber]."; + } else { + mesh.SendCommand({ action: 'getScript', type: args['_'][0] }); + } + break; + } + case 'diagnostic': + { + if (!mesh.DAIPC.listening) { + response = 'Unable to bind to Diagnostic IPC, most likely because the path (' + process.cwd() + ') is not on a local file system'; + break; + } + var diag = diagnosticAgent_installCheck(); + if (diag) { + if (args['_'].length == 1 && args['_'][0] == 'uninstall') { + diagnosticAgent_uninstall(); + response = 'Diagnostic Agent uninstalled'; + } + else { + response = 'Diagnostic Agent installed at: ' + diag.appLocation(); + } + } + else { + if (args['_'].length == 1 && args['_'][0] == 'install') { + diag = diagnosticAgent_installCheck(true); + if (diag) { + response = 'Diagnostic agent was installed at: ' + diag.appLocation(); + } + else { + response = 'Diagnostic agent installation failed'; + } + } + else { + response = 'Diagnostic Agent Not installed. To install: diagnostic install'; + } + } + if (diag) { diag.close(); diag = null; } + break; + } + case 'amtconfig': { + if (apftunnel != null) { response = "Intel AMT server tunnel already active"; break; } + if (amt == null) { response = "No Intel AMT support delected"; break; } + getMeiState(15, function (state) { + var rx = ''; + if ((state == null) || (state.ProvisioningState == null)) { rx = "Intel AMT not ready for configuration."; } else { + var apfarg = { + mpsurl: mesh.ServerUrl.replace('agent.ashx', 'apf.ashx'), + mpsuser: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16), + mpspass: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16), + mpskeepalive: 60000, + clientname: state.OsHostname, + clientaddress: '127.0.0.1', + clientuuid: state.UUID, + conntype: 2, // 0 = CIRA, 1 = Relay, 2 = LMS. The correct value is 2 since we are performing an LMS relay, other values for testing. + meiState: state // MEI state will be passed to MPS server + }; + if ((state.UUID == null) || (state.UUID.length != 36)) { + rx = "Unable to get Intel AMT UUID"; + } else { + apftunnel = require('apfclient')({ debug: false }, apfarg); + apftunnel.onJsonControl = function (data) { + if (data.action == 'console') { require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: data.msg }); } // Display a console message + if (data.action == 'mestate') { getMeiState(15, function (state) { apftunnel.updateMeiState(state); }); } // Update the MEI state + if (data.action == 'deactivate') { // Request CCM deactivation + var amtMeiModule, amtMei; + try { amtMeiModule = require('amt-mei'); amtMei = new amtMeiModule(); } catch (ex) { apftunnel.sendMeiDeactivationState(1); break; } + amtMei.on('error', function (e) { apftunnel.sendMeiDeactivationState(1); }); + amtMei.unprovision(1, function (status) { apftunnel.sendMeiDeactivationState(status); }); // 0 = Success + } + if (data.action == 'close') { try { apftunnel.disconnect(); } catch (e) { } apftunnel = null; } // Close the CIRA-LMS connection + } + apftunnel.onChannelClosed = function () { apftunnel = null; } + try { + apftunnel.connect(); + rx = "Started Intel AMT configuration"; + } catch (ex) { + rx = JSON.stringify(ex); + } + } + } + if (rx != '') { require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: rx }); } + }); + break; + } + case 'apf': { + if (meshCoreObj.intelamt !== null) { + if (args['_'].length == 1) { + var connType = -1, connTypeStr = args['_'][0].toLowerCase(); + if (connTypeStr == 'lms') { connType = 2; } + if (connTypeStr == 'relay') { connType = 1; } + if (connTypeStr == 'cira') { connType = 0; } + if (connTypeStr == 'off') { connType = -2; } + if (connType >= 0) { // Connect + var apfarg = { + mpsurl: mesh.ServerUrl.replace('agent.ashx', 'apf.ashx'), + mpsuser: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16), + mpspass: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16), + mpskeepalive: 60000, + clientname: require('os').hostname(), + clientaddress: '127.0.0.1', + clientuuid: meshCoreObj.intelamt.uuid, + conntype: connType // 0 = CIRA, 1 = Relay, 2 = LMS. The correct value is 2 since we are performing an LMS relay, other values for testing. + }; + if ((apfarg.clientuuid == null) || (apfarg.clientuuid.length != 36)) { + response = "Unable to get Intel AMT UUID: " + apfarg.clientuuid; + } else { + apftunnel = require('apfclient')({ debug: false }, apfarg); + apftunnel.onJsonControl = function (data) { + if (data.action == 'console') { require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: data.msg }); } + if (data.action == 'close') { try { apftunnel.disconnect(); } catch (e) { } apftunnel = null; } + } + apftunnel.onChannelClosed = function () { apftunnel = null; } + try { + apftunnel.connect(); + response = "Started APF tunnel"; + } catch (e) { + response = JSON.stringify(e); + } + } + } else if (connType == -2) { // Disconnect + try { + apftunnel.disconnect(); + response = "Stopped APF tunnel"; + } catch (e) { + response = JSON.stringify(e); + } + apftunnel = null; + } else { + response = "Invalid command.\r\nUse: apf lms|relay|cira|off"; + } + } else { + response = "APF tunnel is " + (apftunnel == null ? "off" : "on") + "\r\nUse: apf lms|relay|cira|off"; + } + } else { + response = "APF tunnel requires Intel AMT"; + } + break; + } + case 'plugin': { + if (typeof args['_'][0] == 'string') { + try { + // Pass off the action to the plugin + // for plugin creators, you'll want to have a plugindir/modules_meshcore/plugin.js + // to control the output / actions here. + response = require(args['_'][0]).consoleaction(args, rights, sessionid, mesh); + } catch (e) { + response = "There was an error in the plugin (" + e + ")"; + } + } else { + response = "Proper usage: plugin [pluginName] [args]."; + } + break; + } + default: { // This is an unknown command, return an error message + response = "Unknown command \"" + cmd + "\", type \"help\" for list of avaialble commands."; + break; + } + } + } catch (e) { response = "Command returned an exception error: " + e; console.log(e); } + if (response != null) { sendConsoleText(response, sessionid); } + } + + // Send a mesh agent console command + function sendConsoleText(text, sessionid) { + if (typeof text == 'object') { text = JSON.stringify(text); } + require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: text, sessionid: sessionid }); + } + + // Called before the process exits + //process.exit = function (code) { console.log("Exit with code: " + code.toString()); } + + // Called when the server connection state changes + function handleServerConnection(state) { + meshServerConnectionState = state; + if (meshServerConnectionState == 0) { + // Server disconnected + if (selfInfoUpdateTimer != null) { clearInterval(selfInfoUpdateTimer); selfInfoUpdateTimer = null; } + lastSelfInfo = null; + } else { + // Server connected, send mesh core information + var oldNodeId = db.Get('OldNodeId'); + if (oldNodeId != null) { mesh.SendCommand({ action: 'mc1migration', oldnodeid: oldNodeId }); } + + // Update the server with basic info, logged in users and more. + mesh.SendCommand(meshCoreObj); + + // Send SMBios tables if present + if (SMBiosTablesRaw != null) { mesh.SendCommand({ action: 'smbios', value: SMBiosTablesRaw }); } + + // Update the server on more advanced stuff, like Intel ME and Network Settings + meInfoStr = null; + sendPeriodicServerUpdate(); + if (selfInfoUpdateTimer == null) { selfInfoUpdateTimer = setInterval(sendPeriodicServerUpdate, 1200000); } // 20 minutes + + // Send any state messages + if (Object.keys(tunnelUserCount.msg).length > 0) { + try { mesh.SendCommand({ action: 'sessions', type: 'msg', value: tunnelUserCount.msg }); } catch (e) { } + broadcastSessionsToRegisteredApps(); + } + + // Send update of registered applications to the server + updateRegisteredAppsToServer(); + } + + // Send server state update to registered applications + broadcastToRegisteredApps({ cmd: 'serverstate', value: meshServerConnectionState, url: require('MeshAgent').ConnectedServer }); + } + + // Update the server with the latest network interface information + var sendNetworkUpdateNagleTimer = null; + function sendNetworkUpdateNagle() { if (sendNetworkUpdateNagleTimer != null) { clearTimeout(sendNetworkUpdateNagleTimer); sendNetworkUpdateNagleTimer = null; } sendNetworkUpdateNagleTimer = setTimeout(sendNetworkUpdate, 5000); } + function sendNetworkUpdate(force) { + sendNetworkUpdateNagleTimer = null; + + // Update the network interfaces information data + var netInfo = { netif2: require('os').networkInterfaces() }; + if (netInfo.netif2) { + netInfo.action = 'netinfo'; + var netInfoStr = JSON.stringify(netInfo); + if ((force == true) || (clearGatewayMac(netInfoStr) != clearGatewayMac(lastNetworkInfo))) { mesh.SendCommand(netInfo); lastNetworkInfo = netInfoStr; } + } + } + + // Called periodically to check if we need to send updates to the server + function sendPeriodicServerUpdate(flags) { + if (meshServerConnectionState == 0) return; // Not connected to server, do nothing. + if (!flags) { flags = 0xFFFFFFFF; } + + if ((flags & 1) && (amt != null)) { + // If we have a connected MEI, get Intel ME information + amt.getAmtInfo(function (meinfo) { + try { + if (meinfo == null) return; + var intelamt = {}; + if (amt != null) + { + switch(amt.lmsstate) + { + case 0: intelamt.microlms = 'DISABLED'; break; + case 1: intelamt.microlms = 'CONNECTING'; break; + case 2: intelamt.microlms = 'CONNECTED'; break; + default: intelamt.microlms = 'unknown'; break; + } + } + var p = false; + if ((meinfo.Versions != null) && (meinfo.Versions.AMT != null)) { intelamt.ver = meinfo.Versions.AMT; p = true; if (meinfo.Versions.Sku != null) { intelamt.sku = parseInt(meinfo.Versions.Sku); } } + if (meinfo.ProvisioningState != null) { intelamt.state = meinfo.ProvisioningState; p = true; } + if (meinfo.Flags != null) { intelamt.flags = meinfo.Flags; p = true; } + if (meinfo.OsHostname != null) { intelamt.host = meinfo.OsHostname; p = true; } + if (meinfo.UUID != null) { intelamt.uuid = meinfo.UUID; p = true; } + if ((meinfo.ProvisioningState == 0) && (meinfo.net0 != null) && (meinfo.net0.enabled == 1)) { // If not activated, look to see if we have wired net working. + // Not activated and we have wired ethernet, look for the trusted DNS + var dns = meinfo.DNS; + if (dns == null) { + // Trusted DNS not set, let's look for the OS network DNS suffix + var interfaces = require('os').networkInterfaces(); + for (var i in interfaces) { + for (var j in interfaces[i]) { + if ((interfaces[i][j].mac == mestate.net0.mac) && (interfaces[i][j].fqdn != null) && (interfaces[i][j].fqdn != '')) { dns = interfaces[i][j].fqdn; } + } + } + } + if (intelamt.dns != dns) { intelamt.dns = dns; p = true; } + } else { if (intelamt.dns != null) { delete intelamt.dns; p = true; } } + if (p == true) { + var meInfoStr = JSON.stringify(intelamt); + if (meInfoStr != lastMeInfo) { + meshCoreObj.intelamt = intelamt; + mesh.SendCommand(meshCoreObj); + lastMeInfo = meInfoStr; + } + } + } catch (e) { } + }); + } + + if (flags & 2) { + // Update network information + sendNetworkUpdateNagle(false); + } + + if ((flags & 4) && (process.platform == 'win32')) { + // Update anti-virus information + // Windows Command: "wmic /Namespace:\\root\SecurityCenter2 Path AntiVirusProduct get /FORMAT:CSV" + var av, pr; + try { av = require('win-info').av(); } catch (e) { av = null; } // Antivirus + //if (process.platform == 'win32') { try { pr = require('win-info').pendingReboot(); } catch (e) { pr = null; } } // Pending reboot + if ((meshCoreObj.av == null) || (JSON.stringify(meshCoreObj.av) != JSON.stringify(av))) { meshCoreObj.av = av; mesh.SendCommand(meshCoreObj); } + } + } + + // Starting function + obj.start = function () { + // Setup the mesh agent event handlers + mesh.AddCommandHandler(handleServerCommand); + mesh.AddConnectHandler(handleServerConnection); + + // Parse input arguments + //var args = parseArgs(process.argv); + //console.log(args); + + //resetMicroLms(); + + // Setup logged in user monitoring (THIS IS BROKEN IN WIN7) + try { + var userSession = require('user-sessions'); + userSession.on('changed', function onUserSessionChanged() { + userSession.enumerateUsers().then(function (users) { + var u = [], a = users.Active; + for (var i = 0; i < a.length; i++) { + var un = a[i].Domain ? (a[i].Domain + '\\' + a[i].Username) : (a[i].Username); + if (u.indexOf(un) == -1) { u.push(un); } // Only push users in the list once. + } + meshCoreObj.users = u; + mesh.SendCommand(meshCoreObj); + }); + }); + userSession.emit('changed'); + //userSession.on('locked', function (user) { sendConsoleText('[' + (user.Domain ? user.Domain + '\\' : '') + user.Username + '] has LOCKED the desktop'); }); + //userSession.on('unlocked', function (user) { sendConsoleText('[' + (user.Domain ? user.Domain + '\\' : '') + user.Username + '] has UNLOCKED the desktop'); }); + } catch (e) { } + } + + obj.stop = function () { + mesh.AddCommandHandler(null); + mesh.AddConnectHandler(null); + } + + function onWebSocketClosed() { sendConsoleText("WebSocket #" + this.httprequest.index + " closed.", this.httprequest.sessionid); delete consoleWebSockets[this.httprequest.index]; } + function onWebSocketData(data) { sendConsoleText("Got WebSocket #" + this.httprequest.index + " data: " + data, this.httprequest.sessionid); } + function onWebSocketSendOk() { sendConsoleText("WebSocket #" + this.index + " SendOK.", this.sessionid); } + + function onWebSocketUpgrade(response, s, head) { + sendConsoleText("WebSocket #" + this.index + " connected.", this.sessionid); + this.s = s; + s.httprequest = this; + s.end = onWebSocketClosed; + s.data = onWebSocketData; + } + + // Get Intel MEI State in a flexible way + // Flags: 1 = Versions, 2 = OsAdmin, 4 = Hashes, 8 = Network + function getMeiState(flags, func) { + var amtMeiModule, amtMei; + try { amtMeiModule = require('amt-mei'); amtMei = new amtMeiModule(); } catch (ex) { func(null); return; } + amtMei.on('error', function (e) { func(null); return; }); + try { + var amtMeiTmpState = { OsHostname: require('os').hostname(), Flags: 0 }; // Flags: 1=EHBC, 2=CCM, 4=ACM + amtMei.getProtocolVersion(function (result) { if (result != null) { amtMeiTmpState.MeiVersion = result; } }); + if ((flags & 1) != 0) { amtMei.getVersion(function (result) { if (result) { amtMeiTmpState.Versions = {}; for (var version in result.Versions) { amtMeiTmpState.Versions[result.Versions[version].Description] = result.Versions[version].Version; } } }); } + amtMei.getProvisioningMode(function (result) { if (result) { amtMeiTmpState.ProvisioningMode = result.mode; } }); + amtMei.getProvisioningState(function (result) { if (result) { amtMeiTmpState.ProvisioningState = result.state; } }); // 0: "Not Activated (Pre)", 1: "Not Activated (In)", 2: "Activated" + amtMei.getEHBCState(function (result) { if ((result != null) && (result.EHBC == true)) { amtMeiTmpState.Flags += 1; } }); + amtMei.getControlMode(function (result) { if (result != null) { if (result.controlMode == 1) { amtMeiTmpState.Flags += 2; } if (result.controlMode == 2) { amtMeiTmpState.Flags += 4; } } }); // Flag 2 = CCM, 4 = ACM + //amtMei.getMACAddresses(function (result) { if (result) { amtMeiTmpState.mac = result; } }); + if ((flags & 8) != 0) { + amtMei.getLanInterfaceSettings(0, function (result) { + if (result) { + amtMeiTmpState.net0 = result; + var fqdn = null, interfaces = require('os').networkInterfaces(); // Look for the DNS suffix for the Intel AMT Ethernet interface + for (var i in interfaces) { for (var j in interfaces[i]) { if ((interfaces[i][j].mac == mestate.net0.mac) && (interfaces[i][j].fqdn != null) && (interfaces[i][j].fqdn != '')) { amtMeiTmpState.OsDnsSuffix = interfaces[i][j].fqdn; } } } + } + }); + amtMei.getLanInterfaceSettings(1, function (result) { if (result) { amtMeiTmpState.net1 = result; } }); + } + amtMei.getUuid(function (result) { if ((result != null) && (result.uuid != null)) { amtMeiTmpState.UUID = result.uuid; } }); + if ((flags & 2) != 0) { amtMei.getLocalSystemAccount(function (x) { if ((x != null) && x.user && x.pass) { amtMeiTmpState.OsAdmin = { user: x.user, pass: x.pass }; } }); } + amtMei.getDnsSuffix(function (result) { if (result != null) { amtMeiTmpState.DnsSuffix = result; } if ((flags & 4) == 0) { if (func != null) { func(amtMeiTmpState); } } }); + if ((flags & 4) != 0) { + amtMei.getHashHandles(function (handles) { + if ((handles != null) && (handles.length > 0)) { amtMeiTmpState.Hashes = []; } else { func(amtMeiTmpState); } + var exitOnCount = handles.length; + for (var i = 0; i < handles.length; ++i) { this.getCertHashEntry(handles[i], function (hashresult) { amtMeiTmpState.Hashes.push(hashresult); if (--exitOnCount == 0) { if (func != null) { func(amtMeiTmpState); } } }); } + }); + } + } catch (e) { if (func != null) { func(null); } return; } + } + + return obj; +} + +// +// Module startup +// + +try { + var xexports = null, mainMeshCore = null; + try { xexports = module.exports; } catch (e) { } + + if (xexports != null) { + // If we are running within NodeJS, export the core + module.exports.createMeshCore = createMeshCore; + } else { + // If we are not running in NodeJS, launch the core + mainMeshCore = createMeshCore(); + mainMeshCore.start(null); + } +} catch (e) { + require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: "uncaughtException2: " + ex }); +} diff --git a/agents/meshcore-old.js b/agents/meshcore-old.js new file mode 100644 index 00000000..e6b01459 --- /dev/null +++ b/agents/meshcore-old.js @@ -0,0 +1,3903 @@ +/* +Copyright 2018-2020 Intel Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +process.on('uncaughtException', function (ex) { + require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: "uncaughtException1: " + ex }); +}); + +// NOTE: This seems to cause big problems, don't enable the debugger in the server's meshcore. +//attachDebugger({ webport: 9999, wait: 1 }).then(function (prt) { console.log('Point Browser for Debug to port: ' + prt); }); + +// Mesh Rights +var MNG_ERROR = 65; +var MESHRIGHT_EDITMESH = 1; +var MESHRIGHT_MANAGEUSERS = 2; +var MESHRIGHT_MANAGECOMPUTERS = 4; +var MESHRIGHT_REMOTECONTROL = 8; +var MESHRIGHT_AGENTCONSOLE = 16; +var MESHRIGHT_SERVERFILES = 32; +var MESHRIGHT_WAKEDEVICE = 64; +var MESHRIGHT_SETNOTES = 128; +var MESHRIGHT_REMOTEVIEW = 256; +var MESHRIGHT_NOTERMINAL = 512; +var MESHRIGHT_NOFILES = 1024; +var MESHRIGHT_NOAMT = 2048; +var MESHRIGHT_LIMITEDINPUT = 4096; +var MESHRIGHT_LIMITEVENTS = 8192; +var MESHRIGHT_CHATNOTIFY = 16384; +var MESHRIGHT_UNINSTALL = 32768; +var MESHRIGHT_NODESKTOP = 65536; + +function createMeshCore(agent) { + var obj = {}; + if (process.platform == 'win32' && require('user-sessions').isRoot()) { + // Check the Agent Uninstall MetaData for correctness, as the installer may have written an incorrect value + try { + var writtenSize = 0, actualSize = Math.floor(require('fs').statSync(process.execPath).size / 1024); + try { writtenSize = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MeshCentralAgent', 'EstimatedSize'); } catch (e) { } + if (writtenSize != actualSize) { try { require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MeshCentralAgent', 'EstimatedSize', actualSize); } catch (e) { } } + } catch (x) { } + + // Check to see if we are the Installed Mesh Agent Service, if we are, make sure we can run in Safe Mode + try { + var meshCheck = false; + try { meshCheck = require('service-manager').manager.getService('Mesh Agent').isMe(); } catch (e) { } + if (meshCheck && require('win-bcd').isSafeModeService && !require('win-bcd').isSafeModeService('Mesh Agent')) { require('win-bcd').enableSafeModeService('Mesh Agent'); } + } catch (e) { } + } + + if (process.platform == 'darwin' && !process.versions) { + // This is an older MacOS Agent, so we'll need to check the service definition so that Auto-Update will function correctly + var child = require('child_process').execFile('/bin/sh', ['sh']); + child.stdout.str = ''; + child.stdout.on('data', function (chunk) { this.str += chunk.toString(); }); + child.stdin.write("cat /Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist | tr '\n' '\.' | awk '{split($0, a, \"KeepAlive\"); split(a[2], b, \"<\"); split(b[2], c, \">\"); "); + child.stdin.write(" if(c[1]==\"dict\"){ split(a[2], d, \"\"); if(split(d[1], truval, \"\")>1) { split(truval[1], kn1, \"\"); split(kn1[2], kn2, \"\"); print kn2[1]; } }"); + child.stdin.write(" else { split(c[1], ka, \"/\"); if(ka[1]==\"true\") {print \"ALWAYS\";} } }'\nexit\n"); + child.waitExit(); + if (child.stdout.str.trim() == 'Crashed') { + child = require('child_process').execFile('/bin/sh', ['sh']); + child.stdout.str = ''; + child.stdout.on('data', function (chunk) { this.str += chunk.toString(); }); + child.stdin.write("launchctl list | grep 'meshagent' | awk '{ if($3==\"meshagent\"){print $1;}}'\nexit\n"); + child.waitExit(); + + if (parseInt(child.stdout.str.trim()) == process.pid) { + // The currently running MeshAgent is us, so we can continue with the update + var plist = require('fs').readFileSync('/Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist').toString(); + var tokens = plist.split('KeepAlive'); + if (tokens[1].split('>')[0].split('<')[1] == 'dict') { + var tmp = tokens[1].split(''); + tmp.shift(); + tokens[1] = '\n ' + tmp.join(''); + tokens = tokens.join('KeepAlive'); + + require('fs').writeFileSync('/Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist', tokens); + + var fix = ''; + fix += ("function macosRepair()\n"); + fix += ("{\n"); + fix += (" var child = require('child_process').execFile('/bin/sh', ['sh']);\n"); + fix += (" child.stdout.str = '';\n"); + fix += (" child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });\n"); + fix += (" child.stderr.on('data', function (chunk) { });\n"); + fix += (" child.stdin.write('launchctl unload /Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist\\n');\n"); + fix += (" child.stdin.write('launchctl load /Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist\\n');\n"); + fix += (" child.stdin.write('rm /Library/LaunchDaemons/meshagentRepair.plist\\n');\n"); + fix += (" child.stdin.write('rm " + process.cwd() + "/macosRepair.js\\n');\n"); + fix += (" child.stdin.write('launchctl stop meshagentRepair\\nexit\\n');\n"); + fix += (" child.waitExit();\n"); + fix += ("}\n"); + fix += ("macosRepair();\n"); + fix += ("process.exit();\n"); + require('fs').writeFileSync(process.cwd() + '/macosRepair.js', fix); + + var plist = '\n'; + plist += '\n'; + plist += '\n'; + plist += ' \n'; + plist += ' Label\n'; + plist += (' meshagentRepair\n'); + plist += ' ProgramArguments\n'; + plist += ' \n'; + plist += (' ' + process.execPath + '\n'); + plist += ' macosRepair.js\n'; + plist += ' \n'; + plist += ' WorkingDirectory\n'; + plist += (' ' + process.cwd() + '\n'); + plist += ' RunAtLoad\n'; + plist += ' \n'; + plist += ' \n'; + plist += ''; + require('fs').writeFileSync('/Library/LaunchDaemons/meshagentRepair.plist', plist); + + child = require('child_process').execFile('/bin/sh', ['sh']); + child.stdout.str = ''; + child.stdout.on('data', function (chunk) { this.str += chunk.toString(); }); + child.stdin.write("launchctl load /Library/LaunchDaemons/meshagentRepair.plist\nexit\n"); + child.waitExit(); + } + } + } + } + + // Create Secure IPC for Diagnostic Agent Communications + obj.DAIPC = require('net').createServer(); + if (process.platform != 'win32') { try { require('fs').unlinkSync(process.cwd() + '/DAIPC'); } catch (e) { } } + obj.DAIPC.IPCPATH = process.platform == 'win32' ? ('\\\\.\\pipe\\' + require('_agentNodeId')() + '-DAIPC') : (process.cwd() + '/DAIPC'); + try { obj.DAIPC.listen({ path: obj.DAIPC.IPCPATH, writableAll: true, maxConnections: 5 }); } catch (e) { } + obj.DAIPC._daipc = []; + obj.DAIPC.on('connection', function (c) { + c._send = function (j) { + var data = JSON.stringify(j); + var packet = Buffer.alloc(data.length + 4); + packet.writeUInt32LE(data.length + 4, 0); + Buffer.from(data).copy(packet, 4); + this.write(packet); + }; + this._daipc.push(c); + c.parent = this; + c.on('end', function () { removeRegisteredApp(this); }); + c.on('data', function (chunk) { + if (chunk.length < 4) { this.unshift(chunk); return; } + var len = chunk.readUInt32LE(0); + if (len > 8192) { removeRegisteredApp(this); this.end(); return; } + if (chunk.length < len) { this.unshift(chunk); return; } + + var data = chunk.slice(4, len); + try { data = JSON.parse(data.toString()); } catch (e) { } + if ((data == null) || (typeof data.cmd != 'string')) return; + + try { + switch (data.cmd) { + case 'requesthelp': + if (this._registered == null) return; + sendConsoleText('Request Help (' + this._registered + '): ' + data.value); + var help = {}; + help[this._registered] = data.value; + try { mesh.SendCommand({ action: 'sessions', type: 'help', value: help }); } catch (e) { } + MeshServerLogEx(98, [this._registered, data.value], "Help Requested, user: " + this._registered + ", details: " + data.value, null); + break; + case 'cancelhelp': + if (this._registered == null) return; + sendConsoleText('Cancel Help (' + this._registered + ')'); + try { mesh.SendCommand({ action: 'sessions', type: 'help', value: {} }); } catch (e) { } + break; + case 'register': + if (typeof data.value == 'string') { + this._registered = data.value; + var apps = {}; + apps[data.value] = 1; + try { mesh.SendCommand({ action: 'sessions', type: 'app', value: apps }); } catch (e) { } + this._send({ cmd: 'serverstate', value: meshServerConnectionState, url: require('MeshAgent').ConnectedServer, amt: (amt != null) }); + } + break; + case 'query': + switch (data.value) { + case 'connection': + data.result = require('MeshAgent').ConnectedServer; + this._send(data); + break; + case 'descriptors': + require('ChainViewer').getSnapshot().then(function (f) { + this.tag.payload.result = f; + this.tag.ipc._send(this.tag.payload); + }).parentPromise.tag = { ipc: this, payload: data }; + break; + } + break; + case 'amtstate': + if (amt == null) return; + var func = function amtStateFunc(state) { if (state != null) { amtStateFunc.pipe._send({ cmd: 'amtstate', value: state }); } } + func.pipe = this; + amt.getAmtInfo(func); + break; + case 'sessions': + this._send({ cmd: 'sessions', sessions: tunnelUserCount }); + break; + } + } + catch (e) { removeRegisteredApp(this); this.end(); return; } + }); + }); + + // Send current sessions to registered apps + function broadcastSessionsToRegisteredApps(x) { + broadcastToRegisteredApps({ cmd: 'sessions', sessions: tunnelUserCount }); + } + + // Send this object to all registered local applications + function broadcastToRegisteredApps(x) { + if ((obj.DAIPC == null) || (obj.DAIPC._daipc == null)) return; + for (var i in obj.DAIPC._daipc) { if (obj.DAIPC._daipc[i]._registered != null) { obj.DAIPC._daipc[i]._send(x); } } + } + + // Send list of registered apps to the server + function updateRegisteredAppsToServer() { + if ((obj.DAIPC == null) || (obj.DAIPC._daipc == null)) return; + var apps = {}; + for (var i in obj.DAIPC._daipc) { if (apps[obj.DAIPC._daipc[i]._registered] == null) { apps[obj.DAIPC._daipc[i]._registered] = 1; } else { apps[obj.DAIPC._daipc[i]._registered]++; } } + try { mesh.SendCommand({ action: 'sessions', type: 'app', value: apps }); } catch (e) { } + } + + // Remove a registered app + function removeRegisteredApp(pipe) { + for (var i = obj.DAIPC._daipc.length - 1; i >= 0; i--) { if (obj.DAIPC._daipc[i] === pipe) { obj.DAIPC._daipc.splice(i, 1); } } + if (pipe._registered != null) updateRegisteredAppsToServer(); + } + + function diagnosticAgent_uninstall() { + require('service-manager').manager.uninstallService('meshagentDiagnostic'); + require('task-scheduler').delete('meshagentDiagnostic/periodicStart'); + }; + function diagnosticAgent_installCheck(install) { + try { + var diag = require('service-manager').manager.getService('meshagentDiagnostic'); + return (diag); + } + catch (e) { + } + if (!install) { return (null); } + + var svc = null; + try { + require('service-manager').manager.installService( + { + name: 'meshagentDiagnostic', + displayName: "Mesh Agent Diagnostic Service", + description: "Mesh Agent Diagnostic Service", + servicePath: process.execPath, + parameters: ['-recovery'] + //files: [{ newName: 'diagnostic.js', _buffer: Buffer.from('LyoNCkNvcHlyaWdodCAyMDE5IEludGVsIENvcnBvcmF0aW9uDQoNCkxpY2Vuc2VkIHVuZGVyIHRoZSBBcGFjaGUgTGljZW5zZSwgVmVyc2lvbiAyLjAgKHRoZSAiTGljZW5zZSIpOw0KeW91IG1heSBub3QgdXNlIHRoaXMgZmlsZSBleGNlcHQgaW4gY29tcGxpYW5jZSB3aXRoIHRoZSBMaWNlbnNlLg0KWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0DQoNCiAgICBodHRwOi8vd3d3LmFwYWNoZS5vcmcvbGljZW5zZXMvTElDRU5TRS0yLjANCg0KVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQ0KZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywNCldJVEhPVVQgV0FSUkFOVElFUyBPUiBDT05ESVRJT05TIE9GIEFOWSBLSU5ELCBlaXRoZXIgZXhwcmVzcyBvciBpbXBsaWVkLg0KU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZA0KbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuDQoqLw0KDQp2YXIgaG9zdCA9IHJlcXVpcmUoJ3NlcnZpY2UtaG9zdCcpLmNyZWF0ZSgnbWVzaGFnZW50RGlhZ25vc3RpYycpOw0KdmFyIFJlY292ZXJ5QWdlbnQgPSByZXF1aXJlKCdNZXNoQWdlbnQnKTsNCg0KaG9zdC5vbignc2VydmljZVN0YXJ0JywgZnVuY3Rpb24gKCkNCnsNCiAgICBjb25zb2xlLnNldERlc3RpbmF0aW9uKGNvbnNvbGUuRGVzdGluYXRpb25zLkxPR0ZJTEUpOw0KICAgIGhvc3Quc3RvcCA9IGZ1bmN0aW9uKCkNCiAgICB7DQogICAgICAgIHJlcXVpcmUoJ3NlcnZpY2UtbWFuYWdlcicpLm1hbmFnZXIuZ2V0U2VydmljZSgnbWVzaGFnZW50RGlhZ25vc3RpYycpLnN0b3AoKTsNCiAgICB9DQogICAgUmVjb3ZlcnlBZ2VudC5vbignQ29ubmVjdGVkJywgZnVuY3Rpb24gKHN0YXR1cykNCiAgICB7DQogICAgICAgIGlmIChzdGF0dXMgPT0gMCkNCiAgICAgICAgew0KICAgICAgICAgICAgY29uc29sZS5sb2coJ0RpYWdub3N0aWMgQWdlbnQ6IFNlcnZlciBjb25uZWN0aW9uIGxvc3QuLi4nKTsNCiAgICAgICAgICAgIHJldHVybjsNCiAgICAgICAgfQ0KICAgICAgICBjb25zb2xlLmxvZygnRGlhZ25vc3RpYyBBZ2VudDogQ29ubmVjdGlvbiBFc3RhYmxpc2hlZCB3aXRoIFNlcnZlcicpOw0KICAgICAgICBzdGFydCgpOw0KICAgIH0pOw0KfSk7DQpob3N0Lm9uKCdub3JtYWxTdGFydCcsIGZ1bmN0aW9uICgpDQp7DQogICAgaG9zdC5zdG9wID0gZnVuY3Rpb24gKCkNCiAgICB7DQogICAgICAgIHByb2Nlc3MuZXhpdCgpOw0KICAgIH0NCiAgICBjb25zb2xlLmxvZygnTm9uIFNlcnZpY2UgTW9kZScpOw0KICAgIFJlY292ZXJ5QWdlbnQub24oJ0Nvbm5lY3RlZCcsIGZ1bmN0aW9uIChzdGF0dXMpDQogICAgew0KICAgICAgICBpZiAoc3RhdHVzID09IDApDQogICAgICAgIHsNCiAgICAgICAgICAgIGNvbnNvbGUubG9nKCdEaWFnbm9zdGljIEFnZW50OiBTZXJ2ZXIgY29ubmVjdGlvbiBsb3N0Li4uJyk7DQogICAgICAgICAgICByZXR1cm47DQogICAgICAgIH0NCiAgICAgICAgY29uc29sZS5sb2coJ0RpYWdub3N0aWMgQWdlbnQ6IENvbm5lY3Rpb24gRXN0YWJsaXNoZWQgd2l0aCBTZXJ2ZXInKTsNCiAgICAgICAgc3RhcnQoKTsNCiAgICB9KTsNCn0pOw0KaG9zdC5vbignc2VydmljZVN0b3AnLCBmdW5jdGlvbiAoKSB7IHByb2Nlc3MuZXhpdCgpOyB9KTsNCmhvc3QucnVuKCk7DQoNCg0KZnVuY3Rpb24gc3RhcnQoKQ0Kew0KDQp9Ow0K', 'base64') }] + }); + svc = require('service-manager').manager.getService('meshagentDiagnostic'); + } + catch (e) { + return (null); + } + var proxyConfig = require('global-tunnel').proxyConfig; + var cert = require('MeshAgent').GenerateAgentCertificate('CN=MeshNodeDiagnosticCertificate'); + var nodeid = require('tls').loadCertificate(cert.root).getKeyHash().toString('base64'); + ddb = require('SimpleDataStore').Create(svc.appWorkingDirectory().replace('\\', '/') + '/meshagentDiagnostic.db'); + ddb.Put('disableUpdate', '1'); + ddb.Put('MeshID', Buffer.from(require('MeshAgent').ServerInfo.MeshID, 'hex')); + ddb.Put('ServerID', require('MeshAgent').ServerInfo.ServerID); + ddb.Put('MeshServer', require('MeshAgent').ServerInfo.ServerUri); + if (cert.root.pfx) { ddb.Put('SelfNodeCert', cert.root.pfx); } + if (cert.tls) { ddb.Put('SelfNodeTlsCert', cert.tls.pfx); } + if (proxyConfig) { + ddb.Put('WebProxy', proxyConfig.host + ':' + proxyConfig.port); + } else { + ddb.Put('ignoreProxyFile', '1'); + } + + require('MeshAgent').SendCommand({ action: 'diagnostic', value: { command: 'register', value: nodeid } }); + require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: "Diagnostic Agent Registered [" + nodeid.length + "/" + nodeid + "]" }); + + delete ddb; + + // Set a recurrent task, to run the Diagnostic Agent every 2 days + require('task-scheduler').create({ name: 'meshagentDiagnostic/periodicStart', daily: 2, time: require('tls').generateRandomInteger('0', '23') + ':' + require('tls').generateRandomInteger('0', '59').padStart(2, '0'), service: 'meshagentDiagnostic' }); + //require('task-scheduler').create({ name: 'meshagentDiagnostic/periodicStart', daily: '1', time: '17:16', service: 'meshagentDiagnostic' }); + + return (svc); + } + + // Monitor the file 'batterystate.txt' in the agent's folder and sends battery update when this file is changed. + if ((require('fs').existsSync(process.cwd() + 'batterystate.txt')) && (require('fs').watch != null)) { + // Setup manual battery monitoring + require('MeshAgent')._batteryFileWatcher = require('fs').watch(process.cwd(), function () { + if (require('MeshAgent')._batteryFileTimer != null) return; + require('MeshAgent')._batteryFileTimer = setTimeout(function () { + try { + require('MeshAgent')._batteryFileTimer = null; + var data = null; + try { data = require('fs').readFileSync(process.cwd() + 'batterystate.txt').toString(); } catch (e) { } + if ((data != null) && (data.length < 10)) { + data = data.split(','); + if ((data.length == 2) && ((data[0] == 'ac') || (data[0] == 'dc'))) { + var level = parseInt(data[1]); + if ((level >= 0) && (level <= 100)) { require('MeshAgent').SendCommand({ action: 'battery', state: data[0], level: level }); } + } + } + } catch (e) { } + }, 1000); + }); + } else { + // Setup normal battery monitoring + if (require('identifiers').isBatteryPowered && require('identifiers').isBatteryPowered()) { + require('MeshAgent')._battLevelChanged = function _battLevelChanged(val) { + _battLevelChanged.self._currentBatteryLevel = val; + _battLevelChanged.self.SendCommand({ action: 'battery', state: _battLevelChanged.self._currentPowerState, level: val }); + }; + require('MeshAgent')._battLevelChanged.self = require('MeshAgent'); + require('MeshAgent')._powerChanged = function _powerChanged(val) { + _powerChanged.self._currentPowerState = (val == 'AC' ? 'ac' : 'dc'); + _powerChanged.self.SendCommand({ action: 'battery', state: (val == 'AC' ? 'ac' : 'dc'), level: _powerChanged.self._currentBatteryLevel }); + }; + require('MeshAgent')._powerChanged.self = require('MeshAgent'); + require('MeshAgent').on('Connected', function (status) { + if (status == 0) { + require('power-monitor').removeListener('acdc', this._powerChanged); + require('power-monitor').removeListener('batteryLevel', this._battLevelChanged); + } else { + require('power-monitor').on('acdc', this._powerChanged); + require('power-monitor').on('batteryLevel', this._battLevelChanged); + } + }); + } + } + + + /* + function borderController() { + this.container = null; + this.Start = function Start(user) { + if (this.container == null) { + if (process.platform == 'win32') { + try { + this.container = require('ScriptContainer').Create({ processIsolation: 1, sessionId: user.SessionId }); + } catch (e) { + this.container = require('ScriptContainer').Create({ processIsolation: 1 }); + } + } else { + this.container = require('ScriptContainer').Create({ processIsolation: 1, sessionId: user.uid }); + } + this.container.parent = this; + this.container.addModule('monitor-info', getJSModule('monitor-info')); + this.container.addModule('monitor-border', getJSModule('monitor-border')); + this.container.addModule('promise', getJSModule('promise')); + this.container.once('exit', function (code) { sendConsoleText('Border Process Exited with code: ' + code); this.parent.container = this.parent._container = null; }); + this.container.ExecuteString("var border = require('monitor-border'); border.Start();"); + } + } + this.Stop = function Stop() { + if (this.container != null) { + this._container = this.container; + this._container.parent = this; + this.container = null; + this._container.exit(); + } + } + } + obj.borderManager = new borderController(); + */ + + // MeshAgent JavaScript Core Module. This code is sent to and running on the mesh agent. + var meshCoreObj = { action: 'coreinfo', value: (require('MeshAgent').coreHash ? ('MeshCore CRC-' + crc32c(require('MeshAgent').coreHash)) : ('MeshCore v6')), caps: 14, root: require('user-sessions').isRoot() }; // Capability bitmask: 1 = Desktop, 2 = Terminal, 4 = Files, 8 = Console, 16 = JavaScript, 32 = Temporary Agent, 64 = Recovery Agent + + + // Get the operating system description string + try { require('os').name().then(function (v) { meshCoreObj.osdesc = v; }); } catch (e) { } + + var meshServerConnectionState = 0; + var tunnels = {}; + var lastMeInfo = null; + var lastNetworkInfo = null; + var lastPublicLocationInfo = null; + var selfInfoUpdateTimer = null; + var http = require('http'); + var net = require('net'); + var fs = require('fs'); + var rtc = require('ILibWebRTC'); + var amt = null; + var processManager = require('process-manager'); + var wifiScannerLib = null; + var wifiScanner = null; + var networkMonitor = null; + var amtscanner = null; + var nextTunnelIndex = 1; + var amtPolicy = null; + var apftunnel = null; + var tunnelUserCount = { terminal: {}, files: {}, tcp: {}, udp: {}, msg: {} }; // List of userid->count sessions for terminal, files and TCP/UDP routing + + // Add to the server event log + function MeshServerLog(msg, state) { + if (typeof msg == 'string') { msg = { action: 'log', msg: msg }; } else { msg.action = 'log'; } + if (state) { + if (state.userid) { msg.userid = state.userid; } + if (state.username) { msg.username = state.username; } + if (state.sessionid) { msg.sessionid = state.sessionid; } + if (state.remoteaddr) { msg.remoteaddr = state.remoteaddr; } + } + mesh.SendCommand(msg); + } + + // Add to the server event log, use internationalized events + function MeshServerLogEx(id, args, msg, state) { + var msg = { action: 'log', msgid: id, msgArgs: args, msg: msg }; + if (state) { + if (state.userid) { msg.userid = state.userid; } + if (state.username) { msg.username = state.username; } + if (state.sessionid) { msg.sessionid = state.sessionid; } + if (state.remoteaddr) { msg.remoteaddr = state.remoteaddr; } + } + mesh.SendCommand(msg); + } + + // If we are running in Duktape, agent will be null + if (agent == null) { + // Running in native agent, Import libraries + db = require('SimpleDataStore').Shared(); + sha = require('SHA256Stream'); + mesh = require('MeshAgent'); + childProcess = require('child_process'); + if (mesh.hasKVM == 1) { // if the agent is compiled with KVM support + // Check if this computer supports a desktop + try + { + if ((process.platform == 'win32') || (process.platform == 'darwin') || (require('monitor-info').kvm_x11_support)) + { + meshCoreObj.caps |= 1; + } + else if(process.platform == 'linux' || process.platform == 'freebsd') + { + require('monitor-info').on('kvmSupportDetected', function (value) + { + meshCoreObj.caps |= 1; + mesh.SendCommand(meshCoreObj); + }); + } + } catch (e) { } + } + } else { + // Running in nodejs + meshCoreObj.value += '-NodeJS'; + meshCoreObj.caps = 8; + mesh = agent.getMeshApi(); + } + + mesh.DAIPC = obj.DAIPC; + + /* + var AMTScanner = require("AMTScanner"); + var scan = new AMTScanner(); + + scan.on("found", function (data) { + if (typeof data === 'string') { + console.log(data); + } else { + console.log(JSON.stringify(data, null, " ")); + } + }); + scan.scan("10.2.55.140", 1000); + scan.scan("10.2.55.139-10.2.55.145", 1000); + scan.scan("10.2.55.128/25", 2000); + */ + + /* + // Try to load up the network monitor + try { + networkMonitor = require('NetworkMonitor'); + networkMonitor.on('change', function () { sendNetworkUpdateNagle(); }); + networkMonitor.on('add', function (addr) { sendNetworkUpdateNagle(); }); + networkMonitor.on('remove', function (addr) { sendNetworkUpdateNagle(); }); + } catch (e) { networkMonitor = null; } + */ + + // Try to load up the Intel AMT scanner + try { + var AMTScannerModule = require('amt-scanner'); + amtscanner = new AMTScannerModule(); + //amtscanner.on('found', function (data) { if (typeof data != 'string') { data = JSON.stringify(data, null, " "); } sendConsoleText(data); }); + } catch (e) { amtscanner = null; } + + // Fetch the SMBios Tables + var SMBiosTables = null; + var SMBiosTablesRaw = null; + try { + var SMBiosModule = null; + try { SMBiosModule = require('smbios'); } catch (e) { } + if (SMBiosModule != null) { + SMBiosModule.get(function (data) { + if (data != null) { + SMBiosTablesRaw = data; + SMBiosTables = require('smbios').parse(data) + if (mesh.isControlChannelConnected) { mesh.SendCommand({ action: 'smbios', value: SMBiosTablesRaw }); } + + // If SMBios tables say that Intel AMT is present, try to connect MEI + if (SMBiosTables.amtInfo && (SMBiosTables.amtInfo.AMT == true)) + { + var amtmodule = require('amt-manage'); + amt = new amtmodule(mesh, db, false); + amt.on('portBinding_LMS', function (map) + { + var j = { action: 'lmsinfo', value: { ports: map.keys() } }; + mesh.SendCommand(j); + }); + amt.on('stateChange_LMS', function (v) + { + if (!meshCoreObj.intelamt) { meshCoreObj.intelamt = {}; } + switch(v) + { + case 0: + meshCoreObj.intelamt.microlms = 'DISABLED'; + break; + case 1: + meshCoreObj.intelamt.microlms = 'CONNECTING'; + break; + case 2: + meshCoreObj.intelamt.microlms = 'CONNECTED'; + break; + default: + break; + } + mesh.SendCommand(meshCoreObj); + }); + amt.onStateChange = function (state) { if (state == 2) { sendPeriodicServerUpdate(1); } } + if (amtPolicy != null) { amt.setPolicy(amtPolicy); } + amt.start(); + } + } + }); + } + } catch (e) { sendConsoleText("ex1: " + e); } + + // Try to load up the WIFI scanner + try { + var wifiScannerLib = require('wifi-scanner'); + wifiScanner = new wifiScannerLib(); + wifiScanner.on('accessPoint', function (data) { sendConsoleText("wifiScanner: " + data); }); + } catch (e) { wifiScannerLib = null; wifiScanner = null; } + + // Get our location (lat/long) using our public IP address + var getIpLocationDataExInProgress = false; + var getIpLocationDataExCounts = [0, 0]; + function getIpLocationDataEx(func) { + if (getIpLocationDataExInProgress == true) { return false; } + try { + getIpLocationDataExInProgress = true; + getIpLocationDataExCounts[0]++; + var options = http.parseUri("http://ipinfo.io/json"); + options.method = 'GET'; + http.request(options, function (resp) { + if (resp.statusCode == 200) { + var geoData = ''; + resp.data = function (geoipdata) { geoData += geoipdata; }; + resp.end = function () { + var location = null; + try { + if (typeof geoData == 'string') { + var result = JSON.parse(geoData); + if (result.ip && result.loc) { location = result; } + } + } catch (e) { } + if (func) { getIpLocationDataExCounts[1]++; func(location); } + } + } else { func(null); } + getIpLocationDataExInProgress = false; + }).end(); + return true; + } + catch (e) { return false; } + } + + // Remove all Gateway MAC addresses for interface list. This is useful because the gateway MAC is not always populated reliably. + function clearGatewayMac(str) { + if (str == null) return null; + var x = JSON.parse(str); + for (var i in x.netif) { if (x.netif[i].gatewaymac) { delete x.netif[i].gatewaymac } } + return JSON.stringify(x); + } + + function getIpLocationData(func) { + // Get the location information for the cache if possible + var publicLocationInfo = db.Get('publicLocationInfo'); + if (publicLocationInfo != null) { publicLocationInfo = JSON.parse(publicLocationInfo); } + if (publicLocationInfo == null) { + // Nothing in the cache, fetch the data + getIpLocationDataEx(function (locationData) { + if (locationData != null) { + publicLocationInfo = {}; + publicLocationInfo.netInfoStr = lastNetworkInfo; + publicLocationInfo.locationData = locationData; + var x = db.Put('publicLocationInfo', JSON.stringify(publicLocationInfo)); // Save to database + if (func) func(locationData); // Report the new location + } else { + if (func) func(null); // Report no location + } + }); + } else { + // Check the cache + if (clearGatewayMac(publicLocationInfo.netInfoStr) == clearGatewayMac(lastNetworkInfo)) { + // Cache match + if (func) func(publicLocationInfo.locationData); + } else { + // Cache mismatch + getIpLocationDataEx(function (locationData) { + if (locationData != null) { + publicLocationInfo = {}; + publicLocationInfo.netInfoStr = lastNetworkInfo; + publicLocationInfo.locationData = locationData; + var x = db.Put('publicLocationInfo', JSON.stringify(publicLocationInfo)); // Save to database + if (func) func(locationData); // Report the new location + } else { + if (func) func(publicLocationInfo.locationData); // Can't get new location, report the old location + } + }); + } + } + } + + // Polyfill String.endsWith + if (!String.prototype.endsWith) { + String.prototype.endsWith = function (searchString, position) { + var subjectString = this.toString(); + if (typeof position !== 'number' || !isFinite(position) || Math.floor(position) !== position || position > subjectString.length) { position = subjectString.length; } + position -= searchString.length; + var lastIndex = subjectString.lastIndexOf(searchString, position); + return lastIndex !== -1 && lastIndex === position; + }; + } + + // Polyfill path.join + obj.path = { + join: function () { + var x = []; + for (var i in arguments) { + var w = arguments[i]; + if (w != null) { + while (w.endsWith('/') || w.endsWith('\\')) { w = w.substring(0, w.length - 1); } + if (i != 0) { + while (w.startsWith('/') || w.startsWith('\\')) { w = w.substring(1); } + } + x.push(w); + } + } + if (x.length == 0) return '/'; + return x.join('/'); + } + }; + + // Replace a string with a number if the string is an exact number + function toNumberIfNumber(x) { if ((typeof x == 'string') && (+parseInt(x) === x)) { x = parseInt(x); } return x; } + + // Convert decimal to hex + function char2hex(i) { return (i + 0x100).toString(16).substr(-2).toUpperCase(); } + + // Convert a raw string to a hex string + function rstr2hex(input) { var r = '', i; for (i = 0; i < input.length; i++) { r += char2hex(input.charCodeAt(i)); } return r; } + + // Convert a buffer into a string + function buf2rstr(buf) { var r = ''; for (var i = 0; i < buf.length; i++) { r += String.fromCharCode(buf[i]); } return r; } + + // Convert a hex string to a raw string // TODO: Do this using Buffer(), will be MUCH faster + function hex2rstr(d) { + if (typeof d != "string" || d.length == 0) return ''; + var r = '', m = ('' + d).match(/../g), t; + while (t = m.shift()) r += String.fromCharCode('0x' + t); + return r + } + + // Convert an object to string with all functions + function objToString(x, p, pad, ret) { + if (ret == undefined) ret = ''; + if (p == undefined) p = 0; + if (x == null) { return '[null]'; } + if (p > 8) { return '[...]'; } + if (x == undefined) { return '[undefined]'; } + if (typeof x == 'string') { if (p == 0) return x; return '"' + x + '"'; } + if (typeof x == 'buffer') { return '[buffer]'; } + if (typeof x != 'object') { return x; } + var r = '{' + (ret ? '\r\n' : ' '); + for (var i in x) { if (i != '_ObjectID') { r += (addPad(p + 2, pad) + i + ': ' + objToString(x[i], p + 2, pad, ret) + (ret ? '\r\n' : ' ')); } } + return r + addPad(p, pad) + '}'; + } + + // Return p number of spaces + function addPad(p, ret) { var r = ''; for (var i = 0; i < p; i++) { r += ret; } return r; } + + // Split a string taking into account the quoats. Used for command line parsing + function splitArgs(str) { + var myArray = [], myRegexp = /[^\s"]+|"([^"]*)"/gi; + do { var match = myRegexp.exec(str); if (match != null) { myArray.push(match[1] ? match[1] : match[0]); } } while (match != null); + return myArray; + } + + // Parse arguments string array into an object + function parseArgs(argv) { + var results = { '_': [] }, current = null; + for (var i = 1, len = argv.length; i < len; i++) { + var x = argv[i]; + if (x.length > 2 && x[0] == '-' && x[1] == '-') { + if (current != null) { results[current] = true; } + current = x.substring(2); + } else { + if (current != null) { results[current] = toNumberIfNumber(x); current = null; } else { results['_'].push(toNumberIfNumber(x)); } + } + } + if (current != null) { results[current] = true; } + return results; + } + + // Get server target url with a custom path + function getServerTargetUrl(path) { + var x = mesh.ServerUrl; + //sendConsoleText("mesh.ServerUrl: " + mesh.ServerUrl); + if (x == null) { return null; } + if (path == null) { path = ''; } + x = http.parseUri(x); + if (x == null) return null; + return x.protocol + '//' + x.host + ':' + x.port + '/' + path; + } + + // Get server url. If the url starts with "*/..." change it, it not use the url as is. + function getServerTargetUrlEx(url) { + if (url.substring(0, 2) == '*/') { return getServerTargetUrl(url.substring(2)); } + return url; + } + + // Send a wake-on-lan packet + function sendWakeOnLan(hexMac) { + hexMac = hexMac.split(':').join(''); + var count = 0; + try { + var interfaces = require('os').networkInterfaces(); + var magic = 'FFFFFFFFFFFF'; + for (var x = 1; x <= 16; ++x) { magic += hexMac; } + var magicbin = Buffer.from(magic, 'hex'); + + for (var adapter in interfaces) { + if (interfaces.hasOwnProperty(adapter)) { + for (var i = 0; i < interfaces[adapter].length; ++i) { + var addr = interfaces[adapter][i]; + if ((addr.family == 'IPv4') && (addr.mac != '00:00:00:00:00:00')) { + try { + var socket = require('dgram').createSocket({ type: 'udp4' }); + socket.bind({ address: addr.address }); + socket.setBroadcast(true); + socket.setMulticastInterface(addr.address); + socket.setMulticastTTL(1); + socket.send(magicbin, 7, '255.255.255.255'); + socket.descriptorMetadata = 'WoL (' + addr.address + ' => ' + hexMac + ')'; + count++; + } + catch (e) { } + } + } + } + } + } catch (e) { } + return count; + } + + // Handle a mesh agent command + function handleServerCommand(data) { + if (typeof data == 'object') { + // If this is a console command, parse it and call the console handler + switch (data.action) { + case 'msg': { + switch (data.type) { + case 'console': { // Process a console command + if (data.value && data.sessionid) { + MeshServerLogEx(17, [data.value], "Processing console command: " + data.value, data); + var args = splitArgs(data.value); + processConsoleCommand(args[0].toLowerCase(), parseArgs(args), data.rights, data.sessionid); + } + break; + } + case 'tunnel': { + if (data.value != null) { // Process a new tunnel connection request + // Create a new tunnel object + var xurl = getServerTargetUrlEx(data.value); + if (xurl != null) { + xurl = xurl.split('$').join('%24').split('@').join('%40'); // Escape the $ and @ characters + var woptions = http.parseUri(xurl); + woptions.perMessageDeflate = false; + if (typeof data.perMessageDeflate == 'boolean') { woptions.perMessageDeflate = data.perMessageDeflate; } + woptions.rejectUnauthorized = 0; + //sendConsoleText(JSON.stringify(woptions)); + //sendConsoleText('TUNNEL: ' + JSON.stringify(data)); + var tunnel = http.request(woptions); + tunnel.upgrade = onTunnelUpgrade; + tunnel.on('error', function (e) { sendConsoleText("ERROR: Unable to connect relay tunnel to: " + this.url + ", " + JSON.stringify(e)); }); + tunnel.sessionid = data.sessionid; + tunnel.rights = data.rights; + tunnel.consent = data.consent; + tunnel.privacybartext = data.privacybartext ? data.privacybartext : "Sharing desktop with: {0}"; + tunnel.username = data.username + (data.guestname ? (' - ' + data.guestname) : ''); + tunnel.realname = (data.realname ? data.realname : data.username) + (data.guestname ? (' - ' + data.guestname) : ''); + tunnel.userid = data.userid; + tunnel.remoteaddr = data.remoteaddr; + tunnel.state = 0; + tunnel.url = xurl; + tunnel.protocol = 0; + tunnel.soptions = data.soptions; + tunnel.tcpaddr = data.tcpaddr; + tunnel.tcpport = data.tcpport; + tunnel.udpaddr = data.udpaddr; + tunnel.udpport = data.udpport; + tunnel.end(); + // Put the tunnel in the tunnels list + var index = nextTunnelIndex++; + tunnel.index = index; + tunnels[index] = tunnel; + + //sendConsoleText('New tunnel connection #' + index + ': ' + tunnel.url + ', rights: ' + tunnel.rights, data.sessionid); + } + } + break; + } + case 'messagebox': { + // Display a message box + if (data.title && data.msg) { + MeshServerLogEx(18, [data.title, data.msg], "Displaying message box, title=" + data.title + ", message=" + data.msg, data); + data.msg = data.msg.split('\r').join('\\r').split('\n').join('\\n'); + try { require('message-box').create(data.title, data.msg, 120); } catch (e) { } + } + break; + } + case 'ps': { + // Return the list of running processes + if (data.sessionid) { + processManager.getProcesses(function (plist) { + mesh.SendCommand({ action: 'msg', type: 'ps', value: JSON.stringify(plist), sessionid: data.sessionid }); + }); + } + break; + } + case 'pskill': { + // Kill a process + if (data.value) { + MeshServerLogEx(19, [data.value], "Killing process " + data.value, data); + try { process.kill(data.value); } catch (e) { sendConsoleText("pskill: " + JSON.stringify(e)); } + } + break; + } + case 'services': { + // Return the list of installed services + var services = null; + try { services = require('service-manager').manager.enumerateService(); } catch (e) { } + if (services != null) { mesh.SendCommand({ action: 'msg', type: 'services', value: JSON.stringify(services), sessionid: data.sessionid }); } + break; + } + case 'serviceStop': { + // Stop a service + try { + var service = require('service-manager').manager.getService(data.serviceName); + if (service != null) { service.stop(); } + } catch (e) { } + break; + } + case 'serviceStart': { + // Start a service + try { + var service = require('service-manager').manager.getService(data.serviceName); + if (service != null) { service.start(); } + } catch (e) { } + break; + } + case 'serviceRestart': { + // Restart a service + try { + var service = require('service-manager').manager.getService(data.serviceName); + if (service != null) { service.restart(); } + } catch (e) { } + break; + } + case 'deskBackground': + { + // Toggle desktop background + try { + if (process.platform == 'win32') { + var stype = require('user-sessions').getProcessOwnerName(process.pid).tsid == 0 ? 1 : 0; + var sid = undefined; + if (stype == 1) { + if (require('MeshAgent')._tsid != null) { + stype = 5; + sid = require('MeshAgent')._tsid; + } + } + var id = require('user-sessions').getProcessOwnerName(process.pid).tsid == 0 ? 1 : 0; + var child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0='], { type: stype, uid: sid }); + child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); + child.stderr.on('data', function () { }); + child.waitExit(); + var current = child.stdout.str.trim(); + if (current != '') { require('MeshAgent')._wallpaper = current; } + child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0=', current != '' ? '""' : require('MeshAgent')._wallpaper], { type: stype, uid: sid }); + child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); + child.stderr.on('data', function () { }); + child.waitExit(); + } else { + var id = require('user-sessions').consoleUid(); + var current = require('linux-gnome-helpers').getDesktopWallpaper(id); + if (current != '/dev/null') { require('MeshAgent')._wallpaper = current; } + require('linux-gnome-helpers').setDesktopWallpaper(id, current != '/dev/null' ? undefined : require('MeshAgent')._wallpaper); + } + } catch (e) { + sendConsoleText(e); + } + break; + } + case 'openUrl': { + // Open a local web browser and return success/fail + MeshServerLogEx(20, [data.url], "Opening: " + data.url, data); + sendConsoleText("OpenURL: " + data.url); + if (data.url) { mesh.SendCommand({ action: 'msg', type: 'openUrl', url: data.url, sessionid: data.sessionid, success: (openUserDesktopUrl(data.url) != null) }); } + break; + } + case 'getclip': { + // Send the load clipboard back to the user + //sendConsoleText('getClip: ' + JSON.stringify(data)); + if (require('MeshAgent').isService) { + require('clipboard').dispatchRead().then(function (str) { + if (str) { + MeshServerLogEx(21, [str.length], "Getting clipboard content, " + str.length + " byte(s)", data); + mesh.SendCommand({ action: 'msg', type: 'getclip', sessionid: data.sessionid, data: str, tag: data.tag }); + } + }); + } else { + require("clipboard").read().then(function (str) { + if (str) { + MeshServerLogEx(21, [str.length], "Getting clipboard content, " + str.length + " byte(s)", data); + mesh.SendCommand({ action: 'msg', type: 'getclip', sessionid: data.sessionid, data: str, tag: data.tag }); + } + }); + } + break; + } + case 'setclip': { + // Set the load clipboard to a user value + //sendConsoleText('setClip: ' + JSON.stringify(data)); + if (typeof data.data == 'string') { + MeshServerLogEx(22, [data.data.length], "Setting clipboard content, " + data.data.length + " byte(s)", data); + if (require('MeshAgent').isService) { require('clipboard').dispatchWrite(data.data); } else { require("clipboard")(data.data); } // Set the clipboard + mesh.SendCommand({ action: 'msg', type: 'setclip', sessionid: data.sessionid, success: true }); + } + break; + } + case 'userSessions': { + // Send back current user sessions list, this is Windows only. + //sendConsoleText('userSessions: ' + JSON.stringify(data)); + if (process.platform != 'win32') break; + var p = require('user-sessions').enumerateUsers(); + p.sessionid = data.sessionid; + p.then(function (u) { mesh.SendCommand({ action: 'msg', type: 'userSessions', sessionid: data.sessionid, data: u, tag: data.tag }); }); + break; + } + default: + // Unknown action, ignore it. + break; + } + break; + } + case 'acmactivate': { + if (amt != null) { + MeshServerLogEx(23, null, "Attempting Intel AMT ACM mode activation", data); + amt.setAcmResponse(data); + } + break; + } + case 'wakeonlan': { + // Send wake-on-lan on all interfaces for all MAC addresses in data.macs array. The array is a list of HEX MAC addresses. + sendConsoleText("Server requesting wake-on-lan for: " + data.macs.join(', ')); + for (var i in data.macs) { sendWakeOnLan(data.macs[i]); } + break; + } + case 'runcommands': { + if (mesh.cmdchild != null) { sendConsoleText("Run commands can't execute, already busy."); break; } + sendConsoleText("Run commands (" + data.runAsUser + "): " + data.cmds); + + // data.runAsUser: 0=Agent,1=UserOrAgent,2=UserOnly + var options = {}; + if (data.runAsUser > 0) { + try { options.uid = require('user-sessions').consoleUid(); } catch (e) { } + options.type = require('child_process').SpawnTypes.TERM; + } + if (data.runAsUser == 2) { + if (options.uid == null) break; + if (((require('user-sessions').minUid != null) && (options.uid < require('user-sessions').minUid()))) break; // This command can only run as user. + } + + if (process.platform == 'win32') { + if (data.type == 1) { + // Windows command shell + mesh.cmdchild = require('child_process').execFile(process.env['windir'] + '\\system32\\cmd.exe', ['cmd'], options); + mesh.cmdchild.descriptorMetadata = 'UserCommandsShell'; + mesh.cmdchild.stdout.on('data', function (c) { sendConsoleText(c.toString()); }); + mesh.cmdchild.stderr.on('data', function (c) { sendConsoleText(c.toString()); }); + mesh.cmdchild.stdin.write(data.cmds + '\r\nexit\r\n'); + mesh.cmdchild.on('exit', function () { sendConsoleText("Run commands completed."); delete mesh.cmdchild; }); + } else if (data.type == 2) { + // Windows Powershell + mesh.cmdchild = require('child_process').execFile(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', ['powershell', '-noprofile', '-nologo', '-command', '-'], options); + mesh.cmdchild.descriptorMetadata = 'UserCommandsPowerShell'; + mesh.cmdchild.stdout.on('data', function (c) { sendConsoleText(c.toString()); }); + mesh.cmdchild.stderr.on('data', function (c) { sendConsoleText(c.toString()); }); + mesh.cmdchild.stdin.write(data.cmds + '\r\nexit\r\n'); + mesh.cmdchild.on('exit', function () { sendConsoleText("Run commands completed."); delete mesh.cmdchild; }); + } + } else if (data.type == 3) { + // Linux shell + mesh.cmdchild = require('child_process').execFile('/bin/sh', ['sh'], options); + mesh.cmdchild.descriptorMetadata = 'UserCommandsShell'; + mesh.cmdchild.stdout.on('data', function (c) { sendConsoleText(c.toString()); }); + mesh.cmdchild.stderr.on('data', function (c) { sendConsoleText(c.toString()); }); + mesh.cmdchild.stdin.write(data.cmds.split('\r').join('') + '\nexit\n'); + mesh.cmdchild.on('exit', function () { sendConsoleText("Run commands completed."); delete mesh.cmdchild; }); + } + break; + } + case 'uninstallagent': + // Uninstall this agent + var agentName = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'; + if (require('service-manager').manager.getService(agentName).isMe()) { + try { diagnosticAgent_uninstall(); } catch (e) { } + var js = "require('service-manager').manager.getService('" + agentName + "').stop(); require('service-manager').manager.uninstallService('" + agentName + "'); process.exit();"; + this.child = require('child_process').execFile(process.execPath, [process.platform == 'win32' ? (process.execPath.split('\\').pop()) : (process.execPath.split('/').pop()), '-b64exec', Buffer.from(js).toString('base64')], { type: 4, detached: true }); + } + break; + case 'poweraction': { + // Server telling us to execute a power action + if ((mesh.ExecPowerState != undefined) && (data.actiontype)) { + var forced = 0; + if (data.forced == 1) { forced = 1; } + data.actiontype = parseInt(data.actiontype); + MeshServerLogEx(25, [data.actiontype, forced], "Performing power action=" + data.actiontype + ", forced=" + forced, data); + sendConsoleText("Performing power action=" + data.actiontype + ", forced=" + forced + '.'); + var r = mesh.ExecPowerState(data.actiontype, forced); + sendConsoleText("ExecPowerState returned code: " + r); + } + break; + } + case 'iplocation': { + // Update the IP location information of this node. Only do this when requested by the server since we have a limited amount of time we can call this per day + getIpLocationData(function (location) { mesh.SendCommand({ action: 'iplocation', type: 'publicip', value: location }); }); + break; + } + case 'toast': { + // Display a toast message + if (data.title && data.msg) { + MeshServerLogEx(26, [data.title, data.msg], "Displaying toast message, title=" + data.title + ", message=" + data.msg, data); + data.msg = data.msg.split('\r').join('\\r').split('\n').join('\\n'); + try { require('toaster').Toast(data.title, data.msg); } catch (e) { } + } + break; + } + case 'openUrl': { + // Open a local web browser and return success/fail + //sendConsoleText('OpenURL: ' + data.url); + MeshServerLogEx(20, [data.url], "Opening: " + data.url, data); + if (data.url) { mesh.SendCommand({ action: 'openUrl', url: data.url, sessionid: data.sessionid, success: (openUserDesktopUrl(data.url) != null) }); } + break; + } + case 'amtPolicy': { + // Store the latest Intel AMT policy + amtPolicy = data.amtPolicy; + if (data.amtPolicy != null) { db.Put('amtPolicy', JSON.stringify(data.amtPolicy)); } else { db.Put('amtPolicy', null); } + if (amt != null) { amt.setPolicy(amtPolicy, true); } + break; + } + case 'getScript': { + // Received a configuration script from the server + sendConsoleText('getScript: ' + JSON.stringify(data)); + break; + } + case 'sysinfo': { + // Fetch system information + getSystemInformation(function (results) { + if ((results != null) && (data.hash != results.hash)) { mesh.SendCommand({ action: 'sysinfo', sessionid: this.sessionid, data: results }); } + }); + break; + } + case 'ping': { mesh.SendCommand('{"action":"pong"}'); break; } + case 'pong': { break; } + case 'plugin': { + try { require(data.plugin).consoleaction(data, data.rights, data.sessionid, this); } catch (e) { throw e; } + break; + } + case 'coredump': + // Set the current agent coredump situation. + if (data.value === true) { + // TODO: This replace() below is not ideal, would be better to remove the .exe at the end instead of replace. + process.coreDumpLocation = (process.platform == 'win32') ? (process.execPath.replace('.exe', '.dmp')) : (process.execPath + '.dmp'); + } else if (data.value === false) { + process.coreDumpLocation = null; + } + break; + case 'getcoredump': + // Ask the agent if a core dump is currently available, if yes, also return the hash of the agent. + var r = { action: 'getcoredump', value: (process.coreDumpLocation != null) }; + var coreDumpPath = null; + if (process.platform == 'win32') { coreDumpPath = process.coreDumpLocation; } else { coreDumpPath = (process.cwd() != '//') ? fs.existsSync(process.cwd() + 'core') : null; } + if ((coreDumpPath != null) && (fs.existsSync(coreDumpPath))) { r.exists = (db.Get('CoreDumpTime') != require('fs').statSync(coreDumpPath).mtime); } + if (r.exists == true) { r.agenthashhex = getSHA384FileHash(process.execPath).toString('hex'); } + mesh.SendCommand(JSON.stringify(r)); + default: + // Unknown action, ignore it. + break; + } + } + } + + // Called when a file changed in the file system + /* + function onFileWatcher(a, b) { + console.log('onFileWatcher', a, b, this.path); + var response = getDirectoryInfo(this.path); + if ((response != undefined) && (response != null)) { this.tunnel.s.write(JSON.stringify(response)); } + } + */ + + function getSystemInformation(func) { + try { + var results = { hardware: require('identifiers').get() }; // Hardware info + if (results.hardware && results.hardware.windows) { + // Remove extra entries and things that change quickly + var x = results.hardware.windows.osinfo; + try { delete x.FreePhysicalMemory; } catch (e) { } + try { delete x.FreeSpaceInPagingFiles; } catch (e) { } + try { delete x.FreeVirtualMemory; } catch (e) { } + try { delete x.LocalDateTime; } catch (e) { } + try { delete x.MaxProcessMemorySize; } catch (e) { } + try { delete x.TotalVirtualMemorySize; } catch (e) { } + try { delete x.TotalVisibleMemorySize; } catch (e) { } + try { + if (results.hardware.windows.memory) { for (var i in results.hardware.windows.memory) { delete results.hardware.windows.memory[i].Node; } } + if (results.hardware.windows.osinfo) { delete results.hardware.windows.osinfo.Node; } + if (results.hardware.windows.partitions) { for (var i in results.hardware.windows.partitions) { delete results.hardware.windows.partitions[i].Node; } } + } catch (e) { } + } + if (process.platform == 'win32') { results.pendingReboot = require('win-info').pendingReboot(); } // Pending reboot + /* + if (process.platform == 'win32') { + var defragResult = function (r) { + if (typeof r == 'object') { results[this.callname] = r; } + if (this.callname == 'defrag') { + var pr = require('win-info').installedApps(); // Installed apps + pr.callname = 'installedApps'; + pr.sessionid = data.sessionid; + pr.then(defragResult, defragResult); + } + else { + results.winpatches = require('win-info').qfe(); // Windows patches + results.hash = require('SHA384Stream').create().syncHash(JSON.stringify(results)).toString('hex'); + func(results); + } + } + var pr = require('win-info').defrag({ volume: 'C:' }); // Defrag TODO + pr.callname = 'defrag'; + pr.sessionid = data.sessionid; + pr.then(defragResult, defragResult); + } else { + */ + results.hash = require('SHA384Stream').create().syncHash(JSON.stringify(results)).toString('hex'); + func(results); + //} + } catch (e) { func(null, e); } + } + + // Get a formated response for a given directory path + function getDirectoryInfo(reqpath) { + var response = { path: reqpath, dir: [] }; + if (((reqpath == undefined) || (reqpath == '')) && (process.platform == 'win32')) { + // List all the drives in the root, or the root itself + var results = null; + try { results = fs.readDrivesSync(); } catch (e) { } // TODO: Anyway to get drive total size and free space? Could draw a progress bar. + if (results != null) { + for (var i = 0; i < results.length; ++i) { + var drive = { n: results[i].name, t: 1 }; + if (results[i].type == 'REMOVABLE') { drive.dt = 'removable'; } // TODO: See if this is USB/CDROM or something else, we can draw icons. + response.dir.push(drive); + } + } + } else { + // List all the files and folders in this path + if (reqpath == '') { reqpath = '/'; } + var results = null, xpath = obj.path.join(reqpath, '*'); + //if (process.platform == "win32") { xpath = xpath.split('/').join('\\'); } + try { results = fs.readdirSync(xpath); } catch (e) { } + if (results != null) { + for (var i = 0; i < results.length; ++i) { + if ((results[i] != '.') && (results[i] != '..')) { + var stat = null, p = obj.path.join(reqpath, results[i]); + //if (process.platform == "win32") { p = p.split('/').join('\\'); } + try { stat = fs.statSync(p); } catch (e) { } // TODO: Get file size/date + if ((stat != null) && (stat != undefined)) { + if (stat.isDirectory() == true) { + response.dir.push({ n: results[i], t: 2, d: stat.mtime }); + } else { + response.dir.push({ n: results[i], t: 3, s: stat.size, d: stat.mtime }); + } + } + } + } + } + } + return response; + } + + // Tunnel callback operations + function onTunnelUpgrade(response, s, head) { + this.s = s; + s.httprequest = this; + s.end = onTunnelClosed; + s.tunnel = this; + s.descriptorMetadata = "MeshAgent_relayTunnel"; + + if (require('MeshAgent').idleTimeout != null) + { + s.setTimeout(require('MeshAgent').idleTimeout * 1000); + s.on('timeout', function () + { + this.ping(); + this.setTimeout(require('MeshAgent').idleTimeout * 1000); + }); + } + + //sendConsoleText('onTunnelUpgrade - ' + this.tcpport + ' - ' + this.udpport); + + if (this.tcpport != null) { + // This is a TCP relay connection, pause now and try to connect to the target. + s.pause(); + s.data = onTcpRelayServerTunnelData; + var connectionOptions = { port: parseInt(this.tcpport) }; + if (this.tcpaddr != null) { connectionOptions.host = this.tcpaddr; } else { connectionOptions.host = '127.0.0.1'; } + s.tcprelay = net.createConnection(connectionOptions, onTcpRelayTargetTunnelConnect); + s.tcprelay.peerindex = this.index; + + // Add the TCP session to the count and update the server + if (s.httprequest.userid != null) { + if (tunnelUserCount.tcp[s.httprequest.userid] == null) { tunnelUserCount.tcp[s.httprequest.userid] = 1; } else { tunnelUserCount.tcp[s.httprequest.userid]++; } + try { mesh.SendCommand({ action: 'sessions', type: 'tcp', value: tunnelUserCount.tcp }); } catch (e) { } + broadcastSessionsToRegisteredApps(); + } + } if (this.udpport != null) { + // This is a UDP relay connection, get the UDP socket setup. // TODO: *************** + s.data = onUdpRelayServerTunnelData; + s.udprelay = require('dgram').createSocket({ type: 'udp4' }); + s.udprelay.bind({ port: 0 }); + s.udprelay.peerindex = this.index; + s.udprelay.on('message', onUdpRelayTargetTunnelConnect); + s.udprelay.udpport = this.udpport; + s.udprelay.udpaddr = this.udpaddr; + s.udprelay.first = true; + + // Add the UDP session to the count and update the server + if (s.httprequest.userid != null) { + if (tunnelUserCount.udp[s.httprequest.userid] == null) { tunnelUserCount.udp[s.httprequest.userid] = 1; } else { tunnelUserCount.udp[s.httprequest.userid]++; } + try { mesh.SendCommand({ action: 'sessions', type: 'udp', value: tunnelUserCount.tcp }); } catch (e) { } + broadcastSessionsToRegisteredApps(); + } + } else { + // This is a normal connect for KVM/Terminal/Files + s.data = onTunnelData; + } + } + + // Called when UDP relay data is received // TODO**** + function onUdpRelayTargetTunnelConnect(data) { + var peerTunnel = tunnels[this.peerindex]; + peerTunnel.s.write(data); + } + + // Called when we get data from the server for a TCP relay (We have to skip the first received 'c' and pipe the rest) + function onUdpRelayServerTunnelData(data) { + if (this.udprelay.first === true) { + delete this.udprelay.first; // Skip the first 'c' that is received. + } else { + this.udprelay.send(data, parseInt(this.udprelay.udpport), this.udprelay.udpaddr ? this.udprelay.udpaddr : '127.0.0.1'); + } + } + + // Called when the TCP relay target is connected + function onTcpRelayTargetTunnelConnect() { + var peerTunnel = tunnels[this.peerindex]; + this.pipe(peerTunnel.s); // Pipe Target --> Server + peerTunnel.s.first = true; + peerTunnel.s.resume(); + } + + // Called when we get data from the server for a TCP relay (We have to skip the first received 'c' and pipe the rest) + function onTcpRelayServerTunnelData(data) { + if (this.first == true) { + this.first = false; + this.pipe(this.tcprelay, { dataTypeSkip: 1 }); // Pipe Server --> Target (don't pipe text type websocket frames) + } + } + + function onTunnelClosed() { + var tunnel = tunnels[this.httprequest.index]; + if (tunnel == null) return; // Stop duplicate calls. + + // If this is a routing session, clean up and send the new session counts. + if (this.httprequest.userid != null) { + if (this.httprequest.tcpport != null) { + if (tunnelUserCount.tcp[this.httprequest.userid] != null) { tunnelUserCount.tcp[this.httprequest.userid]--; if (tunnelUserCount.tcp[this.httprequest.userid] <= 0) { delete tunnelUserCount.tcp[this.httprequest.userid]; } } + try { mesh.SendCommand({ action: 'sessions', type: 'tcp', value: tunnelUserCount.tcp }); } catch (e) { } + broadcastSessionsToRegisteredApps(); + } else if (this.httprequest.udpport != null) { + if (tunnelUserCount.udp[this.httprequest.userid] != null) { tunnelUserCount.udp[this.httprequest.userid]--; if (tunnelUserCount.udp[this.httprequest.userid] <= 0) { delete tunnelUserCount.udp[this.httprequest.userid]; } } + try { mesh.SendCommand({ action: 'sessions', type: 'udp', value: tunnelUserCount.udp }); } catch (e) { } + broadcastSessionsToRegisteredApps(); + } + } + + // Sent tunnel statistics to the server, only send this if compression was used. + if ((this.bytesSent_uncompressed) && (this.bytesSent_uncompressed.toString() != this.bytesSent_actual.toString())) { + mesh.SendCommand({ + action: 'tunnelCloseStats', + url: tunnel.url, + userid: tunnel.userid, + protocol: tunnel.protocol, + sessionid: tunnel.sessionid, + sent: this.bytesSent_uncompressed.toString(), + sentActual: this.bytesSent_actual.toString(), + sentRatio: this.bytesSent_ratio, + received: this.bytesReceived_uncompressed.toString(), + receivedActual: this.bytesReceived_actual.toString(), + receivedRatio: this.bytesReceived_ratio + }); + } + + //sendConsoleText("Tunnel #" + this.httprequest.index + " closed. Sent -> " + this.bytesSent_uncompressed + ' bytes (uncompressed), ' + this.bytesSent_actual + ' bytes (actual), ' + this.bytesSent_ratio + '% compression', this.httprequest.sessionid); + if (this.httprequest.index) { delete tunnels[this.httprequest.index]; } + + /* + // Close the watcher if required + if (this.httprequest.watcher != undefined) { + //console.log('Closing watcher: ' + this.httprequest.watcher.path); + //this.httprequest.watcher.close(); // TODO: This line causes the agent to crash!!!! + delete this.httprequest.watcher; + } + */ + + // If there is a upload or download active on this connection, close the file + if (this.httprequest.uploadFile) { fs.closeSync(this.httprequest.uploadFile); delete this.httprequest.uploadFile; delete this.httprequest.uploadFileid; delete this.httprequest.uploadFilePath; } + if (this.httprequest.downloadFile) { delete this.httprequest.downloadFile; } + + // Clean up WebRTC + if (this.webrtc != null) { + if (this.webrtc.rtcchannel) { try { this.webrtc.rtcchannel.close(); } catch (e) { } this.webrtc.rtcchannel.removeAllListeners('data'); this.webrtc.rtcchannel.removeAllListeners('end'); delete this.webrtc.rtcchannel; } + if (this.webrtc.websocket) { delete this.webrtc.websocket; } + try { this.webrtc.close(); } catch (e) { } + this.webrtc.removeAllListeners('connected'); + this.webrtc.removeAllListeners('disconnected'); + this.webrtc.removeAllListeners('dataChannel'); + delete this.webrtc; + } + + // Clean up WebSocket + this.removeAllListeners('data'); + } + function onTunnelSendOk() { /*sendConsoleText("Tunnel #" + this.index + " SendOK.", this.sessionid);*/ } + function onTunnelData(data) { + //console.log("OnTunnelData"); + //sendConsoleText('OnTunnelData, ' + data.length + ', ' + typeof data + ', ' + data); + + // If this is upload data, save it to file + if ((this.httprequest.uploadFile) && (typeof data == 'object') && (data[0] != 123)) { + // Save the data to file being uploaded. + if (data[0] == 0) { + // If data starts with zero, skip the first byte. This is used to escape binary file data from JSON. + try { fs.writeSync(this.httprequest.uploadFile, data, 1, data.length - 1); } catch (e) { sendConsoleText('FileUpload Error'); this.write(Buffer.from(JSON.stringify({ action: 'uploaderror' }))); return; } // Write to the file, if there is a problem, error out. + } else { + // If data does not start with zero, save as-is. + try { fs.writeSync(this.httprequest.uploadFile, data); } catch (e) { sendConsoleText('FileUpload Error'); this.write(Buffer.from(JSON.stringify({ action: 'uploaderror' }))); return; } // Write to the file, if there is a problem, error out. + } + this.write(Buffer.from(JSON.stringify({ action: 'uploadack', reqid: this.httprequest.uploadFileid }))); // Ask for more data. + return; + } + + if (this.httprequest.state == 0) { + // Check if this is a relay connection + if ((data == 'c') || (data == 'cr')) { this.httprequest.state = 1; /*sendConsoleText("Tunnel #" + this.httprequest.index + " now active", this.httprequest.sessionid);*/ } + } + else + { + // Handle tunnel data + if (this.httprequest.protocol == 0) { // 1 = Terminal (admin), 2 = Desktop, 5 = Files, 6 = PowerShell (admin), 7 = Plugin Data Exchange, 8 = Terminal (user), 9 = PowerShell (user), 10 = FileTransfer + // Take a look at the protocol + if ((data.length > 3) && (data[0] == '{')) { onTunnelControlData(data, this); return; } + this.httprequest.protocol = parseInt(data); + if (typeof this.httprequest.protocol != 'number') { this.httprequest.protocol = 0; } + if (this.httprequest.protocol == 10) { + // + // Basic file transfer + // + var stats = null; + if ((process.platform != 'win32') && (this.httprequest.xoptions.file.startsWith('/') == false)) { this.httprequest.xoptions.file = '/' + this.httprequest.xoptions.file; } + try { stats = require('fs').statSync(this.httprequest.xoptions.file) } catch (e) { } + try { if (stats) { this.httprequest.downloadFile = fs.createReadStream(this.httprequest.xoptions.file, { flags: 'rbN' }); } } catch (e) { } + if (this.httprequest.downloadFile) { + //sendConsoleText('BasicFileTransfer, ok, ' + this.httprequest.xoptions.file + ', ' + JSON.stringify(stats)); + this.write(JSON.stringify({ op: 'ok', size: stats.size })); + this.httprequest.downloadFile.pipe(this); + this.httprequest.downloadFile.end = function () { } + } else { + //sendConsoleText('BasicFileTransfer, cancel, ' + this.httprequest.xoptions.file); + this.write(JSON.stringify({ op: 'cancel' })); + } + } + else if ((this.httprequest.protocol == 1) || (this.httprequest.protocol == 6) || (this.httprequest.protocol == 8) || (this.httprequest.protocol == 9)) + { + // + // Remote Terminal + // + + // Check user access rights for terminal + if (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) || ((this.httprequest.rights != 0xFFFFFFFF) && ((this.httprequest.rights & MESHRIGHT_NOTERMINAL) != 0))) { + // Disengage this tunnel, user does not have the rights to do this!! + this.httprequest.protocol = 999999; + this.httprequest.s.end(); + sendConsoleText("Error: No Terminal Control Rights."); + return; + } + + this.descriptorMetadata = "Remote Terminal"; + + if (process.platform == 'win32') + { + if (!require('win-terminal').PowerShellCapable() && (this.httprequest.protocol == 6 || this.httprequest.protocol == 9)) + { + this.httprequest.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: 'PowerShell is not supported on this version of windows', msgid: 1 })); + this.httprequest.s.end(); + return; + } + } + + var prom = require('promise'); + this.httprequest.tpromise = new prom(function (res, rej) { this._res = res; this._rej = rej; }); + this.httprequest.tpromise.that = this; + this.httprequest.tpromise.httprequest = this.httprequest; + + this.end = function () + { + if (this.httprequest.tpromise._consent) { this.httprequest.tpromise._consent.close(); } + if (this.httprequest.connectionPromise) { this.httprequest.connectionPromise._rej('Closed'); } + + // Remove the terminal session to the count to update the server + if (this.httprequest.userid != null) + { + if (tunnelUserCount.terminal[this.httprequest.userid] != null) { tunnelUserCount.terminal[this.httprequest.userid]--; if (tunnelUserCount.terminal[this.httprequest.userid] <= 0) { delete tunnelUserCount.terminal[this.httprequest.userid]; } } + try { mesh.SendCommand({ action: 'sessions', type: 'terminal', value: tunnelUserCount.terminal }); } catch (e) { } + broadcastSessionsToRegisteredApps(); + } + + if (process.platform == 'win32') + { + // Unpipe the web socket + this.unpipe(this.httprequest._term); + if (this.httprequest._term) { this.httprequest._term.unpipe(this); } + + // Unpipe the WebRTC channel if needed (This will also be done when the WebRTC channel ends). + if (this.rtcchannel) + { + this.rtcchannel.unpipe(this.httprequest._term); + if (this.httprequest._term) { this.httprequest._term.unpipe(this.rtcchannel); } + } + + // Clean up + if (this.httprequest._term) { this.httprequest._term.end(); } + this.httprequest._term = null; + } + }; + + // Perform User-Consent if needed. + if (this.httprequest.consent && (this.httprequest.consent & 16)) + { + this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 })); + var consentMessage = this.httprequest.username + " requesting remote terminal access. Grant access?", consentTitle = 'MeshCentral'; + if (this.httprequest.soptions != null) { + if (this.httprequest.soptions.consentTitle != null) { consentTitle = this.httprequest.soptions.consentTitle; } + if (this.httprequest.soptions.consentMsgTerminal != null) { consentMessage = this.httprequest.soptions.consentMsgTerminal.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); } + } + this.httprequest.tpromise._consent = require('message-box').create(consentTitle, consentMessage, 30); + this.httprequest.tpromise._consent.retPromise = this.httprequest.tpromise; + this.httprequest.tpromise._consent.then( + function () + { + // Success + MeshServerLogEx(27, null, "Local user accepted remote terminal request (" + this.retPromise.httprequest.remoteaddr + ")", this.retPromise.that.httprequest); + this.retPromise.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null, msgid: 0 })); + this.retPromise._consent = null; + this.retPromise._res(); + }, + function (e) + { + // Denied + MeshServerLogEx(28, null, "Local user rejected remote terminal request (" + this.retPromise.that.httprequest.remoteaddr + ")", this.retPromise.that.httprequest); + this.retPromise.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 })); + this.retPromise._rej(e.toString()); + }); + } + else + { + // User-Consent is not required, so just resolve this promise + this.httprequest.tpromise._res(); + } + + + this.httprequest.tpromise.then( + function () + { + this.httprequest.connectionPromise = new prom(function (res, rej) { this._res = res; this._rej = rej; }); + this.httprequest.connectionPromise.ws = this.that; + + // Start Terminal + if(process.platform == 'win32') + { + try + { + var cols = 80, rows = 25; + if (this.httprequest.xoptions) + { + if (this.httprequest.xoptions.rows) { rows = this.httprequest.xoptions.rows; } + if (this.httprequest.xoptions.cols) { cols = this.httprequest.xoptions.cols; } + } + + if ((this.httprequest.protocol == 1) || (this.httprequest.protocol == 6)) + { + // Admin Terminal + if (require('win-virtual-terminal').supported) + { + // ConPTY PseudoTerminal + // this.httprequest._term = require('win-virtual-terminal')[this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'](80, 25); + + // The above line is commented out, because there is a bug with ClosePseudoConsole() API, so this is the workaround + this.httprequest._dispatcher = require('win-dispatcher').dispatch({ modules: [{ name: 'win-virtual-terminal', script: getJSModule('win-virtual-terminal') }], launch: { module: 'win-virtual-terminal', method: (this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'), args: [cols, rows] } }); + this.httprequest._dispatcher.httprequest = this.httprequest; + this.httprequest._dispatcher.on('connection', function (c) + { + if (this.httprequest.connectionPromise.completed) + { + c.end(); + } + else + { + this.httprequest.connectionPromise._res(c); + } + }); + } + else + { + // Legacy Terminal + this.httprequest.connectionPromise._res(require('win-terminal')[this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'](cols, rows)); + } + } + else + { + // Logged in user + var userPromise = require('user-sessions').enumerateUsers(); + userPromise.that = this; + userPromise.then(function (u) + { + var that = this.that; + if (u.Active.length > 0) + { + var username = u.Active[0].Username; + if (require('win-virtual-terminal').supported) + { + // ConPTY PseudoTerminal + that.httprequest._dispatcher = require('win-dispatcher').dispatch({ user: username, modules: [{ name: 'win-virtual-terminal', script: getJSModule('win-virtual-terminal') }], launch: { module: 'win-virtual-terminal', method: (that.httprequest.protocol == 9 ? 'StartPowerShell' : 'Start'), args: [cols, rows] } }); + } + else + { + // Legacy Terminal + that.httprequest._dispatcher = require('win-dispatcher').dispatch({ user: username, modules: [{ name: 'win-terminal', script: getJSModule('win-terminal') }], launch: { module: 'win-terminal', method: (that.httprequest.protocol == 9 ? 'StartPowerShell' : 'Start'), args: [cols, rows] } }); + } + that.httprequest._dispatcher.ws = that; + that.httprequest._dispatcher.on('connection', function (c) + { + if (this.ws.httprequest.connectionPromise.completed) + { + c.end(); + } + else + { + this.ws.httprequest.connectionPromise._res(c); + } + }); + } + }); + } + } + catch (e) + { + this.httprequest.connectionPromise._rej('Failed to start remote terminal session, ' + e.toString()); + } + } + else + { + try + { + var bash = fs.existsSync('/bin/bash') ? '/bin/bash' : false; + var sh = fs.existsSync('/bin/sh') ? '/bin/sh' : false; + var login = process.platform == 'linux' ? '/bin/login' : '/usr/bin/login'; + + var env = { HISTCONTROL: 'ignoreboth' }; + if (this.httprequest.xoptions) + { + if (this.httprequest.xoptions.rows) { env.LINES = ('' + this.httprequest.xoptions.rows); } + if (this.httprequest.xoptions.cols) { env.COLUMNS = ('' + this.httprequest.xoptions.cols); } + } + var options = { type: childProcess.SpawnTypes.TERM, uid: (this.httprequest.protocol == 8) ? require('user-sessions').consoleUid() : null, env: env }; + if (this.httprequest.xoptions && this.httprequest.xoptions.requireLogin) + { + if (!require('fs').existsSync(login)) { throw ('Unable to spawn login process'); } + this.httprequest.connectionPromise._res(childProcess.execFile(login, ['login'], options)); // Start login shell + } + else if (bash) + { + var p = childProcess.execFile(bash, ['bash'], options); // Start bash + // Spaces at the beginning of lines are needed to hide commands from the command history + if (process.platform == 'linux') { p.stdin.write(' alias ls=\'ls --color=auto\';clear\n'); } + this.httprequest.connectionPromise._res(p); + } + else if (sh) + { + var p = childProcess.execFile(sh, ['sh'], options); // Start sh + // Spaces at the beginning of lines are needed to hide commands from the command history + if (process.platform == 'linux') { p.stdin.write(' alias ls=\'ls --color=auto\';clear\n'); } + this.httprequest.connectionPromise._res(p); + } + else + { + this.httprequest.connectionPromise._rej('Failed to start remote terminal session, no shell found'); + } + } + catch (e) + { + this.httprequest.connectionPromise._rej('Failed to start remote terminal session, ' + e.toString()); + } + } + + this.httprequest.connectionPromise.then( + function (term) + { + // SUCCESS + var stdoutstream; + var stdinstream; + if (process.platform == 'win32') + { + this.ws.httprequest._term = term; + this.ws.httprequest._term.tunnel = this.ws; + stdoutstream = stdinstream = term; + } + else + { + term.descriptorMetadata = 'Remote Terminal'; + this.ws.httprequest.process = term; + this.ws.httprequest.process.tunnel = this.ws; + term.stderr.stdout = term.stdout; + term.stderr.on('data', function (c) { this.stdout.write(c); }); + stdoutstream = term.stdout; + stdinstream = term.stdin; + this.ws.prependListener('end', function () { this.httprequest.process.kill(); }); + term.prependListener('exit', function () { this.tunnel.end(); }); + } + + this.ws.removeAllListeners('data'); + this.ws.on('data', onTunnelControlData); + + stdoutstream.pipe(this.ws, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text. + this.ws.pipe(stdinstream, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. + + // Add the terminal session to the count to update the server + if (this.ws.httprequest.userid != null) + { + if (tunnelUserCount.terminal[this.ws.httprequest.userid] == null) { tunnelUserCount.terminal[this.ws.httprequest.userid] = 1; } else { tunnelUserCount.terminal[this.ws.httprequest.userid]++; } + try { mesh.SendCommand({ action: 'sessions', type: 'terminal', value: tunnelUserCount.terminal }); } catch (e) { } + broadcastSessionsToRegisteredApps(); + } + + // Toast Notification, if required + if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 2)) + { + // User Notifications is required + var notifyMessage = this.ws.httprequest.username + " started a remote terminal session.", notifyTitle = "MeshCentral"; + if (this.ws.httprequest.soptions != null) { + if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; } + if (this.ws.httprequest.soptions.notifyMsgTerminal != null) { notifyMessage = this.ws.httprequest.soptions.notifyMsgTerminal.replace('{0}', this.ws.httprequest.realname).replace('{1}', this.ws.httprequest.username); } + } + try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (e) { } + } + }, + function (e) + { + // FAILED to connect terminal + this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 })); + this.ws.end(); + }); + }, + function (e) + { + // DO NOT start terminal + this.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 })); + this.that.end(); + }); + } + else if (this.httprequest.protocol == 2) + { + // + // Remote KVM + // + + // Check user access rights for desktop + if ((((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) && ((this.httprequest.rights & MESHRIGHT_REMOTEVIEW) == 0)) || ((this.httprequest.rights != 0xFFFFFFFF) && ((this.httprequest.rights & MESHRIGHT_NODESKTOP) != 0))) { + // Disengage this tunnel, user does not have the rights to do this!! + this.httprequest.protocol = 999999; + this.httprequest.s.end(); + sendConsoleText("Error: No Desktop Control Rights."); + return; + } + + this.descriptorMetadata = "Remote KVM"; + + // Look for a TSID + var tsid = null; + if ((this.httprequest.xoptions != null) && (typeof this.httprequest.xoptions.tsid == 'number')) { tsid = this.httprequest.xoptions.tsid; } + require('MeshAgent')._tsid = tsid; + + // Remote desktop using native pipes + this.httprequest.desktop = { state: 0, kvm: mesh.getRemoteDesktopStream(tsid), tunnel: this }; + this.httprequest.desktop.kvm.parent = this.httprequest.desktop; + this.desktop = this.httprequest.desktop; + + // Add ourself to the list of remote desktop sessions + if (this.httprequest.desktop.kvm.tunnels == null) { this.httprequest.desktop.kvm.tunnels = []; } + this.httprequest.desktop.kvm.tunnels.push(this); + + // Send a metadata update to all desktop sessions + var users = {}; + if (this.httprequest.desktop.kvm.tunnels != null) { + for (var i in this.httprequest.desktop.kvm.tunnels) { try { var userid = this.httprequest.desktop.kvm.tunnels[i].httprequest.userid; if (users[userid] == null) { users[userid] = 1; } else { users[userid]++; } } catch (e) { } } + for (var i in this.httprequest.desktop.kvm.tunnels) { try { this.httprequest.desktop.kvm.tunnels[i].write(JSON.stringify({ ctrlChannel: '102938', type: 'metadata', users: users })); } catch (e) { } } + tunnelUserCount.desktop = users; + try { mesh.SendCommand({ action: 'sessions', type: 'kvm', value: users }); } catch (e) { } + broadcastSessionsToRegisteredApps(); + } + + this.end = function () { + --this.desktop.kvm.connectionCount; + + // Remove ourself from the list of remote desktop session + var i = this.desktop.kvm.tunnels.indexOf(this); + if (i >= 0) { this.desktop.kvm.tunnels.splice(i, 1); } + + // Send a metadata update to all desktop sessions + var users = {}; + if (this.httprequest.desktop.kvm.tunnels != null) { + for (var i in this.httprequest.desktop.kvm.tunnels) { try { var userid = this.httprequest.desktop.kvm.tunnels[i].httprequest.userid; if (users[userid] == null) { users[userid] = 1; } else { users[userid]++; } } catch (e) { } } + for (var i in this.httprequest.desktop.kvm.tunnels) { try { this.httprequest.desktop.kvm.tunnels[i].write(JSON.stringify({ ctrlChannel: '102938', type: 'metadata', users: users })); } catch (e) { } } + tunnelUserCount.desktop = users; + try { mesh.SendCommand({ action: 'sessions', type: 'kvm', value: users }); } catch (e) { } + broadcastSessionsToRegisteredApps(); + } + + // Unpipe the web socket + try + { + this.unpipe(this.httprequest.desktop.kvm); + this.httprequest.desktop.kvm.unpipe(this); + } + catch(e) { } + + // Unpipe the WebRTC channel if needed (This will also be done when the WebRTC channel ends). + if (this.rtcchannel) + { + try + { + this.rtcchannel.unpipe(this.httprequest.desktop.kvm); + this.httprequest.desktop.kvm.unpipe(this.rtcchannel); + } + catch(e) { } + } + + // Place wallpaper back if needed + // TODO + + if (this.desktop.kvm.connectionCount == 0) { + // Display a toast message. This may not be supported on all platforms. + // try { require('toaster').Toast('MeshCentral', 'Remote Desktop Control Ended.'); } catch (e) { } + + this.httprequest.desktop.kvm.end(); + if (this.httprequest.desktop.kvm.connectionBar) { + this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close'); + this.httprequest.desktop.kvm.connectionBar.close(); + this.httprequest.desktop.kvm.connectionBar = null; + } + } else { + for (var i in this.httprequest.desktop.kvm.users) { + if ((this.httprequest.desktop.kvm.users[i] == this.httprequest.username) && this.httprequest.desktop.kvm.connectionBar) { + for (var j in this.httprequest.desktop.kvm.rusers) { if (this.httprequest.desktop.kvm.rusers[j] == this.httprequest.realname) { this.httprequest.desktop.kvm.rusers.splice(j, 1); break; } } + this.httprequest.desktop.kvm.users.splice(i, 1); + this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close'); + this.httprequest.desktop.kvm.connectionBar.close(); + this.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(this.httprequest.privacybartext.replace('{0}', this.httprequest.desktop.kvm.users.join(', ')).replace('{1}', this.httprequest.desktop.kvm.rusers.join(', ')), require('MeshAgent')._tsid); + this.httprequest.desktop.kvm.connectionBar.httprequest = this.httprequest; + this.httprequest.desktop.kvm.connectionBar.on('close', function () { + MeshServerLogEx(29, null, "Remote Desktop Connection forcefully closed by local user (" + this.httprequest.remoteaddr + ")", this.httprequest); + for (var i in this.httprequest.desktop.kvm._pipedStreams) { + this.httprequest.desktop.kvm._pipedStreams[i].end(); + } + this.httprequest.desktop.kvm.end(); + }); + break; + } + } + } + }; + if (this.httprequest.desktop.kvm.hasOwnProperty('connectionCount')) { + this.httprequest.desktop.kvm.connectionCount++; + this.httprequest.desktop.kvm.rusers.push(this.httprequest.realname); + this.httprequest.desktop.kvm.users.push(this.httprequest.username); + this.httprequest.desktop.kvm.rusers.sort(); + this.httprequest.desktop.kvm.users.sort(); + } else { + this.httprequest.desktop.kvm.connectionCount = 1; + this.httprequest.desktop.kvm.rusers = [this.httprequest.realname]; + this.httprequest.desktop.kvm.users = [this.httprequest.username]; + } + + if ((this.httprequest.rights == 0xFFFFFFFF) || (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) != 0) && ((this.httprequest.rights & MESHRIGHT_REMOTEVIEW) == 0))) { + // If we have remote control rights, pipe the KVM input + this.pipe(this.httprequest.desktop.kvm, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. Pipe the Browser --> KVM input. + } else { + // We need to only pipe non-mouse & non-keyboard inputs. + //sendConsoleText('Warning: No Remote Desktop Input Rights.'); + // TODO!!! + } + + // Perform notification if needed. Toast messages may not be supported on all platforms. + if (this.httprequest.consent && (this.httprequest.consent & 8)) + { + // User Consent Prompt is required + // Send a console message back using the console channel, "\n" is supported. + this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 })); + var consentMessage = this.httprequest.realname + " requesting remote desktop access. Grant access?", consentTitle = 'MeshCentral'; + if (this.httprequest.soptions != null) { + if (this.httprequest.soptions.consentTitle != null) { consentTitle = this.httprequest.soptions.consentTitle; } + if (this.httprequest.soptions.consentMsgDesktop != null) { consentMessage = this.httprequest.soptions.consentMsgDesktop.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); } + } + var pr = require('message-box').create(consentTitle, consentMessage, 30, null, tsid); + pr.ws = this; + this.pause(); + this._consentpromise = pr; + this.prependOnceListener('end', function () { if (this._consentpromise && this._consentpromise.close) { this._consentpromise.close(); }}); + pr.then( + function () + { + // Success + this.ws._consentpromise = null; + MeshServerLogEx(30, null, "Starting remote desktop after local user accepted (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest); + this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null, msgid: 0 })); + if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 1)) { + // User Notifications is required + var notifyMessage = this.ws.httprequest.realname + " started a remote desktop session.", notifyTitle = "MeshCentral"; + if (this.ws.httprequest.soptions != null) { + if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; } + if (this.ws.httprequest.soptions.notifyMsgDesktop != null) { notifyMessage = this.ws.httprequest.soptions.notifyMsgDesktop.replace('{0}', this.ws.httprequest.realname).replace('{1}', this.ws.httprequest.username); } + } + try { require('toaster').Toast(notifyTitle, notifyMessage, tsid); } catch (e) { } + } + if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 0x40)) { + // Connection Bar is required + if (this.ws.httprequest.desktop.kvm.connectionBar) { + this.ws.httprequest.desktop.kvm.connectionBar.removeAllListeners('close'); + this.ws.httprequest.desktop.kvm.connectionBar.close(); + } + try { + this.ws.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(this.ws.httprequest.privacybartext.replace('{0}', this.ws.httprequest.desktop.kvm.users.join(', ')).replace('{1}', this.ws.httprequest.desktop.kvm.rusers.join(', ')), require('MeshAgent')._tsid); + MeshServerLogEx(31, null, "Remote Desktop Connection Bar Activated/Updated (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest); + } + catch (e) { + if (process.platform != 'darwin') { + MeshServerLogEx(32, null, "Remote Desktop Connection Bar Failed or Not Supported (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest); + } + } + if (this.ws.httprequest.desktop.kvm.connectionBar) { + this.ws.httprequest.desktop.kvm.connectionBar.httprequest = this.ws.httprequest; + this.ws.httprequest.desktop.kvm.connectionBar.on('close', function () { + MeshServerLogEx(33, null, "Remote Desktop Connection forcefully closed by local user (" + this.httprequest.remoteaddr + ")", this.httprequest); + for (var i in this.httprequest.desktop.kvm._pipedStreams) { + this.httprequest.desktop.kvm._pipedStreams[i].end(); + } + this.httprequest.desktop.kvm.end(); + }); + } + } + this.ws.httprequest.desktop.kvm.pipe(this.ws, { dataTypeSkip: 1 }); + this.ws.resume(); + }, + function (e) + { + // User Consent Denied/Failed + this.ws._consentpromise = null; + MeshServerLogEx(34, null, "Failed to start remote desktop after local user rejected (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest); + this.ws.end(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 })); + }); + } else { + // User Consent Prompt is not required + if (this.httprequest.consent && (this.httprequest.consent & 1)) { + // User Notifications is required + MeshServerLogEx(35, null, "Started remote desktop with toast notification (" + this.httprequest.remoteaddr + ")", this.httprequest); + var notifyMessage = this.httprequest.realname + " started a remote desktop session.", notifyTitle = "MeshCentral"; + if (this.httprequest.soptions != null) { + if (this.httprequest.soptions.notifyTitle != null) { notifyTitle = this.httprequest.soptions.notifyTitle; } + if (this.httprequest.soptions.notifyMsgDesktop != null) { notifyMessage = this.httprequest.soptions.notifyMsgDesktop.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); } + } + try { require('toaster').Toast(notifyTitle, notifyMessage, tsid); } catch (e) { } + } else { + MeshServerLogEx(36, null, "Started remote desktop without notification (" + this.httprequest.remoteaddr + ")", this.httprequest); + } + if (this.httprequest.consent && (this.httprequest.consent & 0x40)) { + // Connection Bar is required + if (this.httprequest.desktop.kvm.connectionBar) { + this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close'); + this.httprequest.desktop.kvm.connectionBar.close(); + } + try { + this.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(this.httprequest.privacybartext.replace('{0}', this.httprequest.desktop.kvm.rusers.join(', ')).replace('{1}', this.httprequest.desktop.kvm.users.join(', ')), require('MeshAgent')._tsid); + MeshServerLogEx(37, null, "Remote Desktop Connection Bar Activated/Updated (" + this.httprequest.remoteaddr + ")", this.httprequest); + } + catch (e) { + MeshServerLogEx(38, null, "Remote Desktop Connection Bar Failed or not Supported (" + this.httprequest.remoteaddr + ")", this.httprequest); + } + if (this.httprequest.desktop.kvm.connectionBar) { + this.httprequest.desktop.kvm.connectionBar.httprequest = this.httprequest; + this.httprequest.desktop.kvm.connectionBar.on('close', function () { + MeshServerLogEx(39, null, "Remote Desktop Connection forcefully closed by local user (" + this.httprequest.remoteaddr + ")", this.httprequest); + for (var i in this.httprequest.desktop.kvm._pipedStreams) { + this.httprequest.desktop.kvm._pipedStreams[i].end(); + } + this.httprequest.desktop.kvm.end(); + }); + } + } + this.httprequest.desktop.kvm.pipe(this, { dataTypeSkip: 1 }); + } + + this.removeAllListeners('data'); + this.on('data', onTunnelControlData); + //this.write('MeshCore KVM Hello!1'); + + } else if (this.httprequest.protocol == 5) { + // + // Remote Files + // + + // Check user access rights for files + if (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) || ((this.httprequest.rights != 0xFFFFFFFF) && ((this.httprequest.rights & MESHRIGHT_NOFILES) != 0))) { + // Disengage this tunnel, user does not have the rights to do this!! + this.httprequest.protocol = 999999; + this.httprequest.s.end(); + sendConsoleText("Error: No files control rights."); + return; + } + + this.descriptorMetadata = "Remote Files"; + + // Add the files session to the count to update the server + if (this.httprequest.userid != null) { + if (tunnelUserCount.files[this.httprequest.userid] == null) { tunnelUserCount.files[this.httprequest.userid] = 1; } else { tunnelUserCount.files[this.httprequest.userid]++; } + try { mesh.SendCommand({ action: 'sessions', type: 'files', value: tunnelUserCount.files }); } catch (e) { } + broadcastSessionsToRegisteredApps(); + } + + this.end = function () { + // Remove the files session from the count to update the server + if (this.httprequest.userid != null) { + if (tunnelUserCount.files[this.httprequest.userid] != null) { tunnelUserCount.files[this.httprequest.userid]--; if (tunnelUserCount.files[this.httprequest.userid] <= 0) { delete tunnelUserCount.files[this.httprequest.userid]; } } + try { mesh.SendCommand({ action: 'sessions', type: 'files', value: tunnelUserCount.files }); } catch (e) { } + broadcastSessionsToRegisteredApps(); + } + }; + + // Perform notification if needed. Toast messages may not be supported on all platforms. + if (this.httprequest.consent && (this.httprequest.consent & 32)) { + // User Consent Prompt is required + // Send a console message back using the console channel, "\n" is supported. + this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 })); + var consentMessage = this.httprequest.realname + " requesting remote file Access. Grant access?", consentTitle = 'MeshCentral'; + if (this.httprequest.soptions != null) { + if (this.httprequest.soptions.consentTitle != null) { consentTitle = this.httprequest.soptions.consentTitle; } + if (this.httprequest.soptions.consentMsgFiles != null) { consentMessage = this.httprequest.soptions.consentMsgFiles.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); } + } + var pr = require('message-box').create(consentTitle, consentMessage, 30); + pr.ws = this; + this.pause(); + this._consentpromise = pr; + this.prependOnceListener('end', function () { if (this._consentpromise && this._consentpromise.close) { this._consentpromise.close(); } }); + pr.then( + function () + { + // Success + this.ws._consentpromise = null; + MeshServerLogEx(40, null, "Starting remote files after local user accepted (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest); + this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null })); + if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 4)) { + // User Notifications is required + var notifyMessage = this.ws.httprequest.realname + " started a remote file session.", notifyTitle = "MeshCentral"; + if (this.ws.httprequest.soptions != null) { + if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; } + if (this.ws.httprequest.soptions.notifyMsgFiles != null) { notifyMessage = this.ws.httprequest.soptions.notifyMsgFiles.replace('{0}', this.ws.httprequest.realname).replace('{1}', this.ws.httprequest.username); } + } + try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (e) { } + } + this.ws.resume(); + }, + function (e) + { + // User Consent Denied/Failed + this.ws._consentpromise = null; + MeshServerLogEx(41, null, "Failed to start remote files after local user rejected (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest); + this.ws.end(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 })); + }); + } else { + // User Consent Prompt is not required + if (this.httprequest.consent && (this.httprequest.consent & 4)) { + // User Notifications is required + MeshServerLogEx(42, null, "Started remote files with toast notification (" + this.httprequest.remoteaddr + ")", this.httprequest); + var notifyMessage = this.httprequest.realname + " started a remote file session.", notifyTitle = "MeshCentral"; + if (this.httprequest.soptions != null) { + if (this.httprequest.soptions.notifyTitle != null) { notifyTitle = this.httprequest.soptions.notifyTitle; } + if (this.httprequest.soptions.notifyMsgFiles != null) { notifyMessage = this.httprequest.soptions.notifyMsgFiles.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); } + } + try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (e) { } + } else { + MeshServerLogEx(43, null, "Started remote files without notification (" + this.httprequest.remoteaddr + ")", this.httprequest); + } + this.resume(); + } + + // Setup files + // NOP + } + } else if (this.httprequest.protocol == 1) { + // Send data into terminal stdin + //this.write(data); // Echo back the keys (Does not seem to be a good idea) + } else if (this.httprequest.protocol == 2) { + // Send data into remote desktop + if (this.httprequest.desktop.state == 0) { + this.write(Buffer.from(String.fromCharCode(0x11, 0xFE, 0x00, 0x00, 0x4D, 0x45, 0x53, 0x48, 0x00, 0x00, 0x00, 0x00, 0x02))); + this.httprequest.desktop.state = 1; + } else { + this.httprequest.desktop.write(data); + } + } else if (this.httprequest.protocol == 5) { + // Process files commands + var cmd = null; + try { cmd = JSON.parse(data); } catch (e) { }; + if (cmd == null) { return; } + if ((cmd.ctrlChannel == '102938') || ((cmd.type == 'offer') && (cmd.sdp != null))) { onTunnelControlData(cmd, this); return; } // If this is control data, handle it now. + if (cmd.action == undefined) { return; } + //sendConsoleText('CMD: ' + JSON.stringify(cmd)); + + if ((cmd.path != null) && (process.platform != 'win32') && (cmd.path[0] != '/')) { cmd.path = '/' + cmd.path; } // Add '/' to paths on non-windows + //console.log(objToString(cmd, 0, ' ')); + switch (cmd.action) { + case 'ls': { + /* + // Close the watcher if required + var samepath = ((this.httprequest.watcher != undefined) && (cmd.path == this.httprequest.watcher.path)); + if ((this.httprequest.watcher != undefined) && (samepath == false)) { + //console.log('Closing watcher: ' + this.httprequest.watcher.path); + //this.httprequest.watcher.close(); // TODO: This line causes the agent to crash!!!! + delete this.httprequest.watcher; + } + */ + + // Send the folder content to the browser + var response = getDirectoryInfo(cmd.path); + if (cmd.reqid != undefined) { response.reqid = cmd.reqid; } + this.write(Buffer.from(JSON.stringify(response))); + + /* + // Start the directory watcher + if ((cmd.path != '') && (samepath == false)) { + var watcher = fs.watch(cmd.path, onFileWatcher); + watcher.tunnel = this.httprequest; + watcher.path = cmd.path; + this.httprequest.watcher = watcher; + //console.log('Starting watcher: ' + this.httprequest.watcher.path); + } + */ + break; + } + case 'mkdir': { + // Create a new empty folder + fs.mkdirSync(cmd.path); + MeshServerLogEx(44, [cmd.path], "Create folder: \"" + cmd.path + "\"", this.httprequest); + break; + } + case 'rm': { + // Delete, possibly recursive delete + for (var i in cmd.delfiles) { + var p = obj.path.join(cmd.path, cmd.delfiles[i]), delcount = 0; + try { delcount = deleteFolderRecursive(p, cmd.rec); } catch (e) { } + if ((delcount == 1) && !cmd.rec) { + MeshServerLogEx(45, [p], "Delete: \"" + p + "\"", this.httprequest); + } else { + if (cmd.rec) { + MeshServerLogEx(46, [p, delcount], "Delete recursive: \"" + p + "\", " + delcount + " element(s) removed", this.httprequest); + } else { + MeshServerLogEx(47, [p, delcount], "Delete: \"" + p + "\", " + delcount + " element(s) removed", this.httprequest); + } + } + } + break; + } + case 'markcoredump': { + // If we are asking for the coredump file, set the right path. + var coreDumpPath = null; + if (process.platform == 'win32') { + if (fs.existsSync(process.coreDumpLocation)) { coreDumpPath = process.coreDumpLocation; } + } else { + if ((process.cwd() != '//') && fs.existsSync(process.cwd() + 'core')) { coreDumpPath = process.cwd() + 'core'; } + } + if (coreDumpPath != null) { db.Put('CoreDumpTime', require('fs').statSync(coreDumpPath).mtime); } + break; + } + case 'rename': { + // Rename a file or folder + var oldfullpath = obj.path.join(cmd.path, cmd.oldname); + var newfullpath = obj.path.join(cmd.path, cmd.newname); + MeshServerLogEx(48, [oldfullpath, cmd.newname], 'Rename: \"' + oldfullpath + '\" to \"' + cmd.newname + '\"', this.httprequest); + try { fs.renameSync(oldfullpath, newfullpath); } catch (e) { console.log(e); } + break; + } + case 'download': { + // Download a file + var sendNextBlock = 0; + if (cmd.sub == 'start') { // Setup the download + if ((cmd.path == null) && (cmd.ask == 'coredump')) { // If we are asking for the coredump file, set the right path. + if (process.platform == 'win32') { + if (fs.existsSync(process.coreDumpLocation)) { cmd.path = process.coreDumpLocation; } + } else { + if ((process.cwd() != '//') && fs.existsSync(process.cwd() + 'core')) { cmd.path = process.cwd() + 'core'; } + } + } + MeshServerLogEx(49, [cmd.path], 'Download: \"' + cmd.path + '\"', this.httprequest); + if ((cmd.path == null) || (this.filedownload != null)) { this.write({ action: 'download', sub: 'cancel', id: this.filedownload.id }); delete this.filedownload; } + this.filedownload = { id: cmd.id, path: cmd.path, ptr: 0 } + try { this.filedownload.f = fs.openSync(this.filedownload.path, 'rbN'); } catch (e) { this.write({ action: 'download', sub: 'cancel', id: this.filedownload.id }); delete this.filedownload; } + if (this.filedownload) { this.write({ action: 'download', sub: 'start', id: cmd.id }); } + } else if ((this.filedownload != null) && (cmd.id == this.filedownload.id)) { // Download commands + if (cmd.sub == 'startack') { sendNextBlock = ((typeof cmd.ack == 'number') ? cmd.ack : 8); } else if (cmd.sub == 'stop') { delete this.filedownload; } else if (cmd.sub == 'ack') { sendNextBlock = 1; } + } + // Send the next download block(s) + while (sendNextBlock > 0) { + sendNextBlock--; + var buf = Buffer.alloc(16384); + var len = fs.readSync(this.filedownload.f, buf, 4, 16380, null); + this.filedownload.ptr += len; + if (len < 16380) { buf.writeInt32BE(0x01000001, 0); fs.closeSync(this.filedownload.f); delete this.filedownload; sendNextBlock = 0; } else { buf.writeInt32BE(0x01000000, 0); } + this.write(buf.slice(0, len + 4)); // Write as binary + } + break; + } + /* + case 'download': { + // Packet download of a file, agent to browser + if (cmd.path == undefined) break; + var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path; + //console.log('Download: ' + filepath); + try { this.httprequest.downloadFile = fs.openSync(filepath, 'rbN'); } catch (e) { this.write(Buffer.from(JSON.stringify({ action: 'downloaderror', reqid: cmd.reqid }))); break; } + this.httprequest.downloadFileId = cmd.reqid; + this.httprequest.downloadFilePtr = 0; + if (this.httprequest.downloadFile) { this.write(Buffer.from(JSON.stringify({ action: 'downloadstart', reqid: this.httprequest.downloadFileId }))); } + break; + } + case 'download2': { + // Stream download of a file, agent to browser + if (cmd.path == undefined) break; + var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path; + try { this.httprequest.downloadFile = fs.createReadStream(filepath, { flags: 'rbN' }); } catch (e) { console.log(e); } + this.httprequest.downloadFile.pipe(this); + this.httprequest.downloadFile.end = function () { } + break; + } + */ + case 'upload': { + // Upload a file, browser to agent + if (this.httprequest.uploadFile != null) { fs.closeSync(this.httprequest.uploadFile); delete this.httprequest.uploadFile; } + if (cmd.path == undefined) break; + var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path; + this.httprequest.uploadFilePath = filepath; + MeshServerLogEx(50, [filepath], 'Upload: \"' + filepath + '\"', this.httprequest); + try { this.httprequest.uploadFile = fs.openSync(filepath, 'wbN'); } catch (e) { this.write(Buffer.from(JSON.stringify({ action: 'uploaderror', reqid: cmd.reqid }))); break; } + this.httprequest.uploadFileid = cmd.reqid; + if (this.httprequest.uploadFile) { this.write(Buffer.from(JSON.stringify({ action: 'uploadstart', reqid: this.httprequest.uploadFileid }))); } + break; + } + case 'uploaddone': { + // Indicates that an upload is done + if (this.httprequest.uploadFile) { + fs.closeSync(this.httprequest.uploadFile); + this.write(Buffer.from(JSON.stringify({ action: 'uploaddone', reqid: this.httprequest.uploadFileid }))); // Indicate that we closed the file. + delete this.httprequest.uploadFile; + delete this.httprequest.uploadFileid; + delete this.httprequest.uploadFilePath; + } + break; + } + case 'uploadcancel': { + // Indicates that an upload is canceled + if (this.httprequest.uploadFile) { + fs.closeSync(this.httprequest.uploadFile); + fs.unlinkSync(this.httprequest.uploadFilePath); + this.write(Buffer.from(JSON.stringify({ action: 'uploadcancel', reqid: this.httprequest.uploadFileid }))); // Indicate that we closed the file. + delete this.httprequest.uploadFile; + delete this.httprequest.uploadFileid; + delete this.httprequest.uploadFilePath; + } + break; + } + case 'copy': { + // Copy a bunch of files from scpath to dspath + for (var i in cmd.names) { + var sc = obj.path.join(cmd.scpath, cmd.names[i]), ds = obj.path.join(cmd.dspath, cmd.names[i]); + MeshServerLogEx(51, [sc, ds], 'Copy: \"' + sc + '\" to \"' + ds + '\"', this.httprequest); + if (sc != ds) { try { fs.copyFileSync(sc, ds); } catch (e) { } } + } + break; + } + case 'move': { + // Move a bunch of files from scpath to dspath + for (var i in cmd.names) { + var sc = obj.path.join(cmd.scpath, cmd.names[i]), ds = obj.path.join(cmd.dspath, cmd.names[i]); + MeshServerLogEx(52, [sc, ds], 'Move: \"' + sc + '\" to \"' + ds + '\"', this.httprequest); + if (sc != ds) { try { fs.copyFileSync(sc, ds); fs.unlinkSync(sc); } catch (e) { } } + } + break; + } + case 'zip': + // Zip a bunch of files + if (this.zip != null) return; // Zip operating is currently running, exit now. + + // Check that the specified files exist & build full paths + var fp, stat, p = []; + for (var i in cmd.files) { fp = cmd.path + '/' + cmd.files[i]; stat = null; try { stat = fs.statSync(fp); } catch (e) { } if (stat != null) { p.push(fp); } } + if (p.length == 0) return; // No files, quit now. + + // Setup file compression + var ofile = cmd.path + '/' + cmd.output; + this.write(Buffer.from(JSON.stringify({ action: 'dialogmessage', msg: 'zipping' }))); + this.zipfile = ofile; + delete this.zipcancel; + var out = require('fs').createWriteStream(ofile, { flags: 'wb' }); + out.xws = this; + out.on('close', function () { + this.xws.write(Buffer.from(JSON.stringify({ action: 'dialogmessage', msg: null }))); + this.xws.write(Buffer.from(JSON.stringify({ action: 'refresh' }))); + if (this.xws.zipcancel === true) { fs.unlinkSync(this.xws.zipfile); } // Delete the complete file. + delete this.xws.zipcancel; + delete this.xws.zipfile; + delete this.xws.zip; + }); + this.zip = require('zip-writer').write({ files: p, basePath: cmd.path }); + this.zip.xws = this; + this.zip.on('progress', require('events').moderated(function (name, p) { this.xws.write(Buffer.from(JSON.stringify({ action: 'dialogmessage', msg: 'zippingFile', file: ((process.platform == 'win32') ? (name.split('/').join('\\')) : name), progress: p }))); }, 1000)); + this.zip.pipe(out); + break; + case 'cancel': + // Cancel zip operation if present + try { this.zipcancel = true; this.zip.cancel(function () { }); } catch (e) { } + this.zip = null; + break; + default: + // Unknown action, ignore it. + break; + } + } else if (this.httprequest.protocol == 7) { // Plugin data exchange + var cmd = null; + try { cmd = JSON.parse(data); } catch (e) { }; + if (cmd == null) { return; } + if ((cmd.ctrlChannel == '102938') || ((cmd.type == 'offer') && (cmd.sdp != null))) { onTunnelControlData(cmd, this); return; } // If this is control data, handle it now. + if (cmd.action == undefined) return; + + switch (cmd.action) { + case 'plugin': { + try { require(cmd.plugin).consoleaction(cmd, null, null, this); } catch (e) { throw e; } + break; + } + default: { + // probably shouldn't happen, but just in case this feature is expanded + } + } + + } + //sendConsoleText("Got tunnel #" + this.httprequest.index + " data: " + data, this.httprequest.sessionid); + } + } + + // Delete a directory with a files and directories within it + function deleteFolderRecursive(path, rec) { + var count = 0; + if (fs.existsSync(path)) { + if (rec == true) { + fs.readdirSync(obj.path.join(path, '*')).forEach(function (file, index) { + var curPath = obj.path.join(path, file); + if (fs.statSync(curPath).isDirectory()) { // recurse + count += deleteFolderRecursive(curPath, true); + } else { // delete file + fs.unlinkSync(curPath); + count++; + } + }); + } + fs.unlinkSync(path); + count++; + } + return count; + }; + + // Called when receiving control data on WebRTC + function onTunnelWebRTCControlData(data) { + if (typeof data != 'string') return; + var obj; + try { obj = JSON.parse(data); } catch (e) { sendConsoleText('Invalid control JSON on WebRTC: ' + data); return; } + if (obj.type == 'close') { + //sendConsoleText('Tunnel #' + this.xrtc.websocket.tunnel.index + ' WebRTC control close'); + try { this.close(); } catch (e) { } + try { this.xrtc.close(); } catch (e) { } + } + } + + // Called when receiving control data on websocket + function onTunnelControlData(data, ws) { + var obj; + if (ws == null) { ws = this; } + if (typeof data == 'string') { try { obj = JSON.parse(data); } catch (e) { sendConsoleText('Invalid control JSON: ' + data); return; } } + else if (typeof data == 'object') { obj = data; } else { return; } + //sendConsoleText('onTunnelControlData(' + ws.httprequest.protocol + '): ' + JSON.stringify(data)); + //console.log('onTunnelControlData: ' + JSON.stringify(data)); + + if (obj.action) { + switch (obj.action) { + case 'lock': { + // Lock the current user out of the desktop + try { + if (process.platform == 'win32') { + MeshServerLogEx(53, null, "Locking remote user out of desktop", ws.httprequest); + var child = require('child_process'); + child.execFile(process.env['windir'] + '\\system32\\cmd.exe', ['/c', 'RunDll32.exe user32.dll,LockWorkStation'], { type: 1 }); + } + } catch (e) { } + break; + } + default: + // Unknown action, ignore it. + break; + } + return; + } + + switch (obj.type) { + case 'options': { + // These are additional connection options passed in the control channel. + //sendConsoleText('options: ' + JSON.stringify(obj)); + delete obj.type; + ws.httprequest.xoptions = obj; + + // Set additional user consent options if present + if ((obj != null) && (typeof obj.consent == 'number')) { ws.httprequest.consent |= obj.consent; } + + break; + } + case 'close': { + // We received the close on the websocket + //sendConsoleText('Tunnel #' + ws.tunnel.index + ' WebSocket control close'); + try { ws.close(); } catch (e) { } + break; + } + case 'termsize': { + // Indicates a change in terminal size + if (process.platform == 'win32') + { + if (ws.httprequest._dispatcher == null) return; + //sendConsoleText('Win32-TermSize: ' + obj.cols + 'x' + obj.rows); + if (ws.httprequest._dispatcher.invoke) { ws.httprequest._dispatcher.invoke('resizeTerminal', [obj.cols, obj.rows]); } + } else + { + if (ws.httprequest.process == null || ws.httprequest.process.pty == 0) return; + //sendConsoleText('Linux Resize: ' + obj.cols + 'x' + obj.rows); + + if (ws.httprequest.process.tcsetsize) { ws.httprequest.process.tcsetsize(obj.rows, obj.cols); } + } + break; + } + case 'webrtc0': { // Browser indicates we can start WebRTC switch-over. + if (ws.httprequest.protocol == 1) { // Terminal + // This is a terminal data stream, unpipe the terminal now and indicate to the other side that terminal data will no longer be received over WebSocket + if (process.platform == 'win32') { + ws.httprequest._term.unpipe(ws); + } else { + ws.httprequest.process.stdout.unpipe(ws); + ws.httprequest.process.stderr.unpipe(ws); + } + } else if (ws.httprequest.protocol == 2) { // Desktop + // This is a KVM data stream, unpipe the KVM now and indicate to the other side that KVM data will no longer be received over WebSocket + ws.httprequest.desktop.kvm.unpipe(ws); + } else { + // Switch things around so all WebRTC data goes to onTunnelData(). + ws.rtcchannel.httprequest = ws.httprequest; + ws.rtcchannel.removeAllListeners('data'); + ws.rtcchannel.on('data', onTunnelData); + } + ws.write("{\"ctrlChannel\":\"102938\",\"type\":\"webrtc1\"}"); // End of data marker + break; + } + case 'webrtc1': { + if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) { // Terminal + // Switch the user input from websocket to webrtc at this point. + if (process.platform == 'win32') { + ws.unpipe(ws.httprequest._term); + ws.rtcchannel.pipe(ws.httprequest._term, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text. + } else { + ws.unpipe(ws.httprequest.process.stdin); + ws.rtcchannel.pipe(ws.httprequest.process.stdin, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text. + } + ws.resume(); // Resume the websocket to keep receiving control data + } else if (ws.httprequest.protocol == 2) { // Desktop + // Switch the user input from websocket to webrtc at this point. + ws.unpipe(ws.httprequest.desktop.kvm); + try { ws.webrtc.rtcchannel.pipe(ws.httprequest.desktop.kvm, { dataTypeSkip: 1, end: false }); } catch (e) { sendConsoleText('EX2'); } // 0 = Binary, 1 = Text. + ws.resume(); // Resume the websocket to keep receiving control data + } + ws.write('{\"ctrlChannel\":\"102938\",\"type\":\"webrtc2\"}'); // Indicates we will no longer get any data on websocket, switching to WebRTC at this point. + break; + } + case 'webrtc2': { + // Other side received websocket end of data marker, start sending data on WebRTC channel + if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) { // Terminal + if (process.platform == 'win32') { + ws.httprequest._term.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. + } else { + ws.httprequest.process.stdout.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. + ws.httprequest.process.stderr.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. + } + } else if (ws.httprequest.protocol == 2) { // Desktop + ws.httprequest.desktop.kvm.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text. + } + break; + } + case 'offer': { + // This is a WebRTC offer. + if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) return; // TODO: Terminal is currently broken with WebRTC. Reject WebRTC upgrade for now. + ws.webrtc = rtc.createConnection(); + ws.webrtc.websocket = ws; + ws.webrtc.on('connected', function () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC connected');*/ }); + ws.webrtc.on('disconnected', function () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC disconnected');*/ }); + ws.webrtc.on('dataChannel', function (rtcchannel) { + //sendConsoleText('WebRTC Datachannel open, protocol: ' + this.websocket.httprequest.protocol); + rtcchannel.maxFragmentSize = 32768; + rtcchannel.xrtc = this; + rtcchannel.websocket = this.websocket; + this.rtcchannel = rtcchannel; + this.websocket.rtcchannel = rtcchannel; + this.websocket.rtcchannel.on('data', onTunnelWebRTCControlData); + this.websocket.rtcchannel.on('end', function () { + // The WebRTC channel closed, unpipe the KVM now. This is also done when the web socket closes. + //sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC data channel closed'); + if (this.websocket.desktop && this.websocket.desktop.kvm) + { + try + { + this.unpipe(this.websocket.desktop.kvm); + this.websocket.httprequest.desktop.kvm.unpipe(this); + } + catch (e) { } + } + }); + this.websocket.write('{\"ctrlChannel\":\"102938\",\"type\":\"webrtc0\"}'); // Indicate we are ready for WebRTC switch-over. + }); + var sdp = null; + try { sdp = ws.webrtc.setOffer(obj.sdp); } catch (e) { } + if (sdp != null) { ws.write({ type: 'answer', ctrlChannel: '102938', sdp: sdp }); } + break; + } + case 'ping': { + ws.write("{\"ctrlChannel\":\"102938\",\"type\":\"pong\"}"); // Send pong response + break; + } + case 'pong': { // NOP + break; + } + case 'rtt': { + ws.write({ type: 'rtt', ctrlChannel: '102938', time: obj.time }); + break; + } + } + } + + // Console state + var consoleWebSockets = {}; + var consoleHttpRequest = null; + + // Console HTTP response + function consoleHttpResponse(response) { + response.data = function (data) { sendConsoleText(rstr2hex(buf2rstr(data)), this.sessionid); consoleHttpRequest = null; } + response.close = function () { sendConsoleText('httprequest.response.close', this.sessionid); consoleHttpRequest = null; } + }; + + // Open a web browser to a specified URL on current user's desktop + function openUserDesktopUrl(url) { + if ((url.toLowerCase().startsWith('http://') == false) && (url.toLowerCase().startsWith('https://') == false)) { return null; } + var child = null; + try { + switch (process.platform) { + case 'win32': + var user = require('user-sessions').getUsername(require('user-sessions').consoleUid()); + child = require('child_process').execFile(process.env['windir'] + '\\system32\\cmd.exe', ['cmd']); + child.stderr.on('data', function () { }); + child.stdout.on('data', function () { }); + child.stdin.write('SCHTASKS /CREATE /F /TN MeshChatTask /SC ONCE /ST 00:00 /RU ' + user + ' /TR "' + process.env['windir'] + '\\system32\\cmd.exe /C START ' + url + '"\r\n'); + child.stdin.write('SCHTASKS /RUN /TN MeshChatTask\r\n'); + child.stdin.write('SCHTASKS /DELETE /F /TN MeshChatTask\r\n'); + child.stdin.write('exit\r\n'); + child.waitExit(); + break; + case 'linux': + child = require('child_process').execFile('/usr/bin/xdg-open', ['xdg-open', url], { uid: require('user-sessions').consoleUid() }); + break; + case 'darwin': + child = require('child_process').execFile('/usr/bin/open', ['open', url], { uid: require('user-sessions').consoleUid() }); + break; + default: + // Unknown platform, ignore this command. + break; + } + } catch (e) { } + return child; + } + + // Process a mesh agent console command + function processConsoleCommand(cmd, args, rights, sessionid) { + try { + var response = null; + switch (cmd) { + case 'help': { // Displays available commands + var fin = '', f = '', availcommands = 'amtconfig,coredump,service,fdsnapshot,fdcount,startupoptions,alert,agentsize,versions,help,info,osinfo,args,print,type,dbkeys,dbget,dbset,dbcompact,eval,parseuri,httpget,nwslist,plugin,wsconnect,wssend,wsclose,notify,ls,ps,kill,amt,netinfo,location,power,wakeonlan,setdebug,smbios,rawsmbios,toast,lock,users,sendcaps,openurl,amtreset,amtccm,amtacm,amtdeactivate,amtpolicy,getscript,getclip,setclip,log,av,cpuinfo,sysinfo,apf,scanwifi,scanamt,wallpaper,agentmsg'; + if (process.platform == 'win32') { availcommands += ',safemode,wpfhwacceleration,uac'; } + if (process.platform != 'freebsd') { availcommands += ',vm';} + if (require('MeshAgent').maxKvmTileSize != null) { availcommands += ',kvmmode'; } + try { require('zip-reader'); availcommands += ',zip,unzip'; } catch (e) { } + + availcommands = availcommands.split(',').sort(); + while (availcommands.length > 0) { + if (f.length > 90) { fin += (f + ',\r\n'); f = ''; } + f += (((f != '') ? ', ' : ' ') + availcommands.shift()); + } + if (f != '') { fin += f; } + response = "Available commands: \r\n" + fin + "."; + break; + } + case 'agentmsg': { + if (args['_'].length == 0) { + response = "Proper usage:\r\n agentmsg add \"[message]\" [iconIndex]\r\n agentmsg remove [index]\r\n agentmsg list"; // Display usage + } else { + if ((args['_'][0] == 'add') && (args['_'].length > 1)) { + var msgIndex = 1, iconIndex = 0; + while (tunnelUserCount.msg[msgIndex] != null) { msgIndex++; } + if (args['_'].length >= 3) { try { iconIndex = parseInt(args['_'][2]); } catch (e) { } } + if (typeof iconIndex != 'number') { iconIndex = 0; } + tunnelUserCount.msg[msgIndex] = { msg: args['_'][1], icon: iconIndex }; + response = 'Agent message ' + msgIndex + ' added.'; + } else if ((args['_'][0] == 'remove') && (args['_'].length > 1)) { + var msgIndex = 0; + try { msgIndex = parseInt(args['_'][1]); } catch (x) { } + if (tunnelUserCount.msg[msgIndex] == null) { response = "Message not found."; } else { delete tunnelUserCount.msg[msgIndex]; response = "Message removed."; } + } else if (args['_'][0] == 'list') { + response = JSON.stringify(tunnelUserCount.msg, null, 2); + } + try { mesh.SendCommand({ action: 'sessions', type: 'msg', value: tunnelUserCount.msg }); } catch (x) { } + broadcastSessionsToRegisteredApps(); + } + break; + } + case 'clearagentmsg': { + tunnelUserCount.msg = {}; + try { mesh.SendCommand({ action: 'sessions', type: 'msg', value: tunnelUserCount.msg }); } catch (x) { } + broadcastSessionsToRegisteredApps(); + break; + } + case 'coredump': + if (args['_'].length != 1) { + response = "Proper usage: coredump on|off|status|clear"; // Display usage + } else { + switch (args['_'][0].toLowerCase()) + { + case 'on': + process.coreDumpLocation = (process.platform == 'win32') ? (process.execPath.replace('.exe', '.dmp')) : (process.execPath + '.dmp'); + response = 'coredump is now on'; + break; + case 'off': + process.coreDumpLocation = null; + response = 'coredump is now off'; + break; + case 'status': + response = 'coredump is: ' + ((process.coreDumpLocation == null) ? 'off' : 'on'); + if (process.coreDumpLocation != null) { + if (process.platform == 'win32') { + if (fs.existsSync(process.coreDumpLocation)) { response += '\r\n CoreDump present at: ' + process.coreDumpLocation; } + } else { + if ((process.cwd() != '//') && fs.existsSync(process.cwd() + 'core')) { response += '\r\n CoreDump present at: ' + process.cwd() + 'core'; } + } + } + break; + case 'clear': + db.Put('CoreDumpTime', null); + response = 'coredump db cleared'; + break; + default: + response = "Proper usage: coredump on|off|status"; // Display usage + break; + } + } + break; + case 'service': + if (args['_'].length != 1) + { + response = "Proper usage: service status|restart"; // Display usage + } + else + { + var s = require('service-manager').manager.getService(process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'); + switch(args['_'][0].toLowerCase()) + { + case 'status': + response = 'Service ' + (s.isRunning() ? (s.isMe() ? '[SELF]' : '[RUNNING]') : ('[NOT RUNNING]')); + break; + case 'restart': + if (s.isMe()) + { + s.restart(); + } + else + { + response = 'Restarting another agent instance is not allowed'; + } + break; + default: + response = "Proper usage: service status|restart"; // Display usage + break; + } + if (process.platform == 'win32') { s.close(); } + } + break; + case 'zip': + if (args['_'].length == 0) { + response = "Proper usage: zip (output file name), input1 [, input n]"; // Display usage + } else { + var p = args['_'].join(' ').split(','); + var ofile = p.shift(); + sendConsoleText('Writing ' + ofile + '...'); + var out = require('fs').createWriteStream(ofile, { flags: 'wb' }); + out.fname = ofile; + out.sessionid = sessionid; + out.on('close', function () { sendConsoleText('DONE writing ' + this.fname, this.sessionid); }); + var zip = require('zip-writer').write({ files: p }); + zip.pipe(out); + } + break; + case 'unzip': + if (args['_'].length == 0) { + response = "Proper usage: unzip input, destination"; // Display usage + } else { + var p = args['_'].join(' ').split(','); + if (p.length != 2) { response = "Proper usage: unzip input, destination"; break; } // Display usage + var prom = require('zip-reader').read(p[0]); + prom._dest = p[1]; + prom.self = this; + prom.sessionid = sessionid; + prom.then(function (zipped) { + sendConsoleText('Extracting to ' + this._dest + '...', this.sessionid); + zipped.extractAll(this._dest).then(function () { sendConsoleText('finished unzipping', this.sessionid); }, function (e) { sendConsoleText('Error unzipping: ' + e, this.sessionid); }).parentPromise.sessionid = this.sessionid; + }, function (e) { sendConsoleText('Error unzipping: ' + e, this.sessionid); }); + } + break; + case 'setbattery': + // require('MeshAgent').SendCommand({ action: 'battery', state: 'dc', level: 55 }); + if ((args['_'].length > 0) && ((args['_'][0] == 'ac') || (args['_'][0] == 'dc'))) { + var b = { action: 'battery', state: args['_'][0] }; + if (args['_'].length == 2) { b.level = parseInt(args['_'][1]); } + require('MeshAgent').SendCommand(b); + } else { + require('MeshAgent').SendCommand({ action: 'battery' }); + } + break; + case 'fdsnapshot': + require('ChainViewer').getSnapshot().then(function (c) { sendConsoleText(c, this.sessionid); }).parentPromise.sessionid = sessionid; + break; + case 'fdcount': + require('DescriptorEvents').getDescriptorCount().then( + function (c) + { + sendConsoleText('Descriptor Count: ' + c, this.sessionid); + }, function (e) + { + sendConsoleText('Error fetching descriptor count: ' + e, this.sessionid); + }).parentPromise.sessionid = sessionid; + break; + case 'uac': + if (process.platform != 'win32') + { + response = 'Unknown command "uac", type "help" for list of avaialble commands.'; + break; + } + if (args['_'].length != 1) + { + response = 'Proper usage: uac [get|interactive|secure]'; + } + else + { + switch(args['_'][0].toUpperCase()) + { + case 'GET': + var secd = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System', 'PromptOnSecureDesktop'); + response = "UAC mode: " + (secd == 0 ? "Interactive Desktop" : "Secure Desktop"); + break; + case 'INTERACTIVE': + try + { + require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System', 'PromptOnSecureDesktop', 0); + response = 'UAC mode changed to: Interactive Desktop'; + } + catch (e) + { + response = "Unable to change UAC Mode"; + } + break; + case 'SECURE': + try + { + require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System', 'PromptOnSecureDesktop', 1); + response = 'UAC mode changed to: Secure Desktop'; + } + catch(e) + { + response = "Unable to change UAC Mode"; + } + break; + default: + response = 'Proper usage: uac [get|interactive|secure]'; + break; + } + } + break; + case 'vm': + response = 'Virtual Machine = ' + require('identifiers').isVM(); + break; + case 'startupoptions': + response = JSON.stringify(require('MeshAgent').getStartupOptions()); + break; + case 'kvmmode': + if (require('MeshAgent').maxKvmTileSize == null) + { + response = "Unknown command \"kvmmode\", type \"help\" for list of avaialble commands."; + } + else + { + if(require('MeshAgent').maxKvmTileSize == 0) + { + response = 'KVM Mode: Full JUMBO'; + } + else + { + response = 'KVM Mode: ' + (require('MeshAgent').maxKvmTileSize <= 65500 ? 'NO JUMBO' : 'Partial JUMBO'); + response += (', TileLimit: ' + (require('MeshAgent').maxKvmTileSize < 1024 ? (require('MeshAgent').maxKvmTileSize + ' bytes') : (Math.round(require('MeshAgent').maxKvmTileSize/1024) + ' Kbytes'))); + } + } + break; + case 'alert': + if (args['_'].length == 0) + { + response = "Proper usage: alert TITLE, CAPTION [, TIMEOUT]"; // Display usage + } + else + { + var p = args['_'].join(' ').split(','); + if(p.length<2) + { + response = "Proper usage: alert TITLE, CAPTION [, TIMEOUT]"; // Display usage + } + else + { + this._alert = require('message-box').create(p[0], p[1], p.length==3?parseInt(p[2]):9999,1); + } + } + break; + case 'agentsize': + var actualSize = Math.floor(require('fs').statSync(process.execPath).size / 1024); + if (process.platform == 'win32') { + // Check the Agent Uninstall MetaData for correctness, as the installer may have written an incorrect value + var writtenSize = 0; + try { writtenSize = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MeshCentralAgent', 'EstimatedSize'); } catch (e) { response = e; } + if (writtenSize != actualSize) { + response = "Size updated from: " + writtenSize + " to: " + actualSize; + try { require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MeshCentralAgent', 'EstimatedSize', actualSize); } catch (e) { response = e; } + } else { response = "Agent Size: " + actualSize + " kb"; } + } else { response = "Agent Size: " + actualSize + " kb"; } + break; + case 'versions': + response = JSON.stringify(process.versions, null, ' '); + break; + case 'wpfhwacceleration': + if (process.platform != 'win32') { throw ("wpfhwacceleration setting is only supported on Windows"); } + if (args['_'].length != 1) { + response = "Proper usage: wpfhwacceleration (ON|OFF|STATUS)"; // Display usage + } + else { + var reg = require('win-registry'); + var uname = require('user-sessions').getUsername(require('user-sessions').consoleUid()); + var key = reg.usernameToUserKey(uname); + + switch (args['_'][0].toUpperCase()) { + default: + response = "Proper usage: wpfhwacceleration (ON|OFF|STATUS|DEFAULT)"; // Display usage + break; + case 'ON': + try { + reg.WriteKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration', 0); + response = "OK"; + } catch (e) { response = "FAILED"; } + break; + case 'OFF': + try { + reg.WriteKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration', 1); + response = 'OK'; + } catch (e) { response = 'FAILED'; } + break; + case 'STATUS': + var s; + try { s = reg.QueryKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration') == 1 ? 'DISABLED' : 'ENABLED'; } catch (e) { s = 'DEFAULT'; } + response = "WPF Hardware Acceleration: " + s; + break; + case 'DEFAULT': + try { reg.DeleteKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration'); } catch (e) { } + response = 'OK'; + break; + } + } + break; + case 'tsid': + if (process.platform == 'win32') { + if (args['_'].length != 1) { + response = "TSID: " + (require('MeshAgent')._tsid == null ? "console" : require('MeshAgent')._tsid); + } else { + var i = parseInt(args['_'][0]); + require('MeshAgent')._tsid = (isNaN(i) ? null : i); + response = "TSID set to: " + (require('MeshAgent')._tsid == null ? "console" : require('MeshAgent')._tsid); + } + } else { response = "TSID command only supported on Windows"; } + break; + case 'activeusers': + if (process.platform == 'win32') { + var p = require('user-sessions').enumerateUsers(); + p.sessionid = sessionid; + p.then(function (u) { + var v = []; + for (var i in u) { + if (u[i].State == 'Active') { v.push({ tsid: i, type: u[i].StationName, user: u[i].Username, domain: u[i].Domain }); } + } + sendConsoleText(JSON.stringify(v, null, 1), this.sessionid); + }); + } else { response = "activeusers command only supported on Windows"; } + break; + case 'wallpaper': + if (process.platform != 'win32' && !(process.platform == 'linux' && require('linux-gnome-helpers').available)) { + response = "wallpaper command not supported on this platform"; + } + else { + if (args['_'].length != 1) { + response = 'Proper usage: wallpaper (GET|TOGGLE)'; // Display usage + } + else { + switch (args['_'][0].toUpperCase()) { + default: + response = 'Proper usage: wallpaper (GET|TOGGLE)'; // Display usage + break; + case 'GET': + case 'TOGGLE': + if (process.platform == 'win32') { + var id = require('user-sessions').getProcessOwnerName(process.pid).tsid == 0 ? 1 : 0; + var child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0='], { type: id }); + child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); + child.stderr.on('data', function () { }); + child.waitExit(); + var current = child.stdout.str.trim(); + if (args['_'][0].toUpperCase() == 'GET') { + response = current; + break; + } + if (current != '') { + require('MeshAgent')._wallpaper = current; + response = 'Wallpaper cleared'; + } else { + response = 'Wallpaper restored'; + } + child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0=', current != '' ? '""' : require('MeshAgent')._wallpaper], { type: id }); + child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); + child.stderr.on('data', function () { }); + child.waitExit(); + } + else { + var id = require('user-sessions').consoleUid(); + var current = require('linux-gnome-helpers').getDesktopWallpaper(id); + if (args['_'][0].toUpperCase() == 'GET') { + response = current; + break; + } + if (current != '/dev/null') { + require('MeshAgent')._wallpaper = current; + response = 'Wallpaper cleared'; + } else { + response = 'Wallpaper restored'; + } + require('linux-gnome-helpers').setDesktopWallpaper(id, current != '/dev/null' ? undefined : require('MeshAgent')._wallpaper); + } + break; + } + } + } + break; + case 'safemode': + if (process.platform != 'win32') { + response = 'safemode only supported on Windows Platforms' + } + else { + if (args['_'].length != 1) { + response = 'Proper usage: safemode (ON|OFF|STATUS)'; // Display usage + } + else { + switch (args['_'][0].toUpperCase()) { + default: + response = 'Proper usage: safemode (ON|OFF|STATUS)'; // Display usage + break; + case 'ON': + require('win-bcd').setKey('safeboot', 'Network'); + require('win-bcd').enableSafeModeService('Mesh Agent'); + break; + case 'OFF': + require('win-bcd').deleteKey('safeboot'); + break; + case 'STATUS': + var nextboot = require('win-bcd').getKey('safeboot'); + if (nextboot) { + switch (nextboot) { + case 'Network': + case 'network': + nextboot = 'SAFE_MODE_NETWORK'; + break; + default: + nextboot = 'SAFE_MODE'; + break; + } + } + response = 'Current: ' + require('win-bcd').bootMode + ', NextBoot: ' + (nextboot ? nextboot : 'NORMAL'); + break; + } + } + } + break; + /* + case 'border': + { + if ((args['_'].length == 1) && (args['_'][0] == 'on')) { + if (meshCoreObj.users.length > 0) { + obj.borderManager.Start(meshCoreObj.users[0]); + response = 'Border blinking is on.'; + } else { + response = 'Cannot turn on border blinking, no logged in users.'; + } + } else if ((args['_'].length == 1) && (args['_'][0] == 'off')) { + obj.borderManager.Stop(); + response = 'Border blinking is off.'; + } else { + response = 'Proper usage: border "on|off"'; // Display correct command usage + } + } + break; + */ + case 'av': + if (process.platform == 'win32') { + // Windows Command: "wmic /Namespace:\\root\SecurityCenter2 Path AntiVirusProduct get /FORMAT:CSV" + response = JSON.stringify(require('win-info').av(), null, 1); + } else { + response = 'Not supported on the platform'; + } + break; + case 'log': + if (args['_'].length != 1) { response = 'Proper usage: log "sample text"'; } else { MeshServerLog(args['_'][0]); response = 'ok'; } + break; + case 'getclip': + if (require('MeshAgent').isService) { + require('clipboard').dispatchRead().then(function (str) { sendConsoleText(str, sessionid); }); + } else { + require("clipboard").read().then(function (str) { sendConsoleText(str, sessionid); }); + } + break; + case 'setclip': { + if (args['_'].length != 1) { + response = 'Proper usage: setclip "sample text"'; + } else { + if (require('MeshAgent').isService) { + require('clipboard').dispatchWrite(args['_'][0]); + response = 'Setting clipboard to: "' + args['_'][0] + '"'; + } + else { + require("clipboard")(args['_'][0]); response = 'Setting clipboard to: "' + args['_'][0] + '"'; + } + } + break; + } + case 'amtreset': { + if (amt != null) { amt.reset(); response = 'Done.'; } + break; + } + case 'amtlmsreset': { + if (amt != null) { amt.lmsreset(); response = 'Done.'; } + break; + } + case 'amtccm': { + if (amt == null) { response = 'Intel AMT not supported.'; } else { + if (args['_'].length != 1) { response = 'Proper usage: amtccm (adminPassword)'; } // Display usage + else { amt.setPolicy({ type: 0 }); amt.activeToCCM(args['_'][0]); } + } + break; + } + case 'amtacm': { + if (amt == null) { response = 'Intel AMT not supported.'; } else { + amt.setPolicy({ type: 0 }); + amt.getAmtInfo(function (meinfo) { amt.activeToACM(meinfo); }); + } + break; + } + case 'amtdeactivate': { + if (amt == null) { response = 'Intel AMT not supported.'; } else { amt.setPolicy({ type: 0 }); amt.deactivateCCM(); } + break; + } + case 'amtpolicy': { + if (amtPolicy == null) { + response = 'No Intel(R) AMT policy.'; + } else { + response = JSON.stringify(amtPolicy); + } + break; + } + case 'openurl': { + if (args['_'].length != 1) { response = 'Proper usage: openurl (url)'; } // Display usage + else { if (openUserDesktopUrl(args['_'][0]) == null) { response = 'Failed.'; } else { response = 'Success.'; } } + break; + } + case 'users': { + if (meshCoreObj.users == null) { response = 'Active users are unknown.'; } else { response = 'Active Users: ' + meshCoreObj.users.join(', ') + '.'; } + require('user-sessions').enumerateUsers().then(function (u) { for (var i in u) { sendConsoleText(u[i]); } }); + break; + } + case 'toast': { + if (args['_'].length < 1) { response = 'Proper usage: toast "message"'; } else { + if (require('MeshAgent')._tsid == null) { + require('toaster').Toast('MeshCentral', args['_'][0]).then(sendConsoleText, sendConsoleText); + } + else { + require('toaster').Toast('MeshCentral', args['_'][0], require('MeshAgent')._tsid).then(sendConsoleText, sendConsoleText); + } + } + break; + } + case 'setdebug': { + if (args['_'].length < 1) { response = 'Proper usage: setdebug (target), 0 = Disabled, 1 = StdOut, 2 = This Console, * = All Consoles, 4 = WebLog, 8 = Logfile'; } // Display usage + else { if (args['_'][0] == '*') { console.setDestination(2); } else { console.setDestination(parseInt(args['_'][0]), sessionid); } } + break; + } + case 'ps': { + processManager.getProcesses(function (plist) { + var x = ''; + for (var i in plist) { x += i + ((plist[i].user) ? (', ' + plist[i].user) : '') + ', ' + plist[i].cmd + '\r\n'; } + sendConsoleText(x, sessionid); + }); + break; + } + case 'kill': { + if ((args['_'].length < 1)) { + response = 'Proper usage: kill [pid]'; // Display correct command usage + } else { + process.kill(parseInt(args['_'][0])); + response = 'Killed process ' + args['_'][0] + '.'; + } + break; + } + case 'smbios': { + if (SMBiosTables == null) { response = 'SMBios tables not available.'; } else { response = objToString(SMBiosTables, 0, ' ', true); } + break; + } + case 'rawsmbios': { + if (SMBiosTablesRaw == null) { response = 'SMBios tables not available.'; } else { + response = ''; + for (var i in SMBiosTablesRaw) { + var header = false; + for (var j in SMBiosTablesRaw[i]) { + if (SMBiosTablesRaw[i][j].length > 0) { + if (header == false) { response += ('Table type #' + i + ((require('smbios').smTableTypes[i] == null) ? '' : (', ' + require('smbios').smTableTypes[i]))) + '\r\n'; header = true; } + response += (' ' + SMBiosTablesRaw[i][j].toString('hex')) + '\r\n'; + } + } + } + } + break; + } + case 'eval': { // Eval JavaScript + if (args['_'].length < 1) { + response = 'Proper usage: eval "JavaScript code"'; // Display correct command usage + } else { + response = JSON.stringify(mesh.eval(args['_'][0])); // This can only be run by trusted administrator. + } + break; + } + case 'uninstallagent': // Uninstall this agent + var agentName = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'; + if (!require('service-manager').manager.getService(agentName).isMe()) { + response = 'Uininstall failed, this instance is not the service instance'; + } else { + try { diagnosticAgent_uninstall(); } catch (e) { } + var js = "require('service-manager').manager.getService('" + agentName + "').stop(); require('service-manager').manager.uninstallService('" + agentName + "'); process.exit();"; + this.child = require('child_process').execFile(process.execPath, [process.platform == 'win32' ? (process.execPath.split('\\').pop()) : (process.execPath.split('/').pop()), '-b64exec', Buffer.from(js).toString('base64')], { type: 4, detached: true }); + } + break; + case 'notify': { // Send a notification message to the mesh + if (args['_'].length != 1) { + response = 'Proper usage: notify "message" [--session]'; // Display correct command usage + } else { + var notification = { action: 'msg', type: 'notify', value: args['_'][0], tag: 'console' }; + if (args.session) { notification.sessionid = sessionid; } // If "--session" is specified, notify only this session, if not, the server will notify the mesh + mesh.SendCommand(notification); // no sessionid or userid specified, notification will go to the entire mesh + response = "ok"; + } + break; + } + case 'cpuinfo': { // Return system information + // CPU & memory utilization + pr = require('sysinfo').cpuUtilization(); + pr.sessionid = sessionid; + pr.then(function (data) { + sendConsoleText(JSON.stringify({ cpu: data, memory: require('sysinfo').memUtilization() }, null, 1), this.sessionid); + }, function (e) { + sendConsoleText(e); + }); + break; + } + case 'sysinfo': { // Return system information + getSystemInformation(function (results, err) { + if (results == null) { sendConsoleText(err, this.sessionid); } else { + sendConsoleText(JSON.stringify(results, null, 1), this.sessionid); + } + }); + break; + } + case 'info': { // Return information about the agent and agent core module + response = 'Current Core: ' + meshCoreObj.value + '\r\nAgent Time: ' + Date() + '.\r\nUser Rights: 0x' + rights.toString(16) + '.\r\nPlatform: ' + process.platform + '.\r\nCapabilities: ' + meshCoreObj.caps + '.\r\nServer URL: ' + mesh.ServerUrl + '.'; + if (amt != null) { response += '\r\nBuilt-in LMS: ' + ['Disabled', 'Connecting..', 'Connected'][amt.lmsstate] + '.'; } + if (meshCoreObj.osdesc) { response += '\r\nOS: ' + meshCoreObj.osdesc + '.'; } + response += '\r\nModules: ' + addedModules.join(', ') + '.'; + response += '\r\nServer Connection: ' + mesh.isControlChannelConnected + ', State: ' + meshServerConnectionState + '.'; + response += '\r\lastMeInfo: ' + lastMeInfo + '.'; + var oldNodeId = db.Get('OldNodeId'); + if (oldNodeId != null) { response += '\r\nOldNodeID: ' + oldNodeId + '.'; } + if (process.platform == 'linux' || process.platform == 'freebsd') { response += '\r\nX11 support: ' + require('monitor-info').kvm_x11_support + '.'; } + break; + } + case 'osinfo': { // Return the operating system information + var i = 1; + if (args['_'].length > 0) { i = parseInt(args['_'][0]); if (i > 8) { i = 8; } response = 'Calling ' + i + ' times.'; } + for (var j = 0; j < i; j++) { + var pr = require('os').name(); + pr.sessionid = sessionid; + pr.then(function (v) { + sendConsoleText("OS: " + v + (process.platform == 'win32' ? (require('win-virtual-terminal').supported ? ' [ConPTY: YES]' : ' [ConPTY: NO]') : ''), this.sessionid); + }); + } + break; + } + case 'sendcaps': { // Send capability flags to the server + if (args['_'].length == 0) { + response = 'Proper usage: sendcaps (number)'; // Display correct command usage + } else { + meshCoreObj.caps = parseInt(args['_'][0]); + mesh.SendCommand(meshCoreObj); + response = JSON.stringify(meshCoreObj); + } + break; + } + case 'sendosdesc': { // Send OS description + if (args['_'].length > 0) { + meshCoreObj.osdesc = args['_'][0]; + mesh.SendCommand(meshCoreObj); + response = JSON.stringify(meshCoreObj); + } else { + response = 'Proper usage: sendosdesc [os description]'; // Display correct command usage + } + break; + } + case 'args': { // Displays parsed command arguments + response = 'args ' + objToString(args, 0, ' ', true); + break; + } + case 'print': { // Print a message on the mesh agent console, does nothing when running in the background + var r = []; + for (var i in args['_']) { r.push(args['_'][i]); } + console.log(r.join(' ')); + response = 'Message printed on agent console.'; + break; + } + case 'type': { // Returns the content of a file + if (args['_'].length == 0) { + response = 'Proper usage: type (filepath) [maxlength]'; // Display correct command usage + } else { + var max = 4096; + if ((args['_'].length > 1) && (typeof args['_'][1] == 'number')) { max = args['_'][1]; } + if (max > 4096) max = 4096; + var buf = Buffer.alloc(max), fd = fs.openSync(args['_'][0], "r"), r = fs.readSync(fd, buf, 0, max); // Read the file content + response = buf.toString(); + var i = response.indexOf('\n'); + if ((i > 0) && (response[i - 1] != '\r')) { response = response.split('\n').join('\r\n'); } + if (r == max) response += '...'; + fs.closeSync(fd); + } + break; + } + case 'dbkeys': { // Return all data store keys + response = JSON.stringify(db.Keys); + break; + } + case 'dbget': { // Return the data store value for a given key + if (db == null) { response = 'Database not accessible.'; break; } + if (args['_'].length != 1) { + response = 'Proper usage: dbget (key)'; // Display the value for a given database key + } else { + response = db.Get(args['_'][0]); + } + break; + } + case 'dbset': { // Set a data store key and value pair + if (db == null) { response = 'Database not accessible.'; break; } + if (args['_'].length != 2) { + response = 'Proper usage: dbset (key) (value)'; // Set a database key + } else { + var r = db.Put(args['_'][0], args['_'][1]); + response = 'Key set: ' + r; + } + break; + } + case 'dbcompact': { // Compact the data store + if (db == null) { response = 'Database not accessible.'; break; } + var r = db.Compact(); + response = 'Database compacted: ' + r; + break; + } + case 'httpget': { + if (consoleHttpRequest != null) { + response = 'HTTP operation already in progress.'; + } else { + if (args['_'].length != 1) { + response = 'Proper usage: httpget (url)'; + } else { + var options = http.parseUri(args['_'][0]); + options.method = 'GET'; + if (options == null) { + response = 'Invalid url.'; + } else { + try { consoleHttpRequest = http.request(options, consoleHttpResponse); } catch (e) { response = 'Invalid HTTP GET request'; } + consoleHttpRequest.sessionid = sessionid; + if (consoleHttpRequest != null) { + consoleHttpRequest.end(); + response = 'HTTPGET ' + options.protocol + '//' + options.host + ':' + options.port + options.path; + } + } + } + } + break; + } + case 'wslist': { // List all web sockets + response = ''; + for (var i in consoleWebSockets) { + var httprequest = consoleWebSockets[i]; + response += 'Websocket #' + i + ', ' + httprequest.url + '\r\n'; + } + if (response == '') { response = 'no websocket sessions.'; } + break; + } + case 'wsconnect': { // Setup a web socket + if (args['_'].length == 0) { + response = 'Proper usage: wsconnect (url)\r\nFor example: wsconnect wss://localhost:443/meshrelay.ashx?id=abc'; // Display correct command usage + } else { + var httprequest = null; + try { + var options = http.parseUri(args['_'][0].split('$').join('%24').split('@').join('%40')); // Escape the $ and @ characters in the URL + options.rejectUnauthorized = 0; + httprequest = http.request(options); + } catch (e) { response = 'Invalid HTTP websocket request'; } + if (httprequest != null) { + httprequest.upgrade = onWebSocketUpgrade; + httprequest.on('error', function (e) { sendConsoleText("ERROR: Unable to connect to: " + this.url + ", " + JSON.stringify(e)); }); + + var index = 1; + while (consoleWebSockets[index]) { index++; } + httprequest.sessionid = sessionid; + httprequest.index = index; + httprequest.url = args['_'][0]; + consoleWebSockets[index] = httprequest; + response = 'New websocket session #' + index; + } + } + break; + } + case 'wssend': { // Send data on a web socket + if (args['_'].length == 0) { + response = 'Proper usage: wssend (socketnumber)\r\n'; // Display correct command usage + for (var i in consoleWebSockets) { + var httprequest = consoleWebSockets[i]; + response += 'Websocket #' + i + ', ' + httprequest.url + '\r\n'; + } + } else { + var i = parseInt(args['_'][0]); + var httprequest = consoleWebSockets[i]; + if (httprequest != undefined) { + httprequest.s.write(args['_'][1]); + response = 'ok'; + } else { + response = 'Invalid web socket number'; + } + } + break; + } + case 'wsclose': { // Close a websocket + if (args['_'].length == 0) { + response = 'Proper usage: wsclose (socketnumber)'; // Display correct command usage + } else { + var i = parseInt(args['_'][0]); + var httprequest = consoleWebSockets[i]; + if (httprequest != undefined) { + if (httprequest.s != null) { httprequest.s.end(); } else { httprequest.end(); } + response = 'ok'; + } else { + response = 'Invalid web socket number'; + } + } + break; + } + case 'tunnels': { // Show the list of current tunnels + response = ''; + for (var i in tunnels) { response += 'Tunnel #' + i + ', ' + tunnels[i].url + '\r\n'; } + if (response == '') { response = 'No websocket sessions.'; } + break; + } + case 'ls': { // Show list of files and folders + response = ''; + var xpath = '*'; + if (args['_'].length > 0) { xpath = obj.path.join(args['_'][0], '*'); } + response = 'List of ' + xpath + '\r\n'; + var results = fs.readdirSync(xpath); + for (var i = 0; i < results.length; ++i) { + var stat = null, p = obj.path.join(args['_'][0], results[i]); + try { stat = fs.statSync(p); } catch (e) { } + if ((stat == null) || (stat == undefined)) { + response += (results[i] + "\r\n"); + } else { + response += (results[i] + " " + ((stat.isDirectory()) ? "(Folder)" : "(File)") + "\r\n"); + } + } + break; + } + case 'lsx': { // Show list of files and folders + response = objToString(getDirectoryInfo(args['_'][0]), 0, ' ', true); + break; + } + case 'lock': { // Lock the current user out of the desktop + if (process.platform == 'win32') { var child = require('child_process'); child.execFile(process.env['windir'] + '\\system32\\cmd.exe', ['/c', 'RunDll32.exe user32.dll,LockWorkStation'], { type: 1 }); response = 'Ok'; } + else { response = 'Not supported on the platform'; } + break; + } + case 'amt': { // Show Intel AMT status + if (amt != null) { + amt.getAmtInfo(function (state) { + var resp = 'Intel AMT not detected.'; + if (state != null) { resp = objToString(state, 0, ' ', true); } + sendConsoleText(resp, sessionid); + }); + } else { + response = 'Intel AMT not detected.'; + } + break; + } + case 'netinfo': { // Show network interface information + var interfaces = require('os').networkInterfaces(); + response = objToString(interfaces, 0, ' ', true); + break; + } + case 'wakeonlan': { // Send wake-on-lan + if ((args['_'].length != 1) || (args['_'][0].length != 12)) { + response = 'Proper usage: wakeonlan [mac], for example "wakeonlan 010203040506".'; + } else { + var count = sendWakeOnLan(args['_'][0]); + response = 'Sent wake-on-lan on ' + count + ' interface(s).'; + } + break; + } + case 'sendall': { // Send a message to all consoles on this mesh + sendConsoleText(args['_'].join(' ')); + break; + } + case 'power': { // Execute a power action on this computer + if (mesh.ExecPowerState == undefined) { + response = 'Power command not supported on this agent.'; + } else { + if ((args['_'].length == 0) || isNaN(Number(args['_'][0]))) { + response = 'Proper usage: power (actionNumber), where actionNumber is:\r\n LOGOFF = 1\r\n SHUTDOWN = 2\r\n REBOOT = 3\r\n SLEEP = 4\r\n HIBERNATE = 5\r\n DISPLAYON = 6\r\n KEEPAWAKE = 7\r\n BEEP = 8\r\n CTRLALTDEL = 9\r\n VIBRATE = 13\r\n FLASH = 14'; // Display correct command usage + } else { + var r = mesh.ExecPowerState(Number(args['_'][0]), Number(args['_'][1])); + response = 'Power action executed with return code: ' + r + '.'; + } + } + break; + } + case 'location': { + getIpLocationData(function (location) { + sendConsoleText(objToString({ action: 'iplocation', type: 'publicip', value: location }, 0, ' ')); + }); + break; + } + case 'parseuri': { + response = JSON.stringify(http.parseUri(args['_'][0])); + break; + } + case 'scanwifi': { + if (wifiScanner != null) { + var wifiPresent = wifiScanner.hasWireless; + if (wifiPresent) { response = "Perfoming Wifi scan..."; wifiScanner.Scan(); } else { response = "Wifi absent."; } + } else { response = "Wifi module not present."; } + break; + } + case 'scanamt': { + if (amtscanner != null) { + if (args['_'].length != 1) { + response = 'Usage examples:\r\n scanamt 1.2.3.4\r\n scanamt 1.2.3.0-1.2.3.255\r\n scanamt 1.2.3.0/24\r\n'; // Display correct command usage + } else { + response = 'Scanning: ' + args['_'][0] + '...'; + amtscanner.scan(args['_'][0], 2000, function (data) { + if (data.length > 0) { + var r = '', pstates = ['NotActivated', 'InActivation', 'Activated']; + for (var i in data) { + var x = data[i]; + if (r != '') { r += '\r\n'; } + r += x.address + ' - Intel AMT v' + x.majorVersion + '.' + x.minorVersion; + if (x.provisioningState < 3) { r += (', ' + pstates[x.provisioningState]); } + if (x.provisioningState == 2) { r += (', ' + x.openPorts.join(', ')); } + r += '.'; + } + } else { + r = 'No Intel AMT found.'; + } + sendConsoleText(r); + }); + } + } else { response = "Intel AMT scanner module not present."; } + break; + } + case 'modules': { + response = JSON.stringify(addedModules); + break; + } + case 'listservices': { + var services = require('service-manager').manager.enumerateService(); + response = JSON.stringify(services, null, 1); + break; + } + case 'getscript': { + if (args['_'].length != 1) { + response = "Proper usage: getscript [scriptNumber]."; + } else { + mesh.SendCommand({ action: 'getScript', type: args['_'][0] }); + } + break; + } + case 'diagnostic': + { + if (!mesh.DAIPC.listening) { + response = 'Unable to bind to Diagnostic IPC, most likely because the path (' + process.cwd() + ') is not on a local file system'; + break; + } + var diag = diagnosticAgent_installCheck(); + if (diag) { + if (args['_'].length == 1 && args['_'][0] == 'uninstall') { + diagnosticAgent_uninstall(); + response = 'Diagnostic Agent uninstalled'; + } + else { + response = 'Diagnostic Agent installed at: ' + diag.appLocation(); + } + } + else { + if (args['_'].length == 1 && args['_'][0] == 'install') { + diag = diagnosticAgent_installCheck(true); + if (diag) { + response = 'Diagnostic agent was installed at: ' + diag.appLocation(); + } + else { + response = 'Diagnostic agent installation failed'; + } + } + else { + response = 'Diagnostic Agent Not installed. To install: diagnostic install'; + } + } + if (diag) { diag.close(); diag = null; } + break; + } + case 'amtconfig': { + if (apftunnel != null) { response = "Intel AMT server tunnel already active"; break; } + if (amt == null) { response = "No Intel AMT support delected"; break; } + getMeiState(15, function (state) { + var rx = ''; + if ((state == null) || (state.ProvisioningState == null)) { rx = "Intel AMT not ready for configuration."; } else { + var apfarg = { + mpsurl: mesh.ServerUrl.replace('agent.ashx', 'apf.ashx'), + mpsuser: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16), + mpspass: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16), + mpskeepalive: 60000, + clientname: state.OsHostname, + clientaddress: '127.0.0.1', + clientuuid: state.UUID, + conntype: 2, // 0 = CIRA, 1 = Relay, 2 = LMS. The correct value is 2 since we are performing an LMS relay, other values for testing. + meiState: state // MEI state will be passed to MPS server + }; + if ((state.UUID == null) || (state.UUID.length != 36)) { + rx = "Unable to get Intel AMT UUID"; + } else { + apftunnel = require('apfclient')({ debug: false }, apfarg); + apftunnel.onJsonControl = function (data) { + if (data.action == 'console') { require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: data.msg }); } // Display a console message + if (data.action == 'mestate') { getMeiState(15, function (state) { apftunnel.updateMeiState(state); }); } // Update the MEI state + if (data.action == 'deactivate') { // Request CCM deactivation + var amtMeiModule, amtMei; + try { amtMeiModule = require('amt-mei'); amtMei = new amtMeiModule(); } catch (ex) { apftunnel.sendMeiDeactivationState(1); return; } + amtMei.on('error', function (e) { apftunnel.sendMeiDeactivationState(1); }); + amtMei.unprovision(1, function (status) { apftunnel.sendMeiDeactivationState(status); }); // 0 = Success + } + if (data.action == 'close') { try { apftunnel.disconnect(); } catch (e) { } apftunnel = null; } // Close the CIRA-LMS connection + } + apftunnel.onChannelClosed = function () { apftunnel = null; } + try { + apftunnel.connect(); + rx = "Started Intel AMT configuration"; + } catch (ex) { + rx = JSON.stringify(ex); + } + } + } + if (rx != '') { require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: rx }); } + }); + break; + } + case 'apf': { + if (meshCoreObj.intelamt !== null) { + if (args['_'].length == 1) { + var connType = -1, connTypeStr = args['_'][0].toLowerCase(); + if (connTypeStr == 'lms') { connType = 2; } + if (connTypeStr == 'relay') { connType = 1; } + if (connTypeStr == 'cira') { connType = 0; } + if (connTypeStr == 'off') { connType = -2; } + if (connType >= 0) { // Connect + var apfarg = { + mpsurl: mesh.ServerUrl.replace('agent.ashx', 'apf.ashx'), + mpsuser: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16), + mpspass: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16), + mpskeepalive: 60000, + clientname: require('os').hostname(), + clientaddress: '127.0.0.1', + clientuuid: meshCoreObj.intelamt.uuid, + conntype: connType // 0 = CIRA, 1 = Relay, 2 = LMS. The correct value is 2 since we are performing an LMS relay, other values for testing. + }; + if ((apfarg.clientuuid == null) || (apfarg.clientuuid.length != 36)) { + response = "Unable to get Intel AMT UUID: " + apfarg.clientuuid; + } else { + apftunnel = require('apfclient')({ debug: false }, apfarg); + apftunnel.onJsonControl = function (data) { + if (data.action == 'console') { require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: data.msg }); } + if (data.action == 'close') { try { apftunnel.disconnect(); } catch (e) { } apftunnel = null; } + } + apftunnel.onChannelClosed = function () { apftunnel = null; } + try { + apftunnel.connect(); + response = "Started APF tunnel"; + } catch (e) { + response = JSON.stringify(e); + } + } + } else if (connType == -2) { // Disconnect + try { + apftunnel.disconnect(); + response = "Stopped APF tunnel"; + } catch (e) { + response = JSON.stringify(e); + } + apftunnel = null; + } else { + response = "Invalid command.\r\nUse: apf lms|relay|cira|off"; + } + } else { + response = "APF tunnel is " + (apftunnel == null ? "off" : "on") + "\r\nUse: apf lms|relay|cira|off"; + } + } else { + response = "APF tunnel requires Intel AMT"; + } + break; + } + case 'plugin': { + if (typeof args['_'][0] == 'string') { + try { + // Pass off the action to the plugin + // for plugin creators, you'll want to have a plugindir/modules_meshcore/plugin.js + // to control the output / actions here. + response = require(args['_'][0]).consoleaction(args, rights, sessionid, mesh); + } catch (e) { + response = "There was an error in the plugin (" + e + ")"; + } + } else { + response = "Proper usage: plugin [pluginName] [args]."; + } + break; + } + default: { // This is an unknown command, return an error message + response = "Unknown command \"" + cmd + "\", type \"help\" for list of avaialble commands."; + break; + } + } + } catch (e) { response = "Command returned an exception error: " + e; console.log(e); } + if (response != null) { sendConsoleText(response, sessionid); } + } + + // Send a mesh agent console command + function sendConsoleText(text, sessionid) { + if (typeof text == 'object') { text = JSON.stringify(text); } + require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: text, sessionid: sessionid }); + } + + // Called before the process exits + //process.exit = function (code) { console.log("Exit with code: " + code.toString()); } + + // Called when the server connection state changes + function handleServerConnection(state) { + meshServerConnectionState = state; + if (meshServerConnectionState == 0) { + // Server disconnected + if (selfInfoUpdateTimer != null) { clearInterval(selfInfoUpdateTimer); selfInfoUpdateTimer = null; } + lastSelfInfo = null; + } else { + // Server connected, send mesh core information + var oldNodeId = db.Get('OldNodeId'); + if (oldNodeId != null) { mesh.SendCommand({ action: 'mc1migration', oldnodeid: oldNodeId }); } + + // Update the server with basic info, logged in users and more. + mesh.SendCommand(meshCoreObj); + + // Send SMBios tables if present + if (SMBiosTablesRaw != null) { mesh.SendCommand({ action: 'smbios', value: SMBiosTablesRaw }); } + + // Update the server on more advanced stuff, like Intel ME and Network Settings + meInfoStr = null; + sendPeriodicServerUpdate(); + if (selfInfoUpdateTimer == null) { selfInfoUpdateTimer = setInterval(sendPeriodicServerUpdate, 1200000); } // 20 minutes + + // Send any state messages + if (Object.keys(tunnelUserCount.msg).length > 0) { + try { mesh.SendCommand({ action: 'sessions', type: 'msg', value: tunnelUserCount.msg }); } catch (e) { } + broadcastSessionsToRegisteredApps(); + } + + // Send update of registered applications to the server + updateRegisteredAppsToServer(); + } + + // Send server state update to registered applications + broadcastToRegisteredApps({ cmd: 'serverstate', value: meshServerConnectionState, url: require('MeshAgent').ConnectedServer }); + } + + // Update the server with the latest network interface information + var sendNetworkUpdateNagleTimer = null; + function sendNetworkUpdateNagle() { if (sendNetworkUpdateNagleTimer != null) { clearTimeout(sendNetworkUpdateNagleTimer); sendNetworkUpdateNagleTimer = null; } sendNetworkUpdateNagleTimer = setTimeout(sendNetworkUpdate, 5000); } + function sendNetworkUpdate(force) { + sendNetworkUpdateNagleTimer = null; + + // Update the network interfaces information data + var netInfo = { netif2: require('os').networkInterfaces() }; + if (netInfo.netif2) { + netInfo.action = 'netinfo'; + var netInfoStr = JSON.stringify(netInfo); + if ((force == true) || (clearGatewayMac(netInfoStr) != clearGatewayMac(lastNetworkInfo))) { mesh.SendCommand(netInfo); lastNetworkInfo = netInfoStr; } + } + } + + // Called periodically to check if we need to send updates to the server + function sendPeriodicServerUpdate(flags) { + if (meshServerConnectionState == 0) return; // Not connected to server, do nothing. + if (!flags) { flags = 0xFFFFFFFF; } + + if ((flags & 1) && (amt != null)) { + // If we have a connected MEI, get Intel ME information + amt.getAmtInfo(function (meinfo) { + try { + if (meinfo == null) return; + var intelamt = {}; + if (amt != null) + { + switch(amt.lmsstate) + { + case 0: intelamt.microlms = 'DISABLED'; break; + case 1: intelamt.microlms = 'CONNECTING'; break; + case 2: intelamt.microlms = 'CONNECTED'; break; + default: intelamt.microlms = 'unknown'; break; + } + } + var p = false; + if ((meinfo.Versions != null) && (meinfo.Versions.AMT != null)) { intelamt.ver = meinfo.Versions.AMT; p = true; if (meinfo.Versions.Sku != null) { intelamt.sku = parseInt(meinfo.Versions.Sku); } } + if (meinfo.ProvisioningState != null) { intelamt.state = meinfo.ProvisioningState; p = true; } + if (meinfo.Flags != null) { intelamt.flags = meinfo.Flags; p = true; } + if (meinfo.OsHostname != null) { intelamt.host = meinfo.OsHostname; p = true; } + if (meinfo.UUID != null) { intelamt.uuid = meinfo.UUID; p = true; } + if ((meinfo.ProvisioningState == 0) && (meinfo.net0 != null) && (meinfo.net0.enabled == 1)) { // If not activated, look to see if we have wired net working. + // Not activated and we have wired ethernet, look for the trusted DNS + var dns = meinfo.DNS; + if (dns == null) { + // Trusted DNS not set, let's look for the OS network DNS suffix + var interfaces = require('os').networkInterfaces(); + for (var i in interfaces) { + for (var j in interfaces[i]) { + if ((interfaces[i][j].mac == mestate.net0.mac) && (interfaces[i][j].fqdn != null) && (interfaces[i][j].fqdn != '')) { dns = interfaces[i][j].fqdn; } + } + } + } + if (intelamt.dns != dns) { intelamt.dns = dns; p = true; } + } else { if (intelamt.dns != null) { delete intelamt.dns; p = true; } } + if (p == true) { + var meInfoStr = JSON.stringify(intelamt); + if (meInfoStr != lastMeInfo) { + meshCoreObj.intelamt = intelamt; + mesh.SendCommand(meshCoreObj); + lastMeInfo = meInfoStr; + } + } + } catch (e) { } + }); + } + + if (flags & 2) { + // Update network information + sendNetworkUpdateNagle(false); + } + + if ((flags & 4) && (process.platform == 'win32')) { + // Update anti-virus information + // Windows Command: "wmic /Namespace:\\root\SecurityCenter2 Path AntiVirusProduct get /FORMAT:CSV" + var av, pr; + try { av = require('win-info').av(); } catch (e) { av = null; } // Antivirus + //if (process.platform == 'win32') { try { pr = require('win-info').pendingReboot(); } catch (e) { pr = null; } } // Pending reboot + if ((meshCoreObj.av == null) || (JSON.stringify(meshCoreObj.av) != JSON.stringify(av))) { meshCoreObj.av = av; mesh.SendCommand(meshCoreObj); } + } + } + + // Starting function + obj.start = function () { + // Setup the mesh agent event handlers + mesh.AddCommandHandler(handleServerCommand); + mesh.AddConnectHandler(handleServerConnection); + + // Parse input arguments + //var args = parseArgs(process.argv); + //console.log(args); + + //resetMicroLms(); + + // Setup logged in user monitoring (THIS IS BROKEN IN WIN7) + try { + var userSession = require('user-sessions'); + userSession.on('changed', function onUserSessionChanged() { + userSession.enumerateUsers().then(function (users) { + var u = [], a = users.Active; + for (var i = 0; i < a.length; i++) { + var un = a[i].Domain ? (a[i].Domain + '\\' + a[i].Username) : (a[i].Username); + if (u.indexOf(un) == -1) { u.push(un); } // Only push users in the list once. + } + meshCoreObj.users = u; + mesh.SendCommand(meshCoreObj); + }); + }); + userSession.emit('changed'); + //userSession.on('locked', function (user) { sendConsoleText('[' + (user.Domain ? user.Domain + '\\' : '') + user.Username + '] has LOCKED the desktop'); }); + //userSession.on('unlocked', function (user) { sendConsoleText('[' + (user.Domain ? user.Domain + '\\' : '') + user.Username + '] has UNLOCKED the desktop'); }); + } catch (e) { } + } + + obj.stop = function () { + mesh.AddCommandHandler(null); + mesh.AddConnectHandler(null); + } + + function onWebSocketClosed() { sendConsoleText("WebSocket #" + this.httprequest.index + " closed.", this.httprequest.sessionid); delete consoleWebSockets[this.httprequest.index]; } + function onWebSocketData(data) { sendConsoleText("Got WebSocket #" + this.httprequest.index + " data: " + data, this.httprequest.sessionid); } + function onWebSocketSendOk() { sendConsoleText("WebSocket #" + this.index + " SendOK.", this.sessionid); } + + function onWebSocketUpgrade(response, s, head) { + sendConsoleText("WebSocket #" + this.index + " connected.", this.sessionid); + this.s = s; + s.httprequest = this; + s.end = onWebSocketClosed; + s.data = onWebSocketData; + } + + // Get Intel MEI State in a flexible way + // Flags: 1 = Versions, 2 = OsAdmin, 4 = Hashes, 8 = Network + function getMeiState(flags, func) { + var amtMeiModule, amtMei; + try { amtMeiModule = require('amt-mei'); amtMei = new amtMeiModule(); } catch (ex) { func(null); return; } + amtMei.on('error', function (e) { func(null); return; }); + try { + var amtMeiTmpState = { OsHostname: require('os').hostname(), Flags: 0 }; // Flags: 1=EHBC, 2=CCM, 4=ACM + amtMei.getProtocolVersion(function (result) { if (result != null) { amtMeiTmpState.MeiVersion = result; } }); + if ((flags & 1) != 0) { amtMei.getVersion(function (result) { if (result) { amtMeiTmpState.Versions = {}; for (var version in result.Versions) { amtMeiTmpState.Versions[result.Versions[version].Description] = result.Versions[version].Version; } } }); } + amtMei.getProvisioningMode(function (result) { if (result) { amtMeiTmpState.ProvisioningMode = result.mode; } }); + amtMei.getProvisioningState(function (result) { if (result) { amtMeiTmpState.ProvisioningState = result.state; } }); // 0: "Not Activated (Pre)", 1: "Not Activated (In)", 2: "Activated" + amtMei.getEHBCState(function (result) { if ((result != null) && (result.EHBC == true)) { amtMeiTmpState.Flags += 1; } }); + amtMei.getControlMode(function (result) { if (result != null) { if (result.controlMode == 1) { amtMeiTmpState.Flags += 2; } if (result.controlMode == 2) { amtMeiTmpState.Flags += 4; } } }); // Flag 2 = CCM, 4 = ACM + //amtMei.getMACAddresses(function (result) { if (result) { amtMeiTmpState.mac = result; } }); + if ((flags & 8) != 0) { + amtMei.getLanInterfaceSettings(0, function (result) { + if (result) { + amtMeiTmpState.net0 = result; + var fqdn = null, interfaces = require('os').networkInterfaces(); // Look for the DNS suffix for the Intel AMT Ethernet interface + for (var i in interfaces) { for (var j in interfaces[i]) { if ((interfaces[i][j].mac == mestate.net0.mac) && (interfaces[i][j].fqdn != null) && (interfaces[i][j].fqdn != '')) { amtMeiTmpState.OsDnsSuffix = interfaces[i][j].fqdn; } } } + } + }); + amtMei.getLanInterfaceSettings(1, function (result) { if (result) { amtMeiTmpState.net1 = result; } }); + } + amtMei.getUuid(function (result) { if ((result != null) && (result.uuid != null)) { amtMeiTmpState.UUID = result.uuid; } }); + if ((flags & 2) != 0) { amtMei.getLocalSystemAccount(function (x) { if ((x != null) && x.user && x.pass) { amtMeiTmpState.OsAdmin = { user: x.user, pass: x.pass }; } }); } + amtMei.getDnsSuffix(function (result) { if (result != null) { amtMeiTmpState.DnsSuffix = result; } if ((flags & 4) == 0) { if (func != null) { func(amtMeiTmpState); } } }); + if ((flags & 4) != 0) { + amtMei.getHashHandles(function (handles) { + if ((handles != null) && (handles.length > 0)) { amtMeiTmpState.Hashes = []; } else { func(amtMeiTmpState); } + var exitOnCount = handles.length; + for (var i = 0; i < handles.length; ++i) { this.getCertHashEntry(handles[i], function (hashresult) { amtMeiTmpState.Hashes.push(hashresult); if (--exitOnCount == 0) { if (func != null) { func(amtMeiTmpState); } } }); } + }); + } + } catch (e) { if (func != null) { func(null); } return; } + } + + return obj; +} + +// +// Module startup +// + +try { + var xexports = null, mainMeshCore = null; + try { xexports = module.exports; } catch (e) { } + + if (xexports != null) { + // If we are running within NodeJS, export the core + module.exports.createMeshCore = createMeshCore; + } else { + // If we are not running in NodeJS, launch the core + mainMeshCore = createMeshCore(); + mainMeshCore.start(null); + } +} catch (e) { + require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: "uncaughtException2: " + ex }); +} diff --git a/agents/meshcore.js b/agents/meshcore.js index 789f849e..95a33218 100644 --- a/agents/meshcore.js +++ b/agents/meshcore.js @@ -400,7 +400,6 @@ function createMeshCore(agent) { var networkMonitor = null; var amtscanner = null; var nextTunnelIndex = 1; - var amtPolicy = null; var apftunnel = null; var tunnelUserCount = { terminal: {}, files: {}, tcp: {}, udp: {}, msg: {} }; // List of userid->count sessions for terminal, files and TCP/UDP routing @@ -538,7 +537,6 @@ function createMeshCore(agent) { mesh.SendCommand(meshCoreObj); }); amt.onStateChange = function (state) { if (state == 2) { sendPeriodicServerUpdate(1); } } - if (amtPolicy != null) { amt.setPolicy(amtPolicy); } amt.start(); } } @@ -1075,11 +1073,39 @@ function createMeshCore(agent) { if (data.url) { mesh.SendCommand({ action: 'openUrl', url: data.url, sessionid: data.sessionid, success: (openUserDesktopUrl(data.url) != null) }); } break; } - case 'amtPolicy': { - // Store the latest Intel AMT policy - amtPolicy = data.amtPolicy; - if (data.amtPolicy != null) { db.Put('amtPolicy', JSON.stringify(data.amtPolicy)); } else { db.Put('amtPolicy', null); } - if (amt != null) { amt.setPolicy(amtPolicy, true); } + case 'amtconfig': { + // Perform Intel AMT activation and/or configuration + if ((apftunnel != null) || (amt == null)) break; + getMeiState(15, function (state) { + if ((apftunnel != null) || (amt == null)) return; + if ((state == null) || (state.ProvisioningState == null)) return; + if ((state.UUID == null) || (state.UUID.length != 36)) return; // Bad UUID + var apfarg = { + mpsurl: mesh.ServerUrl.replace('agent.ashx', 'apf.ashx'), + mpsuser: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16), // TODO: User a server provided encrypted cookie for CIRA-LMS login + mpspass: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16), + mpskeepalive: 60000, + clientname: state.OsHostname, + clientaddress: '127.0.0.1', + clientuuid: state.UUID, + conntype: 2, // 0 = CIRA, 1 = Relay, 2 = LMS. The correct value is 2 since we are performing an LMS relay, other values for testing. + meiState: state // MEI state will be passed to MPS server + }; + apftunnel = require('apfclient')({ debug: false }, apfarg); + apftunnel.onJsonControl = function (data) { + //if (data.action == 'console') { require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: data.msg }); } // Display a console message (DEBUG) + if (data.action == 'mestate') { getMeiState(15, function (state) { apftunnel.updateMeiState(state); }); } // Update the MEI state + if (data.action == 'deactivate') { // Request CCM deactivation + var amtMeiModule, amtMei; + try { amtMeiModule = require('amt-mei'); amtMei = new amtMeiModule(); } catch (ex) { if (apftunnel) apftunnel.sendMeiDeactivationState(1); return; } + amtMei.on('error', function (e) { if (apftunnel) apftunnel.sendMeiDeactivationState(1); }); + amtMei.unprovision(1, function (status) { if (apftunnel) apftunnel.sendMeiDeactivationState(status); }); // 0 = Success + } + if (data.action == 'close') { try { apftunnel.disconnect(); } catch (e) { } apftunnel = null; } // Close the CIRA-LMS connection + } + apftunnel.onChannelClosed = function () { apftunnel = null; } + try { apftunnel.connect(); } catch (ex) { } + }); break; } case 'getScript': { @@ -2563,7 +2589,7 @@ function createMeshCore(agent) { var response = null; switch (cmd) { case 'help': { // Displays available commands - var fin = '', f = '', availcommands = 'amtconfig,coredump,service,fdsnapshot,fdcount,startupoptions,alert,agentsize,versions,help,info,osinfo,args,print,type,dbkeys,dbget,dbset,dbcompact,eval,parseuri,httpget,nwslist,plugin,wsconnect,wssend,wsclose,notify,ls,ps,kill,amt,netinfo,location,power,wakeonlan,setdebug,smbios,rawsmbios,toast,lock,users,sendcaps,openurl,amtreset,amtccm,amtacm,amtdeactivate,amtpolicy,getscript,getclip,setclip,log,av,cpuinfo,sysinfo,apf,scanwifi,scanamt,wallpaper,agentmsg'; + var fin = '', f = '', availcommands = 'amtconfig,coredump,service,fdsnapshot,fdcount,startupoptions,alert,agentsize,versions,help,info,osinfo,args,print,type,dbkeys,dbget,dbset,dbcompact,eval,parseuri,httpget,nwslist,plugin,wsconnect,wssend,wsclose,notify,ls,ps,kill,amt,netinfo,location,power,wakeonlan,setdebug,smbios,rawsmbios,toast,lock,users,sendcaps,openurl,getscript,getclip,setclip,log,av,cpuinfo,sysinfo,apf,scanwifi,scanamt,wallpaper,agentmsg'; if (process.platform == 'win32') { availcommands += ',safemode,wpfhwacceleration,uac'; } if (process.platform != 'freebsd') { availcommands += ',vm';} if (require('MeshAgent').maxKvmTileSize != null) { availcommands += ',kvmmode'; } @@ -3037,40 +3063,6 @@ function createMeshCore(agent) { } break; } - case 'amtreset': { - if (amt != null) { amt.reset(); response = 'Done.'; } - break; - } - case 'amtlmsreset': { - if (amt != null) { amt.lmsreset(); response = 'Done.'; } - break; - } - case 'amtccm': { - if (amt == null) { response = 'Intel AMT not supported.'; } else { - if (args['_'].length != 1) { response = 'Proper usage: amtccm (adminPassword)'; } // Display usage - else { amt.setPolicy({ type: 0 }); amt.activeToCCM(args['_'][0]); } - } - break; - } - case 'amtacm': { - if (amt == null) { response = 'Intel AMT not supported.'; } else { - amt.setPolicy({ type: 0 }); - amt.getAmtInfo(function (meinfo) { amt.activeToACM(meinfo); }); - } - break; - } - case 'amtdeactivate': { - if (amt == null) { response = 'Intel AMT not supported.'; } else { amt.setPolicy({ type: 0 }); amt.deactivateCCM(); } - break; - } - case 'amtpolicy': { - if (amtPolicy == null) { - response = 'No Intel(R) AMT policy.'; - } else { - response = JSON.stringify(amtPolicy); - } - break; - } case 'openurl': { if (args['_'].length != 1) { response = 'Proper usage: openurl (url)'; } // Display usage else { if (openUserDesktopUrl(args['_'][0]) == null) { response = 'Failed.'; } else { response = 'Success.'; } } @@ -3566,7 +3558,7 @@ function createMeshCore(agent) { if (data.action == 'mestate') { getMeiState(15, function (state) { apftunnel.updateMeiState(state); }); } // Update the MEI state if (data.action == 'deactivate') { // Request CCM deactivation var amtMeiModule, amtMei; - try { amtMeiModule = require('amt-mei'); amtMei = new amtMeiModule(); } catch (ex) { apftunnel.sendMeiDeactivationState(1); break; } + try { amtMeiModule = require('amt-mei'); amtMei = new amtMeiModule(); } catch (ex) { apftunnel.sendMeiDeactivationState(1); return; } amtMei.on('error', function (e) { apftunnel.sendMeiDeactivationState(1); }); amtMei.unprovision(1, function (status) { apftunnel.sendMeiDeactivationState(status); }); // 0 = Success } @@ -3861,7 +3853,7 @@ function createMeshCore(agent) { if (result) { amtMeiTmpState.net0 = result; var fqdn = null, interfaces = require('os').networkInterfaces(); // Look for the DNS suffix for the Intel AMT Ethernet interface - for (var i in interfaces) { for (var j in interfaces[i]) { if ((interfaces[i][j].mac == mestate.net0.mac) && (interfaces[i][j].fqdn != null) && (interfaces[i][j].fqdn != '')) { amtMeiTmpState.OsDnsSuffix = interfaces[i][j].fqdn; } } } + for (var i in interfaces) { for (var j in interfaces[i]) { if ((interfaces[i][j].mac == result.mac) && (interfaces[i][j].fqdn != null) && (interfaces[i][j].fqdn != '')) { amtMeiTmpState.OsDnsSuffix = interfaces[i][j].fqdn; } } } } }); amtMei.getLanInterfaceSettings(1, function (result) { if (result) { amtMeiTmpState.net1 = result; } }); diff --git a/amtmanager.js b/amtmanager.js index ab0f98af..356bec00 100644 --- a/amtmanager.js +++ b/amtmanager.js @@ -259,14 +259,29 @@ module.exports.CreateAmtManager = function(parent) { if (amtPolicy < 2) { ciraPolicy = 0; } dev.policy = { amtPolicy: amtPolicy, ciraPolicy: ciraPolicy, badPass: badPass, password: password }; + // Setup the monitored device + dev.name = node.name; + dev.meshid = node.meshid; + dev.intelamt = node.intelamt; + + // Check if the status of Intel AMT sent by the agents matched what we have in the database + if ((dev.connType == 2) && (dev.mpsConnection != null) && (dev.mpsConnection.tag != null) && (dev.mpsConnection.tag.meiState != null)) { + dev.aquired = {}; + if (typeof dev.mpsConnection.tag.meiState['ProvisioningState'] == 'number') { + dev.intelamt.state = dev.aquired.state = dev.mpsConnection.tag.meiState['ProvisioningState']; + } + if (typeof dev.mpsConnection.tag.meiState['Flags'] == 'number') { + const flags = dev.intelamt.flags = dev.mpsConnection.tag.meiState['Flags']; + if (flags & 2) { dev.aquired.controlMode = 1; } // CCM + if (flags & 4) { dev.aquired.controlMode = 2; } // ACM + } + UpdateDevice(dev); + } + // If there is no Intel AMT policy for this device, stop here. if (amtPolicy == 0) { dev.consoleMsg("Done."); removeAmtDevice(dev); return; } - // Setup the monitored device - dev.name = node.name; - //if (node.host) { dev.host = node.host.toLowerCase(); } - dev.meshid = node.meshid; - dev.intelamt = node.intelamt; + // Initiate the communication to Intel AMT dev.consoleMsg("Checking Intel AMT state..."); attemptInitialContact(dev); }); @@ -435,8 +450,8 @@ module.exports.CreateAmtManager = function(parent) { var verSplit = stack.wsman.comm.amtVersion.split('.'); if (verSplit.length >= 3) { dev.aquired.version = verSplit[0] + '.' + verSplit[1] + '.' + verSplit[2]; dev.aquired.majorver = parseInt(verSplit[0]); dev.aquired.minorver = parseInt(verSplit[1]); } dev.aquired.realm = stack.wsman.comm.digestRealm; - dev.aquired.user = stack.wsman.comm.user; - dev.aquired.pass = stack.wsman.comm.pass; + dev.aquired.user = dev.intelamt.user = stack.wsman.comm.user; + dev.aquired.pass = dev.intelamt.pass = stack.wsman.comm.pass; dev.aquired.lastContact = Date.now(); if ((dev.connType == 1) || (dev.connType == 3)) { dev.aquired.tls = stack.wsman.comm.xtls; } // Only set the TLS state if in relay or local mode. When using CIRA, this is auto-detected. if (stack.wsman.comm.xtls == 1) { dev.aquired.hash = stack.wsman.comm.xtlsCertificate.fingerprint.split(':').join('').toLowerCase(); } else { delete dev.aquired.hash; } @@ -512,19 +527,17 @@ module.exports.CreateAmtManager = function(parent) { // Change the current core information string and event it function UpdateDevice(dev) { - if (isAmtDeviceValid(dev) == false) return; // Device no longer exists, ignore this request. - // Check that the mesh exists const mesh = parent.webserver.meshes[dev.meshid]; - if (mesh == null) { removeAmtDevice(dev); return false; } + if (mesh == null) { removeAmtDevice(dev); console.log('y3'); return false; } // Get the node and change it if needed parent.db.Get(dev.nodeid, function (err, nodes) { - if ((nodes == null) || (nodes.length != 1)) { return false; } + if ((nodes == null) || (nodes.length != 1)) { console.log('y1'); return false; } const device = nodes[0]; var changes = [], change = 0, log = 0; var domain = parent.config.domains[device.domain]; - if (domain == null) { return false; } + if (domain == null) { console.log('y2'); return false; } // Check if anything changes if (device.intelamt == null) { device.intelamt = {}; } @@ -535,7 +548,7 @@ module.exports.CreateAmtManager = function(parent) { if (dev.aquired.realm && (typeof dev.aquired.realm == 'string') && (dev.aquired.realm != device.intelamt.realm)) { change = 1; log = 1; device.intelamt.realm = dev.aquired.realm; changes.push('AMT realm'); } if (dev.aquired.hash && (typeof dev.aquired.hash == 'string') && (dev.aquired.hash != device.intelamt.hash)) { change = 1; log = 1; device.intelamt.hash = dev.aquired.hash; changes.push('AMT hash'); } if (dev.aquired.tls && (typeof dev.aquired.tls == 'number') && (dev.aquired.tls != device.intelamt.tls)) { change = 1; log = 1; device.intelamt.tls = dev.aquired.tls; changes.push('AMT TLS'); } - if (device.intelamt.state != 2) { change = 1; log = 1; device.intelamt.state = 2; changes.push('AMT state'); } + if ((dev.aquired.state != null) && (typeof dev.aquired.state == 'number') && (dev.aquired.state != device.intelamt.state)) { change = 1; log = 1; device.intelamt.state = dev.aquired.state; changes.push('AMT state'); } // Update Intel AMT flags if needed // dev.aquired.controlMode // 1 = CCM, 2 = ACM @@ -544,7 +557,10 @@ module.exports.CreateAmtManager = function(parent) { if (typeof device.intelamt.flags == 'number') { flags = device.intelamt.flags; } if (dev.aquired.controlMode == 1) { if ((flags & 4) != 0) { flags -= 4; } if ((flags & 2) == 0) { flags += 2; } } // CCM if (dev.aquired.controlMode == 2) { if ((flags & 4) == 0) { flags += 4; } if ((flags & 2) != 0) { flags -= 2; } } // ACM - if (device.intelamt.flags != flags) { change = 1; log = 1; device.intelamt.flags = flags; changes.push('AMT flags'); } + if (device.intelamt.flags != flags) { + console.log('ChangeFlags', flags); + change = 1; log = 1; device.intelamt.flags = flags; changes.push('AMT flags'); + } // If there are changes, event the new device if (change == 1) { @@ -1104,13 +1120,8 @@ module.exports.CreateAmtManager = function(parent) { // If this device does not have KVM, ignore the response. This can happen for Intel Standard Manageability (Intel(R) SM). if ((responses['CIM_KVMRedirectionSAP'] == null) || (responses['CIM_KVMRedirectionSAP'].status == 400)) { responses['CIM_KVMRedirectionSAP'] = null; } - // Check redirection services - var redir = (responses['AMT_RedirectionService'].response['ListenerEnabled'] == true); - var sol = ((responses['AMT_RedirectionService'].response['EnabledState'] & 2) != 0); - var ider = ((responses['AMT_RedirectionService'].response['EnabledState'] & 1) != 0); - // Enable SOL & IDER - if (responses['AMT_RedirectionService'].response['EnabledState'] != 32771) { + if ((responses['AMT_RedirectionService'].response['EnabledState'] != 32771) || (responses['AMT_RedirectionService'].response['ListenerEnabled'] == false)) { dev.redirObj = responses['AMT_RedirectionService'].response; dev.redirObj['ListenerEnabled'] = true; dev.redirObj['EnabledState'] = 32771; @@ -1402,12 +1413,11 @@ module.exports.CreateAmtManager = function(parent) { dev.aquired.host = dev.mpsConnection.tag.meiState.OsHostname + '.' + dev.mpsConnection.tag.meiState.OsDnsSuffix; } dev.aquired.realm = dev.amtstack.wsman.comm.digestRealm; - dev.aquired.user = 'admin'; - dev.aquired.pass = dev.temp.pass; + dev.intelamt.user = dev.aquired.user = 'admin'; + dev.intelamt.pass = dev.aquired.pass = dev.temp.pass; + dev.intelamt.tls = dev.aquired.tls = 0; dev.aquired.lastContact = Date.now(); - dev.aquired.tls = 0; - dev.intelamt.user = 'admin'; - dev.intelamt.pass = dev.temp.pass; + dev.aquired.state = 2; // Activated delete dev.acctry; UpdateDevice(dev); @@ -1438,6 +1448,14 @@ module.exports.CreateAmtManager = function(parent) { dev.consoleMsg("Failed to deactivate Intel AMT CCM."); removeAmtDevice(dev); } else { + // Update the device + dev.aquired = {}; + dev.aquired.controlMode = 0; // 1 = CCM, 2 = ACM + dev.aquired.state = 0; // Not activated + delete dev.acctry; + delete dev.amtstack; + UpdateDevice(dev); + if (dev.policy.amtPolicy == 1) { // CCM deactivation policy, we are done. dev.consoleMsg("Deactivation successful."); dev.consoleMsg("Done."); diff --git a/meshagent.js b/meshagent.js index 805458a3..21de2607 100644 --- a/meshagent.js +++ b/meshagent.js @@ -192,7 +192,6 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { 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('agent', "Updating core " + argument.name); - agentCoreIsStable(); } else { // This agent is probably disconnected, nothing to do. parent.parent.taskLimiter.completed(taskid); @@ -898,40 +897,11 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { } } - // Take a basic Intel AMT policy and add all server information to it, making it ready to send to this agent. - /* - function completeIntelAmtPolicy(amtPolicy) { - var r = amtPolicy; - if (amtPolicy == null) return null; - if (amtPolicy.type == 2) { - // CCM - Add server root certificate - if (parent.parent.certificates.rootex == null) { parent.parent.certificates.rootex = parent.parent.certificates.root.cert.split('-----BEGIN CERTIFICATE-----').join('').split('-----END CERTIFICATE-----').join('').split('\r').join('').split('\n').join(''); } - r.rootcert = parent.parent.certificates.rootex; - if ((amtPolicy.cirasetup == 2) && (parent.parent.mpsserver != null) && (parent.parent.certificates.AmtMpsName != null) && (args.lanonly != true) && (args.mpsport != 0)) { - // Add server CIRA settings - r.ciraserver = { - name: parent.parent.certificates.AmtMpsName, - port: (typeof args.mpsaliasport == 'number' ? args.mpsaliasport : args.mpsport), - user: obj.meshid.replace(/\@/g, 'X').replace(/\$/g, 'X').substring(0, 16), - pass: args.mpspass ? args.mpspass : 'A@xew9rt', // If the MPS password is not set, just use anything. TODO: Use the password as an agent identifier? - home: ['sdlwerulis3wpj95dfj'] // Use a random FQDN to not have any home network. - }; - if (Array.isArray(args.ciralocalfqdn)) { r.ciraserver.home = args.ciralocalfqdn; } - } - } else if ((amtPolicy.type == 3) && (domain.amtacmactivation.acmmatch)) { - // ACM - In this mode, don't send much to Intel AMT. Just indicate ACM policy and let the agent try activation when possible. - r = { type: 3, match: domain.amtacmactivation.acmmatch }; - } - return r; - } - */ - - // Send Intel AMT policy + // Indicate to the agent that we want to reconfigure Intel AMT obj.sendUpdatedIntelAmtPolicy = function (policy) { if (obj.agentExeInfo && (obj.agentExeInfo.amt == true)) { // Only send Intel AMT policy to agents what could have AMT. - // TODO - //if (policy == null) { var mesh = parent.meshes[obj.dbMeshKey]; if (mesh == null) return; policy = mesh.amt; } - //if (policy != null) { try { obj.send(JSON.stringify({ action: 'amtPolicy', amtPolicy: completeIntelAmtPolicy(common.Clone(policy)) })); } catch (ex) { } } + if (policy == null) { var mesh = parent.meshes[obj.dbMeshKey]; if (mesh == null) return; policy = mesh.amt; } + if ((policy != null) && (policy.type != 0)) { try { obj.send(JSON.stringify({ action: 'amtconfig' })); } catch (ex) { } } } } @@ -983,12 +953,10 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { } }); - // Send Intel AMT policy - /* - if (obj.agentExeInfo && (obj.agentExeInfo.amt == true) && (mesh.amt != null)) { // Only send Intel AMT policy to agents what could have AMT. - try { obj.send(JSON.stringify({ action: 'amtPolicy', amtPolicy: completeIntelAmtPolicy(common.Clone(mesh.amt)) })); } catch (ex) { } + // Indicate that we want to check the Intel AMT configuration + if (obj.agentExeInfo && (obj.agentExeInfo.amt == true) && (mesh.amt != null) && (mesh.amt.type != 0)) { // Only send yo agents what could have AMT and if the policy is not empty. + try { obj.send(JSON.stringify({ action: 'amtconfig' })); } catch (ex) { } } - */ // Fetch system information db.GetHash('si' + obj.dbNodeKey, function (err, results) { diff --git a/translate/translate.json b/translate/translate.json index 03c566e1..5003846b 100644 --- a/translate/translate.json +++ b/translate/translate.json @@ -224,8 +224,8 @@ "zh-chs": " 用户需要先登录到该服务器一次,然后才能将其添加到设备组。", "zh-cht": " 用戶需要先登入到該伺服器一次,然後才能將其新增到裝置群。", "xloc": [ - "default.handlebars->29->1435", - "default.handlebars->29->1895" + "default.handlebars->29->1428", + "default.handlebars->29->1888" ] }, { @@ -458,7 +458,7 @@ "zh-chs": "*保留空白可为每个设备分配一个随机密码。", "zh-cht": "*保留空白可為每個裝置分配一個隨機密碼。", "xloc": [ - "default.handlebars->29->1405" + "default.handlebars->29->1398" ] }, { @@ -496,7 +496,7 @@ "zh-cht": ",", "xloc": [ "default-mobile.handlebars->9->461", - "default.handlebars->29->1505" + "default.handlebars->29->1498" ] }, { @@ -768,8 +768,8 @@ "xloc": [ "default-mobile.handlebars->9->108", "default-mobile.handlebars->9->301", - "default.handlebars->29->1546", - "default.handlebars->29->2050", + "default.handlebars->29->1539", + "default.handlebars->29->2043", "default.handlebars->29->894" ] }, @@ -826,7 +826,7 @@ "zh-chs": "1个活跃时段", "zh-cht": "1個活躍時段", "xloc": [ - "default.handlebars->29->1964" + "default.handlebars->29->1957" ] }, { @@ -848,7 +848,7 @@ "xloc": [ "default-mobile.handlebars->9->118", "default-mobile.handlebars->9->465", - "default.handlebars->29->1570", + "default.handlebars->29->1563", "download.handlebars->3->1", "download2.handlebars->5->1" ] @@ -913,7 +913,7 @@ "zh-chs": "1组", "zh-cht": "1群", "xloc": [ - "default.handlebars->29->1929" + "default.handlebars->29->1922" ] }, { @@ -1017,7 +1017,7 @@ "zh-chs": "有1个用户没有显示,请使用搜索框查找用户...", "zh-cht": "有1個用戶沒有顯示,請使用搜尋框搜尋用戶...", "xloc": [ - "default.handlebars->29->1719" + "default.handlebars->29->1712" ] }, { @@ -1083,7 +1083,7 @@ "default-mobile.handlebars->9->172", "default-mobile.handlebars->9->175", "default-mobile.handlebars->9->178", - "default.handlebars->29->1723", + "default.handlebars->29->1716", "default.handlebars->29->250", "default.handlebars->29->253", "default.handlebars->29->256", @@ -1500,7 +1500,7 @@ "zh-chs": "2FA备份代码已清除", "zh-cht": "2FA備份代碼已清除", "xloc": [ - "default.handlebars->29->1681" + "default.handlebars->29->1674" ] }, { @@ -1520,8 +1520,8 @@ "zh-chs": "启用第二因素身份验证", "zh-cht": "啟用第二因素身份驗證", "xloc": [ - "default.handlebars->29->1736", - "default.handlebars->29->1951" + "default.handlebars->29->1729", + "default.handlebars->29->1944" ] }, { @@ -2503,7 +2503,7 @@ "zh-chs": "访问服务器档案", "zh-cht": "存取伺服器檔案", "xloc": [ - "default.handlebars->29->1901" + "default.handlebars->29->1894" ] }, { @@ -2656,7 +2656,7 @@ "zh-chs": "帐户已更改:{0}", "zh-cht": "帳戶已更改:{0}", "xloc": [ - "default.handlebars->29->1654" + "default.handlebars->29->1647" ] }, { @@ -2676,7 +2676,7 @@ "zh-chs": "创建帐户,电子邮件为{0}", "zh-cht": "創建帳戶,電子郵件為{0}", "xloc": [ - "default.handlebars->29->1653" + "default.handlebars->29->1646" ] }, { @@ -2696,7 +2696,7 @@ "zh-chs": "创建帐户,用户名是{0}", "zh-cht": "帳戶已創建,用戶名是{0}", "xloc": [ - "default.handlebars->29->1652" + "default.handlebars->29->1645" ] }, { @@ -2716,8 +2716,8 @@ "zh-chs": "帐户已被锁定", "zh-cht": "帳戶已被鎖定", "xloc": [ - "default.handlebars->29->1738", - "default.handlebars->29->1898" + "default.handlebars->29->1731", + "default.handlebars->29->1891" ] }, { @@ -2781,7 +2781,7 @@ "zh-chs": "帐号登录", "zh-cht": "帳號登錄", "xloc": [ - "default.handlebars->29->1589" + "default.handlebars->29->1582" ] }, { @@ -2801,7 +2801,7 @@ "zh-chs": "帐户登出", "zh-cht": "帳戶登出", "xloc": [ - "default.handlebars->29->1590" + "default.handlebars->29->1583" ] }, { @@ -2843,7 +2843,7 @@ "zh-chs": "帐户密码已更改:{0}", "zh-cht": "帳戶密碼已更改:{0}", "xloc": [ - "default.handlebars->29->1662" + "default.handlebars->29->1655" ] }, { @@ -2863,7 +2863,7 @@ "zh-chs": "帐户已删除", "zh-cht": "帳戶已刪除", "xloc": [ - "default.handlebars->29->1651" + "default.handlebars->29->1644" ] }, { @@ -3121,8 +3121,8 @@ "zh-chs": "添加设备", "zh-cht": "新增裝置", "xloc": [ - "default.handlebars->29->1875", - "default.handlebars->29->1999" + "default.handlebars->29->1868", + "default.handlebars->29->1992" ] }, { @@ -3162,9 +3162,9 @@ "zh-chs": "添加设备组", "zh-cht": "新增裝置群", "xloc": [ - "default.handlebars->29->1469", - "default.handlebars->29->1869", - "default.handlebars->29->1987", + "default.handlebars->29->1462", + "default.handlebars->29->1862", + "default.handlebars->29->1980", "default.handlebars->29->237" ] }, @@ -3185,7 +3185,7 @@ "zh-chs": "添加设备组权限", "zh-cht": "新增裝置群權限", "xloc": [ - "default.handlebars->29->1466" + "default.handlebars->29->1459" ] }, { @@ -3205,8 +3205,8 @@ "zh-chs": "添加设备权限", "zh-cht": "新增裝置權限", "xloc": [ - "default.handlebars->29->1471", - "default.handlebars->29->1473" + "default.handlebars->29->1464", + "default.handlebars->29->1466" ] }, { @@ -3306,7 +3306,7 @@ "zh-chs": "添加成员身份", "zh-cht": "新增成員身份", "xloc": [ - "default.handlebars->29->2017" + "default.handlebars->29->2010" ] }, { @@ -3392,7 +3392,7 @@ "zh-chs": "添加用户设备权限", "zh-cht": "新增用戶裝置權限", "xloc": [ - "default.handlebars->29->1476" + "default.handlebars->29->1469" ] }, { @@ -3413,8 +3413,8 @@ "zh-cht": "新增用戶群", "xloc": [ "default.handlebars->29->1365", - "default.handlebars->29->1468", - "default.handlebars->29->1993", + "default.handlebars->29->1461", + "default.handlebars->29->1986", "default.handlebars->29->673" ] }, @@ -3435,7 +3435,7 @@ "zh-chs": "添加用户组设备权限", "zh-cht": "新增用戶群裝置權限", "xloc": [ - "default.handlebars->29->1478" + "default.handlebars->29->1471" ] }, { @@ -3493,7 +3493,7 @@ "zh-cht": "新增用戶", "xloc": [ "default.handlebars->29->1364", - "default.handlebars->29->1864" + "default.handlebars->29->1857" ] }, { @@ -3513,7 +3513,7 @@ "zh-chs": "将用户添加到设备组", "zh-cht": "將用戶新增到裝置群", "xloc": [ - "default.handlebars->29->1465" + "default.handlebars->29->1458" ] }, { @@ -3533,7 +3533,7 @@ "zh-chs": "将用户添加到用户组", "zh-cht": "將用戶新增到用戶群", "xloc": [ - "default.handlebars->29->1897" + "default.handlebars->29->1890" ] }, { @@ -3696,7 +3696,7 @@ "zh-chs": "添加了身份验证应用程序", "zh-cht": "添加了身份驗證應用程序", "xloc": [ - "default.handlebars->29->1678" + "default.handlebars->29->1671" ] }, { @@ -3716,8 +3716,8 @@ "zh-chs": "已将设备{0}添加到设备组{1}", "zh-cht": "已將設備{0}添加到設備組{1}", "xloc": [ - "default.handlebars->29->1645", - "default.handlebars->29->1672" + "default.handlebars->29->1638", + "default.handlebars->29->1665" ] }, { @@ -3737,7 +3737,7 @@ "zh-chs": "添加了安全密钥", "zh-cht": "添加了安全密鑰", "xloc": [ - "default.handlebars->29->1683" + "default.handlebars->29->1676" ] }, { @@ -3757,7 +3757,7 @@ "zh-chs": "已将用户组{0}添加到设备组{1}", "zh-cht": "已將用戶組{0}添加到設備組{1}", "xloc": [ - "default.handlebars->29->1656" + "default.handlebars->29->1649" ] }, { @@ -3777,8 +3777,8 @@ "zh-chs": "已将用户{0}添加到用户组{1}", "zh-cht": "已將用戶{0}添加到用戶組{1}", "xloc": [ - "default.handlebars->29->1659", - "default.handlebars->29->1668" + "default.handlebars->29->1652", + "default.handlebars->29->1661" ] }, { @@ -3901,7 +3901,7 @@ "zh-chs": "管理领域", "zh-cht": "管理領域", "xloc": [ - "default.handlebars->29->1933" + "default.handlebars->29->1926" ] }, { @@ -3942,7 +3942,7 @@ "zh-chs": "管理领域", "zh-cht": "管理領域", "xloc": [ - "default.handlebars->29->1801" + "default.handlebars->29->1794" ] }, { @@ -3962,7 +3962,7 @@ "zh-chs": "管理员", "zh-cht": "管理員", "xloc": [ - "default.handlebars->29->1730" + "default.handlebars->29->1723" ] }, { @@ -4005,8 +4005,8 @@ "default-mobile.handlebars->9->203", "default-mobile.handlebars->9->228", "default-mobile.handlebars->9->244", - "default.handlebars->29->1532", - "default.handlebars->29->1540", + "default.handlebars->29->1525", + "default.handlebars->29->1533", "default.handlebars->29->213", "default.handlebars->29->444", "default.handlebars->container->column_l->p15->consoleTable->1->6->1->1->1->0->p15outputselecttd->p15outputselect->1" @@ -4029,8 +4029,8 @@ "zh-chs": "代理+英特尔AMT", "zh-cht": "代理+Intel® AMT", "xloc": [ - "default.handlebars->29->1534", - "default.handlebars->29->1542" + "default.handlebars->29->1527", + "default.handlebars->29->1535" ] }, { @@ -4071,7 +4071,7 @@ "zh-cht": "代理控制台", "xloc": [ "default-mobile.handlebars->9->443", - "default.handlebars->29->1486" + "default.handlebars->29->1479" ] }, { @@ -4091,7 +4091,7 @@ "zh-chs": "代理错误计数器", "zh-cht": "代理錯誤計數器", "xloc": [ - "default.handlebars->29->2060" + "default.handlebars->29->2053" ] }, { @@ -4194,7 +4194,7 @@ "zh-chs": "代理时段", "zh-cht": "代理時段", "xloc": [ - "default.handlebars->29->2076" + "default.handlebars->29->2069" ] }, { @@ -4235,7 +4235,7 @@ "zh-chs": "代理类型", "zh-cht": "代理類型", "xloc": [ - "default.handlebars->29->1538", + "default.handlebars->29->1531", "default.handlebars->container->column_l->p21->3->1->meshOsChartDiv->1" ] }, @@ -4256,7 +4256,7 @@ "zh-chs": "代理关闭了与服务器压缩的{0}%代理会话。已发送:{1},已压缩:{2}", "zh-cht": "代理關閉了與{0}%代理到服務器壓縮的會話。已發送:{1},已壓縮:{2}", "xloc": [ - "default.handlebars->29->1642" + "default.handlebars->29->1635" ] }, { @@ -4365,7 +4365,7 @@ "zh-chs": "代理", "zh-cht": "代理", "xloc": [ - "default.handlebars->29->2092" + "default.handlebars->29->2085" ] }, { @@ -4428,7 +4428,7 @@ "zh-chs": "全部可用", "zh-cht": "全部可用", "xloc": [ - "default.handlebars->29->1693" + "default.handlebars->29->1686" ] }, { @@ -4465,7 +4465,7 @@ "zh-chs": "所有事件", "zh-cht": "所有事件", "xloc": [ - "default.handlebars->29->1691" + "default.handlebars->29->1684" ] }, { @@ -4507,8 +4507,8 @@ "zh-chs": "允许用户管理此设备组和该组中的设备。", "zh-cht": "允許用戶管理此裝置群和該群中的裝置。", "xloc": [ - "default.handlebars->29->1433", - "default.handlebars->29->1894" + "default.handlebars->29->1426", + "default.handlebars->29->1887" ] }, { @@ -4528,7 +4528,7 @@ "zh-chs": "允许用户管理此设备。", "zh-cht": "允許用戶管理此裝置。", "xloc": [ - "default.handlebars->29->1434" + "default.handlebars->29->1427" ] }, { @@ -4634,8 +4634,8 @@ "zh-cht": "一直通知", "xloc": [ "default.handlebars->29->1345", - "default.handlebars->29->1855", - "default.handlebars->29->1942", + "default.handlebars->29->1848", + "default.handlebars->29->1935", "default.handlebars->29->611" ] }, @@ -4657,8 +4657,8 @@ "zh-cht": "一直提示", "xloc": [ "default.handlebars->29->1346", - "default.handlebars->29->1856", - "default.handlebars->29->1943", + "default.handlebars->29->1849", + "default.handlebars->29->1936", "default.handlebars->29->612" ] }, @@ -5284,7 +5284,7 @@ "zh-cht": "你確定要刪除群{0}嗎?刪除裝置群還將刪除該群中有關裝置的所有訊息。", "xloc": [ "default-mobile.handlebars->9->412", - "default.handlebars->29->1409" + "default.handlebars->29->1402" ] }, { @@ -5364,7 +5364,7 @@ "zh-chs": "您确定要{0}插件吗:{1}", "zh-cht": "你確定要{0}外掛嗎:{1}", "xloc": [ - "default.handlebars->29->2132" + "default.handlebars->29->2125" ] }, { @@ -5484,7 +5484,7 @@ "zh-chs": "尝试激活英特尔(R)AMT ACM模式", "zh-cht": "嘗試激活英特爾(R)AMT ACM模式", "xloc": [ - "default.handlebars->29->1611" + "default.handlebars->29->1604" ] }, { @@ -5504,7 +5504,7 @@ "zh-chs": "认证软件", "zh-cht": "認證軟體", "xloc": [ - "default.handlebars->29->1946" + "default.handlebars->29->1939" ] }, { @@ -5675,7 +5675,7 @@ "zh-chs": "可用內存", "zh-cht": "可用內存", "xloc": [ - "default.handlebars->29->2085" + "default.handlebars->29->2078" ] }, { @@ -5831,8 +5831,8 @@ "zh-chs": "背景与互动", "zh-cht": "背景與互動", "xloc": [ + "default.handlebars->29->1508", "default.handlebars->29->1515", - "default.handlebars->29->1522", "default.handlebars->29->357", "default.handlebars->29->371" ] @@ -5854,8 +5854,8 @@ "zh-chs": "仅背景", "zh-cht": "僅背景", "xloc": [ + "default.handlebars->29->1509", "default.handlebars->29->1516", - "default.handlebars->29->1523", "default.handlebars->29->358", "default.handlebars->29->372", "default.handlebars->29->386" @@ -5898,7 +5898,7 @@ "zh-chs": "备用码", "zh-cht": "備用碼", "xloc": [ - "default.handlebars->29->1948" + "default.handlebars->29->1941" ] }, { @@ -5918,7 +5918,7 @@ "zh-chs": "错误的签名", "zh-cht": "錯誤的簽名", "xloc": [ - "default.handlebars->29->2067" + "default.handlebars->29->2060" ] }, { @@ -5938,7 +5938,7 @@ "zh-chs": "错误的网络证书", "zh-cht": "錯誤的網絡憑證", "xloc": [ - "default.handlebars->29->2066" + "default.handlebars->29->2059" ] }, { @@ -6101,7 +6101,7 @@ "zh-chs": "广播", "zh-cht": "廣播", "xloc": [ - "default.handlebars->29->1862", + "default.handlebars->29->1855", "default.handlebars->container->column_l->p4->3->1->0->3->1" ] }, @@ -6122,7 +6122,7 @@ "zh-chs": "广播消息", "zh-cht": "廣播消息", "xloc": [ - "default.handlebars->29->1783" + "default.handlebars->29->1776" ] }, { @@ -6142,7 +6142,7 @@ "zh-chs": "向所有连接的用户广播消息。", "zh-cht": "向所有連接的用戶廣播消息。", "xloc": [ - "default.handlebars->29->1778" + "default.handlebars->29->1771" ] }, { @@ -6224,8 +6224,7 @@ "zh-cht": "CIRA", "xloc": [ "default-mobile.handlebars->9->204", - "default.handlebars->29->1397", - "default.handlebars->29->1402", + "default.handlebars->29->1394", "default.handlebars->29->215", "default.handlebars->29->446" ] @@ -6247,7 +6246,7 @@ "zh-chs": "CIRA服务器", "zh-cht": "CIRA伺服器", "xloc": [ - "default.handlebars->29->2120" + "default.handlebars->29->2113" ] }, { @@ -6267,7 +6266,7 @@ "zh-chs": "CIRA服务器命令", "zh-cht": "CIRA伺服器指令", "xloc": [ - "default.handlebars->29->2121" + "default.handlebars->29->2114" ] }, { @@ -6308,7 +6307,7 @@ "zh-chs": "CPU负载", "zh-cht": "CPU負載", "xloc": [ - "default.handlebars->29->2081" + "default.handlebars->29->2074" ] }, { @@ -6328,7 +6327,7 @@ "zh-chs": "最近15分钟的CPU负载", "zh-cht": "最近15分鐘的CPU負載", "xloc": [ - "default.handlebars->29->2084" + "default.handlebars->29->2077" ] }, { @@ -6348,7 +6347,7 @@ "zh-chs": "最近5分钟的CPU负载", "zh-cht": "最近5分鐘的CPU負載", "xloc": [ - "default.handlebars->29->2083" + "default.handlebars->29->2076" ] }, { @@ -6368,7 +6367,7 @@ "zh-chs": "最近一分钟的CPU负载", "zh-cht": "最近一分鐘的CPU負載", "xloc": [ - "default.handlebars->29->2082" + "default.handlebars->29->2075" ] }, { @@ -6410,7 +6409,7 @@ "zh-chs": "CSV", "zh-cht": "CSV", "xloc": [ - "default.handlebars->29->1701" + "default.handlebars->29->1694" ] }, { @@ -6430,8 +6429,8 @@ "zh-chs": "CSV格式", "zh-cht": "CSV格式", "xloc": [ - "default.handlebars->29->1705", - "default.handlebars->29->1770", + "default.handlebars->29->1698", + "default.handlebars->29->1763", "default.handlebars->29->496" ] }, @@ -6452,7 +6451,7 @@ "zh-chs": "呼叫错误", "zh-cht": "呼叫錯誤", "xloc": [ - "default.handlebars->29->2133" + "default.handlebars->29->2126" ] }, { @@ -6629,7 +6628,7 @@ "zh-chs": "更改{0}的电邮", "zh-cht": "更改{0}的電郵", "xloc": [ - "default.handlebars->29->1976" + "default.handlebars->29->1969" ] }, { @@ -6673,7 +6672,7 @@ "xloc": [ "default-mobile.handlebars->9->90", "default.handlebars->29->1277", - "default.handlebars->29->1963" + "default.handlebars->29->1956" ] }, { @@ -6693,7 +6692,7 @@ "zh-chs": "更改{0}的密码", "zh-cht": "更改{0}的密碼", "xloc": [ - "default.handlebars->29->1983" + "default.handlebars->29->1976" ] }, { @@ -6713,7 +6712,7 @@ "zh-chs": "更改{0}的真实名称", "zh-cht": "更改{0}的真實名稱", "xloc": [ - "default.handlebars->29->1971" + "default.handlebars->29->1964" ] }, { @@ -6795,7 +6794,7 @@ "zh-chs": "更改该用户的密码", "zh-cht": "更改該用戶的密碼", "xloc": [ - "default.handlebars->29->1962" + "default.handlebars->29->1955" ] }, { @@ -6875,7 +6874,7 @@ "zh-chs": "更改帐户凭据", "zh-cht": "帳戶憑證已更改", "xloc": [ - "default.handlebars->29->1663" + "default.handlebars->29->1656" ] }, { @@ -6895,7 +6894,7 @@ "zh-chs": "{1}组中的设备{0}已更改:{2}", "zh-cht": "{1}組中的設備{0}已更改:{2}", "xloc": [ - "default.handlebars->29->1647" + "default.handlebars->29->1640" ] }, { @@ -6915,7 +6914,7 @@ "zh-chs": "语言从{1}更改为{2}", "zh-cht": "語言從{1}更改為{2}", "xloc": [ - "default.handlebars->29->1591" + "default.handlebars->29->1584" ] }, { @@ -6935,8 +6934,8 @@ "zh-chs": "已更改{0}的用户设备权限", "zh-cht": "已更改{0}的用戶設備權限", "xloc": [ - "default.handlebars->29->1649", - "default.handlebars->29->1670" + "default.handlebars->29->1642", + "default.handlebars->29->1663" ] }, { @@ -6976,7 +6975,7 @@ "zh-chs": "聊天", "zh-cht": "聊天", "xloc": [ - "default.handlebars->29->1722", + "default.handlebars->29->1715", "default.handlebars->29->693", "default.handlebars->29->714" ] @@ -7000,8 +6999,8 @@ "xloc": [ "default-mobile.handlebars->9->433", "default-mobile.handlebars->9->453", - "default.handlebars->29->1461", - "default.handlebars->29->1497" + "default.handlebars->29->1454", + "default.handlebars->29->1490" ] }, { @@ -7142,7 +7141,7 @@ "zh-cht": "檢查...", "xloc": [ "default.handlebars->29->1049", - "default.handlebars->29->2127" + "default.handlebars->29->2120" ] }, { @@ -7369,7 +7368,7 @@ "default-mobile.handlebars->9->324", "default-mobile.handlebars->9->326", "default-mobile.handlebars->9->59", - "default.handlebars->29->1585", + "default.handlebars->29->1578", "default.handlebars->29->914", "default.handlebars->29->916", "default.handlebars->29->918", @@ -7416,7 +7415,7 @@ "zh-chs": "全部清除", "zh-cht": "全部清除", "xloc": [ - "default.handlebars->29->2054" + "default.handlebars->29->2047" ] }, { @@ -7484,7 +7483,7 @@ "zh-chs": "清除此通知", "zh-cht": "清除此通知", "xloc": [ - "default.handlebars->29->2053" + "default.handlebars->29->2046" ] }, { @@ -7545,7 +7544,7 @@ "zh-cht": "單擊此處編輯裝置群名稱", "xloc": [ "default.handlebars->29->1321", - "default.handlebars->29->1536" + "default.handlebars->29->1529" ] }, { @@ -7585,7 +7584,7 @@ "zh-chs": "单击此处编辑用户组名称", "zh-cht": "單擊此處編輯用戶群名稱", "xloc": [ - "default.handlebars->29->1839" + "default.handlebars->29->1832" ] }, { @@ -7727,8 +7726,7 @@ "zh-chs": "客户端启动的远程访问", "zh-cht": "客戶端啟動的遠程訪問", "xloc": [ - "default.handlebars->29->1396", - "default.handlebars->29->1401" + "default.handlebars->29->1393" ] }, { @@ -7812,7 +7810,7 @@ "zh-chs": "封闭式桌面多路复用会话,{0}秒", "zh-cht": "封閉式桌面多路復用會話,{0}秒", "xloc": [ - "default.handlebars->29->1596" + "default.handlebars->29->1589" ] }, { @@ -7895,7 +7893,7 @@ "nl": "Opdrachten", "xloc": [ "default-mobile.handlebars->9->455", - "default.handlebars->29->1499", + "default.handlebars->29->1492", "default.handlebars->29->695", "default.handlebars->29->716" ] @@ -7917,8 +7915,8 @@ "zh-chs": "通用设备组", "zh-cht": "通用裝置群", "xloc": [ - "default.handlebars->29->1870", - "default.handlebars->29->1988" + "default.handlebars->29->1863", + "default.handlebars->29->1981" ] }, { @@ -7938,8 +7936,8 @@ "zh-chs": "通用设备", "zh-cht": "通用裝置", "xloc": [ - "default.handlebars->29->1876", - "default.handlebars->29->2000" + "default.handlebars->29->1869", + "default.handlebars->29->1993" ] }, { @@ -7980,7 +7978,7 @@ "zh-cht": "將{1}入口{2}中的{0}限製到此位置?", "xloc": [ "default-mobile.handlebars->9->127", - "default.handlebars->29->1580" + "default.handlebars->29->1573" ] }, { @@ -8002,11 +8000,11 @@ "xloc": [ "default-mobile.handlebars->9->282", "default-mobile.handlebars->9->413", - "default.handlebars->29->1410", - "default.handlebars->29->1750", - "default.handlebars->29->1829", - "default.handlebars->29->1890", - "default.handlebars->29->1986", + "default.handlebars->29->1403", + "default.handlebars->29->1743", + "default.handlebars->29->1822", + "default.handlebars->29->1883", + "default.handlebars->29->1979", "default.handlebars->29->472", "default.handlebars->29->784", "default.handlebars->29->793" @@ -8090,7 +8088,7 @@ "zh-chs": "确认删除选定的帐户?", "zh-cht": "確認刪除所選帳戶?", "xloc": [ - "default.handlebars->29->1749" + "default.handlebars->29->1742" ] }, { @@ -8130,7 +8128,7 @@ "zh-chs": "确认删除选定的用户组?", "zh-cht": "確認刪除所選用戶群?", "xloc": [ - "default.handlebars->29->1828" + "default.handlebars->29->1821" ] }, { @@ -8150,7 +8148,7 @@ "zh-chs": "确认删除用户{0}?", "zh-cht": "確認刪除用戶{0}?", "xloc": [ - "default.handlebars->29->1985" + "default.handlebars->29->1978" ] }, { @@ -8170,7 +8168,7 @@ "zh-chs": "确认删除用户“ {0} ”的成员身份?", "zh-cht": "確認刪除用戶“ {0} ”的成員身份?", "xloc": [ - "default.handlebars->29->1893" + "default.handlebars->29->1886" ] }, { @@ -8190,7 +8188,7 @@ "zh-chs": "确认删除用户组“ {0} ”的成员身份?", "zh-cht": "確認刪除用戶群“ {0} ”的成員身份?", "xloc": [ - "default.handlebars->29->2015" + "default.handlebars->29->2008" ] }, { @@ -8271,7 +8269,7 @@ "zh-chs": "确认覆盖?", "zh-cht": "確認覆蓋?", "xloc": [ - "default.handlebars->29->1579" + "default.handlebars->29->1572" ] }, { @@ -8291,8 +8289,8 @@ "zh-chs": "确认删除设备“ {0} ”的访问权限?", "zh-cht": "確認刪除裝置“ {0} ”的訪問權限?", "xloc": [ - "default.handlebars->29->1883", - "default.handlebars->29->2006" + "default.handlebars->29->1876", + "default.handlebars->29->1999" ] }, { @@ -8312,8 +8310,8 @@ "zh-chs": "确认删除设备组“ {0} ”的访问权限?", "zh-cht": "確認刪除裝置群“ {0} ”的訪問權限?", "xloc": [ - "default.handlebars->29->1885", - "default.handlebars->29->2019" + "default.handlebars->29->1878", + "default.handlebars->29->2012" ] }, { @@ -8333,7 +8331,7 @@ "zh-chs": "确认删除用户“ {0} ”的访问权限?", "zh-cht": "確認刪除用戶“ {0} ”的訪問權限?", "xloc": [ - "default.handlebars->29->2008" + "default.handlebars->29->2001" ] }, { @@ -8353,7 +8351,7 @@ "zh-chs": "确认删除用户组“ {0} ”的访问权限?", "zh-cht": "確認刪除用戶群“ {0} ”的訪問權限?", "xloc": [ - "default.handlebars->29->2011" + "default.handlebars->29->2004" ] }, { @@ -8373,8 +8371,8 @@ "zh-chs": "确认删除访问权限?", "zh-cht": "確認刪除訪問權限?", "xloc": [ - "default.handlebars->29->2009", - "default.handlebars->29->2012" + "default.handlebars->29->2002", + "default.handlebars->29->2005" ] }, { @@ -8466,7 +8464,7 @@ "zh-chs": "确认删除用户“ {0} ”的权限?", "zh-cht": "確認刪除用戶“ {0} ”的權限?", "xloc": [ - "default.handlebars->29->1508" + "default.handlebars->29->1501" ] }, { @@ -8486,7 +8484,7 @@ "zh-chs": "确认删除用户组“ {0} ”的权限?", "zh-cht": "確認刪除用戶群“ {0} ”的權限?", "xloc": [ - "default.handlebars->29->1510" + "default.handlebars->29->1503" ] }, { @@ -8594,8 +8592,7 @@ "zh-chs": "连接到服务器", "zh-cht": "連接到伺服器", "xloc": [ - "default.handlebars->29->1400", - "default.handlebars->29->1404" + "default.handlebars->29->1397" ] }, { @@ -8716,7 +8713,7 @@ "zh-chs": "已连接的英特尔®AMT", "zh-cht": "已連接的Intel® AMT", "xloc": [ - "default.handlebars->29->2072" + "default.handlebars->29->2065" ] }, { @@ -8736,7 +8733,7 @@ "zh-chs": "已连接的用户", "zh-cht": "已连接的用户", "xloc": [ - "default.handlebars->29->2077" + "default.handlebars->29->2070" ] }, { @@ -8826,7 +8823,7 @@ "zh-chs": "连接数量", "zh-cht": "連接數量", "xloc": [ - "default.handlebars->29->2091" + "default.handlebars->29->2084" ] }, { @@ -8846,7 +8843,7 @@ "zh-chs": "连接转发器", "zh-cht": "連接轉發器", "xloc": [ - "default.handlebars->29->2119" + "default.handlebars->29->2112" ] }, { @@ -8907,7 +8904,7 @@ "zh-cht": "連接性", "xloc": [ "default-mobile.handlebars->9->249", - "default.handlebars->29->1543", + "default.handlebars->29->1536", "default.handlebars->29->234", "default.handlebars->29->625", "default.handlebars->container->column_l->p21->3->1->meshConnChartDiv->1" @@ -8996,7 +8993,7 @@ "zh-chs": "Cookie编码器", "zh-cht": "Cookie編碼器", "xloc": [ - "default.handlebars->29->2105" + "default.handlebars->29->2098" ] }, { @@ -9153,8 +9150,8 @@ "zh-chs": "复制连结到剪贴板", "zh-cht": "複製連結到剪貼板", "xloc": [ - "default.handlebars->29->1548", - "default.handlebars->29->1567", + "default.handlebars->29->1541", + "default.handlebars->29->1560", "default.handlebars->29->195", "default.handlebars->29->374" ] @@ -9257,7 +9254,7 @@ "zh-chs": "复制:“{0}”到“{1}”", "zh-cht": "複製:“{0}”到“{1}”", "xloc": [ - "default.handlebars->29->1639" + "default.handlebars->29->1632" ] }, { @@ -9403,7 +9400,7 @@ "zh-chs": "核心服务器", "zh-cht": "核心伺服器", "xloc": [ - "default.handlebars->29->2104" + "default.handlebars->29->2097" ] }, { @@ -9443,7 +9440,7 @@ "zh-chs": "创建帐号", "zh-cht": "創建帳號", "xloc": [ - "default.handlebars->29->1797", + "default.handlebars->29->1790", "login-mobile.handlebars->container->page_content->column_l->1->1->0->1->createpanel->1->1->9->1->12->1->1", "login.handlebars->container->column_l->centralTable->1->0->logincell->createpanel->1->9->1->12->1->1", "login2.handlebars->centralTable->1->0->logincell->createpanel->1->9->1->12->1->1" @@ -9486,7 +9483,7 @@ "zh-chs": "创建用户组", "zh-cht": "創建用戶群", "xloc": [ - "default.handlebars->29->1836" + "default.handlebars->29->1829" ] }, { @@ -9566,7 +9563,7 @@ "zh-chs": "创建文件夹:“{0}”", "zh-cht": "創建文件夾:“{0}”", "xloc": [ - "default.handlebars->29->1632" + "default.handlebars->29->1625" ] }, { @@ -9586,7 +9583,7 @@ "zh-chs": "通过导入以下格式的JSON档案一次创建多个帐户:", "zh-cht": "通過導入以下格式的JSON檔案一次創建多個帳戶:", "xloc": [ - "default.handlebars->29->1761" + "default.handlebars->29->1754" ] }, { @@ -9628,7 +9625,7 @@ "zh-chs": "创建的设备组:{0}", "zh-cht": "創建的設備組:{0}", "xloc": [ - "default.handlebars->29->1643" + "default.handlebars->29->1636" ] }, { @@ -9668,7 +9665,7 @@ "zh-chs": "创建", "zh-cht": "創建", "xloc": [ - "default.handlebars->29->1922" + "default.handlebars->29->1915" ] }, { @@ -10080,7 +10077,7 @@ "zh-chs": "停用客户端控制模式(CCM)", "zh-cht": "停用客戶端控制模式(CCM)", "xloc": [ - "default.handlebars->29->1388" + "default.handlebars->29->1385" ] }, { @@ -10121,10 +10118,10 @@ "zh-chs": "默认", "zh-cht": "默認", "xloc": [ - "default.handlebars->29->1784", - "default.handlebars->29->1832", - "default.handlebars->29->1843", - "default.handlebars->29->1911" + "default.handlebars->29->1777", + "default.handlebars->29->1825", + "default.handlebars->29->1836", + "default.handlebars->29->1904" ] }, { @@ -10148,7 +10145,7 @@ "default-mobile.handlebars->9->307", "default-mobile.handlebars->container->page_content->column_l->p10->p10files->p13toolbar->1->2->1->1", "default-mobile.handlebars->container->page_content->column_l->p5->p5myfiles->p5toolbar->1->0->1->1", - "default.handlebars->29->1574", + "default.handlebars->29->1567", "default.handlebars->29->529", "default.handlebars->29->900", "default.handlebars->container->column_l->p13->p13toolbar->1->2->1->3", @@ -10198,7 +10195,7 @@ "zh-chs": "删除帐户", "zh-cht": "刪除帳戶", "xloc": [ - "default.handlebars->29->1751" + "default.handlebars->29->1744" ] }, { @@ -10242,7 +10239,7 @@ "default-mobile.handlebars->9->411", "default-mobile.handlebars->9->414", "default.handlebars->29->1381", - "default.handlebars->29->1411" + "default.handlebars->29->1404" ] }, { @@ -10303,7 +10300,7 @@ "zh-chs": "删除用户", "zh-cht": "刪除用戶", "xloc": [ - "default.handlebars->29->1961" + "default.handlebars->29->1954" ] }, { @@ -10323,8 +10320,8 @@ "zh-chs": "删除用户群组", "zh-cht": "刪除用戶群組", "xloc": [ - "default.handlebars->29->1881", - "default.handlebars->29->1891" + "default.handlebars->29->1874", + "default.handlebars->29->1884" ] }, { @@ -10344,7 +10341,7 @@ "zh-chs": "删除用户群组", "zh-cht": "刪除用戶群組", "xloc": [ - "default.handlebars->29->1830" + "default.handlebars->29->1823" ] }, { @@ -10364,7 +10361,7 @@ "zh-chs": "删除用户{0}", "zh-cht": "刪除用戶{0}", "xloc": [ - "default.handlebars->29->1984" + "default.handlebars->29->1977" ] }, { @@ -10385,7 +10382,7 @@ "zh-cht": "刪除帳戶", "xloc": [ "default-mobile.handlebars->container->page_content->column_l->p3->p3info->1->p3AccountActions->p2AccountActions->3->9->0", - "default.handlebars->29->1747", + "default.handlebars->29->1740", "default.handlebars->container->column_l->p2->p2info->p2AccountActions->3->p2AccountPassActions->7" ] }, @@ -10426,7 +10423,7 @@ "zh-chs": "删除群组", "zh-cht": "刪除群組", "xloc": [ - "default.handlebars->29->1826" + "default.handlebars->29->1819" ] }, { @@ -10466,7 +10463,7 @@ "zh-chs": "递归删除:“{0}”,{1}个元素已删除", "zh-cht": "遞歸刪除:“{0}”,{1}個元素已刪除", "xloc": [ - "default.handlebars->29->1634" + "default.handlebars->29->1627" ] }, { @@ -10488,7 +10485,7 @@ "xloc": [ "default-mobile.handlebars->9->124", "default-mobile.handlebars->9->309", - "default.handlebars->29->1576", + "default.handlebars->29->1569", "default.handlebars->29->902" ] }, @@ -10509,7 +10506,7 @@ "zh-chs": "删除用户群组{0}?", "zh-cht": "刪除用戶群組{0}?", "xloc": [ - "default.handlebars->29->1889" + "default.handlebars->29->1882" ] }, { @@ -10531,7 +10528,7 @@ "xloc": [ "default-mobile.handlebars->9->123", "default-mobile.handlebars->9->308", - "default.handlebars->29->1575", + "default.handlebars->29->1568", "default.handlebars->29->901" ] }, @@ -10572,7 +10569,7 @@ "zh-chs": "删除:“{0}”", "zh-cht": "刪除:“{0}”", "xloc": [ - "default.handlebars->29->1633" + "default.handlebars->29->1626" ] }, { @@ -10592,7 +10589,7 @@ "zh-chs": "删除:“{0}”,{1}个元素已删除", "zh-cht": "刪除:“{0}”,已刪除{1}個元素", "xloc": [ - "default.handlebars->29->1635" + "default.handlebars->29->1628" ] }, { @@ -10711,11 +10708,11 @@ "default-mobile.handlebars->9->416", "default.handlebars->29->1289", "default.handlebars->29->1326", - "default.handlebars->29->1413", - "default.handlebars->29->1835", - "default.handlebars->29->1845", - "default.handlebars->29->1846", - "default.handlebars->29->1887", + "default.handlebars->29->1406", + "default.handlebars->29->1828", + "default.handlebars->29->1838", + "default.handlebars->29->1839", + "default.handlebars->29->1880", "default.handlebars->29->570", "default.handlebars->29->571", "default.handlebars->29->77", @@ -10760,8 +10757,8 @@ "zh-cht": "桌面", "xloc": [ "default-mobile.handlebars->9->261", - "default.handlebars->29->1419", - "default.handlebars->29->2031", + "default.handlebars->29->1412", + "default.handlebars->29->2024", "default.handlebars->29->535", "default.handlebars->29->872", "default.handlebars->container->topbar->1->1->MainSubMenuSpan->MainSubMenu->1->0->MainDevDesktop", @@ -10807,8 +10804,8 @@ "zh-cht": "桌面通知", "xloc": [ "default.handlebars->29->1340", - "default.handlebars->29->1850", - "default.handlebars->29->1937", + "default.handlebars->29->1843", + "default.handlebars->29->1930", "default.handlebars->29->606" ] }, @@ -10830,8 +10827,8 @@ "zh-cht": "桌面提示", "xloc": [ "default.handlebars->29->1339", - "default.handlebars->29->1849", - "default.handlebars->29->1936", + "default.handlebars->29->1842", + "default.handlebars->29->1929", "default.handlebars->29->605" ] }, @@ -10853,8 +10850,8 @@ "zh-cht": "桌面提示+工具欄", "xloc": [ "default.handlebars->29->1337", - "default.handlebars->29->1847", - "default.handlebars->29->1934", + "default.handlebars->29->1840", + "default.handlebars->29->1927", "default.handlebars->29->603" ] }, @@ -10897,8 +10894,8 @@ "zh-cht": "桌面工具欄", "xloc": [ "default.handlebars->29->1338", - "default.handlebars->29->1848", - "default.handlebars->29->1935", + "default.handlebars->29->1841", + "default.handlebars->29->1928", "default.handlebars->29->604" ] }, @@ -11002,9 +10999,9 @@ "zh-chs": "设备", "zh-cht": "裝置", "xloc": [ - "default.handlebars->29->1442", + "default.handlebars->29->1435", "default.handlebars->29->186", - "default.handlebars->29->2003", + "default.handlebars->29->1996", "default.handlebars->container->column_l->p1->devListToolbarSpan->1->0->9->devListToolbarSort->sortselect->5" ] }, @@ -11053,14 +11050,14 @@ "zh-chs": "设备组", "zh-cht": "裝置群", "xloc": [ - "default.handlebars->29->1437", - "default.handlebars->29->1440", - "default.handlebars->29->1441", - "default.handlebars->29->1698", - "default.handlebars->29->1873", - "default.handlebars->29->1879", - "default.handlebars->29->1991", - "default.handlebars->29->2040" + "default.handlebars->29->1430", + "default.handlebars->29->1433", + "default.handlebars->29->1434", + "default.handlebars->29->1691", + "default.handlebars->29->1866", + "default.handlebars->29->1872", + "default.handlebars->29->1984", + "default.handlebars->29->2033" ] }, { @@ -11081,7 +11078,7 @@ "zh-cht": "裝置群用戶", "xloc": [ "default-mobile.handlebars->9->462", - "default.handlebars->29->1506" + "default.handlebars->29->1499" ] }, { @@ -11102,11 +11099,11 @@ "zh-cht": "裝置群", "xloc": [ "default-mobile.handlebars->container->page_content->column_l->p3->p3info->1->3", - "default.handlebars->29->1714", - "default.handlebars->29->1820", - "default.handlebars->29->1860", - "default.handlebars->29->1931", - "default.handlebars->29->2075", + "default.handlebars->29->1707", + "default.handlebars->29->1813", + "default.handlebars->29->1853", + "default.handlebars->29->1924", + "default.handlebars->29->2068", "default.handlebars->container->column_l->p2->p2info->7" ] }, @@ -11188,7 +11185,7 @@ "zh-cht": "裝置名稱", "xloc": [ "default-mobile.handlebars->9->284", - "default.handlebars->29->2039", + "default.handlebars->29->2032", "default.handlebars->29->291", "default.handlebars->29->825", "player.handlebars->3->9" @@ -11253,7 +11250,7 @@ "zh-cht": "裝置連接。", "xloc": [ "default.handlebars->29->1257", - "default.handlebars->29->1527" + "default.handlebars->29->1520" ] }, { @@ -11274,7 +11271,7 @@ "zh-cht": "裝置斷開連接。", "xloc": [ "default.handlebars->29->1258", - "default.handlebars->29->1528" + "default.handlebars->29->1521" ] }, { @@ -11294,7 +11291,7 @@ "zh-chs": "创建的设备组:{0}", "zh-cht": "設備組已創建:{0}", "xloc": [ - "default.handlebars->29->1664" + "default.handlebars->29->1657" ] }, { @@ -11314,7 +11311,7 @@ "zh-chs": "设备组已删除:{0}", "zh-cht": "設備組已刪除:{0}", "xloc": [ - "default.handlebars->29->1665" + "default.handlebars->29->1658" ] }, { @@ -11334,7 +11331,7 @@ "zh-chs": "设备组成员身份已更改:{0}", "zh-cht": "設備組成員身份已更改:{0}", "xloc": [ - "default.handlebars->29->1666" + "default.handlebars->29->1659" ] }, { @@ -11374,7 +11371,7 @@ "zh-chs": "设备组通知已更改", "zh-cht": "設備組通知已更改", "xloc": [ - "default.handlebars->29->1661" + "default.handlebars->29->1654" ] }, { @@ -11394,7 +11391,7 @@ "zh-chs": "未删除的设备组:{0}", "zh-cht": "未刪除的設備組:{0}", "xloc": [ - "default.handlebars->29->1644" + "default.handlebars->29->1637" ] }, { @@ -11787,7 +11784,7 @@ "zh-chs": "设备请求激活Intel(R)AMT ACM,FQDN:{0}", "zh-cht": "設備請求激活Intel(R)AMT ACM,FQDN:{0}", "xloc": [ - "default.handlebars->29->1646" + "default.handlebars->29->1639" ] }, { @@ -11824,8 +11821,8 @@ "zh-chs": "设备", "zh-cht": "裝置", "xloc": [ - "default.handlebars->29->1821", - "default.handlebars->29->1861" + "default.handlebars->29->1814", + "default.handlebars->29->1854" ] }, { @@ -11865,7 +11862,7 @@ "zh-chs": "禁用的电子邮件两因素身份验证", "zh-cht": "禁用的電子郵件兩因素身份驗證", "xloc": [ - "default.handlebars->29->1677" + "default.handlebars->29->1670" ] }, { @@ -12065,7 +12062,7 @@ "zh-chs": "显示公共连结", "zh-cht": "顯示公共鏈結", "xloc": [ - "default.handlebars->29->1547" + "default.handlebars->29->1540" ] }, { @@ -12085,7 +12082,7 @@ "zh-chs": "显示消息框,标题= “{0}”,消息= “{1}”", "zh-cht": "顯示消息框,標題= “{0}”,消息= “{1}”", "xloc": [ - "default.handlebars->29->1606" + "default.handlebars->29->1599" ] }, { @@ -12105,7 +12102,7 @@ "zh-chs": "显示吐司消息,标题= “{0}”,消息= “{1}”", "zh-cht": "顯示吐司消息,標題= “{0}”,消息= “{1}”", "xloc": [ - "default.handlebars->29->1614" + "default.handlebars->29->1607" ] }, { @@ -12125,7 +12122,7 @@ "zh-chs": "什么都不做", "zh-cht": "什麼都不做", "xloc": [ - "default.handlebars->29->1394" + "default.handlebars->29->1391" ] }, { @@ -12145,10 +12142,10 @@ "zh-chs": "域", "zh-cht": "域", "xloc": [ - "default.handlebars->29->1785", - "default.handlebars->29->1833", - "default.handlebars->29->1842", - "default.handlebars->29->1910", + "default.handlebars->29->1778", + "default.handlebars->29->1826", + "default.handlebars->29->1835", + "default.handlebars->29->1903", "mstsc.handlebars->main->1->3->1->2->1->0", "mstsc.handlebars->main->1->3->1->2->3" ] @@ -12170,8 +12167,7 @@ "zh-chs": "不要配置", "zh-cht": "不要配置", "xloc": [ - "default.handlebars->29->1398", - "default.handlebars->29->1403" + "default.handlebars->29->1395" ] }, { @@ -12191,7 +12187,7 @@ "zh-chs": "不要连接到服务器", "zh-cht": "不要連接到伺服器", "xloc": [ - "default.handlebars->29->1399" + "default.handlebars->29->1396" ] }, { @@ -12411,7 +12407,7 @@ "zh-chs": "下载报告", "zh-cht": "下載報告", "xloc": [ - "default.handlebars->29->1703", + "default.handlebars->29->1696", "default.handlebars->container->column_l->p3->3->1->0->3" ] }, @@ -12612,7 +12608,7 @@ "zh-chs": "使用以下一种档案格式下载事件列表。", "zh-cht": "使用以下一種檔案格式下載事件列表。", "xloc": [ - "default.handlebars->29->1704" + "default.handlebars->29->1697" ] }, { @@ -12632,7 +12628,7 @@ "zh-chs": "使用以下一种档案格式下载用户列表。", "zh-cht": "使用以下一種檔案格式下載用戶列表。", "xloc": [ - "default.handlebars->29->1769" + "default.handlebars->29->1762" ] }, { @@ -12713,7 +12709,7 @@ "zh-chs": "下载:“{0}”", "zh-cht": "下載:“{0}”", "xloc": [ - "default.handlebars->29->1637" + "default.handlebars->29->1630" ] }, { @@ -12753,7 +12749,7 @@ "zh-chs": "代理重复", "zh-cht": "代理重複", "xloc": [ - "default.handlebars->29->2071" + "default.handlebars->29->2064" ] }, { @@ -12793,7 +12789,7 @@ "zh-chs": "复制用户组", "zh-cht": "複製用戶群", "xloc": [ - "default.handlebars->29->1837" + "default.handlebars->29->1830" ] }, { @@ -12830,8 +12826,8 @@ "zh-chs": "持续时间", "zh-cht": "持續時間", "xloc": [ - "default.handlebars->29->2025", - "default.handlebars->29->2045", + "default.handlebars->29->2018", + "default.handlebars->29->2038", "player.handlebars->3->2" ] }, @@ -12852,7 +12848,7 @@ "zh-chs": "在激活期间,代理将有权取得管理员密码信息。", "zh-cht": "在啟動期間,代理將有權取得管理員密碼訊息。", "xloc": [ - "default.handlebars->29->1408" + "default.handlebars->29->1401" ] }, { @@ -13185,10 +13181,10 @@ "default-mobile.handlebars->9->417", "default-mobile.handlebars->9->419", "default-mobile.handlebars->9->439", - "default.handlebars->29->1414", - "default.handlebars->29->1446", - "default.handlebars->29->1470", - "default.handlebars->29->1482" + "default.handlebars->29->1407", + "default.handlebars->29->1439", + "default.handlebars->29->1463", + "default.handlebars->29->1475" ] }, { @@ -13208,7 +13204,7 @@ "zh-chs": "编辑设备组功能", "zh-cht": "編輯裝置群功能", "xloc": [ - "default.handlebars->29->1432" + "default.handlebars->29->1425" ] }, { @@ -13228,8 +13224,8 @@ "zh-chs": "编辑设备组权限", "zh-cht": "編輯裝置群權限", "xloc": [ - "default.handlebars->29->1467", - "default.handlebars->29->1479" + "default.handlebars->29->1460", + "default.handlebars->29->1472" ] }, { @@ -13249,7 +13245,7 @@ "zh-chs": "编辑设备组用户同意", "zh-cht": "編輯裝置群用戶同意", "xloc": [ - "default.handlebars->29->1415" + "default.handlebars->29->1408" ] }, { @@ -13270,7 +13266,7 @@ "zh-cht": "編輯裝置筆記", "xloc": [ "default-mobile.handlebars->9->431", - "default.handlebars->29->1459" + "default.handlebars->29->1452" ] }, { @@ -13290,8 +13286,8 @@ "zh-chs": "编辑设备权限", "zh-cht": "編輯裝置權限", "xloc": [ - "default.handlebars->29->1472", - "default.handlebars->29->1474" + "default.handlebars->29->1465", + "default.handlebars->29->1467" ] }, { @@ -13331,7 +13327,7 @@ "zh-chs": "编辑设备用户同意", "zh-cht": "編輯裝置用戶同意", "xloc": [ - "default.handlebars->29->1417" + "default.handlebars->29->1410" ] }, { @@ -13395,7 +13391,7 @@ "zh-cht": "編輯筆記", "xloc": [ "default-mobile.handlebars->9->446", - "default.handlebars->29->1489" + "default.handlebars->29->1482" ] }, { @@ -13415,7 +13411,7 @@ "zh-chs": "编辑用户同意", "zh-cht": "編輯用戶同意", "xloc": [ - "default.handlebars->29->1416" + "default.handlebars->29->1409" ] }, { @@ -13435,7 +13431,7 @@ "zh-chs": "编辑用户设备组权限", "zh-cht": "編輯用戶裝置群權限", "xloc": [ - "default.handlebars->29->1480" + "default.handlebars->29->1473" ] }, { @@ -13455,7 +13451,7 @@ "zh-chs": "编辑用户设备权限", "zh-cht": "編輯用戶裝置權限", "xloc": [ - "default.handlebars->29->1475" + "default.handlebars->29->1468" ] }, { @@ -13475,7 +13471,7 @@ "zh-chs": "编辑用户组", "zh-cht": "編輯用戶群", "xloc": [ - "default.handlebars->29->1888" + "default.handlebars->29->1881" ] }, { @@ -13495,14 +13491,14 @@ "zh-chs": "编辑用户组设备权限", "zh-cht": "編輯用戶群裝置權限", "xloc": [ - "default.handlebars->29->1477" + "default.handlebars->29->1470" ] }, { "en": "Edit User Group User Consent", "nl": "Bewerk groepsgebruikerstoestemming", "xloc": [ - "default.handlebars->29->1418" + "default.handlebars->29->1411" ] }, { @@ -13564,11 +13560,11 @@ "zh-cht": "電郵", "xloc": [ "default-mobile.handlebars->9->78", - "default.handlebars->29->1787", - "default.handlebars->29->1914", - "default.handlebars->29->1916", - "default.handlebars->29->1956", - "default.handlebars->29->1972", + "default.handlebars->29->1780", + "default.handlebars->29->1907", + "default.handlebars->29->1909", + "default.handlebars->29->1949", + "default.handlebars->29->1965", "default.handlebars->29->341", "login-mobile.handlebars->5->42", "login-mobile.handlebars->container->page_content->column_l->1->1->0->1->tokenpanel->1->7->1->4->1->3", @@ -13722,7 +13718,7 @@ "zh-chs": "电邮未验证", "zh-cht": "電郵未驗證", "xloc": [ - "default.handlebars->29->1733" + "default.handlebars->29->1726" ] }, { @@ -13742,8 +13738,8 @@ "zh-chs": "电邮已验证", "zh-cht": "電郵已驗證", "xloc": [ - "default.handlebars->29->1734", - "default.handlebars->29->1908" + "default.handlebars->29->1727", + "default.handlebars->29->1901" ] }, { @@ -13763,7 +13759,7 @@ "zh-chs": "电邮已验证。", "zh-cht": "電郵已驗證。", "xloc": [ - "default.handlebars->29->1793" + "default.handlebars->29->1786" ] }, { @@ -13783,7 +13779,7 @@ "zh-chs": "电邮未验证", "zh-cht": "電郵未驗證", "xloc": [ - "default.handlebars->29->1909" + "default.handlebars->29->1902" ] }, { @@ -13847,7 +13843,7 @@ "zh-chs": "已通过电邮验证,并且需要重置密码。", "zh-cht": "已通過電郵驗證,並且需要重置密碼。", "xloc": [ - "default.handlebars->29->1794" + "default.handlebars->29->1787" ] }, { @@ -13867,7 +13863,7 @@ "zh-chs": "电邮/短信流量", "zh-cht": "電郵/短信流量", "xloc": [ - "default.handlebars->29->2113" + "default.handlebars->29->2106" ] }, { @@ -13913,7 +13909,7 @@ "zh-chs": "启用邀请代码", "zh-cht": "啟用邀請代碼", "xloc": [ - "default.handlebars->29->1512" + "default.handlebars->29->1505" ] }, { @@ -13994,7 +13990,7 @@ "zh-chs": "已启用", "zh-cht": "已啟用", "xloc": [ - "default.handlebars->29->2047" + "default.handlebars->29->2040" ] }, { @@ -14014,7 +14010,7 @@ "zh-chs": "启用电子邮件两因素身份验证", "zh-cht": "啟用電子郵件兩因素身份驗證", "xloc": [ - "default.handlebars->29->1676" + "default.handlebars->29->1669" ] }, { @@ -14054,7 +14050,7 @@ "zh-chs": "时间结束", "zh-cht": "時間結束", "xloc": [ - "default.handlebars->29->2044" + "default.handlebars->29->2037" ] }, { @@ -14074,7 +14070,7 @@ "zh-chs": "从{1}到{2},{3}秒结束了桌面会话“{0}”", "zh-cht": "從{1}到{2},{3}秒結束了桌面會話“{0}”", "xloc": [ - "default.handlebars->29->1599" + "default.handlebars->29->1592" ] }, { @@ -14094,7 +14090,7 @@ "zh-chs": "从{1}到{2},{3}秒结束了文件管理会话“{0}”", "zh-cht": "從{1}到{2},{3}秒結束了文件管理會話“{0}”", "xloc": [ - "default.handlebars->29->1600" + "default.handlebars->29->1593" ] }, { @@ -14114,7 +14110,7 @@ "zh-chs": "从{1}到{2},{3}秒结束了中继会话“{0}”", "zh-cht": "從{1}到{2},{3}秒結束了中繼會話“{0}”", "xloc": [ - "default.handlebars->29->1597" + "default.handlebars->29->1590" ] }, { @@ -14134,7 +14130,7 @@ "zh-chs": "从{1}到{2},{3}秒结束了终端会话“{0}”", "zh-cht": "從{1}到{2},{3}秒結束了終端會話“{0}”", "xloc": [ - "default.handlebars->29->1598" + "default.handlebars->29->1591" ] }, { @@ -14435,7 +14431,7 @@ "zh-chs": "输入管理领域名称的逗号分隔列表。", "zh-cht": "輸入管理領域名稱的逗號分隔列表。", "xloc": [ - "default.handlebars->29->1798" + "default.handlebars->29->1791" ] }, { @@ -14658,7 +14654,7 @@ "zh-chs": "事件列表输出", "zh-cht": "事件列表輸出", "xloc": [ - "default.handlebars->29->1709" + "default.handlebars->29->1702" ] }, { @@ -14850,7 +14846,7 @@ "zh-chs": "外部", "zh-cht": "外部", "xloc": [ - "default.handlebars->29->2098" + "default.handlebars->29->2091" ] }, { @@ -14950,7 +14946,7 @@ "zh-chs": "本地用户拒绝后无法启动远程桌面", "zh-cht": "本地用戶拒絕後無法啟動遠程桌面", "xloc": [ - "default.handlebars->29->1622" + "default.handlebars->29->1615" ] }, { @@ -14970,7 +14966,7 @@ "zh-chs": "本地用户拒绝后无法启动远程文件", "zh-cht": "本地用戶拒絕後無法啟動遠程文件", "xloc": [ - "default.handlebars->29->1629" + "default.handlebars->29->1622" ] }, { @@ -15198,8 +15194,8 @@ "xloc": [ "default-mobile.handlebars->9->171", "default-mobile.handlebars->9->262", - "default.handlebars->29->1426", - "default.handlebars->29->2032", + "default.handlebars->29->1419", + "default.handlebars->29->2025", "default.handlebars->29->258", "default.handlebars->container->topbar->1->1->MainSubMenuSpan->MainSubMenu->1->0->MainDevFiles", "default.handlebars->contextMenu->cxfiles" @@ -15243,8 +15239,8 @@ "zh-cht": "檔案通知", "xloc": [ "default.handlebars->29->1344", - "default.handlebars->29->1854", - "default.handlebars->29->1941", + "default.handlebars->29->1847", + "default.handlebars->29->1934", "default.handlebars->29->610" ] }, @@ -15266,8 +15262,8 @@ "zh-cht": "檔案提示", "xloc": [ "default.handlebars->29->1343", - "default.handlebars->29->1853", - "default.handlebars->29->1940", + "default.handlebars->29->1846", + "default.handlebars->29->1933", "default.handlebars->29->609" ] }, @@ -15311,7 +15307,7 @@ "zh-chs": "录制会话已完成,{0}秒", "zh-cht": "錄製會話已完成,{0}秒", "xloc": [ - "default.handlebars->29->1595" + "default.handlebars->29->1588" ] }, { @@ -15478,8 +15474,8 @@ "zh-chs": "下次登录时强制重置密码。", "zh-cht": "下次登入時強制重置密碼。", "xloc": [ - "default.handlebars->29->1792", - "default.handlebars->29->1981" + "default.handlebars->29->1785", + "default.handlebars->29->1974" ] }, { @@ -15562,7 +15558,7 @@ "zh-chs": "格式化", "zh-cht": "格式化", "xloc": [ - "default.handlebars->29->1700" + "default.handlebars->29->1693" ] }, { @@ -15603,8 +15599,8 @@ "zh-chs": "自由", "zh-cht": "自由", "xloc": [ - "default.handlebars->29->2056", - "default.handlebars->29->2058" + "default.handlebars->29->2049", + "default.handlebars->29->2051" ] }, { @@ -15830,8 +15826,8 @@ "default-mobile.handlebars->9->418", "default-mobile.handlebars->9->438", "default.handlebars->29->1298", - "default.handlebars->29->1445", - "default.handlebars->29->1804" + "default.handlebars->29->1438", + "default.handlebars->29->1797" ] }, { @@ -15851,7 +15847,7 @@ "zh-chs": "完整管理员(保留所有权利)", "zh-cht": "完整管理員(保留所有權利)", "xloc": [ - "default.handlebars->29->1481" + "default.handlebars->29->1474" ] }, { @@ -15950,7 +15946,7 @@ "zh-chs": "完整管理员", "zh-cht": "完整管理員", "xloc": [ - "default.handlebars->29->1902" + "default.handlebars->29->1895" ] }, { @@ -16338,7 +16334,7 @@ "zh-chs": "正在获取剪贴板内容,{0}个字节", "zh-cht": "正在獲取剪貼板內容,{0}個字節", "xloc": [ - "default.handlebars->29->1609" + "default.handlebars->29->1602" ] }, { @@ -16569,8 +16565,8 @@ "zh-chs": "集体指令", "zh-cht": "集體指令", "xloc": [ - "default.handlebars->29->1748", - "default.handlebars->29->1827", + "default.handlebars->29->1741", + "default.handlebars->29->1820", "default.handlebars->29->470", "default.handlebars->container->column_l->p1->devListToolbarSpan->1->0->devListToolbar", "default.handlebars->container->column_l->p4->3->1->0->3->3", @@ -16594,7 +16590,7 @@ "zh-chs": "通过...分组", "zh-cht": "通過...分群", "xloc": [ - "default.handlebars->29->1696" + "default.handlebars->29->1689" ] }, { @@ -16614,7 +16610,7 @@ "zh-chs": "组标识符", "zh-cht": "群標識符", "xloc": [ - "default.handlebars->29->1844" + "default.handlebars->29->1837" ] }, { @@ -16634,7 +16630,7 @@ "zh-chs": "群组成员", "zh-cht": "群組成員", "xloc": [ - "default.handlebars->29->1865" + "default.handlebars->29->1858" ] }, { @@ -16654,7 +16650,7 @@ "zh-chs": "用户{0}的群组权限。", "zh-cht": "用戶{0}的群組權限。", "xloc": [ - "default.handlebars->29->1444" + "default.handlebars->29->1437" ] }, { @@ -16674,7 +16670,7 @@ "zh-chs": "{0}的群组权限。", "zh-cht": "{0}的群組權限。", "xloc": [ - "default.handlebars->29->1443" + "default.handlebars->29->1436" ] }, { @@ -16865,7 +16861,7 @@ "zh-chs": "堆总数", "zh-cht": "堆總數", "xloc": [ - "default.handlebars->29->2100" + "default.handlebars->29->2093" ] }, { @@ -16885,7 +16881,7 @@ "zh-chs": "堆使用", "zh-cht": "堆使用", "xloc": [ - "default.handlebars->29->2099" + "default.handlebars->29->2092" ] }, { @@ -16940,7 +16936,7 @@ "en": "Help Requested, user: {0}, details: {1}", "nl": "Hulp verzoek van, user: {0}, details: {1}", "xloc": [ - "default.handlebars->29->1686" + "default.handlebars->29->1679" ] }, { @@ -17201,7 +17197,7 @@ "zh-cht": "保存{0}項目{1}給{2}", "xloc": [ "default-mobile.handlebars->9->129", - "default.handlebars->29->1582" + "default.handlebars->29->1575" ] }, { @@ -17918,8 +17914,8 @@ "zh-chs": "安装类型", "zh-cht": "安裝方式", "xloc": [ + "default.handlebars->29->1507", "default.handlebars->29->1514", - "default.handlebars->29->1521", "default.handlebars->29->356", "default.handlebars->29->370", "default.handlebars->29->384" @@ -17963,7 +17959,7 @@ "zh-chs": "英特尔AMT", "zh-cht": "英特爾AMT", "xloc": [ - "default.handlebars->29->2096" + "default.handlebars->29->2089" ] }, { @@ -18008,9 +18004,9 @@ "default-mobile.handlebars->9->246", "default.handlebars->29->1351", "default.handlebars->29->1361", - "default.handlebars->29->1533", - "default.handlebars->29->1541", - "default.handlebars->29->2118", + "default.handlebars->29->1526", + "default.handlebars->29->1534", + "default.handlebars->29->2111", "default.handlebars->29->537", "default.handlebars->29->592", "default.handlebars->29->620" @@ -18159,7 +18155,7 @@ "zh-chs": "英特尔®AMT政策", "zh-cht": "Intel® AMT政策", "xloc": [ - "default.handlebars->29->1390" + "default.handlebars->29->1387" ] }, { @@ -18179,7 +18175,7 @@ "zh-chs": "英特尔®AMT重定向", "zh-cht": "Intel® AMT重定向", "xloc": [ - "default.handlebars->29->2034", + "default.handlebars->29->2027", "player.handlebars->3->14" ] }, @@ -18220,7 +18216,7 @@ "zh-chs": "英特尔®AMT WSMAN", "zh-cht": "Intle® AMT WSMAN", "xloc": [ - "default.handlebars->29->2033", + "default.handlebars->29->2026", "player.handlebars->3->13" ] }, @@ -18285,7 +18281,7 @@ "zh-cht": "Intel® AMT桌面和串行事件。", "xloc": [ "default.handlebars->29->1259", - "default.handlebars->29->1529" + "default.handlebars->29->1522" ] }, { @@ -18757,8 +18753,8 @@ "zh-chs": "仅限互动", "zh-cht": "僅限互動", "xloc": [ + "default.handlebars->29->1510", "default.handlebars->29->1517", - "default.handlebars->29->1524", "default.handlebars->29->359", "default.handlebars->29->373", "default.handlebars->29->387" @@ -18821,7 +18817,7 @@ "zh-chs": "无效的设备组类型", "zh-cht": "無效的裝置群類型", "xloc": [ - "default.handlebars->29->2070" + "default.handlebars->29->2063" ] }, { @@ -18841,7 +18837,7 @@ "zh-chs": "无效的JSON", "zh-cht": "無效的JSON", "xloc": [ - "default.handlebars->29->2064" + "default.handlebars->29->2057" ] }, { @@ -18861,8 +18857,8 @@ "zh-chs": "无效的JSON档案格式。", "zh-cht": "無效的JSON檔案格式。", "xloc": [ - "default.handlebars->29->1766", - "default.handlebars->29->1768" + "default.handlebars->29->1759", + "default.handlebars->29->1761" ] }, { @@ -18882,7 +18878,7 @@ "zh-chs": "无效的JSON档案:{0}。", "zh-cht": "無效的JSON檔案:{0}。", "xloc": [ - "default.handlebars->29->1764" + "default.handlebars->29->1757" ] }, { @@ -18902,7 +18898,7 @@ "zh-chs": "无效的PKCS签名", "zh-cht": "無效的PKCS簽名", "xloc": [ - "default.handlebars->29->2062" + "default.handlebars->29->2055" ] }, { @@ -18922,7 +18918,7 @@ "zh-chs": "無效的RSA密碼", "zh-cht": "無效的RSA密碼", "xloc": [ - "default.handlebars->29->2063" + "default.handlebars->29->2056" ] }, { @@ -19049,7 +19045,7 @@ "zh-chs": "使电邮无效", "zh-cht": "使電郵無效", "xloc": [ - "default.handlebars->29->1742" + "default.handlebars->29->1735" ] }, { @@ -19126,7 +19122,7 @@ "zh-chs": "任何人都可以使用邀请代码通过以下公共连接将设备加入该设备组:", "zh-cht": "任何人都可以使用邀請代碼通過以下公共鏈結將裝置加入該裝置群:", "xloc": [ - "default.handlebars->29->1519" + "default.handlebars->29->1512" ] }, { @@ -19189,10 +19185,10 @@ "zh-cht": "邀請碼", "xloc": [ "default.handlebars->29->1355", + "default.handlebars->29->1506", + "default.handlebars->29->1511", "default.handlebars->29->1513", - "default.handlebars->29->1518", - "default.handlebars->29->1520", - "default.handlebars->29->1525" + "default.handlebars->29->1518" ] }, { @@ -19333,7 +19329,7 @@ "zh-chs": "JSON", "zh-cht": "JSON", "xloc": [ - "default.handlebars->29->1702" + "default.handlebars->29->1695" ] }, { @@ -19353,8 +19349,8 @@ "zh-chs": "JSON格式", "zh-cht": "JSON格式", "xloc": [ - "default.handlebars->29->1707", - "default.handlebars->29->1772", + "default.handlebars->29->1700", + "default.handlebars->29->1765", "default.handlebars->29->498" ] }, @@ -19395,7 +19391,7 @@ "zh-chs": "已加入桌面Multiplex会话", "zh-cht": "已加入桌面Multiplex會話", "xloc": [ - "default.handlebars->29->1592" + "default.handlebars->29->1585" ] }, { @@ -19576,7 +19572,7 @@ "zh-chs": "杀死进程{0}", "zh-cht": "殺死進程{0}", "xloc": [ - "default.handlebars->29->1607" + "default.handlebars->29->1600" ] }, { @@ -19991,7 +19987,7 @@ "zh-chs": "最后访问", "zh-cht": "最後訪問", "xloc": [ - "default.handlebars->29->1715" + "default.handlebars->29->1708" ] }, { @@ -20011,7 +20007,7 @@ "zh-chs": "上次登录", "zh-cht": "上次登入", "xloc": [ - "default.handlebars->29->1923" + "default.handlebars->29->1916" ] }, { @@ -20083,7 +20079,7 @@ "zh-chs": "上次更改:{0}", "zh-cht": "上次更改:{0}", "xloc": [ - "default.handlebars->29->1927" + "default.handlebars->29->1920" ] }, { @@ -20143,7 +20139,7 @@ "zh-chs": "上次登录:{0}", "zh-cht": "上次登入:{0}", "xloc": [ - "default.handlebars->29->1725" + "default.handlebars->29->1718" ] }, { @@ -20344,7 +20340,7 @@ "zh-chs": "如没有请留空。", "zh-cht": "如沒有請留空。", "xloc": [ - "default.handlebars->29->1967" + "default.handlebars->29->1960" ] }, { @@ -20389,7 +20385,7 @@ "zh-chs": "离开桌面多路复用会话", "zh-cht": "離開桌面多路復用會話", "xloc": [ - "default.handlebars->29->1593" + "default.handlebars->29->1586" ] }, { @@ -20409,7 +20405,7 @@ "zh-chs": "减", "zh-cht": "減", "xloc": [ - "default.handlebars->29->2135" + "default.handlebars->29->2128" ] }, { @@ -20472,7 +20468,7 @@ "zh-cht": "有限輸入", "xloc": [ "default-mobile.handlebars->9->451", - "default.handlebars->29->1495", + "default.handlebars->29->1488", "default.handlebars->29->685", "default.handlebars->29->706" ] @@ -20495,7 +20491,7 @@ "zh-cht": "僅有限輸入", "xloc": [ "default-mobile.handlebars->9->424", - "default.handlebars->29->1451" + "default.handlebars->29->1444" ] }, { @@ -20968,7 +20964,7 @@ "default.handlebars->29->1037", "default.handlebars->29->1314", "default.handlebars->29->1318", - "default.handlebars->29->2021", + "default.handlebars->29->2014", "default.handlebars->29->797" ] }, @@ -21051,7 +21047,7 @@ "zh-chs": "本地用户接受的远程终端请求", "zh-cht": "本地用戶接受的遠程終端請求", "xloc": [ - "default.handlebars->29->1615" + "default.handlebars->29->1608" ] }, { @@ -21071,7 +21067,7 @@ "zh-chs": "本地用户拒绝了远程终端请求", "zh-cht": "本地用戶拒絕了遠程終端請求", "xloc": [ - "default.handlebars->29->1616" + "default.handlebars->29->1609" ] }, { @@ -21152,7 +21148,7 @@ "zh-chs": "锁定账户", "zh-cht": "鎖定賬戶", "xloc": [ - "default.handlebars->29->1812" + "default.handlebars->29->1805" ] }, { @@ -21172,7 +21168,7 @@ "zh-chs": "锁定帐户设置", "zh-cht": "鎖定帳戶設置", "xloc": [ - "default.handlebars->29->1815" + "default.handlebars->29->1808" ] }, { @@ -21192,7 +21188,7 @@ "zh-chs": "锁定账户", "zh-cht": "鎖定賬戶", "xloc": [ - "default.handlebars->29->1745" + "default.handlebars->29->1738" ] }, { @@ -21212,7 +21208,7 @@ "zh-chs": "已锁定", "zh-cht": "已鎖定", "xloc": [ - "default.handlebars->29->1726" + "default.handlebars->29->1719" ] }, { @@ -21232,7 +21228,7 @@ "zh-chs": "被锁定账户", "zh-cht": "被鎖定賬戶", "xloc": [ - "default.handlebars->29->1899" + "default.handlebars->29->1892" ] }, { @@ -21252,7 +21248,7 @@ "zh-chs": "将远程用户锁定在桌面之外", "zh-cht": "將遠程用戶鎖定在桌面之外", "xloc": [ - "default.handlebars->29->1641" + "default.handlebars->29->1634" ] }, { @@ -21876,7 +21872,7 @@ "zh-chs": "主服务器信息", "zh-cht": "主伺服器訊息", "xloc": [ - "default.handlebars->29->2107" + "default.handlebars->29->2100" ] }, { @@ -21979,8 +21975,8 @@ "xloc": [ "default-mobile.handlebars->9->421", "default-mobile.handlebars->9->441", - "default.handlebars->29->1448", - "default.handlebars->29->1484" + "default.handlebars->29->1441", + "default.handlebars->29->1477" ] }, { @@ -22002,8 +21998,8 @@ "xloc": [ "default-mobile.handlebars->9->420", "default-mobile.handlebars->9->440", - "default.handlebars->29->1447", - "default.handlebars->29->1483" + "default.handlebars->29->1440", + "default.handlebars->29->1476" ] }, { @@ -22043,7 +22039,7 @@ "zh-chs": "管理录音", "zh-cht": "管理錄音", "xloc": [ - "default.handlebars->29->1810" + "default.handlebars->29->1803" ] }, { @@ -22083,7 +22079,7 @@ "zh-chs": "管理用户组", "zh-cht": "管理用戶群", "xloc": [ - "default.handlebars->29->1809" + "default.handlebars->29->1802" ] }, { @@ -22103,7 +22099,7 @@ "zh-chs": "管理用户", "zh-cht": "管理用戶", "xloc": [ - "default.handlebars->29->1808", + "default.handlebars->29->1801", "default.handlebars->29->700" ] }, @@ -22271,7 +22267,7 @@ "zh-chs": "经理", "zh-cht": "經理", "xloc": [ - "default.handlebars->29->1731" + "default.handlebars->29->1724" ] }, { @@ -22392,7 +22388,7 @@ "zh-chs": "达到连接数量上限", "zh-cht": "達到連接數量上限", "xloc": [ - "default.handlebars->29->2068" + "default.handlebars->29->2061" ] }, { @@ -22457,7 +22453,7 @@ "zh-chs": "Megabyte", "zh-cht": "Megabyte", "xloc": [ - "default.handlebars->29->2097" + "default.handlebars->29->2090" ] }, { @@ -22479,7 +22475,7 @@ "xloc": [ "default-mobile.handlebars->9->392", "default.handlebars->29->1003", - "default.handlebars->29->2088", + "default.handlebars->29->2081", "default.handlebars->container->column_l->p40->3->1->p40type->3" ] }, @@ -22533,7 +22529,7 @@ "zh-cht": "網格代理控制台", "xloc": [ "default-mobile.handlebars->9->428", - "default.handlebars->29->1456" + "default.handlebars->29->1449" ] }, { @@ -22638,7 +22634,7 @@ "zh-chs": "MeshAgent流量", "zh-cht": "MeshAgent流量", "xloc": [ - "default.handlebars->29->2109" + "default.handlebars->29->2102" ] }, { @@ -22658,7 +22654,7 @@ "zh-chs": "MeshAgent更新", "zh-cht": "MeshAgent更新", "xloc": [ - "default.handlebars->29->2110" + "default.handlebars->29->2103" ] }, { @@ -22779,7 +22775,7 @@ "zh-chs": "MeshCentral服务器同级化", "zh-cht": "MeshCentral伺服器同級化", "xloc": [ - "default.handlebars->29->2108" + "default.handlebars->29->2101" ] }, { @@ -23108,7 +23104,7 @@ "zh-chs": "消息调度器", "zh-cht": "電郵調度器", "xloc": [ - "default.handlebars->29->2106" + "default.handlebars->29->2099" ] }, { @@ -23291,7 +23287,7 @@ "zh-chs": "更多", "zh-cht": "更多", "xloc": [ - "default.handlebars->29->2134" + "default.handlebars->29->2127" ] }, { @@ -23372,7 +23368,7 @@ "zh-chs": "移动:“{0}”到“{1}”", "zh-cht": "移動:“{0}”到“{1}”", "xloc": [ - "default.handlebars->29->1640" + "default.handlebars->29->1633" ] }, { @@ -23392,7 +23388,7 @@ "zh-chs": "将设备{0}移动到组{1}", "zh-cht": "將設備{0}移動到組{1}", "xloc": [ - "default.handlebars->29->1673" + "default.handlebars->29->1666" ] }, { @@ -23432,7 +23428,7 @@ "zh-chs": "多路复用器", "zh-cht": "多工器", "xloc": [ - "default.handlebars->29->2046" + "default.handlebars->29->2039" ] }, { @@ -23785,13 +23781,13 @@ "default-mobile.handlebars->container->page_content->column_l->p10->p10desktop->deskarea3->deskarea3x->DeskTools->5->1->1", "default.handlebars->29->1285", "default.handlebars->29->1325", - "default.handlebars->29->1412", - "default.handlebars->29->1713", - "default.handlebars->29->1818", + "default.handlebars->29->1405", + "default.handlebars->29->1706", + "default.handlebars->29->1811", + "default.handlebars->29->1827", "default.handlebars->29->1834", - "default.handlebars->29->1841", - "default.handlebars->29->1886", - "default.handlebars->29->1905", + "default.handlebars->29->1879", + "default.handlebars->29->1898", "default.handlebars->29->560", "default.handlebars->29->76", "default.handlebars->29->856", @@ -23840,7 +23836,7 @@ "zh-chs": "名称1,名称2,名称3", "zh-cht": "名稱1,名稱2,名稱3", "xloc": [ - "default.handlebars->29->1800" + "default.handlebars->29->1793" ] }, { @@ -24000,7 +23996,7 @@ "zh-chs": "生成新的2FA备份代码", "zh-cht": "生成新的2FA備份代碼", "xloc": [ - "default.handlebars->29->1680" + "default.handlebars->29->1673" ] }, { @@ -24065,7 +24061,7 @@ "xloc": [ "default-mobile.handlebars->9->120", "default-mobile.handlebars->9->305", - "default.handlebars->29->1572", + "default.handlebars->29->1565", "default.handlebars->29->898", "default.handlebars->container->column_l->p13->p13toolbar->1->2->1->3", "default.handlebars->container->column_l->p5->p5toolbar->1->0->p5filehead->3" @@ -24233,7 +24229,7 @@ "zh-chs": "没有桌面", "zh-cht": "沒有桌面", "xloc": [ - "default.handlebars->29->1491", + "default.handlebars->29->1484", "default.handlebars->29->686", "default.handlebars->29->707" ] @@ -24255,7 +24251,7 @@ "zh-chs": "不能访问桌面", "zh-cht": "不能訪問桌面", "xloc": [ - "default.handlebars->29->1452" + "default.handlebars->29->1445" ] }, { @@ -24275,8 +24271,8 @@ "zh-chs": "找不到事件", "zh-cht": "找不到事件", "xloc": [ - "default.handlebars->29->1689", - "default.handlebars->29->2020", + "default.handlebars->29->1682", + "default.handlebars->29->2013", "default.handlebars->29->933" ] }, @@ -24298,7 +24294,7 @@ "zh-cht": "不能存取檔案", "xloc": [ "default-mobile.handlebars->9->426", - "default.handlebars->29->1454" + "default.handlebars->29->1447" ] }, { @@ -24319,7 +24315,7 @@ "zh-cht": "沒有檔案", "xloc": [ "default-mobile.handlebars->9->449", - "default.handlebars->29->1493", + "default.handlebars->29->1486", "default.handlebars->29->683", "default.handlebars->29->704" ] @@ -24364,8 +24360,8 @@ "xloc": [ "default-mobile.handlebars->9->427", "default-mobile.handlebars->9->450", - "default.handlebars->29->1455", - "default.handlebars->29->1494" + "default.handlebars->29->1448", + "default.handlebars->29->1487" ] }, { @@ -24445,7 +24441,7 @@ "zh-chs": "没有成员", "zh-cht": "沒有成員", "xloc": [ - "default.handlebars->29->1868" + "default.handlebars->29->1861" ] }, { @@ -24465,7 +24461,7 @@ "zh-chs": "没有新的设备组", "zh-cht": "沒有新的裝置群", "xloc": [ - "default.handlebars->29->1813" + "default.handlebars->29->1806" ] }, { @@ -24486,8 +24482,7 @@ "zh-cht": "沒有政策", "xloc": [ "default.handlebars->29->1356", - "default.handlebars->29->1384", - "default.handlebars->29->1387" + "default.handlebars->29->1384" ] }, { @@ -24511,7 +24506,7 @@ "default-mobile.handlebars->9->410", "default-mobile.handlebars->9->457", "default.handlebars->29->1299", - "default.handlebars->29->1501", + "default.handlebars->29->1494", "default.handlebars->29->697", "default.handlebars->29->718" ] @@ -24556,7 +24551,7 @@ "zh-cht": "沒有終端", "xloc": [ "default-mobile.handlebars->9->448", - "default.handlebars->29->1492", + "default.handlebars->29->1485", "default.handlebars->29->682", "default.handlebars->29->703" ] @@ -24579,7 +24574,7 @@ "zh-cht": "不能訪問終端", "xloc": [ "default-mobile.handlebars->9->425", - "default.handlebars->29->1453" + "default.handlebars->29->1446" ] }, { @@ -24599,7 +24594,7 @@ "zh-chs": "没有工具(MeshCmd /路由器)", "zh-cht": "沒有工具(MeshCmd /路由器)", "xloc": [ - "default.handlebars->29->1814" + "default.handlebars->29->1807" ] }, { @@ -24627,8 +24622,8 @@ "zh-chs": "没有共同的设备组", "zh-cht": "沒有共同的裝置群", "xloc": [ - "default.handlebars->29->1874", - "default.handlebars->29->1992" + "default.handlebars->29->1867", + "default.handlebars->29->1985" ] }, { @@ -24748,8 +24743,8 @@ "zh-chs": "没有共同的设备", "zh-cht": "沒有共同的裝置", "xloc": [ - "default.handlebars->29->1880", - "default.handlebars->29->2004" + "default.handlebars->29->1873", + "default.handlebars->29->1997" ] }, { @@ -24769,7 +24764,7 @@ "zh-chs": "该设备组中没有设备。", "zh-cht": "該裝置群中沒有裝置。", "xloc": [ - "default.handlebars->29->1544" + "default.handlebars->29->1537" ] }, { @@ -24852,7 +24847,7 @@ "zh-chs": "找不到群组。", "zh-cht": "找不到群組。", "xloc": [ - "default.handlebars->29->1817" + "default.handlebars->29->1810" ] }, { @@ -24973,7 +24968,7 @@ "zh-chs": "没有录音。", "zh-cht": "沒有錄音。", "xloc": [ - "default.handlebars->29->2022" + "default.handlebars->29->2015" ] }, { @@ -24993,7 +24988,7 @@ "zh-chs": "没有服务器权限", "zh-cht": "沒有伺服器權限", "xloc": [ - "default.handlebars->29->1900" + "default.handlebars->29->1893" ] }, { @@ -25013,7 +25008,7 @@ "zh-chs": "没有用户组成员身份", "zh-cht": "沒有用戶群成員身份", "xloc": [ - "default.handlebars->29->1998" + "default.handlebars->29->1991" ] }, { @@ -25033,7 +25028,7 @@ "zh-chs": "未找到相应的用户。", "zh-cht": "未找到相應的用戶。", "xloc": [ - "default.handlebars->29->1721" + "default.handlebars->29->1714" ] }, { @@ -25129,19 +25124,19 @@ "default.handlebars->29->1347", "default.handlebars->29->1352", "default.handlebars->29->1354", - "default.handlebars->29->1535", - "default.handlebars->29->1554", - "default.handlebars->29->1559", - "default.handlebars->29->1697", + "default.handlebars->29->1528", + "default.handlebars->29->1547", + "default.handlebars->29->1552", + "default.handlebars->29->1690", "default.handlebars->29->177", - "default.handlebars->29->1838", - "default.handlebars->29->1840", - "default.handlebars->29->1857", - "default.handlebars->29->1919", + "default.handlebars->29->1831", + "default.handlebars->29->1833", + "default.handlebars->29->1850", + "default.handlebars->29->1912", "default.handlebars->29->192", - "default.handlebars->29->1928", - "default.handlebars->29->1932", - "default.handlebars->29->1944", + "default.handlebars->29->1921", + "default.handlebars->29->1925", + "default.handlebars->29->1937", "default.handlebars->29->208", "default.handlebars->29->209", "default.handlebars->29->557", @@ -25296,8 +25291,8 @@ "zh-chs": "未连接", "zh-cht": "未連接", "xloc": [ - "default.handlebars->29->1531", - "default.handlebars->29->1539" + "default.handlebars->29->1524", + "default.handlebars->29->1532" ] }, { @@ -25338,7 +25333,7 @@ "zh-chs": "不在服务器上", "zh-cht": "不在伺服器上", "xloc": [ - "default.handlebars->29->2038" + "default.handlebars->29->2031" ] }, { @@ -25358,8 +25353,8 @@ "zh-chs": "没有设置", "zh-cht": "沒有設置", "xloc": [ - "default.handlebars->29->1906", - "default.handlebars->29->1907" + "default.handlebars->29->1899", + "default.handlebars->29->1900" ] }, { @@ -25379,7 +25374,7 @@ "zh-chs": "未经审核的", "zh-cht": "未經審核的", "xloc": [ - "default.handlebars->29->1974" + "default.handlebars->29->1967" ] }, { @@ -25400,7 +25395,7 @@ "zh-cht": "筆記", "xloc": [ "default.handlebars->29->1362", - "default.handlebars->29->1952", + "default.handlebars->29->1945", "default.handlebars->29->629", "default.handlebars->29->691", "default.handlebars->29->712", @@ -25445,7 +25440,7 @@ "zh-cht": "通知設定", "xloc": [ "default.handlebars->29->1260", - "default.handlebars->29->1530", + "default.handlebars->29->1523", "default.handlebars->container->column_l->p2->p2info->p2AccountActions->3->10" ] }, @@ -25466,7 +25461,7 @@ "zh-chs": "通知设置还必须在帐户设置中启用。", "zh-cht": "通知設定還必須在帳戶設定中啟用。", "xloc": [ - "default.handlebars->29->1526" + "default.handlebars->29->1519" ] }, { @@ -25527,7 +25522,7 @@ "zh-cht": "通知", "xloc": [ "default.handlebars->29->189", - "default.handlebars->29->1958" + "default.handlebars->29->1951" ] }, { @@ -25567,9 +25562,9 @@ "zh-chs": "通知使用者", "zh-cht": "通知使用者", "xloc": [ - "default.handlebars->29->1420", - "default.handlebars->29->1424", - "default.handlebars->29->1427" + "default.handlebars->29->1413", + "default.handlebars->29->1417", + "default.handlebars->29->1420" ] }, { @@ -25589,7 +25584,7 @@ "zh-chs": "通知{0}", "zh-cht": "通知{0}", "xloc": [ - "default.handlebars->29->1760" + "default.handlebars->29->1753" ] }, { @@ -25681,7 +25676,7 @@ "zh-chs": "发生在{0}", "zh-cht": "發生在{0}", "xloc": [ - "default.handlebars->29->2052" + "default.handlebars->29->2045" ] }, { @@ -25708,7 +25703,7 @@ "zh-chs": "离线用户", "zh-cht": "離線用戶", "xloc": [ - "default.handlebars->29->1718" + "default.handlebars->29->1711" ] }, { @@ -25749,7 +25744,7 @@ "zh-chs": "一天", "zh-cht": "一天", "xloc": [ - "default.handlebars->29->1694" + "default.handlebars->29->1687" ] }, { @@ -25812,7 +25807,7 @@ "zh-chs": "在线用户", "zh-cht": "在線用戶", "xloc": [ - "default.handlebars->29->1717" + "default.handlebars->29->1710" ] }, { @@ -25996,7 +25991,7 @@ "zh-chs": "开头:{0}", "zh-cht": "開場:{0}", "xloc": [ - "default.handlebars->29->1608" + "default.handlebars->29->1601" ] }, { @@ -26042,8 +26037,8 @@ "zh-cht": "操作", "xloc": [ "default-mobile.handlebars->9->269", - "default.handlebars->29->1744", - "default.handlebars->29->1825", + "default.handlebars->29->1737", + "default.handlebars->29->1818", "default.handlebars->29->468", "default.handlebars->29->483", "default.handlebars->29->757" @@ -26251,7 +26246,7 @@ "zh-chs": "部分的", "zh-cht": "部分的", "xloc": [ - "default.handlebars->29->1732" + "default.handlebars->29->1725" ] }, { @@ -26327,7 +26322,7 @@ "zh-chs": "部分权限", "zh-cht": "部分權限", "xloc": [ - "default.handlebars->29->1903" + "default.handlebars->29->1896" ] }, { @@ -26368,12 +26363,12 @@ "zh-cht": "密碼", "xloc": [ "default-mobile.handlebars->9->275", - "default.handlebars->29->1788", - "default.handlebars->29->1789", - "default.handlebars->29->1924", - "default.handlebars->29->1926", - "default.handlebars->29->1977", - "default.handlebars->29->1978", + "default.handlebars->29->1781", + "default.handlebars->29->1782", + "default.handlebars->29->1917", + "default.handlebars->29->1919", + "default.handlebars->29->1970", + "default.handlebars->29->1971", "default.handlebars->29->296", "default.handlebars->29->327", "default.handlebars->29->772", @@ -26517,7 +26512,7 @@ "zh-chs": "密码提示", "zh-cht": "密碼提示", "xloc": [ - "default.handlebars->29->1979" + "default.handlebars->29->1972" ] }, { @@ -26558,7 +26553,7 @@ "zh-chs": "密码不符", "zh-cht": "密碼不符", "xloc": [ - "default.handlebars->29->1393" + "default.handlebars->29->1390" ] }, { @@ -26600,8 +26595,8 @@ "zh-chs": "密码*", "zh-cht": "密碼*", "xloc": [ - "default.handlebars->29->1391", - "default.handlebars->29->1392" + "default.handlebars->29->1388", + "default.handlebars->29->1389" ] }, { @@ -26663,7 +26658,7 @@ "default-mobile.handlebars->9->318", "default-mobile.handlebars->container->page_content->column_l->p10->p10files->p13toolbar->1->2->1->3", "default-mobile.handlebars->container->page_content->column_l->p5->p5myfiles->p5toolbar->1->0->1->3", - "default.handlebars->29->1581", + "default.handlebars->29->1574", "default.handlebars->29->886", "default.handlebars->29->912", "default.handlebars->container->column_l->p12->termTable->1->1->6->1->3", @@ -26927,7 +26922,7 @@ "zh-chs": "执行电源操作= {0},强制执行= {1}", "zh-cht": "執行電源操作= {0},強制執行= {1}", "xloc": [ - "default.handlebars->29->1613" + "default.handlebars->29->1606" ] }, { @@ -26948,8 +26943,8 @@ "zh-cht": "權限", "xloc": [ "default-mobile.handlebars->9->460", - "default.handlebars->29->1504", - "default.handlebars->29->1716" + "default.handlebars->29->1497", + "default.handlebars->29->1709" ] }, { @@ -26995,7 +26990,7 @@ "default.handlebars->29->1029", "default.handlebars->29->1032", "default.handlebars->29->159", - "default.handlebars->29->1969" + "default.handlebars->29->1962" ] }, { @@ -27015,7 +27010,7 @@ "zh-chs": "电话号码", "zh-cht": "電話號碼", "xloc": [ - "default.handlebars->29->1918" + "default.handlebars->29->1911" ] }, { @@ -27037,7 +27032,7 @@ "xloc": [ "default-mobile.handlebars->9->66", "default.handlebars->29->1031", - "default.handlebars->29->1968" + "default.handlebars->29->1961" ] }, { @@ -27197,7 +27192,7 @@ "zh-cht": "外掛指令", "xloc": [ "default.handlebars->29->199", - "default.handlebars->29->2131" + "default.handlebars->29->2124" ] }, { @@ -27482,7 +27477,7 @@ "zh-chs": "电源状态", "zh-cht": "電源狀態", "xloc": [ - "default.handlebars->29->1537", + "default.handlebars->29->1530", "default.handlebars->container->column_l->p21->3->1->meshPowerChartDiv->1" ] }, @@ -27592,7 +27587,7 @@ "zh-chs": "存在于服务器上", "zh-cht": "存在於伺服器上", "xloc": [ - "default.handlebars->29->2037" + "default.handlebars->29->2030" ] }, { @@ -27738,7 +27733,7 @@ "zh-chs": "处理控制台命令:“{0}”", "zh-cht": "處理控制台命令:“{0}”", "xloc": [ - "default.handlebars->29->1605" + "default.handlebars->29->1598" ] }, { @@ -27798,9 +27793,9 @@ "zh-chs": "用户同意提示", "zh-cht": "用戶同意提示", "xloc": [ - "default.handlebars->29->1421", - "default.handlebars->29->1425", - "default.handlebars->29->1428" + "default.handlebars->29->1414", + "default.handlebars->29->1418", + "default.handlebars->29->1421" ] }, { @@ -27820,7 +27815,7 @@ "zh-chs": "协议", "zh-cht": "協議", "xloc": [ - "default.handlebars->29->2035", + "default.handlebars->29->2028", "player.handlebars->3->16" ] }, @@ -27880,7 +27875,7 @@ "zh-cht": "公開鏈結", "xloc": [ "default-mobile.handlebars->9->115", - "default.handlebars->29->1566" + "default.handlebars->29->1559" ] }, { @@ -28187,7 +28182,7 @@ "zh-chs": "RSS", "zh-cht": "RSS", "xloc": [ - "default.handlebars->29->2101" + "default.handlebars->29->2094" ] }, { @@ -28207,7 +28202,7 @@ "zh-chs": "随机密码。", "zh-cht": "隨機密碼。", "xloc": [ - "default.handlebars->29->1790" + "default.handlebars->29->1783" ] }, { @@ -28247,7 +28242,7 @@ "zh-chs": "重新激活英特尔®AMT", "zh-cht": "重新啟動Intel® AMT", "xloc": [ - "default.handlebars->29->1395" + "default.handlebars->29->1392" ] }, { @@ -28267,9 +28262,9 @@ "zh-chs": "真正的名字", "zh-cht": "真正的名字", "xloc": [ - "default.handlebars->29->1915", - "default.handlebars->29->1917", - "default.handlebars->29->1970" + "default.handlebars->29->1908", + "default.handlebars->29->1910", + "default.handlebars->29->1963" ] }, { @@ -28289,7 +28284,7 @@ "zh-chs": "境界", "zh-cht": "境界", "xloc": [ - "default.handlebars->29->1799" + "default.handlebars->29->1792" ] }, { @@ -28344,7 +28339,7 @@ "en": "Record sessions", "nl": "Sessies opnemen", "xloc": [ - "default.handlebars->29->1431" + "default.handlebars->29->1424" ] }, { @@ -28364,7 +28359,7 @@ "zh-chs": "记录细节", "zh-cht": "記錄細節", "xloc": [ - "default.handlebars->29->2049" + "default.handlebars->29->2042" ] }, { @@ -28406,7 +28401,7 @@ "xloc": [ "default-mobile.handlebars->9->121", "default-mobile.handlebars->9->306", - "default.handlebars->29->1573", + "default.handlebars->29->1566", "default.handlebars->29->899" ] }, @@ -28525,7 +28520,7 @@ "zh-chs": "中继数量", "zh-cht": "中繼數量", "xloc": [ - "default.handlebars->29->2080" + "default.handlebars->29->2073" ] }, { @@ -28545,7 +28540,7 @@ "zh-chs": "中继错误", "zh-cht": "中繼錯誤", "xloc": [ - "default.handlebars->29->2073" + "default.handlebars->29->2066" ] }, { @@ -28565,8 +28560,8 @@ "zh-chs": "中继连接", "zh-cht": "中繼連接", "xloc": [ - "default.handlebars->29->2079", - "default.handlebars->29->2095" + "default.handlebars->29->2072", + "default.handlebars->29->2088" ] }, { @@ -28711,7 +28706,7 @@ "nl": "Externe opdrachten", "xloc": [ "default-mobile.handlebars->9->435", - "default.handlebars->29->1463" + "default.handlebars->29->1456" ] }, { @@ -28733,8 +28728,8 @@ "xloc": [ "default-mobile.handlebars->9->422", "default-mobile.handlebars->9->442", - "default.handlebars->29->1449", - "default.handlebars->29->1485" + "default.handlebars->29->1442", + "default.handlebars->29->1478" ] }, { @@ -28777,8 +28772,8 @@ "zh-chs": "远程桌面连接栏已激活/更新", "zh-cht": "遠程桌面連接欄已激活/更新", "xloc": [ - "default.handlebars->29->1619", - "default.handlebars->29->1625" + "default.handlebars->29->1612", + "default.handlebars->29->1618" ] }, { @@ -28798,7 +28793,7 @@ "zh-chs": "远程桌面连接栏失败或不受支持", "zh-cht": "遠程桌面連接欄失敗或不受支持", "xloc": [ - "default.handlebars->29->1620" + "default.handlebars->29->1613" ] }, { @@ -28818,7 +28813,7 @@ "zh-chs": "远程桌面连接栏失败或不受支持", "zh-cht": "遠程桌面連接欄失敗或不受支持", "xloc": [ - "default.handlebars->29->1626" + "default.handlebars->29->1619" ] }, { @@ -28838,9 +28833,9 @@ "zh-chs": "本地用户强行关闭了远程桌面连接", "zh-cht": "本地用戶強行關閉了遠程桌面連接", "xloc": [ - "default.handlebars->29->1617", - "default.handlebars->29->1621", - "default.handlebars->29->1627" + "default.handlebars->29->1610", + "default.handlebars->29->1614", + "default.handlebars->29->1620" ] }, { @@ -28963,8 +28958,8 @@ "xloc": [ "default-mobile.handlebars->9->423", "default-mobile.handlebars->9->447", - "default.handlebars->29->1450", - "default.handlebars->29->1490" + "default.handlebars->29->1443", + "default.handlebars->29->1483" ] }, { @@ -29099,8 +29094,8 @@ "zh-chs": "删除设备组权限", "zh-cht": "刪除裝置群權限", "xloc": [ - "default.handlebars->29->1884", - "default.handlebars->29->2018" + "default.handlebars->29->1877", + "default.handlebars->29->2011" ] }, { @@ -29120,8 +29115,8 @@ "zh-chs": "删除设备权限", "zh-cht": "刪除裝置權限", "xloc": [ - "default.handlebars->29->1882", - "default.handlebars->29->2005" + "default.handlebars->29->1875", + "default.handlebars->29->1998" ] }, { @@ -29158,7 +29153,7 @@ "zh-chs": "删除用户组成员身份", "zh-cht": "刪除用戶群成員身份", "xloc": [ - "default.handlebars->29->2014" + "default.handlebars->29->2007" ] }, { @@ -29178,8 +29173,8 @@ "zh-chs": "删除用户组权限", "zh-cht": "刪除用戶群權限", "xloc": [ - "default.handlebars->29->1509", - "default.handlebars->29->2010" + "default.handlebars->29->1502", + "default.handlebars->29->2003" ] }, { @@ -29199,7 +29194,7 @@ "zh-chs": "删除用户成员资格", "zh-cht": "刪除用戶成員資格", "xloc": [ - "default.handlebars->29->1892" + "default.handlebars->29->1885" ] }, { @@ -29219,8 +29214,8 @@ "zh-chs": "删除用户权限", "zh-cht": "刪除用戶權限", "xloc": [ - "default.handlebars->29->1507", - "default.handlebars->29->2007" + "default.handlebars->29->1500", + "default.handlebars->29->2000" ] }, { @@ -29240,7 +29235,7 @@ "zh-chs": "删除所有两因素认证。", "zh-cht": "刪除所有二因子鑑別。", "xloc": [ - "default.handlebars->29->1982" + "default.handlebars->29->1975" ] }, { @@ -29260,7 +29255,7 @@ "zh-chs": "删除此用户标识的所有先前事件。", "zh-cht": "刪除此用戶標識的所有先前事件。", "xloc": [ - "default.handlebars->29->1791" + "default.handlebars->29->1784" ] }, { @@ -29280,7 +29275,7 @@ "zh-chs": "断开连接后移除设备", "zh-cht": "斷開連接後删除裝置", "xloc": [ - "default.handlebars->29->1429" + "default.handlebars->29->1422" ] }, { @@ -29381,7 +29376,7 @@ "zh-chs": "删除此用户", "zh-cht": "刪除此用戶", "xloc": [ - "default.handlebars->29->1960" + "default.handlebars->29->1953" ] }, { @@ -29401,7 +29396,7 @@ "zh-chs": "删除用户组成员身份", "zh-cht": "刪除用戶群成員身份", "xloc": [ - "default.handlebars->29->1996" + "default.handlebars->29->1989" ] }, { @@ -29421,7 +29416,7 @@ "zh-chs": "删除此设备的用户组权限", "zh-cht": "刪除此裝置的用戶群權限", "xloc": [ - "default.handlebars->29->1878" + "default.handlebars->29->1871" ] }, { @@ -29441,7 +29436,7 @@ "zh-chs": "删除此设备组的用户组权限", "zh-cht": "刪除此裝置群的用戶群權限", "xloc": [ - "default.handlebars->29->1872", + "default.handlebars->29->1865", "default.handlebars->29->675" ] }, @@ -29463,9 +29458,9 @@ "zh-cht": "刪除此裝置群的用戶權限", "xloc": [ "default.handlebars->29->1379", - "default.handlebars->29->1866", - "default.handlebars->29->1990", - "default.handlebars->29->2002", + "default.handlebars->29->1859", + "default.handlebars->29->1983", + "default.handlebars->29->1995", "default.handlebars->29->676" ] }, @@ -29486,7 +29481,7 @@ "zh-chs": "删除身份验证应用程序", "zh-cht": "刪除身份驗證應用程序", "xloc": [ - "default.handlebars->29->1679" + "default.handlebars->29->1672" ] }, { @@ -29506,7 +29501,7 @@ "zh-chs": "从设备组{1}中删除了设备{0}", "zh-cht": "從設備組{1}中刪除了設備{0}", "xloc": [ - "default.handlebars->29->1675" + "default.handlebars->29->1668" ] }, { @@ -29526,7 +29521,7 @@ "zh-chs": "已删除用户{0}的电话号码", "zh-cht": "已刪除用戶{0}的電話號碼", "xloc": [ - "default.handlebars->29->1685" + "default.handlebars->29->1678" ] }, { @@ -29546,7 +29541,7 @@ "zh-chs": "移除安全密钥", "zh-cht": "移除安全密鑰", "xloc": [ - "default.handlebars->29->1682" + "default.handlebars->29->1675" ] }, { @@ -29566,9 +29561,9 @@ "zh-chs": "删除了{0}的用户设备权限", "zh-cht": "刪除了{0}的用戶設備權限", "xloc": [ - "default.handlebars->29->1648", - "default.handlebars->29->1669", - "default.handlebars->29->1674" + "default.handlebars->29->1641", + "default.handlebars->29->1662", + "default.handlebars->29->1667" ] }, { @@ -29588,7 +29583,7 @@ "zh-chs": "从设备组{1}中删除了用户组{0}", "zh-cht": "從設備組{1}中刪除了用戶組{0}", "xloc": [ - "default.handlebars->29->1658" + "default.handlebars->29->1651" ] }, { @@ -29608,7 +29603,7 @@ "zh-chs": "从设备组{1}中删除了用户{0}", "zh-cht": "已從設備組{1}中刪除用戶{0}", "xloc": [ - "default.handlebars->29->1671" + "default.handlebars->29->1664" ] }, { @@ -29628,8 +29623,8 @@ "zh-chs": "从用户组{1}中删除了用户{0}", "zh-cht": "從用戶組{1}中刪除了用戶{0}", "xloc": [ - "default.handlebars->29->1650", - "default.handlebars->29->1660" + "default.handlebars->29->1643", + "default.handlebars->29->1653" ] }, { @@ -29653,7 +29648,7 @@ "default-mobile.handlebars->9->310", "default-mobile.handlebars->container->page_content->column_l->p10->p10files->p13toolbar->1->2->1->1", "default-mobile.handlebars->container->page_content->column_l->p5->p5myfiles->p5toolbar->1->0->1->1", - "default.handlebars->29->1577", + "default.handlebars->29->1570", "default.handlebars->29->526", "default.handlebars->29->903", "default.handlebars->container->column_l->p13->p13toolbar->1->2->1->3", @@ -29678,7 +29673,7 @@ "zh-chs": "重命名:“{0}”为“{1}”", "zh-cht": "重命名:“{0}”為“{1}”", "xloc": [ - "default.handlebars->29->1636" + "default.handlebars->29->1629" ] }, { @@ -29698,7 +29693,7 @@ "zh-chs": "报告日", "zh-cht": "報告日", "xloc": [ - "default.handlebars->29->1695" + "default.handlebars->29->1688" ] }, { @@ -29718,7 +29713,7 @@ "zh-chs": "报告类型", "zh-cht": "報告類型", "xloc": [ - "default.handlebars->29->1690" + "default.handlebars->29->1683" ] }, { @@ -29759,8 +29754,8 @@ "zh-cht": "要求:{0}。", "xloc": [ "default-mobile.handlebars->9->89", - "default.handlebars->29->1796", - "default.handlebars->29->1980" + "default.handlebars->29->1789", + "default.handlebars->29->1973" ] }, { @@ -29853,7 +29848,7 @@ "nl": "Reset / uitschakelen", "xloc": [ "default-mobile.handlebars->9->436", - "default.handlebars->29->1464" + "default.handlebars->29->1457" ] }, { @@ -29967,7 +29962,7 @@ "nl": "Reset/Uit", "xloc": [ "default-mobile.handlebars->9->456", - "default.handlebars->29->1500", + "default.handlebars->29->1493", "default.handlebars->29->696", "default.handlebars->29->717" ] @@ -30078,7 +30073,7 @@ "zh-chs": "限制条件", "zh-cht": "限制條件", "xloc": [ - "default.handlebars->29->1904" + "default.handlebars->29->1897" ] }, { @@ -30160,7 +30155,7 @@ "xloc": [ "default-mobile.handlebars->9->107", "default-mobile.handlebars->9->300", - "default.handlebars->29->1545", + "default.handlebars->29->1538", "default.handlebars->29->893" ] }, @@ -30422,19 +30417,19 @@ "zh-chs": "运行命令", "zh-cht": "運行命令", "xloc": [ - "default.handlebars->29->1612" + "default.handlebars->29->1605" ] }, { "en": "Running commands as user", "xloc": [ - "default.handlebars->29->1687" + "default.handlebars->29->1680" ] }, { "en": "Running commands as user if possible", "xloc": [ - "default.handlebars->29->1688" + "default.handlebars->29->1681" ] }, { @@ -30494,8 +30489,8 @@ "zh-chs": "短信", "zh-cht": "短信", "xloc": [ - "default.handlebars->29->1949", - "default.handlebars->29->1954", + "default.handlebars->29->1942", + "default.handlebars->29->1947", "login-mobile.handlebars->container->page_content->column_l->1->1->0->1->tokenpanel->1->7->1->4->1->3", "login.handlebars->container->column_l->centralTable->1->0->logincell->resettokenpanel->1->5->1->2->1->3", "login.handlebars->container->column_l->centralTable->1->0->logincell->tokenpanel->1->7->1->4->1->3", @@ -30520,7 +30515,7 @@ "zh-chs": "此用户的短信功能电话号码。", "zh-cht": "此用戶的短信功能電話號碼。", "xloc": [ - "default.handlebars->29->1966" + "default.handlebars->29->1959" ] }, { @@ -31070,7 +31065,7 @@ "xloc": [ "default-mobile.handlebars->9->276", "default-mobile.handlebars->9->369", - "default.handlebars->29->1950", + "default.handlebars->29->1943", "default.handlebars->29->297", "default.handlebars->29->773", "default.handlebars->29->980" @@ -31093,7 +31088,7 @@ "zh-chs": "安全密钥", "zh-cht": "安全密鑰", "xloc": [ - "default.handlebars->29->1947" + "default.handlebars->29->1940" ] }, { @@ -31133,9 +31128,9 @@ "zh-chs": "全选", "zh-cht": "全選", "xloc": [ - "default.handlebars->29->1569", - "default.handlebars->29->1740", - "default.handlebars->29->1823", + "default.handlebars->29->1562", + "default.handlebars->29->1733", + "default.handlebars->29->1816", "default.handlebars->29->455", "default.handlebars->29->895", "default.handlebars->29->897", @@ -31164,9 +31159,9 @@ "zh-chs": "选择无", "zh-cht": "選擇無", "xloc": [ - "default.handlebars->29->1568", - "default.handlebars->29->1739", - "default.handlebars->29->1822", + "default.handlebars->29->1561", + "default.handlebars->29->1732", + "default.handlebars->29->1815", "default.handlebars->29->454", "default.handlebars->29->896", "default.handlebars->meshContextMenu->cxselectnone" @@ -31269,8 +31264,8 @@ "zh-chs": "选择要对所有选定用户执行的操作。", "zh-cht": "選擇要對所有選定用戶執行的操作。", "xloc": [ - "default.handlebars->29->1743", - "default.handlebars->29->1824" + "default.handlebars->29->1736", + "default.handlebars->29->1817" ] }, { @@ -31333,7 +31328,7 @@ "zh-cht": "僅自我事件", "xloc": [ "default-mobile.handlebars->9->452", - "default.handlebars->29->1496" + "default.handlebars->29->1489" ] }, { @@ -31376,7 +31371,7 @@ "zh-chs": "发电邮", "zh-cht": "發電郵", "xloc": [ - "default.handlebars->29->1754" + "default.handlebars->29->1747" ] }, { @@ -31437,7 +31432,7 @@ "zh-chs": "发送短信", "zh-cht": "發送簡訊", "xloc": [ - "default.handlebars->29->1752" + "default.handlebars->29->1745" ] }, { @@ -31457,7 +31452,7 @@ "zh-chs": "发送短信给该用户", "zh-cht": "發送短信給該用戶", "xloc": [ - "default.handlebars->29->1955" + "default.handlebars->29->1948" ] }, { @@ -31477,7 +31472,7 @@ "zh-chs": "发送电邮给该用户", "zh-cht": "發送電郵給該用戶", "xloc": [ - "default.handlebars->29->1957" + "default.handlebars->29->1950" ] }, { @@ -31497,7 +31492,7 @@ "zh-chs": "向该组中的所有用户发送通知。", "zh-cht": "向該群中的所有用戶發送通知。", "xloc": [ - "default.handlebars->29->1863" + "default.handlebars->29->1856" ] }, { @@ -31517,7 +31512,7 @@ "zh-chs": "向该用户发送文本通知。", "zh-cht": "向該用戶發送文本通知。", "xloc": [ - "default.handlebars->29->1755" + "default.handlebars->29->1748" ] }, { @@ -31537,7 +31532,7 @@ "zh-chs": "发送电邮给用户", "zh-cht": "發送電郵給用戶", "xloc": [ - "default.handlebars->29->1735" + "default.handlebars->29->1728" ] }, { @@ -31577,7 +31572,7 @@ "zh-chs": "发送邀请电邮。", "zh-cht": "發送邀請電郵。", "xloc": [ - "default.handlebars->29->1795" + "default.handlebars->29->1788" ] }, { @@ -31699,7 +31694,7 @@ "zh-chs": "发送用户通知", "zh-cht": "發送用戶通知", "xloc": [ - "default.handlebars->29->1959" + "default.handlebars->29->1952" ] }, { @@ -31760,7 +31755,7 @@ "zh-chs": "服务器备份", "zh-cht": "伺服器備份", "xloc": [ - "default.handlebars->29->1805" + "default.handlebars->29->1798" ] }, { @@ -31780,7 +31775,7 @@ "zh-chs": "服务器证书", "zh-cht": "伺服器憑證", "xloc": [ - "default.handlebars->29->2111" + "default.handlebars->29->2104" ] }, { @@ -31800,7 +31795,7 @@ "zh-chs": "服务器数据库", "zh-cht": "伺服器數據庫", "xloc": [ - "default.handlebars->29->2112" + "default.handlebars->29->2105" ] }, { @@ -31822,9 +31817,9 @@ "xloc": [ "default-mobile.handlebars->9->429", "default-mobile.handlebars->9->444", - "default.handlebars->29->1457", - "default.handlebars->29->1487", - "default.handlebars->29->1802", + "default.handlebars->29->1450", + "default.handlebars->29->1480", + "default.handlebars->29->1795", "default.handlebars->29->689", "default.handlebars->29->710" ] @@ -31846,8 +31841,8 @@ "zh-chs": "服务器权限", "zh-cht": "伺服器權限", "xloc": [ - "default.handlebars->29->1727", - "default.handlebars->29->1816" + "default.handlebars->29->1720", + "default.handlebars->29->1809" ] }, { @@ -31867,7 +31862,7 @@ "zh-chs": "服务器配额", "zh-cht": "伺服器配額", "xloc": [ - "default.handlebars->29->1921" + "default.handlebars->29->1914" ] }, { @@ -31887,7 +31882,7 @@ "zh-chs": "服务器还原", "zh-cht": "伺服器還原", "xloc": [ - "default.handlebars->29->1806" + "default.handlebars->29->1799" ] }, { @@ -31907,7 +31902,7 @@ "zh-chs": "服务器权限", "zh-cht": "伺服器權限", "xloc": [ - "default.handlebars->29->1920" + "default.handlebars->29->1913" ] }, { @@ -31927,7 +31922,7 @@ "zh-chs": "服务器状态", "zh-cht": "伺服器狀態", "xloc": [ - "default.handlebars->29->2059" + "default.handlebars->29->2052" ] }, { @@ -31967,7 +31962,7 @@ "zh-chs": "服务器跟踪", "zh-cht": "伺服器追蹤", "xloc": [ - "default.handlebars->29->2122" + "default.handlebars->29->2115" ] }, { @@ -31987,7 +31982,7 @@ "zh-chs": "服务器更新", "zh-cht": "伺服器更新", "xloc": [ - "default.handlebars->29->1807" + "default.handlebars->29->1800" ] }, { @@ -32130,7 +32125,7 @@ "zh-chs": "ServerStats.csv", "zh-cht": "ServerStats.csv", "xloc": [ - "default.handlebars->29->2103" + "default.handlebars->29->2096" ] }, { @@ -32190,7 +32185,7 @@ "zh-chs": "会话", "zh-cht": "節", "xloc": [ - "default.handlebars->29->2023" + "default.handlebars->29->2016" ] }, { @@ -32315,7 +32310,7 @@ "zh-chs": "设置剪贴板内容,{0}个字节", "zh-cht": "設置剪貼板內容,{0}個字節", "xloc": [ - "default.handlebars->29->1610" + "default.handlebars->29->1603" ] }, { @@ -32696,7 +32691,7 @@ "zh-cht": "只顯示自己的事件", "xloc": [ "default-mobile.handlebars->9->432", - "default.handlebars->29->1460" + "default.handlebars->29->1453" ] }, { @@ -32716,7 +32711,7 @@ "zh-chs": "显示连接工具栏", "zh-cht": "顯示連接工具欄", "xloc": [ - "default.handlebars->29->1422" + "default.handlebars->29->1415" ] }, { @@ -32796,8 +32791,8 @@ "zh-chs": "显示1分钟", "zh-cht": "顯示1分鐘", "xloc": [ - "default.handlebars->29->1758", - "default.handlebars->29->1781" + "default.handlebars->29->1751", + "default.handlebars->29->1774" ] }, { @@ -32817,8 +32812,8 @@ "zh-chs": "显示10秒", "zh-cht": "顯示10秒", "xloc": [ - "default.handlebars->29->1757", - "default.handlebars->29->1780" + "default.handlebars->29->1750", + "default.handlebars->29->1773" ] }, { @@ -32838,8 +32833,8 @@ "zh-chs": "显示5分钟", "zh-cht": "顯示5分鐘", "xloc": [ - "default.handlebars->29->1759", - "default.handlebars->29->1782" + "default.handlebars->29->1752", + "default.handlebars->29->1775" ] }, { @@ -32859,8 +32854,8 @@ "zh-chs": "显示消息,直到被用户拒绝", "zh-cht": "顯示消息,直到被用戶拒絕", "xloc": [ - "default.handlebars->29->1756", - "default.handlebars->29->1779" + "default.handlebars->29->1749", + "default.handlebars->29->1772" ] }, { @@ -33076,8 +33071,7 @@ "zh-cht": "簡單客戶端控制模式(CCM)", "xloc": [ "default.handlebars->29->1357", - "default.handlebars->29->1385", - "default.handlebars->29->1389" + "default.handlebars->29->1386" ] }, { @@ -33159,8 +33153,8 @@ "zh-chs": "尺寸", "zh-cht": "尺寸", "xloc": [ - "default.handlebars->29->2026", - "default.handlebars->29->2041", + "default.handlebars->29->2019", + "default.handlebars->29->2034", "default.handlebars->container->column_l->p1->devListToolbarSpan->1->0->9->devListToolbarSize" ] }, @@ -34150,8 +34144,8 @@ "zh-chs": "开始时间", "zh-cht": "開始時間", "xloc": [ - "default.handlebars->29->2024", - "default.handlebars->29->2043", + "default.handlebars->29->2017", + "default.handlebars->29->2036", "default.handlebars->29->839", "desktop.handlebars->3->15" ] @@ -34173,7 +34167,7 @@ "zh-chs": "开始桌面多重会话", "zh-cht": "啟動桌面多路復用會話", "xloc": [ - "default.handlebars->29->1594" + "default.handlebars->29->1587" ] }, { @@ -34193,7 +34187,7 @@ "zh-chs": "从{1}到{2}开始了桌面会话“{0}”", "zh-cht": "從{1}到{2}開始了桌面會話“{0}”", "xloc": [ - "default.handlebars->29->1603" + "default.handlebars->29->1596" ] }, { @@ -34213,7 +34207,7 @@ "zh-chs": "从{1}到{2}开始文件管理会话“{0}”", "zh-cht": "從{1}到{2}開始文件管理會話“{0}”", "xloc": [ - "default.handlebars->29->1604" + "default.handlebars->29->1597" ] }, { @@ -34233,7 +34227,7 @@ "zh-chs": "从{1}到{2}开始中继会话“{0}”", "zh-cht": "從{1}到{2}開始中繼會話“{0}”", "xloc": [ - "default.handlebars->29->1601" + "default.handlebars->29->1594" ] }, { @@ -34253,7 +34247,7 @@ "zh-chs": "使用Toast通知启动远程桌面", "zh-cht": "使用Toast通知啟動遠程桌面", "xloc": [ - "default.handlebars->29->1623" + "default.handlebars->29->1616" ] }, { @@ -34273,7 +34267,7 @@ "zh-chs": "启动远程桌面,而无需通知", "zh-cht": "啟動遠程桌面,而無需通知", "xloc": [ - "default.handlebars->29->1624" + "default.handlebars->29->1617" ] }, { @@ -34293,7 +34287,7 @@ "zh-chs": "启动带有Toast通知的远程文件", "zh-cht": "啟動帶有Toast通知的遠程文件", "xloc": [ - "default.handlebars->29->1630" + "default.handlebars->29->1623" ] }, { @@ -34313,7 +34307,7 @@ "zh-chs": "已启动的远程文件,恕不另行通知", "zh-cht": "已啟動的遠程文件,恕不另行通知", "xloc": [ - "default.handlebars->29->1631" + "default.handlebars->29->1624" ] }, { @@ -34333,7 +34327,7 @@ "zh-chs": "从{1}到{2}开始了终端会话“{0}”", "zh-cht": "從{1}到{2}開始了終端會話“{0}”", "xloc": [ - "default.handlebars->29->1602" + "default.handlebars->29->1595" ] }, { @@ -34353,7 +34347,7 @@ "zh-chs": "接受本地用户后启动远程桌面", "zh-cht": "接受本地用戶後啟動遠程桌面", "xloc": [ - "default.handlebars->29->1618" + "default.handlebars->29->1611" ] }, { @@ -34373,7 +34367,7 @@ "zh-chs": "本地用户接受后启动远程文件", "zh-cht": "本地用戶接受後啟動遠程文件", "xloc": [ - "default.handlebars->29->1628" + "default.handlebars->29->1621" ] }, { @@ -34434,8 +34428,8 @@ "zh-chs": "状况", "zh-cht": "狀態", "xloc": [ - "default.handlebars->29->1973", - "default.handlebars->29->2036", + "default.handlebars->29->1966", + "default.handlebars->29->2029", "default.handlebars->container->column_l->p42->p42tbl->1->0->7" ] }, @@ -34563,7 +34557,7 @@ "zh-chs": "超出储存空间", "zh-cht": "超出儲存空間", "xloc": [ - "default.handlebars->29->1549" + "default.handlebars->29->1542" ] }, { @@ -34628,7 +34622,7 @@ "zh-chs": "主题", "zh-cht": "主題", "xloc": [ - "default.handlebars->29->1753" + "default.handlebars->29->1746" ] }, { @@ -34835,7 +34829,7 @@ "zh-chs": "将服务器设备名称同步到主机名称", "zh-cht": "將伺服器裝置名稱同步到主機名稱", "xloc": [ - "default.handlebars->29->1430" + "default.handlebars->29->1423" ] }, { @@ -35154,8 +35148,8 @@ "zh-cht": "終端機", "xloc": [ "default-mobile.handlebars->9->168", - "default.handlebars->29->1423", - "default.handlebars->29->2030", + "default.handlebars->29->1416", + "default.handlebars->29->2023", "default.handlebars->29->255", "default.handlebars->29->536", "default.handlebars->container->topbar->1->1->MainSubMenuSpan->MainSubMenu->1->0->MainDevTerminal", @@ -35200,8 +35194,8 @@ "zh-cht": "終端機通知", "xloc": [ "default.handlebars->29->1342", - "default.handlebars->29->1852", - "default.handlebars->29->1939", + "default.handlebars->29->1845", + "default.handlebars->29->1932", "default.handlebars->29->608" ] }, @@ -35223,8 +35217,8 @@ "zh-cht": "終端機提示", "xloc": [ "default.handlebars->29->1341", - "default.handlebars->29->1851", - "default.handlebars->29->1938", + "default.handlebars->29->1844", + "default.handlebars->29->1931", "default.handlebars->29->607" ] }, @@ -35395,7 +35389,7 @@ "zh-chs": "目前没有任何通知", "zh-cht": "目前沒有任何通知", "xloc": [ - "default.handlebars->29->2051" + "default.handlebars->29->2044" ] }, { @@ -35533,7 +35527,7 @@ "zh-chs": "这是不安全政策,因为将用代理执行激活。", "zh-cht": "這是不安全政策,因為將用代理執行啟動。", "xloc": [ - "default.handlebars->29->1407" + "default.handlebars->29->1400" ] }, { @@ -35574,7 +35568,7 @@ "zh-chs": "此政策不会影响采用ACM模式的英特尔®AMT的设备。", "zh-cht": "此政策不會影響採用ACM模式的Intel® AMT的裝置。", "xloc": [ - "default.handlebars->29->1406" + "default.handlebars->29->1399" ] }, { @@ -35774,7 +35768,7 @@ "zh-chs": "时间跨度", "zh-cht": "時間跨度", "xloc": [ - "default.handlebars->29->1692" + "default.handlebars->29->1685" ] }, { @@ -36636,7 +36630,6 @@ "default.handlebars->29->1286", "default.handlebars->29->1328", "default.handlebars->29->1383", - "default.handlebars->29->1386", "default.handlebars->29->865", "default.handlebars->container->column_l->p11->deskarea0->deskarea4->3", "desktop.handlebars->p11->deskarea0->deskarea4->3" @@ -36951,7 +36944,7 @@ "zh-cht": "卸載", "xloc": [ "default-mobile.handlebars->9->454", - "default.handlebars->29->1498", + "default.handlebars->29->1491", "default.handlebars->29->694", "default.handlebars->29->715" ] @@ -36995,7 +36988,7 @@ "zh-chs": "卸载代理/删除设备", "zh-cht": "卸載代理/刪除設備", "xloc": [ - "default.handlebars->29->1462" + "default.handlebars->29->1455" ] }, { @@ -37048,9 +37041,9 @@ "default.handlebars->29->13", "default.handlebars->29->1315", "default.handlebars->29->1316", - "default.handlebars->29->2013", - "default.handlebars->29->2028", - "default.handlebars->29->2029", + "default.handlebars->29->2006", + "default.handlebars->29->2021", + "default.handlebars->29->2022", "default.handlebars->29->42", "default.handlebars->29->453", "default.handlebars->29->972", @@ -37095,7 +37088,7 @@ "zh-chs": "未知动作", "zh-cht": "未知動作", "xloc": [ - "default.handlebars->29->2065" + "default.handlebars->29->2058" ] }, { @@ -37115,8 +37108,8 @@ "zh-chs": "未知设备", "zh-cht": "未知裝置", "xloc": [ - "default.handlebars->29->1877", - "default.handlebars->29->2001" + "default.handlebars->29->1870", + "default.handlebars->29->1994" ] }, { @@ -37136,9 +37129,9 @@ "zh-chs": "未知设备组", "zh-cht": "未知裝置群", "xloc": [ - "default.handlebars->29->1871", - "default.handlebars->29->1989", - "default.handlebars->29->2069" + "default.handlebars->29->1864", + "default.handlebars->29->1982", + "default.handlebars->29->2062" ] }, { @@ -37158,7 +37151,7 @@ "zh-chs": "未知群组", "zh-cht": "未知群組", "xloc": [ - "default.handlebars->29->2061" + "default.handlebars->29->2054" ] }, { @@ -37199,7 +37192,7 @@ "zh-chs": "未知用户组", "zh-cht": "未知用戶群", "xloc": [ - "default.handlebars->29->1995" + "default.handlebars->29->1988" ] }, { @@ -37262,7 +37255,7 @@ "zh-chs": "解锁帐户", "zh-cht": "解鎖帳戶", "xloc": [ - "default.handlebars->29->1746" + "default.handlebars->29->1739" ] }, { @@ -37306,7 +37299,7 @@ "zh-chs": "最新", "zh-cht": "最新", "xloc": [ - "default.handlebars->29->2129" + "default.handlebars->29->2122" ] }, { @@ -37354,8 +37347,8 @@ "default-mobile.handlebars->9->311", "default-mobile.handlebars->9->329", "default-mobile.handlebars->9->332", - "default.handlebars->29->1578", - "default.handlebars->29->1586", + "default.handlebars->29->1571", + "default.handlebars->29->1579", "default.handlebars->29->904", "default.handlebars->29->928", "default.handlebars->29->931", @@ -37480,7 +37473,7 @@ "zh-cht": "上傳將覆蓋1個檔案。繼續?", "xloc": [ "default-mobile.handlebars->9->330", - "default.handlebars->29->1587", + "default.handlebars->29->1580", "default.handlebars->29->929" ] }, @@ -37502,7 +37495,7 @@ "zh-cht": "上傳將覆蓋{0}個檔案。繼續?", "xloc": [ "default-mobile.handlebars->9->331", - "default.handlebars->29->1588", + "default.handlebars->29->1581", "default.handlebars->29->930" ] }, @@ -37540,7 +37533,7 @@ "zh-chs": "上传:“{0}”", "zh-cht": "上傳:“{0}”", "xloc": [ - "default.handlebars->29->1638" + "default.handlebars->29->1631" ] }, { @@ -37644,8 +37637,8 @@ "zh-chs": "用过的", "zh-cht": "用過的", "xloc": [ - "default.handlebars->29->2055", - "default.handlebars->29->2057" + "default.handlebars->29->2048", + "default.handlebars->29->2050" ] }, { @@ -37666,10 +37659,10 @@ "zh-cht": "用戶", "xloc": [ "default.handlebars->29->1380", - "default.handlebars->29->1699", - "default.handlebars->29->1728", - "default.handlebars->29->1867", - "default.handlebars->29->2048", + "default.handlebars->29->1692", + "default.handlebars->29->1721", + "default.handlebars->29->1860", + "default.handlebars->29->2041", "default.handlebars->29->232", "default.handlebars->29->678" ] @@ -37691,7 +37684,7 @@ "zh-chs": "用户+档案", "zh-cht": "用戶+檔案", "xloc": [ - "default.handlebars->29->1729" + "default.handlebars->29->1722" ] }, { @@ -37711,10 +37704,10 @@ "zh-chs": "用户帐户导入", "zh-cht": "用戶帳戶導入", "xloc": [ - "default.handlebars->29->1762", - "default.handlebars->29->1763", - "default.handlebars->29->1765", - "default.handlebars->29->1767" + "default.handlebars->29->1755", + "default.handlebars->29->1756", + "default.handlebars->29->1758", + "default.handlebars->29->1760" ] }, { @@ -37734,7 +37727,7 @@ "zh-chs": "用户帐户", "zh-cht": "用戶帳戶", "xloc": [ - "default.handlebars->29->2074" + "default.handlebars->29->2067" ] }, { @@ -37777,9 +37770,9 @@ "zh-cht": "用戶同意", "xloc": [ "default.handlebars->29->1348", - "default.handlebars->29->1858", + "default.handlebars->29->1851", "default.handlebars->29->193", - "default.handlebars->29->1945", + "default.handlebars->29->1938", "default.handlebars->29->614", "default.handlebars->29->745" ] @@ -37801,11 +37794,11 @@ "zh-chs": "用户组", "zh-cht": "用戶群", "xloc": [ - "default.handlebars->29->1438", - "default.handlebars->29->1439", - "default.handlebars->29->1831", - "default.handlebars->29->1997", - "default.handlebars->29->2016", + "default.handlebars->29->1431", + "default.handlebars->29->1432", + "default.handlebars->29->1824", + "default.handlebars->29->1990", + "default.handlebars->29->2009", "default.handlebars->29->677" ] }, @@ -37846,7 +37839,7 @@ "zh-chs": "用户组成员", "zh-cht": "用戶群成員", "xloc": [ - "default.handlebars->29->1994" + "default.handlebars->29->1987" ] }, { @@ -37886,9 +37879,9 @@ "zh-chs": "用户识别码", "zh-cht": "用戶識別碼", "xloc": [ - "default.handlebars->29->1503", - "default.handlebars->29->1912", - "default.handlebars->29->1913" + "default.handlebars->29->1496", + "default.handlebars->29->1905", + "default.handlebars->29->1906" ] }, { @@ -37908,8 +37901,8 @@ "zh-chs": "用户标识符", "zh-cht": "用戶標識符", "xloc": [ - "default.handlebars->29->1436", - "default.handlebars->29->1896" + "default.handlebars->29->1429", + "default.handlebars->29->1889" ] }, { @@ -37929,7 +37922,7 @@ "zh-chs": "用户列表输出", "zh-cht": "用戶列表輸出", "xloc": [ - "default.handlebars->29->1774" + "default.handlebars->29->1767" ] }, { @@ -37950,7 +37943,7 @@ "zh-cht": "用戶名", "xloc": [ "default-mobile.handlebars->9->458", - "default.handlebars->29->1502" + "default.handlebars->29->1495" ] }, { @@ -38025,7 +38018,7 @@ "zh-chs": "用户节", "zh-cht": "用戶節", "xloc": [ - "default.handlebars->29->2094" + "default.handlebars->29->2087" ] }, { @@ -38150,7 +38143,7 @@ "zh-chs": "用户组已更改:{0}", "zh-cht": "用戶組已更改:{0}", "xloc": [ - "default.handlebars->29->1667" + "default.handlebars->29->1660" ] }, { @@ -38170,7 +38163,7 @@ "zh-chs": "已创建用户组:{0}", "zh-cht": "已創建用戶組:{0}", "xloc": [ - "default.handlebars->29->1657" + "default.handlebars->29->1650" ] }, { @@ -38190,7 +38183,7 @@ "zh-chs": "用户组成员身份已更改:{0}", "zh-cht": "用戶組成員身份已更改:{0}", "xloc": [ - "default.handlebars->29->1655" + "default.handlebars->29->1648" ] }, { @@ -38256,7 +38249,7 @@ "zh-cht": "用戶名", "xloc": [ "default-mobile.handlebars->9->274", - "default.handlebars->29->1786", + "default.handlebars->29->1779", "default.handlebars->29->294", "default.handlebars->29->326", "default.handlebars->29->771", @@ -38331,9 +38324,9 @@ "zh-chs": "用户", "zh-cht": "用戶", "xloc": [ - "default.handlebars->29->1819", - "default.handlebars->29->1859", - "default.handlebars->29->2093", + "default.handlebars->29->1812", + "default.handlebars->29->1852", + "default.handlebars->29->2086", "default.handlebars->container->topbar->1->1->UsersSubMenuSpan->UsersSubMenu->1->0->UsersGeneral" ] }, @@ -38354,7 +38347,7 @@ "zh-chs": "用户会话", "zh-cht": "用戶節", "xloc": [ - "default.handlebars->29->2078" + "default.handlebars->29->2071" ] }, { @@ -38394,7 +38387,7 @@ "zh-chs": "验证电邮", "zh-cht": "驗證電郵", "xloc": [ - "default.handlebars->29->1741" + "default.handlebars->29->1734" ] }, { @@ -38478,7 +38471,7 @@ "zh-chs": "已验证", "zh-cht": "已驗證", "xloc": [ - "default.handlebars->29->1975" + "default.handlebars->29->1968" ] }, { @@ -38521,7 +38514,7 @@ "xloc": [ "default-mobile.handlebars->9->63", "default.handlebars->29->1027", - "default.handlebars->29->1737" + "default.handlebars->29->1730" ] }, { @@ -38541,7 +38534,7 @@ "zh-chs": "用户{0}的已验证电话号码", "zh-cht": "用戶{0}的已驗證電話號碼", "xloc": [ - "default.handlebars->29->1684" + "default.handlebars->29->1677" ] }, { @@ -38630,7 +38623,7 @@ "zh-chs": "版本不兼容,请先升级您的MeshCentral", "zh-cht": "版本不兼容,請先升級你的MeshCentral", "xloc": [ - "default.handlebars->29->2125" + "default.handlebars->29->2118" ] }, { @@ -38712,7 +38705,7 @@ "zh-chs": "查看所有事件", "zh-cht": "查看所有事件", "xloc": [ - "default.handlebars->29->1811" + "default.handlebars->29->1804" ] }, { @@ -38732,8 +38725,8 @@ "zh-chs": "查看变更日志", "zh-cht": "查看變更日誌", "xloc": [ - "default.handlebars->29->2128", - "default.handlebars->29->2130" + "default.handlebars->29->2121", + "default.handlebars->29->2123" ] }, { @@ -38793,7 +38786,7 @@ "zh-chs": "查看有关此用户的注释", "zh-cht": "查看有關此用戶的註釋", "xloc": [ - "default.handlebars->29->1953" + "default.handlebars->29->1946" ] }, { @@ -38917,8 +38910,8 @@ "xloc": [ "default-mobile.handlebars->9->430", "default-mobile.handlebars->9->445", - "default.handlebars->29->1458", - "default.handlebars->29->1488" + "default.handlebars->29->1451", + "default.handlebars->29->1481" ] }, { @@ -39044,8 +39037,8 @@ "zh-chs": "网络服务器", "zh-cht": "網絡伺服器", "xloc": [ - "default.handlebars->29->2114", - "default.handlebars->29->2115" + "default.handlebars->29->2107", + "default.handlebars->29->2108" ] }, { @@ -39065,7 +39058,7 @@ "zh-chs": "Web服务器请求", "zh-cht": "Web伺服器請求", "xloc": [ - "default.handlebars->29->2116" + "default.handlebars->29->2109" ] }, { @@ -39085,7 +39078,7 @@ "zh-chs": "Web套接字中继", "zh-cht": "Web插座中繼", "xloc": [ - "default.handlebars->29->2117" + "default.handlebars->29->2110" ] }, { @@ -39187,7 +39180,7 @@ "zh-chs": "启用后,任何人都可以使用邀请代码通过以下公共连结将设备加入该设备组:", "zh-cht": "啟用後,任何人都可以使用邀請代碼通過以下公共鏈結將裝置加入該裝置群:", "xloc": [ - "default.handlebars->29->1511" + "default.handlebars->29->1504" ] }, { @@ -39228,7 +39221,7 @@ "zh-chs": "下次登录时将更改。", "zh-cht": "下次登入時將更改。", "xloc": [ - "default.handlebars->29->1925" + "default.handlebars->29->1918" ] }, { @@ -40481,7 +40474,7 @@ "zh-chs": "\\\\'", "zh-cht": "\\\\'", "xloc": [ - "default.handlebars->29->2126" + "default.handlebars->29->2119" ] }, { @@ -40728,7 +40721,7 @@ "zh-cht": "複製", "xloc": [ "default-mobile.handlebars->9->130", - "default.handlebars->29->1583" + "default.handlebars->29->1576" ] }, { @@ -40827,8 +40820,8 @@ "zh-chs": "eventslist.csv", "zh-cht": "eventslist.csv", "xloc": [ - "default.handlebars->29->1706", - "default.handlebars->29->1711" + "default.handlebars->29->1699", + "default.handlebars->29->1704" ] }, { @@ -40848,8 +40841,8 @@ "zh-chs": "eventslist.json", "zh-cht": "eventslist.json", "xloc": [ - "default.handlebars->29->1708", - "default.handlebars->29->1712" + "default.handlebars->29->1701", + "default.handlebars->29->1705" ] }, { @@ -40889,8 +40882,8 @@ "zh-chs": "免费", "zh-cht": "免費", "xloc": [ - "default.handlebars->29->2086", - "default.handlebars->29->2089" + "default.handlebars->29->2079", + "default.handlebars->29->2082" ] }, { @@ -41105,7 +41098,7 @@ "zh-chs": "id, name, email, creation, lastlogin, groups, authfactors", "zh-cht": "id, name, email, creation, lastlogin, groups, authfactors", "xloc": [ - "default.handlebars->29->1775" + "default.handlebars->29->1768" ] }, { @@ -41228,7 +41221,7 @@ "zh-chs": "k max,默认为空白", "zh-cht": "k max,默認為空白", "xloc": [ - "default.handlebars->29->1803" + "default.handlebars->29->1796" ] }, { @@ -41370,7 +41363,7 @@ "zh-cht": "移動", "xloc": [ "default-mobile.handlebars->9->131", - "default.handlebars->29->1584" + "default.handlebars->29->1577" ] }, { @@ -41487,7 +41480,7 @@ "zh-chs": "servertrace.csv", "zh-cht": "servertrace.csv", "xloc": [ - "default.handlebars->29->2124" + "default.handlebars->29->2117" ] }, { @@ -41553,7 +41546,7 @@ "zh-chs": "time, conn.agent, conn.users, conn.usersessions, conn.relaysession, conn.intelamt, mem.external, mem.heapused, mem.heaptotal, mem.rss", "zh-cht": "time, conn.agent, conn.users, conn.usersessions, conn.relaysession, conn.intelamt, mem.external, mem.heapused, mem.heaptotal, mem.rss", "xloc": [ - "default.handlebars->29->2102" + "default.handlebars->29->2095" ] }, { @@ -41573,7 +41566,7 @@ "zh-chs": "时间,来源,信息", "zh-cht": "時間,來源,訊息", "xloc": [ - "default.handlebars->29->2123" + "default.handlebars->29->2116" ] }, { @@ -41610,8 +41603,8 @@ "zh-chs": "总计", "zh-cht": "總", "xloc": [ - "default.handlebars->29->2087", - "default.handlebars->29->2090" + "default.handlebars->29->2080", + "default.handlebars->29->2083" ] }, { @@ -41694,8 +41687,8 @@ "zh-chs": "userlist.csv", "zh-cht": "userlist.csv", "xloc": [ - "default.handlebars->29->1771", - "default.handlebars->29->1776" + "default.handlebars->29->1764", + "default.handlebars->29->1769" ] }, { @@ -41715,8 +41708,8 @@ "zh-chs": "userlist.json", "zh-cht": "userlist.json", "xloc": [ - "default.handlebars->29->1773", - "default.handlebars->29->1777" + "default.handlebars->29->1766", + "default.handlebars->29->1770" ] }, { @@ -41736,7 +41729,7 @@ "zh-chs": "utc,时间,类型,指令,用户,设备,消息", "zh-cht": "utc,時間,類型,指令,用戶,裝置,消息", "xloc": [ - "default.handlebars->29->1710" + "default.handlebars->29->1703" ] }, { @@ -41776,8 +41769,8 @@ "zh-chs": "{0} Gb", "zh-cht": "{0} Gb", "xloc": [ - "default.handlebars->29->1558", - "default.handlebars->29->1563" + "default.handlebars->29->1551", + "default.handlebars->29->1556" ] }, { @@ -41797,9 +41790,9 @@ "zh-chs": "{0} Kb", "zh-cht": "{0} Kb", "xloc": [ - "default.handlebars->29->1556", - "default.handlebars->29->1561", - "default.handlebars->29->2027" + "default.handlebars->29->1549", + "default.handlebars->29->1554", + "default.handlebars->29->2020" ] }, { @@ -41823,8 +41816,8 @@ "default-mobile.handlebars->9->395", "default.handlebars->29->1001", "default.handlebars->29->1006", - "default.handlebars->29->1557", - "default.handlebars->29->1562" + "default.handlebars->29->1550", + "default.handlebars->29->1555" ] }, { @@ -41865,7 +41858,7 @@ "zh-chs": "{0}个活跃会话", "zh-cht": "{0}個活躍節", "xloc": [ - "default.handlebars->29->1965" + "default.handlebars->29->1958" ] }, { @@ -41885,8 +41878,8 @@ "zh-chs": "{0} b", "zh-cht": "{0} b", "xloc": [ - "default.handlebars->29->1555", - "default.handlebars->29->1560" + "default.handlebars->29->1548", + "default.handlebars->29->1553" ] }, { @@ -41907,8 +41900,8 @@ "zh-cht": "{0}個字節", "xloc": [ "default-mobile.handlebars->9->119", - "default.handlebars->29->1571", - "default.handlebars->29->2042", + "default.handlebars->29->1564", + "default.handlebars->29->2035", "download.handlebars->3->2", "download2.handlebars->5->2" ] @@ -41930,7 +41923,7 @@ "zh-chs": "剩余{0}个字节", "zh-cht": "剩餘{0}個字節", "xloc": [ - "default.handlebars->29->1550" + "default.handlebars->29->1543" ] }, { @@ -41971,7 +41964,7 @@ "zh-chs": "剩余{0} GB", "zh-cht": "剩餘{0} GB", "xloc": [ - "default.handlebars->29->1553" + "default.handlebars->29->1546" ] }, { @@ -41991,7 +41984,7 @@ "zh-chs": "{0}个群组", "zh-cht": "{0}個群組", "xloc": [ - "default.handlebars->29->1930" + "default.handlebars->29->1923" ] }, { @@ -42048,7 +42041,7 @@ "zh-chs": "剩余{0}千字节", "zh-cht": "剩餘{0}千字節", "xloc": [ - "default.handlebars->29->1551" + "default.handlebars->29->1544" ] }, { @@ -42090,7 +42083,7 @@ "zh-chs": "剩余{0}兆字节", "zh-cht": "剩餘{0}兆字節", "xloc": [ - "default.handlebars->29->1552" + "default.handlebars->29->1545" ] }, { @@ -42130,7 +42123,7 @@ "zh-chs": "{0}未显示更多用户,请使用搜索框查找用户...", "zh-cht": "{0}未顯示更多用戶,請使用搜索框查找用戶...", "xloc": [ - "default.handlebars->29->1720" + "default.handlebars->29->1713" ] }, { @@ -42294,7 +42287,7 @@ "default-mobile.handlebars->9->173", "default-mobile.handlebars->9->176", "default-mobile.handlebars->9->179", - "default.handlebars->29->1724", + "default.handlebars->29->1717", "default.handlebars->29->251", "default.handlebars->29->254", "default.handlebars->29->257", @@ -42442,7 +42435,7 @@ "zh-chs": "{0}k在1档案内。最多{1}k", "zh-cht": "{0}k在1檔案內。最多{1}k", "xloc": [ - "default.handlebars->29->1565" + "default.handlebars->29->1558" ] }, { @@ -42462,7 +42455,7 @@ "zh-chs": "{1}k在{0}个档案中。最多{2}k", "zh-cht": "{1}k在{0}個檔案中。最多{2}k", "xloc": [ - "default.handlebars->29->1564" + "default.handlebars->29->1557" ] }, { diff --git a/views/default.handlebars b/views/default.handlebars index 3ca8dab5..8b2b65c6 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -5674,8 +5674,8 @@ str += (', v' + EscapeHtml(node.intelamt.ver)); } - if (node.intelamt.tls == 1) { str += ', ' + "TLS" + ''; } if (node.intelamt.state == 2) { + if (node.intelamt.tls == 1) { str += ', ' + "TLS" + ''; } if (node.intelamt.user == null || node.intelamt.user == '') { if ((meshrights & 4) != 0) { str += ', ' + "No Credentials" + '';