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" + '';