Intel AMT configuration now automated thru agent.

Ylian Saint-Hilaire committed Oct 21, 2020 at 15:36 UTC b04d1b7d4a581e1b3d59653f98a624ebc77fca9b
7 files changed +8651 -853
agents/meshcore-bad.js new
+3924
@@ -0,0 +1,3924 @@
1 +/*
2 +Copyright 2018-2020 Intel Corporation
3 +
4 +Licensed under the Apache License, Version 2.0 (the "License");
5 +you may not use this file except in compliance with the License.
6 +You may obtain a copy of the License at
7 +
8 + http://www.apache.org/licenses/LICENSE-2.0
9 +
10 +Unless required by applicable law or agreed to in writing, software
11 +distributed under the License is distributed on an "AS IS" BASIS,
12 +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 +See the License for the specific language governing permissions and
14 +limitations under the License.
15 +*/
16 +
17 +process.on('uncaughtException', function (ex) {
18 + require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: "uncaughtException1: " + ex });
19 +});
20 +
21 +// NOTE: This seems to cause big problems, don't enable the debugger in the server's meshcore.
22 +//attachDebugger({ webport: 9999, wait: 1 }).then(function (prt) { console.log('Point Browser for Debug to port: ' + prt); });
23 +
24 +// Mesh Rights
25 +var MNG_ERROR = 65;
26 +var MESHRIGHT_EDITMESH = 1;
27 +var MESHRIGHT_MANAGEUSERS = 2;
28 +var MESHRIGHT_MANAGECOMPUTERS = 4;
29 +var MESHRIGHT_REMOTECONTROL = 8;
30 +var MESHRIGHT_AGENTCONSOLE = 16;
31 +var MESHRIGHT_SERVERFILES = 32;
32 +var MESHRIGHT_WAKEDEVICE = 64;
33 +var MESHRIGHT_SETNOTES = 128;
34 +var MESHRIGHT_REMOTEVIEW = 256;
35 +var MESHRIGHT_NOTERMINAL = 512;
36 +var MESHRIGHT_NOFILES = 1024;
37 +var MESHRIGHT_NOAMT = 2048;
38 +var MESHRIGHT_LIMITEDINPUT = 4096;
39 +var MESHRIGHT_LIMITEVENTS = 8192;
40 +var MESHRIGHT_CHATNOTIFY = 16384;
41 +var MESHRIGHT_UNINSTALL = 32768;
42 +var MESHRIGHT_NODESKTOP = 65536;
43 +
44 +function createMeshCore(agent) {
45 + var obj = {};
46 + if (process.platform == 'win32' && require('user-sessions').isRoot()) {
47 + // Check the Agent Uninstall MetaData for correctness, as the installer may have written an incorrect value
48 + try {
49 + var writtenSize = 0, actualSize = Math.floor(require('fs').statSync(process.execPath).size / 1024);
50 + try { writtenSize = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MeshCentralAgent', 'EstimatedSize'); } catch (e) { }
51 + if (writtenSize != actualSize) { try { require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MeshCentralAgent', 'EstimatedSize', actualSize); } catch (e) { } }
52 + } catch (x) { }
53 +
54 + // Check to see if we are the Installed Mesh Agent Service, if we are, make sure we can run in Safe Mode
55 + try {
56 + var meshCheck = false;
57 + try { meshCheck = require('service-manager').manager.getService('Mesh Agent').isMe(); } catch (e) { }
58 + if (meshCheck && require('win-bcd').isSafeModeService && !require('win-bcd').isSafeModeService('Mesh Agent')) { require('win-bcd').enableSafeModeService('Mesh Agent'); }
59 + } catch (e) { }
60 + }
61 +
62 + if (process.platform == 'darwin' && !process.versions) {
63 + // This is an older MacOS Agent, so we'll need to check the service definition so that Auto-Update will function correctly
64 + var child = require('child_process').execFile('/bin/sh', ['sh']);
65 + child.stdout.str = '';
66 + child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });
67 + child.stdin.write("cat /Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist | tr '\n' '\.' | awk '{split($0, a, \"<key>KeepAlive</key>\"); split(a[2], b, \"<\"); split(b[2], c, \">\"); ");
68 + child.stdin.write(" if(c[1]==\"dict\"){ split(a[2], d, \"</dict>\"); if(split(d[1], truval, \"<true/>\")>1) { split(truval[1], kn1, \"<key>\"); split(kn1[2], kn2, \"</key>\"); print kn2[1]; } }");
69 + child.stdin.write(" else { split(c[1], ka, \"/\"); if(ka[1]==\"true\") {print \"ALWAYS\";} } }'\nexit\n");
70 + child.waitExit();
71 + if (child.stdout.str.trim() == 'Crashed') {
72 + child = require('child_process').execFile('/bin/sh', ['sh']);
73 + child.stdout.str = '';
74 + child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });
75 + child.stdin.write("launchctl list | grep 'meshagent' | awk '{ if($3==\"meshagent\"){print $1;}}'\nexit\n");
76 + child.waitExit();
77 +
78 + if (parseInt(child.stdout.str.trim()) == process.pid) {
79 + // The currently running MeshAgent is us, so we can continue with the update
80 + var plist = require('fs').readFileSync('/Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist').toString();
81 + var tokens = plist.split('<key>KeepAlive</key>');
82 + if (tokens[1].split('>')[0].split('<')[1] == 'dict') {
83 + var tmp = tokens[1].split('</dict>');
84 + tmp.shift();
85 + tokens[1] = '\n <true/>' + tmp.join('</dict>');
86 + tokens = tokens.join('<key>KeepAlive</key>');
87 +
88 + require('fs').writeFileSync('/Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist', tokens);
89 +
90 + var fix = '';
91 + fix += ("function macosRepair()\n");
92 + fix += ("{\n");
93 + fix += (" var child = require('child_process').execFile('/bin/sh', ['sh']);\n");
94 + fix += (" child.stdout.str = '';\n");
95 + fix += (" child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });\n");
96 + fix += (" child.stderr.on('data', function (chunk) { });\n");
97 + fix += (" child.stdin.write('launchctl unload /Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist\\n');\n");
98 + fix += (" child.stdin.write('launchctl load /Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist\\n');\n");
99 + fix += (" child.stdin.write('rm /Library/LaunchDaemons/meshagentRepair.plist\\n');\n");
100 + fix += (" child.stdin.write('rm " + process.cwd() + "/macosRepair.js\\n');\n");
101 + fix += (" child.stdin.write('launchctl stop meshagentRepair\\nexit\\n');\n");
102 + fix += (" child.waitExit();\n");
103 + fix += ("}\n");
104 + fix += ("macosRepair();\n");
105 + fix += ("process.exit();\n");
106 + require('fs').writeFileSync(process.cwd() + '/macosRepair.js', fix);
107 +
108 + var plist = '<?xml version="1.0" encoding="UTF-8"?>\n';
109 + plist += '<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n';
110 + plist += '<plist version="1.0">\n';
111 + plist += ' <dict>\n';
112 + plist += ' <key>Label</key>\n';
113 + plist += (' <string>meshagentRepair</string>\n');
114 + plist += ' <key>ProgramArguments</key>\n';
115 + plist += ' <array>\n';
116 + plist += (' <string>' + process.execPath + '</string>\n');
117 + plist += ' <string>macosRepair.js</string>\n';
118 + plist += ' </array>\n';
119 + plist += ' <key>WorkingDirectory</key>\n';
120 + plist += (' <string>' + process.cwd() + '</string>\n');
121 + plist += ' <key>RunAtLoad</key>\n';
122 + plist += ' <true/>\n';
123 + plist += ' </dict>\n';
124 + plist += '</plist>';
125 + require('fs').writeFileSync('/Library/LaunchDaemons/meshagentRepair.plist', plist);
126 +
127 + child = require('child_process').execFile('/bin/sh', ['sh']);
128 + child.stdout.str = '';
129 + child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });
130 + child.stdin.write("launchctl load /Library/LaunchDaemons/meshagentRepair.plist\nexit\n");
131 + child.waitExit();
132 + }
133 + }
134 + }
135 + }
136 +
137 + // Create Secure IPC for Diagnostic Agent Communications
138 + obj.DAIPC = require('net').createServer();
139 + if (process.platform != 'win32') { try { require('fs').unlinkSync(process.cwd() + '/DAIPC'); } catch (e) { } }
140 + obj.DAIPC.IPCPATH = process.platform == 'win32' ? ('\\\\.\\pipe\\' + require('_agentNodeId')() + '-DAIPC') : (process.cwd() + '/DAIPC');
141 + try { obj.DAIPC.listen({ path: obj.DAIPC.IPCPATH, writableAll: true, maxConnections: 5 }); } catch (e) { }
142 + obj.DAIPC._daipc = [];
143 + obj.DAIPC.on('connection', function (c) {
144 + c._send = function (j) {
145 + var data = JSON.stringify(j);
146 + var packet = Buffer.alloc(data.length + 4);
147 + packet.writeUInt32LE(data.length + 4, 0);
148 + Buffer.from(data).copy(packet, 4);
149 + this.write(packet);
150 + };
151 + this._daipc.push(c);
152 + c.parent = this;
153 + c.on('end', function () { removeRegisteredApp(this); });
154 + c.on('data', function (chunk) {
155 + if (chunk.length < 4) { this.unshift(chunk); return; }
156 + var len = chunk.readUInt32LE(0);
157 + if (len > 8192) { removeRegisteredApp(this); this.end(); return; }
158 + if (chunk.length < len) { this.unshift(chunk); return; }
159 +
160 + var data = chunk.slice(4, len);
161 + try { data = JSON.parse(data.toString()); } catch (e) { }
162 + if ((data == null) || (typeof data.cmd != 'string')) return;
163 +
164 + try {
165 + switch (data.cmd) {
166 + case 'requesthelp':
167 + if (this._registered == null) return;
168 + sendConsoleText('Request Help (' + this._registered + '): ' + data.value);
169 + var help = {};
170 + help[this._registered] = data.value;
171 + try { mesh.SendCommand({ action: 'sessions', type: 'help', value: help }); } catch (e) { }
172 + MeshServerLogEx(98, [this._registered, data.value], "Help Requested, user: " + this._registered + ", details: " + data.value, null);
173 + break;
174 + case 'cancelhelp':
175 + if (this._registered == null) return;
176 + sendConsoleText('Cancel Help (' + this._registered + ')');
177 + try { mesh.SendCommand({ action: 'sessions', type: 'help', value: {} }); } catch (e) { }
178 + break;
179 + case 'register':
180 + if (typeof data.value == 'string') {
181 + this._registered = data.value;
182 + var apps = {};
183 + apps[data.value] = 1;
184 + try { mesh.SendCommand({ action: 'sessions', type: 'app', value: apps }); } catch (e) { }
185 + this._send({ cmd: 'serverstate', value: meshServerConnectionState, url: require('MeshAgent').ConnectedServer, amt: (amt != null) });
186 + }
187 + break;
188 + case 'query':
189 + switch (data.value) {
190 + case 'connection':
191 + data.result = require('MeshAgent').ConnectedServer;
192 + this._send(data);
193 + break;
194 + case 'descriptors':
195 + require('ChainViewer').getSnapshot().then(function (f) {
196 + this.tag.payload.result = f;
197 + this.tag.ipc._send(this.tag.payload);
198 + }).parentPromise.tag = { ipc: this, payload: data };
199 + break;
200 + }
201 + break;
202 + case 'amtstate':
203 + if (amt == null) return;
204 + var func = function amtStateFunc(state) { if (state != null) { amtStateFunc.pipe._send({ cmd: 'amtstate', value: state }); } }
205 + func.pipe = this;
206 + amt.getAmtInfo(func);
207 + break;
208 + case 'sessions':
209 + this._send({ cmd: 'sessions', sessions: tunnelUserCount });
210 + break;
211 + }
212 + }
213 + catch (e) { removeRegisteredApp(this); this.end(); return; }
214 + });
215 + });
216 +
217 + // Send current sessions to registered apps
218 + function broadcastSessionsToRegisteredApps(x) {
219 + broadcastToRegisteredApps({ cmd: 'sessions', sessions: tunnelUserCount });
220 + }
221 +
222 + // Send this object to all registered local applications
223 + function broadcastToRegisteredApps(x) {
224 + if ((obj.DAIPC == null) || (obj.DAIPC._daipc == null)) return;
225 + for (var i in obj.DAIPC._daipc) { if (obj.DAIPC._daipc[i]._registered != null) { obj.DAIPC._daipc[i]._send(x); } }
226 + }
227 +
228 + // Send list of registered apps to the server
229 + function updateRegisteredAppsToServer() {
230 + if ((obj.DAIPC == null) || (obj.DAIPC._daipc == null)) return;
231 + var apps = {};
232 + 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]++; } }
233 + try { mesh.SendCommand({ action: 'sessions', type: 'app', value: apps }); } catch (e) { }
234 + }
235 +
236 + // Remove a registered app
237 + function removeRegisteredApp(pipe) {
238 + for (var i = obj.DAIPC._daipc.length - 1; i >= 0; i--) { if (obj.DAIPC._daipc[i] === pipe) { obj.DAIPC._daipc.splice(i, 1); } }
239 + if (pipe._registered != null) updateRegisteredAppsToServer();
240 + }
241 +
242 + function diagnosticAgent_uninstall() {
243 + require('service-manager').manager.uninstallService('meshagentDiagnostic');
244 + require('task-scheduler').delete('meshagentDiagnostic/periodicStart');
245 + };
246 + function diagnosticAgent_installCheck(install) {
247 + try {
248 + var diag = require('service-manager').manager.getService('meshagentDiagnostic');
249 + return (diag);
250 + }
251 + catch (e) {
252 + }
253 + if (!install) { return (null); }
254 +
255 + var svc = null;
256 + try {
257 + require('service-manager').manager.installService(
258 + {
259 + name: 'meshagentDiagnostic',
260 + displayName: "Mesh Agent Diagnostic Service",
261 + description: "Mesh Agent Diagnostic Service",
262 + servicePath: process.execPath,
263 + parameters: ['-recovery']
264 + //files: [{ newName: 'diagnostic.js', _buffer: Buffer.from('LyoNCkNvcHlyaWdodCAyMDE5IEludGVsIENvcnBvcmF0aW9uDQoNCkxpY2Vuc2VkIHVuZGVyIHRoZSBBcGFjaGUgTGljZW5zZSwgVmVyc2lvbiAyLjAgKHRoZSAiTGljZW5zZSIpOw0KeW91IG1heSBub3QgdXNlIHRoaXMgZmlsZSBleGNlcHQgaW4gY29tcGxpYW5jZSB3aXRoIHRoZSBMaWNlbnNlLg0KWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0DQoNCiAgICBodHRwOi8vd3d3LmFwYWNoZS5vcmcvbGljZW5zZXMvTElDRU5TRS0yLjANCg0KVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQ0KZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywNCldJVEhPVVQgV0FSUkFOVElFUyBPUiBDT05ESVRJT05TIE9GIEFOWSBLSU5ELCBlaXRoZXIgZXhwcmVzcyBvciBpbXBsaWVkLg0KU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZA0KbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuDQoqLw0KDQp2YXIgaG9zdCA9IHJlcXVpcmUoJ3NlcnZpY2UtaG9zdCcpLmNyZWF0ZSgnbWVzaGFnZW50RGlhZ25vc3RpYycpOw0KdmFyIFJlY292ZXJ5QWdlbnQgPSByZXF1aXJlKCdNZXNoQWdlbnQnKTsNCg0KaG9zdC5vbignc2VydmljZVN0YXJ0JywgZnVuY3Rpb24gKCkNCnsNCiAgICBjb25zb2xlLnNldERlc3RpbmF0aW9uKGNvbnNvbGUuRGVzdGluYXRpb25zLkxPR0ZJTEUpOw0KICAgIGhvc3Quc3RvcCA9IGZ1bmN0aW9uKCkNCiAgICB7DQogICAgICAgIHJlcXVpcmUoJ3NlcnZpY2UtbWFuYWdlcicpLm1hbmFnZXIuZ2V0U2VydmljZSgnbWVzaGFnZW50RGlhZ25vc3RpYycpLnN0b3AoKTsNCiAgICB9DQogICAgUmVjb3ZlcnlBZ2VudC5vbignQ29ubmVjdGVkJywgZnVuY3Rpb24gKHN0YXR1cykNCiAgICB7DQogICAgICAgIGlmIChzdGF0dXMgPT0gMCkNCiAgICAgICAgew0KICAgICAgICAgICAgY29uc29sZS5sb2coJ0RpYWdub3N0aWMgQWdlbnQ6IFNlcnZlciBjb25uZWN0aW9uIGxvc3QuLi4nKTsNCiAgICAgICAgICAgIHJldHVybjsNCiAgICAgICAgfQ0KICAgICAgICBjb25zb2xlLmxvZygnRGlhZ25vc3RpYyBBZ2VudDogQ29ubmVjdGlvbiBFc3RhYmxpc2hlZCB3aXRoIFNlcnZlcicpOw0KICAgICAgICBzdGFydCgpOw0KICAgIH0pOw0KfSk7DQpob3N0Lm9uKCdub3JtYWxTdGFydCcsIGZ1bmN0aW9uICgpDQp7DQogICAgaG9zdC5zdG9wID0gZnVuY3Rpb24gKCkNCiAgICB7DQogICAgICAgIHByb2Nlc3MuZXhpdCgpOw0KICAgIH0NCiAgICBjb25zb2xlLmxvZygnTm9uIFNlcnZpY2UgTW9kZScpOw0KICAgIFJlY292ZXJ5QWdlbnQub24oJ0Nvbm5lY3RlZCcsIGZ1bmN0aW9uIChzdGF0dXMpDQogICAgew0KICAgICAgICBpZiAoc3RhdHVzID09IDApDQogICAgICAgIHsNCiAgICAgICAgICAgIGNvbnNvbGUubG9nKCdEaWFnbm9zdGljIEFnZW50OiBTZXJ2ZXIgY29ubmVjdGlvbiBsb3N0Li4uJyk7DQogICAgICAgICAgICByZXR1cm47DQogICAgICAgIH0NCiAgICAgICAgY29uc29sZS5sb2coJ0RpYWdub3N0aWMgQWdlbnQ6IENvbm5lY3Rpb24gRXN0YWJsaXNoZWQgd2l0aCBTZXJ2ZXInKTsNCiAgICAgICAgc3RhcnQoKTsNCiAgICB9KTsNCn0pOw0KaG9zdC5vbignc2VydmljZVN0b3AnLCBmdW5jdGlvbiAoKSB7IHByb2Nlc3MuZXhpdCgpOyB9KTsNCmhvc3QucnVuKCk7DQoNCg0KZnVuY3Rpb24gc3RhcnQoKQ0Kew0KDQp9Ow0K', 'base64') }]
265 + });
266 + svc = require('service-manager').manager.getService('meshagentDiagnostic');
267 + }
268 + catch (e) {
269 + return (null);
270 + }
271 + var proxyConfig = require('global-tunnel').proxyConfig;
272 + var cert = require('MeshAgent').GenerateAgentCertificate('CN=MeshNodeDiagnosticCertificate');
273 + var nodeid = require('tls').loadCertificate(cert.root).getKeyHash().toString('base64');
274 + ddb = require('SimpleDataStore').Create(svc.appWorkingDirectory().replace('\\', '/') + '/meshagentDiagnostic.db');
275 + ddb.Put('disableUpdate', '1');
276 + ddb.Put('MeshID', Buffer.from(require('MeshAgent').ServerInfo.MeshID, 'hex'));
277 + ddb.Put('ServerID', require('MeshAgent').ServerInfo.ServerID);
278 + ddb.Put('MeshServer', require('MeshAgent').ServerInfo.ServerUri);
279 + if (cert.root.pfx) { ddb.Put('SelfNodeCert', cert.root.pfx); }
280 + if (cert.tls) { ddb.Put('SelfNodeTlsCert', cert.tls.pfx); }
281 + if (proxyConfig) {
282 + ddb.Put('WebProxy', proxyConfig.host + ':' + proxyConfig.port);
283 + } else {
284 + ddb.Put('ignoreProxyFile', '1');
285 + }
286 +
287 + require('MeshAgent').SendCommand({ action: 'diagnostic', value: { command: 'register', value: nodeid } });
288 + require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: "Diagnostic Agent Registered [" + nodeid.length + "/" + nodeid + "]" });
289 +
290 + delete ddb;
291 +
292 + // Set a recurrent task, to run the Diagnostic Agent every 2 days
293 + 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' });
294 + //require('task-scheduler').create({ name: 'meshagentDiagnostic/periodicStart', daily: '1', time: '17:16', service: 'meshagentDiagnostic' });
295 +
296 + return (svc);
297 + }
298 +
299 + // Monitor the file 'batterystate.txt' in the agent's folder and sends battery update when this file is changed.
300 + if ((require('fs').existsSync(process.cwd() + 'batterystate.txt')) && (require('fs').watch != null)) {
301 + // Setup manual battery monitoring
302 + require('MeshAgent')._batteryFileWatcher = require('fs').watch(process.cwd(), function () {
303 + if (require('MeshAgent')._batteryFileTimer != null) return;
304 + require('MeshAgent')._batteryFileTimer = setTimeout(function () {
305 + try {
306 + require('MeshAgent')._batteryFileTimer = null;
307 + var data = null;
308 + try { data = require('fs').readFileSync(process.cwd() + 'batterystate.txt').toString(); } catch (e) { }
309 + if ((data != null) && (data.length < 10)) {
310 + data = data.split(',');
311 + if ((data.length == 2) && ((data[0] == 'ac') || (data[0] == 'dc'))) {
312 + var level = parseInt(data[1]);
313 + if ((level >= 0) && (level <= 100)) { require('MeshAgent').SendCommand({ action: 'battery', state: data[0], level: level }); }
314 + }
315 + }
316 + } catch (e) { }
317 + }, 1000);
318 + });
319 + } else {
320 + // Setup normal battery monitoring
321 + if (require('identifiers').isBatteryPowered && require('identifiers').isBatteryPowered()) {
322 + require('MeshAgent')._battLevelChanged = function _battLevelChanged(val) {
323 + _battLevelChanged.self._currentBatteryLevel = val;
324 + _battLevelChanged.self.SendCommand({ action: 'battery', state: _battLevelChanged.self._currentPowerState, level: val });
325 + };
326 + require('MeshAgent')._battLevelChanged.self = require('MeshAgent');
327 + require('MeshAgent')._powerChanged = function _powerChanged(val) {
328 + _powerChanged.self._currentPowerState = (val == 'AC' ? 'ac' : 'dc');
329 + _powerChanged.self.SendCommand({ action: 'battery', state: (val == 'AC' ? 'ac' : 'dc'), level: _powerChanged.self._currentBatteryLevel });
330 + };
331 + require('MeshAgent')._powerChanged.self = require('MeshAgent');
332 + require('MeshAgent').on('Connected', function (status) {
333 + if (status == 0) {
334 + require('power-monitor').removeListener('acdc', this._powerChanged);
335 + require('power-monitor').removeListener('batteryLevel', this._battLevelChanged);
336 + } else {
337 + require('power-monitor').on('acdc', this._powerChanged);
338 + require('power-monitor').on('batteryLevel', this._battLevelChanged);
339 + }
340 + });
341 + }
342 + }
343 +
344 +
345 + /*
346 + function borderController() {
347 + this.container = null;
348 + this.Start = function Start(user) {
349 + if (this.container == null) {
350 + if (process.platform == 'win32') {
351 + try {
352 + this.container = require('ScriptContainer').Create({ processIsolation: 1, sessionId: user.SessionId });
353 + } catch (e) {
354 + this.container = require('ScriptContainer').Create({ processIsolation: 1 });
355 + }
356 + } else {
357 + this.container = require('ScriptContainer').Create({ processIsolation: 1, sessionId: user.uid });
358 + }
359 + this.container.parent = this;
360 + this.container.addModule('monitor-info', getJSModule('monitor-info'));
361 + this.container.addModule('monitor-border', getJSModule('monitor-border'));
362 + this.container.addModule('promise', getJSModule('promise'));
363 + this.container.once('exit', function (code) { sendConsoleText('Border Process Exited with code: ' + code); this.parent.container = this.parent._container = null; });
364 + this.container.ExecuteString("var border = require('monitor-border'); border.Start();");
365 + }
366 + }
367 + this.Stop = function Stop() {
368 + if (this.container != null) {
369 + this._container = this.container;
370 + this._container.parent = this;
371 + this.container = null;
372 + this._container.exit();
373 + }
374 + }
375 + }
376 + obj.borderManager = new borderController();
377 + */
378 +
379 + // MeshAgent JavaScript Core Module. This code is sent to and running on the mesh agent.
380 + 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
381 +
382 +
383 + // Get the operating system description string
384 + try { require('os').name().then(function (v) { meshCoreObj.osdesc = v; }); } catch (e) { }
385 +
386 + var meshServerConnectionState = 0;
387 + var tunnels = {};
388 + var lastMeInfo = null;
389 + var lastNetworkInfo = null;
390 + var lastPublicLocationInfo = null;
391 + var selfInfoUpdateTimer = null;
392 + var http = require('http');
393 + var net = require('net');
394 + var fs = require('fs');
395 + var rtc = require('ILibWebRTC');
396 + var amt = null;
397 + var processManager = require('process-manager');
398 + var wifiScannerLib = null;
399 + var wifiScanner = null;
400 + var networkMonitor = null;
401 + var amtscanner = null;
402 + var nextTunnelIndex = 1;
403 + var apftunnel = null;
404 + var tunnelUserCount = { terminal: {}, files: {}, tcp: {}, udp: {}, msg: {} }; // List of userid->count sessions for terminal, files and TCP/UDP routing
405 +
406 + // Add to the server event log
407 + function MeshServerLog(msg, state) {
408 + if (typeof msg == 'string') { msg = { action: 'log', msg: msg }; } else { msg.action = 'log'; }
409 + if (state) {
410 + if (state.userid) { msg.userid = state.userid; }
411 + if (state.username) { msg.username = state.username; }
412 + if (state.sessionid) { msg.sessionid = state.sessionid; }
413 + if (state.remoteaddr) { msg.remoteaddr = state.remoteaddr; }
414 + }
415 + mesh.SendCommand(msg);
416 + }
417 +
418 + // Add to the server event log, use internationalized events
419 + function MeshServerLogEx(id, args, msg, state) {
420 + var msg = { action: 'log', msgid: id, msgArgs: args, msg: msg };
421 + if (state) {
422 + if (state.userid) { msg.userid = state.userid; }
423 + if (state.username) { msg.username = state.username; }
424 + if (state.sessionid) { msg.sessionid = state.sessionid; }
425 + if (state.remoteaddr) { msg.remoteaddr = state.remoteaddr; }
426 + }
427 + mesh.SendCommand(msg);
428 + }
429 +
430 + // If we are running in Duktape, agent will be null
431 + if (agent == null) {
432 + // Running in native agent, Import libraries
433 + db = require('SimpleDataStore').Shared();
434 + sha = require('SHA256Stream');
435 + mesh = require('MeshAgent');
436 + childProcess = require('child_process');
437 + if (mesh.hasKVM == 1) { // if the agent is compiled with KVM support
438 + // Check if this computer supports a desktop
439 + try
440 + {
441 + if ((process.platform == 'win32') || (process.platform == 'darwin') || (require('monitor-info').kvm_x11_support))
442 + {
443 + meshCoreObj.caps |= 1;
444 + }
445 + else if(process.platform == 'linux' || process.platform == 'freebsd')
446 + {
447 + require('monitor-info').on('kvmSupportDetected', function (value)
448 + {
449 + meshCoreObj.caps |= 1;
450 + mesh.SendCommand(meshCoreObj);
451 + });
452 + }
453 + } catch (e) { }
454 + }
455 + } else {
456 + // Running in nodejs
457 + meshCoreObj.value += '-NodeJS';
458 + meshCoreObj.caps = 8;
459 + mesh = agent.getMeshApi();
460 + }
461 +
462 + mesh.DAIPC = obj.DAIPC;
463 +
464 + /*
465 + var AMTScanner = require("AMTScanner");
466 + var scan = new AMTScanner();
467 +
468 + scan.on("found", function (data) {
469 + if (typeof data === 'string') {
470 + console.log(data);
471 + } else {
472 + console.log(JSON.stringify(data, null, " "));
473 + }
474 + });
475 + scan.scan("10.2.55.140", 1000);
476 + scan.scan("10.2.55.139-10.2.55.145", 1000);
477 + scan.scan("10.2.55.128/25", 2000);
478 + */
479 +
480 + /*
481 + // Try to load up the network monitor
482 + try {
483 + networkMonitor = require('NetworkMonitor');
484 + networkMonitor.on('change', function () { sendNetworkUpdateNagle(); });
485 + networkMonitor.on('add', function (addr) { sendNetworkUpdateNagle(); });
486 + networkMonitor.on('remove', function (addr) { sendNetworkUpdateNagle(); });
487 + } catch (e) { networkMonitor = null; }
488 + */
489 +
490 + // Try to load up the Intel AMT scanner
491 + try {
492 + var AMTScannerModule = require('amt-scanner');
493 + amtscanner = new AMTScannerModule();
494 + //amtscanner.on('found', function (data) { if (typeof data != 'string') { data = JSON.stringify(data, null, " "); } sendConsoleText(data); });
495 + } catch (e) { amtscanner = null; }
496 +
497 + // Fetch the SMBios Tables
498 + var SMBiosTables = null;
499 + var SMBiosTablesRaw = null;
500 + try {
501 + var SMBiosModule = null;
502 + try { SMBiosModule = require('smbios'); } catch (e) { }
503 + if (SMBiosModule != null) {
504 + SMBiosModule.get(function (data) {
505 + if (data != null) {
506 + SMBiosTablesRaw = data;
507 + SMBiosTables = require('smbios').parse(data)
508 + if (mesh.isControlChannelConnected) { mesh.SendCommand({ action: 'smbios', value: SMBiosTablesRaw }); }
509 +
510 + // If SMBios tables say that Intel AMT is present, try to connect MEI
511 + if (SMBiosTables.amtInfo && (SMBiosTables.amtInfo.AMT == true))
512 + {
513 + var amtmodule = require('amt-manage');
514 + amt = new amtmodule(mesh, db, false);
515 + amt.on('portBinding_LMS', function (map)
516 + {
517 + var j = { action: 'lmsinfo', value: { ports: map.keys() } };
518 + mesh.SendCommand(j);
519 + });
520 + amt.on('stateChange_LMS', function (v)
521 + {
522 + if (!meshCoreObj.intelamt) { meshCoreObj.intelamt = {}; }
523 + switch(v)
524 + {
525 + case 0:
526 + meshCoreObj.intelamt.microlms = 'DISABLED';
527 + break;
528 + case 1:
529 + meshCoreObj.intelamt.microlms = 'CONNECTING';
530 + break;
531 + case 2:
532 + meshCoreObj.intelamt.microlms = 'CONNECTED';
533 + break;
534 + default:
535 + break;
536 + }
537 + mesh.SendCommand(meshCoreObj);
538 + });
539 + amt.onStateChange = function (state) { if (state == 2) { sendPeriodicServerUpdate(1); } }
540 + amt.start();
541 + }
542 + }
543 + });
544 + }
545 + } catch (e) { sendConsoleText("ex1: " + e); }
546 +
547 + // Try to load up the WIFI scanner
548 + try {
549 + var wifiScannerLib = require('wifi-scanner');
550 + wifiScanner = new wifiScannerLib();
551 + wifiScanner.on('accessPoint', function (data) { sendConsoleText("wifiScanner: " + data); });
552 + } catch (e) { wifiScannerLib = null; wifiScanner = null; }
553 +
554 + // Get our location (lat/long) using our public IP address
555 + var getIpLocationDataExInProgress = false;
556 + var getIpLocationDataExCounts = [0, 0];
557 + function getIpLocationDataEx(func) {
558 + if (getIpLocationDataExInProgress == true) { return false; }
559 + try {
560 + getIpLocationDataExInProgress = true;
561 + getIpLocationDataExCounts[0]++;
562 + var options = http.parseUri("http://ipinfo.io/json");
563 + options.method = 'GET';
564 + http.request(options, function (resp) {
565 + if (resp.statusCode == 200) {
566 + var geoData = '';
567 + resp.data = function (geoipdata) { geoData += geoipdata; };
568 + resp.end = function () {
569 + var location = null;
570 + try {
571 + if (typeof geoData == 'string') {
572 + var result = JSON.parse(geoData);
573 + if (result.ip && result.loc) { location = result; }
574 + }
575 + } catch (e) { }
576 + if (func) { getIpLocationDataExCounts[1]++; func(location); }
577 + }
578 + } else { func(null); }
579 + getIpLocationDataExInProgress = false;
580 + }).end();
581 + return true;
582 + }
583 + catch (e) { return false; }
584 + }
585 +
586 + // Remove all Gateway MAC addresses for interface list. This is useful because the gateway MAC is not always populated reliably.
587 + function clearGatewayMac(str) {
588 + if (str == null) return null;
589 + var x = JSON.parse(str);
590 + for (var i in x.netif) { if (x.netif[i].gatewaymac) { delete x.netif[i].gatewaymac } }
591 + return JSON.stringify(x);
592 + }
593 +
594 + function getIpLocationData(func) {
595 + // Get the location information for the cache if possible
596 + var publicLocationInfo = db.Get('publicLocationInfo');
597 + if (publicLocationInfo != null) { publicLocationInfo = JSON.parse(publicLocationInfo); }
598 + if (publicLocationInfo == null) {
599 + // Nothing in the cache, fetch the data
600 + getIpLocationDataEx(function (locationData) {
601 + if (locationData != null) {
602 + publicLocationInfo = {};
603 + publicLocationInfo.netInfoStr = lastNetworkInfo;
604 + publicLocationInfo.locationData = locationData;
605 + var x = db.Put('publicLocationInfo', JSON.stringify(publicLocationInfo)); // Save to database
606 + if (func) func(locationData); // Report the new location
607 + } else {
608 + if (func) func(null); // Report no location
609 + }
610 + });
611 + } else {
612 + // Check the cache
613 + if (clearGatewayMac(publicLocationInfo.netInfoStr) == clearGatewayMac(lastNetworkInfo)) {
614 + // Cache match
615 + if (func) func(publicLocationInfo.locationData);
616 + } else {
617 + // Cache mismatch
618 + getIpLocationDataEx(function (locationData) {
619 + if (locationData != null) {
620 + publicLocationInfo = {};
621 + publicLocationInfo.netInfoStr = lastNetworkInfo;
622 + publicLocationInfo.locationData = locationData;
623 + var x = db.Put('publicLocationInfo', JSON.stringify(publicLocationInfo)); // Save to database
624 + if (func) func(locationData); // Report the new location
625 + } else {
626 + if (func) func(publicLocationInfo.locationData); // Can't get new location, report the old location
627 + }
628 + });
629 + }
630 + }
631 + }
632 +
633 + // Polyfill String.endsWith
634 + if (!String.prototype.endsWith) {
635 + String.prototype.endsWith = function (searchString, position) {
636 + var subjectString = this.toString();
637 + if (typeof position !== 'number' || !isFinite(position) || Math.floor(position) !== position || position > subjectString.length) { position = subjectString.length; }
638 + position -= searchString.length;
639 + var lastIndex = subjectString.lastIndexOf(searchString, position);
640 + return lastIndex !== -1 && lastIndex === position;
641 + };
642 + }
643 +
644 + // Polyfill path.join
645 + obj.path = {
646 + join: function () {
647 + var x = [];
648 + for (var i in arguments) {
649 + var w = arguments[i];
650 + if (w != null) {
651 + while (w.endsWith('/') || w.endsWith('\\')) { w = w.substring(0, w.length - 1); }
652 + if (i != 0) {
653 + while (w.startsWith('/') || w.startsWith('\\')) { w = w.substring(1); }
654 + }
655 + x.push(w);
656 + }
657 + }
658 + if (x.length == 0) return '/';
659 + return x.join('/');
660 + }
661 + };
662 +
663 + // Replace a string with a number if the string is an exact number
664 + function toNumberIfNumber(x) { if ((typeof x == 'string') && (+parseInt(x) === x)) { x = parseInt(x); } return x; }
665 +
666 + // Convert decimal to hex
667 + function char2hex(i) { return (i + 0x100).toString(16).substr(-2).toUpperCase(); }
668 +
669 + // Convert a raw string to a hex string
670 + function rstr2hex(input) { var r = '', i; for (i = 0; i < input.length; i++) { r += char2hex(input.charCodeAt(i)); } return r; }
671 +
672 + // Convert a buffer into a string
673 + function buf2rstr(buf) { var r = ''; for (var i = 0; i < buf.length; i++) { r += String.fromCharCode(buf[i]); } return r; }
674 +
675 + // Convert a hex string to a raw string // TODO: Do this using Buffer(), will be MUCH faster
676 + function hex2rstr(d) {
677 + if (typeof d != "string" || d.length == 0) return '';
678 + var r = '', m = ('' + d).match(/../g), t;
679 + while (t = m.shift()) r += String.fromCharCode('0x' + t);
680 + return r
681 + }
682 +
683 + // Convert an object to string with all functions
684 + function objToString(x, p, pad, ret) {
685 + if (ret == undefined) ret = '';
686 + if (p == undefined) p = 0;
687 + if (x == null) { return '[null]'; }
688 + if (p > 8) { return '[...]'; }
689 + if (x == undefined) { return '[undefined]'; }
690 + if (typeof x == 'string') { if (p == 0) return x; return '"' + x + '"'; }
691 + if (typeof x == 'buffer') { return '[buffer]'; }
692 + if (typeof x != 'object') { return x; }
693 + var r = '{' + (ret ? '\r\n' : ' ');
694 + for (var i in x) { if (i != '_ObjectID') { r += (addPad(p + 2, pad) + i + ': ' + objToString(x[i], p + 2, pad, ret) + (ret ? '\r\n' : ' ')); } }
695 + return r + addPad(p, pad) + '}';
696 + }
697 +
698 + // Return p number of spaces
699 + function addPad(p, ret) { var r = ''; for (var i = 0; i < p; i++) { r += ret; } return r; }
700 +
701 + // Split a string taking into account the quoats. Used for command line parsing
702 + function splitArgs(str) {
703 + var myArray = [], myRegexp = /[^\s"]+|"([^"]*)"/gi;
704 + do { var match = myRegexp.exec(str); if (match != null) { myArray.push(match[1] ? match[1] : match[0]); } } while (match != null);
705 + return myArray;
706 + }
707 +
708 + // Parse arguments string array into an object
709 + function parseArgs(argv) {
710 + var results = { '_': [] }, current = null;
711 + for (var i = 1, len = argv.length; i < len; i++) {
712 + var x = argv[i];
713 + if (x.length > 2 && x[0] == '-' && x[1] == '-') {
714 + if (current != null) { results[current] = true; }
715 + current = x.substring(2);
716 + } else {
717 + if (current != null) { results[current] = toNumberIfNumber(x); current = null; } else { results['_'].push(toNumberIfNumber(x)); }
718 + }
719 + }
720 + if (current != null) { results[current] = true; }
721 + return results;
722 + }
723 +
724 + // Get server target url with a custom path
725 + function getServerTargetUrl(path) {
726 + var x = mesh.ServerUrl;
727 + //sendConsoleText("mesh.ServerUrl: " + mesh.ServerUrl);
728 + if (x == null) { return null; }
729 + if (path == null) { path = ''; }
730 + x = http.parseUri(x);
731 + if (x == null) return null;
732 + return x.protocol + '//' + x.host + ':' + x.port + '/' + path;
733 + }
734 +
735 + // Get server url. If the url starts with "*/..." change it, it not use the url as is.
736 + function getServerTargetUrlEx(url) {
737 + if (url.substring(0, 2) == '*/') { return getServerTargetUrl(url.substring(2)); }
738 + return url;
739 + }
740 +
741 + // Send a wake-on-lan packet
742 + function sendWakeOnLan(hexMac) {
743 + hexMac = hexMac.split(':').join('');
744 + var count = 0;
745 + try {
746 + var interfaces = require('os').networkInterfaces();
747 + var magic = 'FFFFFFFFFFFF';
748 + for (var x = 1; x <= 16; ++x) { magic += hexMac; }
749 + var magicbin = Buffer.from(magic, 'hex');
750 +
751 + for (var adapter in interfaces) {
752 + if (interfaces.hasOwnProperty(adapter)) {
753 + for (var i = 0; i < interfaces[adapter].length; ++i) {
754 + var addr = interfaces[adapter][i];
755 + if ((addr.family == 'IPv4') && (addr.mac != '00:00:00:00:00:00')) {
756 + try {
757 + var socket = require('dgram').createSocket({ type: 'udp4' });
758 + socket.bind({ address: addr.address });
759 + socket.setBroadcast(true);
760 + socket.setMulticastInterface(addr.address);
761 + socket.setMulticastTTL(1);
762 + socket.send(magicbin, 7, '255.255.255.255');
763 + socket.descriptorMetadata = 'WoL (' + addr.address + ' => ' + hexMac + ')';
764 + count++;
765 + }
766 + catch (e) { }
767 + }
768 + }
769 + }
770 + }
771 + } catch (e) { }
772 + return count;
773 + }
774 +
775 + // Handle a mesh agent command
776 + function handleServerCommand(data) {
777 + if (typeof data == 'object') {
778 + // If this is a console command, parse it and call the console handler
779 + switch (data.action) {
780 + case 'msg': {
781 + switch (data.type) {
782 + case 'console': { // Process a console command
783 + if (data.value && data.sessionid) {
784 + MeshServerLogEx(17, [data.value], "Processing console command: " + data.value, data);
785 + var args = splitArgs(data.value);
786 + processConsoleCommand(args[0].toLowerCase(), parseArgs(args), data.rights, data.sessionid);
787 + }
788 + break;
789 + }
790 + case 'tunnel': {
791 + if (data.value != null) { // Process a new tunnel connection request
792 + // Create a new tunnel object
793 + var xurl = getServerTargetUrlEx(data.value);
794 + if (xurl != null) {
795 + xurl = xurl.split('$').join('%24').split('@').join('%40'); // Escape the $ and @ characters
796 + var woptions = http.parseUri(xurl);
797 + woptions.perMessageDeflate = false;
798 + if (typeof data.perMessageDeflate == 'boolean') { woptions.perMessageDeflate = data.perMessageDeflate; }
799 + woptions.rejectUnauthorized = 0;
800 + //sendConsoleText(JSON.stringify(woptions));
801 + //sendConsoleText('TUNNEL: ' + JSON.stringify(data));
802 + var tunnel = http.request(woptions);
803 + tunnel.upgrade = onTunnelUpgrade;
804 + tunnel.on('error', function (e) { sendConsoleText("ERROR: Unable to connect relay tunnel to: " + this.url + ", " + JSON.stringify(e)); });
805 + tunnel.sessionid = data.sessionid;
806 + tunnel.rights = data.rights;
807 + tunnel.consent = data.consent;
808 + tunnel.privacybartext = data.privacybartext ? data.privacybartext : "Sharing desktop with: {0}";
809 + tunnel.username = data.username + (data.guestname ? (' - ' + data.guestname) : '');
810 + tunnel.realname = (data.realname ? data.realname : data.username) + (data.guestname ? (' - ' + data.guestname) : '');
811 + tunnel.userid = data.userid;
812 + tunnel.remoteaddr = data.remoteaddr;
813 + tunnel.state = 0;
814 + tunnel.url = xurl;
815 + tunnel.protocol = 0;
816 + tunnel.soptions = data.soptions;
817 + tunnel.tcpaddr = data.tcpaddr;
818 + tunnel.tcpport = data.tcpport;
819 + tunnel.udpaddr = data.udpaddr;
820 + tunnel.udpport = data.udpport;
821 + tunnel.end();
822 + // Put the tunnel in the tunnels list
823 + var index = nextTunnelIndex++;
824 + tunnel.index = index;
825 + tunnels[index] = tunnel;
826 +
827 + //sendConsoleText('New tunnel connection #' + index + ': ' + tunnel.url + ', rights: ' + tunnel.rights, data.sessionid);
828 + }
829 + }
830 + break;
831 + }
832 + case 'messagebox': {
833 + // Display a message box
834 + if (data.title && data.msg) {
835 + MeshServerLogEx(18, [data.title, data.msg], "Displaying message box, title=" + data.title + ", message=" + data.msg, data);
836 + data.msg = data.msg.split('\r').join('\\r').split('\n').join('\\n');
837 + try { require('message-box').create(data.title, data.msg, 120); } catch (e) { }
838 + }
839 + break;
840 + }
841 + case 'ps': {
842 + // Return the list of running processes
843 + if (data.sessionid) {
844 + processManager.getProcesses(function (plist) {
845 + mesh.SendCommand({ action: 'msg', type: 'ps', value: JSON.stringify(plist), sessionid: data.sessionid });
846 + });
847 + }
848 + break;
849 + }
850 + case 'pskill': {
851 + // Kill a process
852 + if (data.value) {
853 + MeshServerLogEx(19, [data.value], "Killing process " + data.value, data);
854 + try { process.kill(data.value); } catch (e) { sendConsoleText("pskill: " + JSON.stringify(e)); }
855 + }
856 + break;
857 + }
858 + case 'services': {
859 + // Return the list of installed services
860 + var services = null;
861 + try { services = require('service-manager').manager.enumerateService(); } catch (e) { }
862 + if (services != null) { mesh.SendCommand({ action: 'msg', type: 'services', value: JSON.stringify(services), sessionid: data.sessionid }); }
863 + break;
864 + }
865 + case 'serviceStop': {
866 + // Stop a service
867 + try {
868 + var service = require('service-manager').manager.getService(data.serviceName);
869 + if (service != null) { service.stop(); }
870 + } catch (e) { }
871 + break;
872 + }
873 + case 'serviceStart': {
874 + // Start a service
875 + try {
876 + var service = require('service-manager').manager.getService(data.serviceName);
877 + if (service != null) { service.start(); }
878 + } catch (e) { }
879 + break;
880 + }
881 + case 'serviceRestart': {
882 + // Restart a service
883 + try {
884 + var service = require('service-manager').manager.getService(data.serviceName);
885 + if (service != null) { service.restart(); }
886 + } catch (e) { }
887 + break;
888 + }
889 + case 'deskBackground':
890 + {
891 + // Toggle desktop background
892 + try {
893 + if (process.platform == 'win32') {
894 + var stype = require('user-sessions').getProcessOwnerName(process.pid).tsid == 0 ? 1 : 0;
895 + var sid = undefined;
896 + if (stype == 1) {
897 + if (require('MeshAgent')._tsid != null) {
898 + stype = 5;
899 + sid = require('MeshAgent')._tsid;
900 + }
901 + }
902 + var id = require('user-sessions').getProcessOwnerName(process.pid).tsid == 0 ? 1 : 0;
903 + var child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0='], { type: stype, uid: sid });
904 + child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
905 + child.stderr.on('data', function () { });
906 + child.waitExit();
907 + var current = child.stdout.str.trim();
908 + if (current != '') { require('MeshAgent')._wallpaper = current; }
909 + child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0=', current != '' ? '""' : require('MeshAgent')._wallpaper], { type: stype, uid: sid });
910 + child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
911 + child.stderr.on('data', function () { });
912 + child.waitExit();
913 + } else {
914 + var id = require('user-sessions').consoleUid();
915 + var current = require('linux-gnome-helpers').getDesktopWallpaper(id);
916 + if (current != '/dev/null') { require('MeshAgent')._wallpaper = current; }
917 + require('linux-gnome-helpers').setDesktopWallpaper(id, current != '/dev/null' ? undefined : require('MeshAgent')._wallpaper);
918 + }
919 + } catch (e) {
920 + sendConsoleText(e);
921 + }
922 + break;
923 + }
924 + case 'openUrl': {
925 + // Open a local web browser and return success/fail
926 + MeshServerLogEx(20, [data.url], "Opening: " + data.url, data);
927 + sendConsoleText("OpenURL: " + data.url);
928 + if (data.url) { mesh.SendCommand({ action: 'msg', type: 'openUrl', url: data.url, sessionid: data.sessionid, success: (openUserDesktopUrl(data.url) != null) }); }
929 + break;
930 + }
931 + case 'getclip': {
932 + // Send the load clipboard back to the user
933 + //sendConsoleText('getClip: ' + JSON.stringify(data));
934 + if (require('MeshAgent').isService) {
935 + require('clipboard').dispatchRead().then(function (str) {
936 + if (str) {
937 + MeshServerLogEx(21, [str.length], "Getting clipboard content, " + str.length + " byte(s)", data);
938 + mesh.SendCommand({ action: 'msg', type: 'getclip', sessionid: data.sessionid, data: str, tag: data.tag });
939 + }
940 + });
941 + } else {
942 + require("clipboard").read().then(function (str) {
943 + if (str) {
944 + MeshServerLogEx(21, [str.length], "Getting clipboard content, " + str.length + " byte(s)", data);
945 + mesh.SendCommand({ action: 'msg', type: 'getclip', sessionid: data.sessionid, data: str, tag: data.tag });
946 + }
947 + });
948 + }
949 + break;
950 + }
951 + case 'setclip': {
952 + // Set the load clipboard to a user value
953 + //sendConsoleText('setClip: ' + JSON.stringify(data));
954 + if (typeof data.data == 'string') {
955 + MeshServerLogEx(22, [data.data.length], "Setting clipboard content, " + data.data.length + " byte(s)", data);
956 + if (require('MeshAgent').isService) { require('clipboard').dispatchWrite(data.data); } else { require("clipboard")(data.data); } // Set the clipboard
957 + mesh.SendCommand({ action: 'msg', type: 'setclip', sessionid: data.sessionid, success: true });
958 + }
959 + break;
960 + }
961 + case 'userSessions': {
962 + // Send back current user sessions list, this is Windows only.
963 + //sendConsoleText('userSessions: ' + JSON.stringify(data));
964 + if (process.platform != 'win32') break;
965 + var p = require('user-sessions').enumerateUsers();
966 + p.sessionid = data.sessionid;
967 + p.then(function (u) { mesh.SendCommand({ action: 'msg', type: 'userSessions', sessionid: data.sessionid, data: u, tag: data.tag }); });
968 + break;
969 + }
970 + default:
971 + // Unknown action, ignore it.
972 + break;
973 + }
974 + break;
975 + }
976 + case 'acmactivate': {
977 + if (amt != null) {
978 + MeshServerLogEx(23, null, "Attempting Intel AMT ACM mode activation", data);
979 + amt.setAcmResponse(data);
980 + }
981 + break;
982 + }
983 + case 'wakeonlan': {
984 + // Send wake-on-lan on all interfaces for all MAC addresses in data.macs array. The array is a list of HEX MAC addresses.
985 + sendConsoleText("Server requesting wake-on-lan for: " + data.macs.join(', '));
986 + for (var i in data.macs) { sendWakeOnLan(data.macs[i]); }
987 + break;
988 + }
989 + case 'runcommands': {
990 + if (mesh.cmdchild != null) { sendConsoleText("Run commands can't execute, already busy."); break; }
991 + sendConsoleText("Run commands (" + data.runAsUser + "): " + data.cmds);
992 +
993 + // data.runAsUser: 0=Agent,1=UserOrAgent,2=UserOnly
994 + var options = {};
995 + if (data.runAsUser > 0) {
996 + try { options.uid = require('user-sessions').consoleUid(); } catch (e) { }
997 + options.type = require('child_process').SpawnTypes.TERM;
998 + }
999 + if (data.runAsUser == 2) {
1000 + if (options.uid == null) break;
1001 + if (((require('user-sessions').minUid != null) && (options.uid < require('user-sessions').minUid()))) break; // This command can only run as user.
1002 + }
1003 +
1004 + if (process.platform == 'win32') {
1005 + if (data.type == 1) {
1006 + // Windows command shell
1007 + mesh.cmdchild = require('child_process').execFile(process.env['windir'] + '\\system32\\cmd.exe', ['cmd'], options);
1008 + mesh.cmdchild.descriptorMetadata = 'UserCommandsShell';
1009 + mesh.cmdchild.stdout.on('data', function (c) { sendConsoleText(c.toString()); });
1010 + mesh.cmdchild.stderr.on('data', function (c) { sendConsoleText(c.toString()); });
1011 + mesh.cmdchild.stdin.write(data.cmds + '\r\nexit\r\n');
1012 + mesh.cmdchild.on('exit', function () { sendConsoleText("Run commands completed."); delete mesh.cmdchild; });
1013 + } else if (data.type == 2) {
1014 + // Windows Powershell
1015 + mesh.cmdchild = require('child_process').execFile(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', ['powershell', '-noprofile', '-nologo', '-command', '-'], options);
1016 + mesh.cmdchild.descriptorMetadata = 'UserCommandsPowerShell';
1017 + mesh.cmdchild.stdout.on('data', function (c) { sendConsoleText(c.toString()); });
1018 + mesh.cmdchild.stderr.on('data', function (c) { sendConsoleText(c.toString()); });
1019 + mesh.cmdchild.stdin.write(data.cmds + '\r\nexit\r\n');
1020 + mesh.cmdchild.on('exit', function () { sendConsoleText("Run commands completed."); delete mesh.cmdchild; });
1021 + }
1022 + } else if (data.type == 3) {
1023 + // Linux shell
1024 + mesh.cmdchild = require('child_process').execFile('/bin/sh', ['sh'], options);
1025 + mesh.cmdchild.descriptorMetadata = 'UserCommandsShell';
1026 + mesh.cmdchild.stdout.on('data', function (c) { sendConsoleText(c.toString()); });
1027 + mesh.cmdchild.stderr.on('data', function (c) { sendConsoleText(c.toString()); });
1028 + mesh.cmdchild.stdin.write(data.cmds.split('\r').join('') + '\nexit\n');
1029 + mesh.cmdchild.on('exit', function () { sendConsoleText("Run commands completed."); delete mesh.cmdchild; });
1030 + }
1031 + break;
1032 + }
1033 + case 'uninstallagent':
1034 + // Uninstall this agent
1035 + var agentName = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent';
1036 + if (require('service-manager').manager.getService(agentName).isMe()) {
1037 + try { diagnosticAgent_uninstall(); } catch (e) { }
1038 + var js = "require('service-manager').manager.getService('" + agentName + "').stop(); require('service-manager').manager.uninstallService('" + agentName + "'); process.exit();";
1039 + 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 });
1040 + }
1041 + break;
1042 + case 'poweraction': {
1043 + // Server telling us to execute a power action
1044 + if ((mesh.ExecPowerState != undefined) && (data.actiontype)) {
1045 + var forced = 0;
1046 + if (data.forced == 1) { forced = 1; }
1047 + data.actiontype = parseInt(data.actiontype);
1048 + MeshServerLogEx(25, [data.actiontype, forced], "Performing power action=" + data.actiontype + ", forced=" + forced, data);
1049 + sendConsoleText("Performing power action=" + data.actiontype + ", forced=" + forced + '.');
1050 + var r = mesh.ExecPowerState(data.actiontype, forced);
1051 + sendConsoleText("ExecPowerState returned code: " + r);
1052 + }
1053 + break;
1054 + }
1055 + case 'iplocation': {
1056 + // 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
1057 + getIpLocationData(function (location) { mesh.SendCommand({ action: 'iplocation', type: 'publicip', value: location }); });
1058 + break;
1059 + }
1060 + case 'toast': {
1061 + // Display a toast message
1062 + if (data.title && data.msg) {
1063 + MeshServerLogEx(26, [data.title, data.msg], "Displaying toast message, title=" + data.title + ", message=" + data.msg, data);
1064 + data.msg = data.msg.split('\r').join('\\r').split('\n').join('\\n');
1065 + try { require('toaster').Toast(data.title, data.msg); } catch (e) { }
1066 + }
1067 + break;
1068 + }
1069 + case 'openUrl': {
1070 + // Open a local web browser and return success/fail
1071 + //sendConsoleText('OpenURL: ' + data.url);
1072 + MeshServerLogEx(20, [data.url], "Opening: " + data.url, data);
1073 + if (data.url) { mesh.SendCommand({ action: 'openUrl', url: data.url, sessionid: data.sessionid, success: (openUserDesktopUrl(data.url) != null) }); }
1074 + break;
1075 + }
1076 + case 'amtconfig': {
1077 + // Perform Intel AMT activation and/or configuration
1078 + require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: 'amtconfig' });
1079 + if ((apftunnel != null) || (amt == null)) break;
1080 + getMeiState(15, function (state) {
1081 + if ((state == null) || (state.ProvisioningState == null)) return;
1082 + if ((state.UUID == null) || (state.UUID.length != 36)) return; // Bad UUID
1083 + var apfarg = {
1084 + mpsurl: mesh.ServerUrl.replace('agent.ashx', 'apf.ashx'),
1085 + mpsuser: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16), // TODO: User a server provided encrypted cookie for CIRA-LMS login
1086 + mpspass: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16),
1087 + mpskeepalive: 60000,
1088 + clientname: state.OsHostname,
1089 + clientaddress: '127.0.0.1',
1090 + clientuuid: state.UUID,
1091 + conntype: 2, // 0 = CIRA, 1 = Relay, 2 = LMS. The correct value is 2 since we are performing an LMS relay, other values for testing.
1092 + meiState: state // MEI state will be passed to MPS server
1093 + };
1094 + require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: 'apf-on' });
1095 + apftunnel = require('apfclient')({ debug: false }, apfarg);
1096 + apftunnel.onJsonControl = function (data) {
1097 + if (data.action == 'console') { require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: data.msg }); } // Display a console message (DEBUG)
1098 + if (data.action == 'mestate') { getMeiState(15, function (state) { apftunnel.updateMeiState(state); }); } // Update the MEI state
1099 + if (data.action == 'deactivate') { // Request CCM deactivation
1100 + var amtMeiModule, amtMei;
1101 + try { amtMeiModule = require('amt-mei'); amtMei = new amtMeiModule(); } catch (ex) { apftunnel.sendMeiDeactivationState(1); return; }
1102 + amtMei.on('error', function (e) { apftunnel.sendMeiDeactivationState(1); });
1103 + amtMei.unprovision(1, function (status) { apftunnel.sendMeiDeactivationState(status); }); // 0 = Success
1104 + }
1105 + if (data.action == 'close') { try { apftunnel.disconnect(); } catch (e) { } apftunnel = null; } // Close the CIRA-LMS connection
1106 + }
1107 + apftunnel.onChannelClosed = function () { apftunnel = null; require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: 'apf-off' }); }
1108 + try { apftunnel.connect(); } catch (ex) { }
1109 + });
1110 + break;
1111 + }
1112 + case 'getScript': {
1113 + // Received a configuration script from the server
1114 + sendConsoleText('getScript: ' + JSON.stringify(data));
1115 + break;
1116 + }
1117 + case 'sysinfo': {
1118 + // Fetch system information
1119 + getSystemInformation(function (results) {
1120 + if ((results != null) && (data.hash != results.hash)) { mesh.SendCommand({ action: 'sysinfo', sessionid: this.sessionid, data: results }); }
1121 + });
1122 + break;
1123 + }
1124 + case 'ping': { mesh.SendCommand('{"action":"pong"}'); break; }
1125 + case 'pong': { break; }
1126 + case 'plugin': {
1127 + try { require(data.plugin).consoleaction(data, data.rights, data.sessionid, this); } catch (e) { throw e; }
1128 + break;
1129 + }
1130 + case 'coredump':
1131 + // Set the current agent coredump situation.
1132 + if (data.value === true) {
1133 + // TODO: This replace() below is not ideal, would be better to remove the .exe at the end instead of replace.
1134 + process.coreDumpLocation = (process.platform == 'win32') ? (process.execPath.replace('.exe', '.dmp')) : (process.execPath + '.dmp');
1135 + } else if (data.value === false) {
1136 + process.coreDumpLocation = null;
1137 + }
1138 + break;
1139 + case 'getcoredump':
1140 + // Ask the agent if a core dump is currently available, if yes, also return the hash of the agent.
1141 + var r = { action: 'getcoredump', value: (process.coreDumpLocation != null) };
1142 + var coreDumpPath = null;
1143 + if (process.platform == 'win32') { coreDumpPath = process.coreDumpLocation; } else { coreDumpPath = (process.cwd() != '//') ? fs.existsSync(process.cwd() + 'core') : null; }
1144 + if ((coreDumpPath != null) && (fs.existsSync(coreDumpPath))) { r.exists = (db.Get('CoreDumpTime') != require('fs').statSync(coreDumpPath).mtime); }
1145 + if (r.exists == true) { r.agenthashhex = getSHA384FileHash(process.execPath).toString('hex'); }
1146 + mesh.SendCommand(JSON.stringify(r));
1147 + default:
1148 + // Unknown action, ignore it.
1149 + break;
1150 + }
1151 + }
1152 + }
1153 +
1154 + // Called when a file changed in the file system
1155 + /*
1156 + function onFileWatcher(a, b) {
1157 + console.log('onFileWatcher', a, b, this.path);
1158 + var response = getDirectoryInfo(this.path);
1159 + if ((response != undefined) && (response != null)) { this.tunnel.s.write(JSON.stringify(response)); }
1160 + }
1161 + */
1162 +
1163 + function getSystemInformation(func) {
1164 + try {
1165 + var results = { hardware: require('identifiers').get() }; // Hardware info
1166 + if (results.hardware && results.hardware.windows) {
1167 + // Remove extra entries and things that change quickly
1168 + var x = results.hardware.windows.osinfo;
1169 + try { delete x.FreePhysicalMemory; } catch (e) { }
1170 + try { delete x.FreeSpaceInPagingFiles; } catch (e) { }
1171 + try { delete x.FreeVirtualMemory; } catch (e) { }
1172 + try { delete x.LocalDateTime; } catch (e) { }
1173 + try { delete x.MaxProcessMemorySize; } catch (e) { }
1174 + try { delete x.TotalVirtualMemorySize; } catch (e) { }
1175 + try { delete x.TotalVisibleMemorySize; } catch (e) { }
1176 + try {
1177 + if (results.hardware.windows.memory) { for (var i in results.hardware.windows.memory) { delete results.hardware.windows.memory[i].Node; } }
1178 + if (results.hardware.windows.osinfo) { delete results.hardware.windows.osinfo.Node; }
1179 + if (results.hardware.windows.partitions) { for (var i in results.hardware.windows.partitions) { delete results.hardware.windows.partitions[i].Node; } }
1180 + } catch (e) { }
1181 + }
1182 + if (process.platform == 'win32') { results.pendingReboot = require('win-info').pendingReboot(); } // Pending reboot
1183 + /*
1184 + if (process.platform == 'win32') {
1185 + var defragResult = function (r) {
1186 + if (typeof r == 'object') { results[this.callname] = r; }
1187 + if (this.callname == 'defrag') {
1188 + var pr = require('win-info').installedApps(); // Installed apps
1189 + pr.callname = 'installedApps';
1190 + pr.sessionid = data.sessionid;
1191 + pr.then(defragResult, defragResult);
1192 + }
1193 + else {
1194 + results.winpatches = require('win-info').qfe(); // Windows patches
1195 + results.hash = require('SHA384Stream').create().syncHash(JSON.stringify(results)).toString('hex');
1196 + func(results);
1197 + }
1198 + }
1199 + var pr = require('win-info').defrag({ volume: 'C:' }); // Defrag TODO
1200 + pr.callname = 'defrag';
1201 + pr.sessionid = data.sessionid;
1202 + pr.then(defragResult, defragResult);
1203 + } else {
1204 + */
1205 + results.hash = require('SHA384Stream').create().syncHash(JSON.stringify(results)).toString('hex');
1206 + func(results);
1207 + //}
1208 + } catch (e) { func(null, e); }
1209 + }
1210 +
1211 + // Get a formated response for a given directory path
1212 + function getDirectoryInfo(reqpath) {
1213 + var response = { path: reqpath, dir: [] };
1214 + if (((reqpath == undefined) || (reqpath == '')) && (process.platform == 'win32')) {
1215 + // List all the drives in the root, or the root itself
1216 + var results = null;
1217 + try { results = fs.readDrivesSync(); } catch (e) { } // TODO: Anyway to get drive total size and free space? Could draw a progress bar.
1218 + if (results != null) {
1219 + for (var i = 0; i < results.length; ++i) {
1220 + var drive = { n: results[i].name, t: 1 };
1221 + if (results[i].type == 'REMOVABLE') { drive.dt = 'removable'; } // TODO: See if this is USB/CDROM or something else, we can draw icons.
1222 + response.dir.push(drive);
1223 + }
1224 + }
1225 + } else {
1226 + // List all the files and folders in this path
1227 + if (reqpath == '') { reqpath = '/'; }
1228 + var results = null, xpath = obj.path.join(reqpath, '*');
1229 + //if (process.platform == "win32") { xpath = xpath.split('/').join('\\'); }
1230 + try { results = fs.readdirSync(xpath); } catch (e) { }
1231 + if (results != null) {
1232 + for (var i = 0; i < results.length; ++i) {
1233 + if ((results[i] != '.') && (results[i] != '..')) {
1234 + var stat = null, p = obj.path.join(reqpath, results[i]);
1235 + //if (process.platform == "win32") { p = p.split('/').join('\\'); }
1236 + try { stat = fs.statSync(p); } catch (e) { } // TODO: Get file size/date
1237 + if ((stat != null) && (stat != undefined)) {
1238 + if (stat.isDirectory() == true) {
1239 + response.dir.push({ n: results[i], t: 2, d: stat.mtime });
1240 + } else {
1241 + response.dir.push({ n: results[i], t: 3, s: stat.size, d: stat.mtime });
1242 + }
1243 + }
1244 + }
1245 + }
1246 + }
1247 + }
1248 + return response;
1249 + }
1250 +
1251 + // Tunnel callback operations
1252 + function onTunnelUpgrade(response, s, head) {
1253 + this.s = s;
1254 + s.httprequest = this;
1255 + s.end = onTunnelClosed;
1256 + s.tunnel = this;
1257 + s.descriptorMetadata = "MeshAgent_relayTunnel";
1258 +
1259 + if (require('MeshAgent').idleTimeout != null)
1260 + {
1261 + s.setTimeout(require('MeshAgent').idleTimeout * 1000);
1262 + s.on('timeout', function ()
1263 + {
1264 + this.ping();
1265 + this.setTimeout(require('MeshAgent').idleTimeout * 1000);
1266 + });
1267 + }
1268 +
1269 + //sendConsoleText('onTunnelUpgrade - ' + this.tcpport + ' - ' + this.udpport);
1270 +
1271 + if (this.tcpport != null) {
1272 + // This is a TCP relay connection, pause now and try to connect to the target.
1273 + s.pause();
1274 + s.data = onTcpRelayServerTunnelData;
1275 + var connectionOptions = { port: parseInt(this.tcpport) };
1276 + if (this.tcpaddr != null) { connectionOptions.host = this.tcpaddr; } else { connectionOptions.host = '127.0.0.1'; }
1277 + s.tcprelay = net.createConnection(connectionOptions, onTcpRelayTargetTunnelConnect);
1278 + s.tcprelay.peerindex = this.index;
1279 +
1280 + // Add the TCP session to the count and update the server
1281 + if (s.httprequest.userid != null) {
1282 + if (tunnelUserCount.tcp[s.httprequest.userid] == null) { tunnelUserCount.tcp[s.httprequest.userid] = 1; } else { tunnelUserCount.tcp[s.httprequest.userid]++; }
1283 + try { mesh.SendCommand({ action: 'sessions', type: 'tcp', value: tunnelUserCount.tcp }); } catch (e) { }
1284 + broadcastSessionsToRegisteredApps();
1285 + }
1286 + } if (this.udpport != null) {
1287 + // This is a UDP relay connection, get the UDP socket setup. // TODO: ***************
1288 + s.data = onUdpRelayServerTunnelData;
1289 + s.udprelay = require('dgram').createSocket({ type: 'udp4' });
1290 + s.udprelay.bind({ port: 0 });
1291 + s.udprelay.peerindex = this.index;
1292 + s.udprelay.on('message', onUdpRelayTargetTunnelConnect);
1293 + s.udprelay.udpport = this.udpport;
1294 + s.udprelay.udpaddr = this.udpaddr;
1295 + s.udprelay.first = true;
1296 +
1297 + // Add the UDP session to the count and update the server
1298 + if (s.httprequest.userid != null) {
1299 + if (tunnelUserCount.udp[s.httprequest.userid] == null) { tunnelUserCount.udp[s.httprequest.userid] = 1; } else { tunnelUserCount.udp[s.httprequest.userid]++; }
1300 + try { mesh.SendCommand({ action: 'sessions', type: 'udp', value: tunnelUserCount.tcp }); } catch (e) { }
1301 + broadcastSessionsToRegisteredApps();
1302 + }
1303 + } else {
1304 + // This is a normal connect for KVM/Terminal/Files
1305 + s.data = onTunnelData;
1306 + }
1307 + }
1308 +
1309 + // Called when UDP relay data is received // TODO****
1310 + function onUdpRelayTargetTunnelConnect(data) {
1311 + var peerTunnel = tunnels[this.peerindex];
1312 + peerTunnel.s.write(data);
1313 + }
1314 +
1315 + // Called when we get data from the server for a TCP relay (We have to skip the first received 'c' and pipe the rest)
1316 + function onUdpRelayServerTunnelData(data) {
1317 + if (this.udprelay.first === true) {
1318 + delete this.udprelay.first; // Skip the first 'c' that is received.
1319 + } else {
1320 + this.udprelay.send(data, parseInt(this.udprelay.udpport), this.udprelay.udpaddr ? this.udprelay.udpaddr : '127.0.0.1');
1321 + }
1322 + }
1323 +
1324 + // Called when the TCP relay target is connected
1325 + function onTcpRelayTargetTunnelConnect() {
1326 + var peerTunnel = tunnels[this.peerindex];
1327 + this.pipe(peerTunnel.s); // Pipe Target --> Server
1328 + peerTunnel.s.first = true;
1329 + peerTunnel.s.resume();
1330 + }
1331 +
1332 + // Called when we get data from the server for a TCP relay (We have to skip the first received 'c' and pipe the rest)
1333 + function onTcpRelayServerTunnelData(data) {
1334 + if (this.first == true) {
1335 + this.first = false;
1336 + this.pipe(this.tcprelay, { dataTypeSkip: 1 }); // Pipe Server --> Target (don't pipe text type websocket frames)
1337 + }
1338 + }
1339 +
1340 + function onTunnelClosed() {
1341 + var tunnel = tunnels[this.httprequest.index];
1342 + if (tunnel == null) return; // Stop duplicate calls.
1343 +
1344 + // If this is a routing session, clean up and send the new session counts.
1345 + if (this.httprequest.userid != null) {
1346 + if (this.httprequest.tcpport != null) {
1347 + 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]; } }
1348 + try { mesh.SendCommand({ action: 'sessions', type: 'tcp', value: tunnelUserCount.tcp }); } catch (e) { }
1349 + broadcastSessionsToRegisteredApps();
1350 + } else if (this.httprequest.udpport != null) {
1351 + 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]; } }
1352 + try { mesh.SendCommand({ action: 'sessions', type: 'udp', value: tunnelUserCount.udp }); } catch (e) { }
1353 + broadcastSessionsToRegisteredApps();
1354 + }
1355 + }
1356 +
1357 + // Sent tunnel statistics to the server, only send this if compression was used.
1358 + if ((this.bytesSent_uncompressed) && (this.bytesSent_uncompressed.toString() != this.bytesSent_actual.toString())) {
1359 + mesh.SendCommand({
1360 + action: 'tunnelCloseStats',
1361 + url: tunnel.url,
1362 + userid: tunnel.userid,
1363 + protocol: tunnel.protocol,
1364 + sessionid: tunnel.sessionid,
1365 + sent: this.bytesSent_uncompressed.toString(),
1366 + sentActual: this.bytesSent_actual.toString(),
1367 + sentRatio: this.bytesSent_ratio,
1368 + received: this.bytesReceived_uncompressed.toString(),
1369 + receivedActual: this.bytesReceived_actual.toString(),
1370 + receivedRatio: this.bytesReceived_ratio
1371 + });
1372 + }
1373 +
1374 + //sendConsoleText("Tunnel #" + this.httprequest.index + " closed. Sent -> " + this.bytesSent_uncompressed + ' bytes (uncompressed), ' + this.bytesSent_actual + ' bytes (actual), ' + this.bytesSent_ratio + '% compression', this.httprequest.sessionid);
1375 + if (this.httprequest.index) { delete tunnels[this.httprequest.index]; }
1376 +
1377 + /*
1378 + // Close the watcher if required
1379 + if (this.httprequest.watcher != undefined) {
1380 + //console.log('Closing watcher: ' + this.httprequest.watcher.path);
1381 + //this.httprequest.watcher.close(); // TODO: This line causes the agent to crash!!!!
1382 + delete this.httprequest.watcher;
1383 + }
1384 + */
1385 +
1386 + // If there is a upload or download active on this connection, close the file
1387 + if (this.httprequest.uploadFile) { fs.closeSync(this.httprequest.uploadFile); delete this.httprequest.uploadFile; delete this.httprequest.uploadFileid; delete this.httprequest.uploadFilePath; }
1388 + if (this.httprequest.downloadFile) { delete this.httprequest.downloadFile; }
1389 +
1390 + // Clean up WebRTC
1391 + if (this.webrtc != null) {
1392 + 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; }
1393 + if (this.webrtc.websocket) { delete this.webrtc.websocket; }
1394 + try { this.webrtc.close(); } catch (e) { }
1395 + this.webrtc.removeAllListeners('connected');
1396 + this.webrtc.removeAllListeners('disconnected');
1397 + this.webrtc.removeAllListeners('dataChannel');
1398 + delete this.webrtc;
1399 + }
1400 +
1401 + // Clean up WebSocket
1402 + this.removeAllListeners('data');
1403 + }
1404 + function onTunnelSendOk() { /*sendConsoleText("Tunnel #" + this.index + " SendOK.", this.sessionid);*/ }
1405 + function onTunnelData(data) {
1406 + //console.log("OnTunnelData");
1407 + //sendConsoleText('OnTunnelData, ' + data.length + ', ' + typeof data + ', ' + data);
1408 +
1409 + // If this is upload data, save it to file
1410 + if ((this.httprequest.uploadFile) && (typeof data == 'object') && (data[0] != 123)) {
1411 + // Save the data to file being uploaded.
1412 + if (data[0] == 0) {
1413 + // If data starts with zero, skip the first byte. This is used to escape binary file data from JSON.
1414 + 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.
1415 + } else {
1416 + // If data does not start with zero, save as-is.
1417 + 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.
1418 + }
1419 + this.write(Buffer.from(JSON.stringify({ action: 'uploadack', reqid: this.httprequest.uploadFileid }))); // Ask for more data.
1420 + return;
1421 + }
1422 +
1423 + if (this.httprequest.state == 0) {
1424 + // Check if this is a relay connection
1425 + if ((data == 'c') || (data == 'cr')) { this.httprequest.state = 1; /*sendConsoleText("Tunnel #" + this.httprequest.index + " now active", this.httprequest.sessionid);*/ }
1426 + }
1427 + else
1428 + {
1429 + // Handle tunnel data
1430 + 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
1431 + // Take a look at the protocol
1432 + if ((data.length > 3) && (data[0] == '{')) { onTunnelControlData(data, this); return; }
1433 + this.httprequest.protocol = parseInt(data);
1434 + if (typeof this.httprequest.protocol != 'number') { this.httprequest.protocol = 0; }
1435 + if (this.httprequest.protocol == 10) {
1436 + //
1437 + // Basic file transfer
1438 + //
1439 + var stats = null;
1440 + if ((process.platform != 'win32') && (this.httprequest.xoptions.file.startsWith('/') == false)) { this.httprequest.xoptions.file = '/' + this.httprequest.xoptions.file; }
1441 + try { stats = require('fs').statSync(this.httprequest.xoptions.file) } catch (e) { }
1442 + try { if (stats) { this.httprequest.downloadFile = fs.createReadStream(this.httprequest.xoptions.file, { flags: 'rbN' }); } } catch (e) { }
1443 + if (this.httprequest.downloadFile) {
1444 + //sendConsoleText('BasicFileTransfer, ok, ' + this.httprequest.xoptions.file + ', ' + JSON.stringify(stats));
1445 + this.write(JSON.stringify({ op: 'ok', size: stats.size }));
1446 + this.httprequest.downloadFile.pipe(this);
1447 + this.httprequest.downloadFile.end = function () { }
1448 + } else {
1449 + //sendConsoleText('BasicFileTransfer, cancel, ' + this.httprequest.xoptions.file);
1450 + this.write(JSON.stringify({ op: 'cancel' }));
1451 + }
1452 + }
1453 + else if ((this.httprequest.protocol == 1) || (this.httprequest.protocol == 6) || (this.httprequest.protocol == 8) || (this.httprequest.protocol == 9))
1454 + {
1455 + //
1456 + // Remote Terminal
1457 + //
1458 +
1459 + // Check user access rights for terminal
1460 + if (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) || ((this.httprequest.rights != 0xFFFFFFFF) && ((this.httprequest.rights & MESHRIGHT_NOTERMINAL) != 0))) {
1461 + // Disengage this tunnel, user does not have the rights to do this!!
1462 + this.httprequest.protocol = 999999;
1463 + this.httprequest.s.end();
1464 + sendConsoleText("Error: No Terminal Control Rights.");
1465 + return;
1466 + }
1467 +
1468 + this.descriptorMetadata = "Remote Terminal";
1469 +
1470 + if (process.platform == 'win32')
1471 + {
1472 + if (!require('win-terminal').PowerShellCapable() && (this.httprequest.protocol == 6 || this.httprequest.protocol == 9))
1473 + {
1474 + this.httprequest.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: 'PowerShell is not supported on this version of windows', msgid: 1 }));
1475 + this.httprequest.s.end();
1476 + return;
1477 + }
1478 + }
1479 +
1480 + var prom = require('promise');
1481 + this.httprequest.tpromise = new prom(function (res, rej) { this._res = res; this._rej = rej; });
1482 + this.httprequest.tpromise.that = this;
1483 + this.httprequest.tpromise.httprequest = this.httprequest;
1484 +
1485 + this.end = function ()
1486 + {
1487 + if (this.httprequest.tpromise._consent) { this.httprequest.tpromise._consent.close(); }
1488 + if (this.httprequest.connectionPromise) { this.httprequest.connectionPromise._rej('Closed'); }
1489 +
1490 + // Remove the terminal session to the count to update the server
1491 + if (this.httprequest.userid != null)
1492 + {
1493 + 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]; } }
1494 + try { mesh.SendCommand({ action: 'sessions', type: 'terminal', value: tunnelUserCount.terminal }); } catch (e) { }
1495 + broadcastSessionsToRegisteredApps();
1496 + }
1497 +
1498 + if (process.platform == 'win32')
1499 + {
1500 + // Unpipe the web socket
1501 + this.unpipe(this.httprequest._term);
1502 + if (this.httprequest._term) { this.httprequest._term.unpipe(this); }
1503 +
1504 + // Unpipe the WebRTC channel if needed (This will also be done when the WebRTC channel ends).
1505 + if (this.rtcchannel)
1506 + {
1507 + this.rtcchannel.unpipe(this.httprequest._term);
1508 + if (this.httprequest._term) { this.httprequest._term.unpipe(this.rtcchannel); }
1509 + }
1510 +
1511 + // Clean up
1512 + if (this.httprequest._term) { this.httprequest._term.end(); }
1513 + this.httprequest._term = null;
1514 + }
1515 + };
1516 +
1517 + // Perform User-Consent if needed.
1518 + if (this.httprequest.consent && (this.httprequest.consent & 16))
1519 + {
1520 + this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 }));
1521 + var consentMessage = this.httprequest.username + " requesting remote terminal access. Grant access?", consentTitle = 'MeshCentral';
1522 + if (this.httprequest.soptions != null) {
1523 + if (this.httprequest.soptions.consentTitle != null) { consentTitle = this.httprequest.soptions.consentTitle; }
1524 + if (this.httprequest.soptions.consentMsgTerminal != null) { consentMessage = this.httprequest.soptions.consentMsgTerminal.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); }
1525 + }
1526 + this.httprequest.tpromise._consent = require('message-box').create(consentTitle, consentMessage, 30);
1527 + this.httprequest.tpromise._consent.retPromise = this.httprequest.tpromise;
1528 + this.httprequest.tpromise._consent.then(
1529 + function ()
1530 + {
1531 + // Success
1532 + MeshServerLogEx(27, null, "Local user accepted remote terminal request (" + this.retPromise.httprequest.remoteaddr + ")", this.retPromise.that.httprequest);
1533 + this.retPromise.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null, msgid: 0 }));
1534 + this.retPromise._consent = null;
1535 + this.retPromise._res();
1536 + },
1537 + function (e)
1538 + {
1539 + // Denied
1540 + MeshServerLogEx(28, null, "Local user rejected remote terminal request (" + this.retPromise.that.httprequest.remoteaddr + ")", this.retPromise.that.httprequest);
1541 + this.retPromise.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
1542 + this.retPromise._rej(e.toString());
1543 + });
1544 + }
1545 + else
1546 + {
1547 + // User-Consent is not required, so just resolve this promise
1548 + this.httprequest.tpromise._res();
1549 + }
1550 +
1551 +
1552 + this.httprequest.tpromise.then(
1553 + function ()
1554 + {
1555 + this.httprequest.connectionPromise = new prom(function (res, rej) { this._res = res; this._rej = rej; });
1556 + this.httprequest.connectionPromise.ws = this.that;
1557 +
1558 + // Start Terminal
1559 + if(process.platform == 'win32')
1560 + {
1561 + try
1562 + {
1563 + var cols = 80, rows = 25;
1564 + if (this.httprequest.xoptions)
1565 + {
1566 + if (this.httprequest.xoptions.rows) { rows = this.httprequest.xoptions.rows; }
1567 + if (this.httprequest.xoptions.cols) { cols = this.httprequest.xoptions.cols; }
1568 + }
1569 +
1570 + if ((this.httprequest.protocol == 1) || (this.httprequest.protocol == 6))
1571 + {
1572 + // Admin Terminal
1573 + if (require('win-virtual-terminal').supported)
1574 + {
1575 + // ConPTY PseudoTerminal
1576 + // this.httprequest._term = require('win-virtual-terminal')[this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'](80, 25);
1577 +
1578 + // The above line is commented out, because there is a bug with ClosePseudoConsole() API, so this is the workaround
1579 + 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] } });
1580 + this.httprequest._dispatcher.httprequest = this.httprequest;
1581 + this.httprequest._dispatcher.on('connection', function (c)
1582 + {
1583 + if (this.httprequest.connectionPromise.completed)
1584 + {
1585 + c.end();
1586 + }
1587 + else
1588 + {
1589 + this.httprequest.connectionPromise._res(c);
1590 + }
1591 + });
1592 + }
1593 + else
1594 + {
1595 + // Legacy Terminal
1596 + this.httprequest.connectionPromise._res(require('win-terminal')[this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'](cols, rows));
1597 + }
1598 + }
1599 + else
1600 + {
1601 + // Logged in user
1602 + var userPromise = require('user-sessions').enumerateUsers();
1603 + userPromise.that = this;
1604 + userPromise.then(function (u)
1605 + {
1606 + var that = this.that;
1607 + if (u.Active.length > 0)
1608 + {
1609 + var username = u.Active[0].Username;
1610 + if (require('win-virtual-terminal').supported)
1611 + {
1612 + // ConPTY PseudoTerminal
1613 + 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] } });
1614 + }
1615 + else
1616 + {
1617 + // Legacy Terminal
1618 + 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] } });
1619 + }
1620 + that.httprequest._dispatcher.ws = that;
1621 + that.httprequest._dispatcher.on('connection', function (c)
1622 + {
1623 + if (this.ws.httprequest.connectionPromise.completed)
1624 + {
1625 + c.end();
1626 + }
1627 + else
1628 + {
1629 + this.ws.httprequest.connectionPromise._res(c);
1630 + }
1631 + });
1632 + }
1633 + });
1634 + }
1635 + }
1636 + catch (e)
1637 + {
1638 + this.httprequest.connectionPromise._rej('Failed to start remote terminal session, ' + e.toString());
1639 + }
1640 + }
1641 + else
1642 + {
1643 + try
1644 + {
1645 + var bash = fs.existsSync('/bin/bash') ? '/bin/bash' : false;
1646 + var sh = fs.existsSync('/bin/sh') ? '/bin/sh' : false;
1647 + var login = process.platform == 'linux' ? '/bin/login' : '/usr/bin/login';
1648 +
1649 + var env = { HISTCONTROL: 'ignoreboth' };
1650 + if (this.httprequest.xoptions)
1651 + {
1652 + if (this.httprequest.xoptions.rows) { env.LINES = ('' + this.httprequest.xoptions.rows); }
1653 + if (this.httprequest.xoptions.cols) { env.COLUMNS = ('' + this.httprequest.xoptions.cols); }
1654 + }
1655 + var options = { type: childProcess.SpawnTypes.TERM, uid: (this.httprequest.protocol == 8) ? require('user-sessions').consoleUid() : null, env: env };
1656 + if (this.httprequest.xoptions && this.httprequest.xoptions.requireLogin)
1657 + {
1658 + if (!require('fs').existsSync(login)) { throw ('Unable to spawn login process'); }
1659 + this.httprequest.connectionPromise._res(childProcess.execFile(login, ['login'], options)); // Start login shell
1660 + }
1661 + else if (bash)
1662 + {
1663 + var p = childProcess.execFile(bash, ['bash'], options); // Start bash
1664 + // Spaces at the beginning of lines are needed to hide commands from the command history
1665 + if (process.platform == 'linux') { p.stdin.write(' alias ls=\'ls --color=auto\';clear\n'); }
1666 + this.httprequest.connectionPromise._res(p);
1667 + }
1668 + else if (sh)
1669 + {
1670 + var p = childProcess.execFile(sh, ['sh'], options); // Start sh
1671 + // Spaces at the beginning of lines are needed to hide commands from the command history
1672 + if (process.platform == 'linux') { p.stdin.write(' alias ls=\'ls --color=auto\';clear\n'); }
1673 + this.httprequest.connectionPromise._res(p);
1674 + }
1675 + else
1676 + {
1677 + this.httprequest.connectionPromise._rej('Failed to start remote terminal session, no shell found');
1678 + }
1679 + }
1680 + catch (e)
1681 + {
1682 + this.httprequest.connectionPromise._rej('Failed to start remote terminal session, ' + e.toString());
1683 + }
1684 + }
1685 +
1686 + this.httprequest.connectionPromise.then(
1687 + function (term)
1688 + {
1689 + // SUCCESS
1690 + var stdoutstream;
1691 + var stdinstream;
1692 + if (process.platform == 'win32')
1693 + {
1694 + this.ws.httprequest._term = term;
1695 + this.ws.httprequest._term.tunnel = this.ws;
1696 + stdoutstream = stdinstream = term;
1697 + }
1698 + else
1699 + {
1700 + term.descriptorMetadata = 'Remote Terminal';
1701 + this.ws.httprequest.process = term;
1702 + this.ws.httprequest.process.tunnel = this.ws;
1703 + term.stderr.stdout = term.stdout;
1704 + term.stderr.on('data', function (c) { this.stdout.write(c); });
1705 + stdoutstream = term.stdout;
1706 + stdinstream = term.stdin;
1707 + this.ws.prependListener('end', function () { this.httprequest.process.kill(); });
1708 + term.prependListener('exit', function () { this.tunnel.end(); });
1709 + }
1710 +
1711 + this.ws.removeAllListeners('data');
1712 + this.ws.on('data', onTunnelControlData);
1713 +
1714 + stdoutstream.pipe(this.ws, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
1715 + this.ws.pipe(stdinstream, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
1716 +
1717 + // Add the terminal session to the count to update the server
1718 + if (this.ws.httprequest.userid != null)
1719 + {
1720 + if (tunnelUserCount.terminal[this.ws.httprequest.userid] == null) { tunnelUserCount.terminal[this.ws.httprequest.userid] = 1; } else { tunnelUserCount.terminal[this.ws.httprequest.userid]++; }
1721 + try { mesh.SendCommand({ action: 'sessions', type: 'terminal', value: tunnelUserCount.terminal }); } catch (e) { }
1722 + broadcastSessionsToRegisteredApps();
1723 + }
1724 +
1725 + // Toast Notification, if required
1726 + if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 2))
1727 + {
1728 + // User Notifications is required
1729 + var notifyMessage = this.ws.httprequest.username + " started a remote terminal session.", notifyTitle = "MeshCentral";
1730 + if (this.ws.httprequest.soptions != null) {
1731 + if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; }
1732 + 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); }
1733 + }
1734 + try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (e) { }
1735 + }
1736 + },
1737 + function (e)
1738 + {
1739 + // FAILED to connect terminal
1740 + this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
1741 + this.ws.end();
1742 + });
1743 + },
1744 + function (e)
1745 + {
1746 + // DO NOT start terminal
1747 + this.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
1748 + this.that.end();
1749 + });
1750 + }
1751 + else if (this.httprequest.protocol == 2)
1752 + {
1753 + //
1754 + // Remote KVM
1755 + //
1756 +
1757 + // Check user access rights for desktop
1758 + if ((((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) && ((this.httprequest.rights & MESHRIGHT_REMOTEVIEW) == 0)) || ((this.httprequest.rights != 0xFFFFFFFF) && ((this.httprequest.rights & MESHRIGHT_NODESKTOP) != 0))) {
1759 + // Disengage this tunnel, user does not have the rights to do this!!
1760 + this.httprequest.protocol = 999999;
1761 + this.httprequest.s.end();
1762 + sendConsoleText("Error: No Desktop Control Rights.");
1763 + return;
1764 + }
1765 +
1766 + this.descriptorMetadata = "Remote KVM";
1767 +
1768 + // Look for a TSID
1769 + var tsid = null;
1770 + if ((this.httprequest.xoptions != null) && (typeof this.httprequest.xoptions.tsid == 'number')) { tsid = this.httprequest.xoptions.tsid; }
1771 + require('MeshAgent')._tsid = tsid;
1772 +
1773 + // Remote desktop using native pipes
1774 + this.httprequest.desktop = { state: 0, kvm: mesh.getRemoteDesktopStream(tsid), tunnel: this };
1775 + this.httprequest.desktop.kvm.parent = this.httprequest.desktop;
1776 + this.desktop = this.httprequest.desktop;
1777 +
1778 + // Add ourself to the list of remote desktop sessions
1779 + if (this.httprequest.desktop.kvm.tunnels == null) { this.httprequest.desktop.kvm.tunnels = []; }
1780 + this.httprequest.desktop.kvm.tunnels.push(this);
1781 +
1782 + // Send a metadata update to all desktop sessions
1783 + var users = {};
1784 + if (this.httprequest.desktop.kvm.tunnels != null) {
1785 + 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) { } }
1786 + 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) { } }
1787 + tunnelUserCount.desktop = users;
1788 + try { mesh.SendCommand({ action: 'sessions', type: 'kvm', value: users }); } catch (e) { }
1789 + broadcastSessionsToRegisteredApps();
1790 + }
1791 +
1792 + this.end = function () {
1793 + --this.desktop.kvm.connectionCount;
1794 +
1795 + // Remove ourself from the list of remote desktop session
1796 + var i = this.desktop.kvm.tunnels.indexOf(this);
1797 + if (i >= 0) { this.desktop.kvm.tunnels.splice(i, 1); }
1798 +
1799 + // Send a metadata update to all desktop sessions
1800 + var users = {};
1801 + if (this.httprequest.desktop.kvm.tunnels != null) {
1802 + 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) { } }
1803 + 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) { } }
1804 + tunnelUserCount.desktop = users;
1805 + try { mesh.SendCommand({ action: 'sessions', type: 'kvm', value: users }); } catch (e) { }
1806 + broadcastSessionsToRegisteredApps();
1807 + }
1808 +
1809 + // Unpipe the web socket
1810 + try
1811 + {
1812 + this.unpipe(this.httprequest.desktop.kvm);
1813 + this.httprequest.desktop.kvm.unpipe(this);
1814 + }
1815 + catch(e) { }
1816 +
1817 + // Unpipe the WebRTC channel if needed (This will also be done when the WebRTC channel ends).
1818 + if (this.rtcchannel)
1819 + {
1820 + try
1821 + {
1822 + this.rtcchannel.unpipe(this.httprequest.desktop.kvm);
1823 + this.httprequest.desktop.kvm.unpipe(this.rtcchannel);
1824 + }
1825 + catch(e) { }
1826 + }
1827 +
1828 + // Place wallpaper back if needed
1829 + // TODO
1830 +
1831 + if (this.desktop.kvm.connectionCount == 0) {
1832 + // Display a toast message. This may not be supported on all platforms.
1833 + // try { require('toaster').Toast('MeshCentral', 'Remote Desktop Control Ended.'); } catch (e) { }
1834 +
1835 + this.httprequest.desktop.kvm.end();
1836 + if (this.httprequest.desktop.kvm.connectionBar) {
1837 + this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close');
1838 + this.httprequest.desktop.kvm.connectionBar.close();
1839 + this.httprequest.desktop.kvm.connectionBar = null;
1840 + }
1841 + } else {
1842 + for (var i in this.httprequest.desktop.kvm.users) {
1843 + if ((this.httprequest.desktop.kvm.users[i] == this.httprequest.username) && this.httprequest.desktop.kvm.connectionBar) {
1844 + 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; } }
1845 + this.httprequest.desktop.kvm.users.splice(i, 1);
1846 + this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close');
1847 + this.httprequest.desktop.kvm.connectionBar.close();
1848 + 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);
1849 + this.httprequest.desktop.kvm.connectionBar.httprequest = this.httprequest;
1850 + this.httprequest.desktop.kvm.connectionBar.on('close', function () {
1851 + MeshServerLogEx(29, null, "Remote Desktop Connection forcefully closed by local user (" + this.httprequest.remoteaddr + ")", this.httprequest);
1852 + for (var i in this.httprequest.desktop.kvm._pipedStreams) {
1853 + this.httprequest.desktop.kvm._pipedStreams[i].end();
1854 + }
1855 + this.httprequest.desktop.kvm.end();
1856 + });
1857 + break;
1858 + }
1859 + }
1860 + }
1861 + };
1862 + if (this.httprequest.desktop.kvm.hasOwnProperty('connectionCount')) {
1863 + this.httprequest.desktop.kvm.connectionCount++;
1864 + this.httprequest.desktop.kvm.rusers.push(this.httprequest.realname);
1865 + this.httprequest.desktop.kvm.users.push(this.httprequest.username);
1866 + this.httprequest.desktop.kvm.rusers.sort();
1867 + this.httprequest.desktop.kvm.users.sort();
1868 + } else {
1869 + this.httprequest.desktop.kvm.connectionCount = 1;
1870 + this.httprequest.desktop.kvm.rusers = [this.httprequest.realname];
1871 + this.httprequest.desktop.kvm.users = [this.httprequest.username];
1872 + }
1873 +
1874 + if ((this.httprequest.rights == 0xFFFFFFFF) || (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) != 0) && ((this.httprequest.rights & MESHRIGHT_REMOTEVIEW) == 0))) {
1875 + // If we have remote control rights, pipe the KVM input
1876 + this.pipe(this.httprequest.desktop.kvm, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. Pipe the Browser --> KVM input.
1877 + } else {
1878 + // We need to only pipe non-mouse & non-keyboard inputs.
1879 + //sendConsoleText('Warning: No Remote Desktop Input Rights.');
1880 + // TODO!!!
1881 + }
1882 +
1883 + // Perform notification if needed. Toast messages may not be supported on all platforms.
1884 + if (this.httprequest.consent && (this.httprequest.consent & 8))
1885 + {
1886 + // User Consent Prompt is required
1887 + // Send a console message back using the console channel, "\n" is supported.
1888 + this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 }));
1889 + var consentMessage = this.httprequest.realname + " requesting remote desktop access. Grant access?", consentTitle = 'MeshCentral';
1890 + if (this.httprequest.soptions != null) {
1891 + if (this.httprequest.soptions.consentTitle != null) { consentTitle = this.httprequest.soptions.consentTitle; }
1892 + if (this.httprequest.soptions.consentMsgDesktop != null) { consentMessage = this.httprequest.soptions.consentMsgDesktop.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); }
1893 + }
1894 + var pr = require('message-box').create(consentTitle, consentMessage, 30, null, tsid);
1895 + pr.ws = this;
1896 + this.pause();
1897 + this._consentpromise = pr;
1898 + this.prependOnceListener('end', function () { if (this._consentpromise && this._consentpromise.close) { this._consentpromise.close(); }});
1899 + pr.then(
1900 + function ()
1901 + {
1902 + // Success
1903 + this.ws._consentpromise = null;
1904 + MeshServerLogEx(30, null, "Starting remote desktop after local user accepted (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
1905 + this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null, msgid: 0 }));
1906 + if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 1)) {
1907 + // User Notifications is required
1908 + var notifyMessage = this.ws.httprequest.realname + " started a remote desktop session.", notifyTitle = "MeshCentral";
1909 + if (this.ws.httprequest.soptions != null) {
1910 + if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; }
1911 + 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); }
1912 + }
1913 + try { require('toaster').Toast(notifyTitle, notifyMessage, tsid); } catch (e) { }
1914 + }
1915 + if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 0x40)) {
1916 + // Connection Bar is required
1917 + if (this.ws.httprequest.desktop.kvm.connectionBar) {
1918 + this.ws.httprequest.desktop.kvm.connectionBar.removeAllListeners('close');
1919 + this.ws.httprequest.desktop.kvm.connectionBar.close();
1920 + }
1921 + try {
1922 + 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);
1923 + MeshServerLogEx(31, null, "Remote Desktop Connection Bar Activated/Updated (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
1924 + }
1925 + catch (e) {
1926 + if (process.platform != 'darwin') {
1927 + MeshServerLogEx(32, null, "Remote Desktop Connection Bar Failed or Not Supported (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
1928 + }
1929 + }
1930 + if (this.ws.httprequest.desktop.kvm.connectionBar) {
1931 + this.ws.httprequest.desktop.kvm.connectionBar.httprequest = this.ws.httprequest;
1932 + this.ws.httprequest.desktop.kvm.connectionBar.on('close', function () {
1933 + MeshServerLogEx(33, null, "Remote Desktop Connection forcefully closed by local user (" + this.httprequest.remoteaddr + ")", this.httprequest);
1934 + for (var i in this.httprequest.desktop.kvm._pipedStreams) {
1935 + this.httprequest.desktop.kvm._pipedStreams[i].end();
1936 + }
1937 + this.httprequest.desktop.kvm.end();
1938 + });
1939 + }
1940 + }
1941 + this.ws.httprequest.desktop.kvm.pipe(this.ws, { dataTypeSkip: 1 });
1942 + this.ws.resume();
1943 + },
1944 + function (e)
1945 + {
1946 + // User Consent Denied/Failed
1947 + this.ws._consentpromise = null;
1948 + MeshServerLogEx(34, null, "Failed to start remote desktop after local user rejected (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
1949 + this.ws.end(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
1950 + });
1951 + } else {
1952 + // User Consent Prompt is not required
1953 + if (this.httprequest.consent && (this.httprequest.consent & 1)) {
1954 + // User Notifications is required
1955 + MeshServerLogEx(35, null, "Started remote desktop with toast notification (" + this.httprequest.remoteaddr + ")", this.httprequest);
1956 + var notifyMessage = this.httprequest.realname + " started a remote desktop session.", notifyTitle = "MeshCentral";
1957 + if (this.httprequest.soptions != null) {
1958 + if (this.httprequest.soptions.notifyTitle != null) { notifyTitle = this.httprequest.soptions.notifyTitle; }
1959 + if (this.httprequest.soptions.notifyMsgDesktop != null) { notifyMessage = this.httprequest.soptions.notifyMsgDesktop.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); }
1960 + }
1961 + try { require('toaster').Toast(notifyTitle, notifyMessage, tsid); } catch (e) { }
1962 + } else {
1963 + MeshServerLogEx(36, null, "Started remote desktop without notification (" + this.httprequest.remoteaddr + ")", this.httprequest);
1964 + }
1965 + if (this.httprequest.consent && (this.httprequest.consent & 0x40)) {
1966 + // Connection Bar is required
1967 + if (this.httprequest.desktop.kvm.connectionBar) {
1968 + this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close');
1969 + this.httprequest.desktop.kvm.connectionBar.close();
1970 + }
1971 + try {
1972 + 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);
1973 + MeshServerLogEx(37, null, "Remote Desktop Connection Bar Activated/Updated (" + this.httprequest.remoteaddr + ")", this.httprequest);
1974 + }
1975 + catch (e) {
1976 + MeshServerLogEx(38, null, "Remote Desktop Connection Bar Failed or not Supported (" + this.httprequest.remoteaddr + ")", this.httprequest);
1977 + }
1978 + if (this.httprequest.desktop.kvm.connectionBar) {
1979 + this.httprequest.desktop.kvm.connectionBar.httprequest = this.httprequest;
1980 + this.httprequest.desktop.kvm.connectionBar.on('close', function () {
1981 + MeshServerLogEx(39, null, "Remote Desktop Connection forcefully closed by local user (" + this.httprequest.remoteaddr + ")", this.httprequest);
1982 + for (var i in this.httprequest.desktop.kvm._pipedStreams) {
1983 + this.httprequest.desktop.kvm._pipedStreams[i].end();
1984 + }
1985 + this.httprequest.desktop.kvm.end();
1986 + });
1987 + }
1988 + }
1989 + this.httprequest.desktop.kvm.pipe(this, { dataTypeSkip: 1 });
1990 + }
1991 +
1992 + this.removeAllListeners('data');
1993 + this.on('data', onTunnelControlData);
1994 + //this.write('MeshCore KVM Hello!1');
1995 +
1996 + } else if (this.httprequest.protocol == 5) {
1997 + //
1998 + // Remote Files
1999 + //
2000 +
2001 + // Check user access rights for files
2002 + if (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) || ((this.httprequest.rights != 0xFFFFFFFF) && ((this.httprequest.rights & MESHRIGHT_NOFILES) != 0))) {
2003 + // Disengage this tunnel, user does not have the rights to do this!!
2004 + this.httprequest.protocol = 999999;
2005 + this.httprequest.s.end();
2006 + sendConsoleText("Error: No files control rights.");
2007 + return;
2008 + }
2009 +
2010 + this.descriptorMetadata = "Remote Files";
2011 +
2012 + // Add the files session to the count to update the server
2013 + if (this.httprequest.userid != null) {
2014 + if (tunnelUserCount.files[this.httprequest.userid] == null) { tunnelUserCount.files[this.httprequest.userid] = 1; } else { tunnelUserCount.files[this.httprequest.userid]++; }
2015 + try { mesh.SendCommand({ action: 'sessions', type: 'files', value: tunnelUserCount.files }); } catch (e) { }
2016 + broadcastSessionsToRegisteredApps();
2017 + }
2018 +
2019 + this.end = function () {
2020 + // Remove the files session from the count to update the server
2021 + if (this.httprequest.userid != null) {
2022 + 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]; } }
2023 + try { mesh.SendCommand({ action: 'sessions', type: 'files', value: tunnelUserCount.files }); } catch (e) { }
2024 + broadcastSessionsToRegisteredApps();
2025 + }
2026 + };
2027 +
2028 + // Perform notification if needed. Toast messages may not be supported on all platforms.
2029 + if (this.httprequest.consent && (this.httprequest.consent & 32)) {
2030 + // User Consent Prompt is required
2031 + // Send a console message back using the console channel, "\n" is supported.
2032 + this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 }));
2033 + var consentMessage = this.httprequest.realname + " requesting remote file Access. Grant access?", consentTitle = 'MeshCentral';
2034 + if (this.httprequest.soptions != null) {
2035 + if (this.httprequest.soptions.consentTitle != null) { consentTitle = this.httprequest.soptions.consentTitle; }
2036 + if (this.httprequest.soptions.consentMsgFiles != null) { consentMessage = this.httprequest.soptions.consentMsgFiles.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); }
2037 + }
2038 + var pr = require('message-box').create(consentTitle, consentMessage, 30);
2039 + pr.ws = this;
2040 + this.pause();
2041 + this._consentpromise = pr;
2042 + this.prependOnceListener('end', function () { if (this._consentpromise && this._consentpromise.close) { this._consentpromise.close(); } });
2043 + pr.then(
2044 + function ()
2045 + {
2046 + // Success
2047 + this.ws._consentpromise = null;
2048 + MeshServerLogEx(40, null, "Starting remote files after local user accepted (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
2049 + this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null }));
2050 + if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 4)) {
2051 + // User Notifications is required
2052 + var notifyMessage = this.ws.httprequest.realname + " started a remote file session.", notifyTitle = "MeshCentral";
2053 + if (this.ws.httprequest.soptions != null) {
2054 + if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; }
2055 + 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); }
2056 + }
2057 + try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (e) { }
2058 + }
2059 + this.ws.resume();
2060 + },
2061 + function (e)
2062 + {
2063 + // User Consent Denied/Failed
2064 + this.ws._consentpromise = null;
2065 + MeshServerLogEx(41, null, "Failed to start remote files after local user rejected (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
2066 + this.ws.end(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
2067 + });
2068 + } else {
2069 + // User Consent Prompt is not required
2070 + if (this.httprequest.consent && (this.httprequest.consent & 4)) {
2071 + // User Notifications is required
2072 + MeshServerLogEx(42, null, "Started remote files with toast notification (" + this.httprequest.remoteaddr + ")", this.httprequest);
2073 + var notifyMessage = this.httprequest.realname + " started a remote file session.", notifyTitle = "MeshCentral";
2074 + if (this.httprequest.soptions != null) {
2075 + if (this.httprequest.soptions.notifyTitle != null) { notifyTitle = this.httprequest.soptions.notifyTitle; }
2076 + if (this.httprequest.soptions.notifyMsgFiles != null) { notifyMessage = this.httprequest.soptions.notifyMsgFiles.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); }
2077 + }
2078 + try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (e) { }
2079 + } else {
2080 + MeshServerLogEx(43, null, "Started remote files without notification (" + this.httprequest.remoteaddr + ")", this.httprequest);
2081 + }
2082 + this.resume();
2083 + }
2084 +
2085 + // Setup files
2086 + // NOP
2087 + }
2088 + } else if (this.httprequest.protocol == 1) {
2089 + // Send data into terminal stdin
2090 + //this.write(data); // Echo back the keys (Does not seem to be a good idea)
2091 + } else if (this.httprequest.protocol == 2) {
2092 + // Send data into remote desktop
2093 + if (this.httprequest.desktop.state == 0) {
2094 + this.write(Buffer.from(String.fromCharCode(0x11, 0xFE, 0x00, 0x00, 0x4D, 0x45, 0x53, 0x48, 0x00, 0x00, 0x00, 0x00, 0x02)));
2095 + this.httprequest.desktop.state = 1;
2096 + } else {
2097 + this.httprequest.desktop.write(data);
2098 + }
2099 + } else if (this.httprequest.protocol == 5) {
2100 + // Process files commands
2101 + var cmd = null;
2102 + try { cmd = JSON.parse(data); } catch (e) { };
2103 + if (cmd == null) { return; }
2104 + if ((cmd.ctrlChannel == '102938') || ((cmd.type == 'offer') && (cmd.sdp != null))) { onTunnelControlData(cmd, this); return; } // If this is control data, handle it now.
2105 + if (cmd.action == undefined) { return; }
2106 + //sendConsoleText('CMD: ' + JSON.stringify(cmd));
2107 +
2108 + if ((cmd.path != null) && (process.platform != 'win32') && (cmd.path[0] != '/')) { cmd.path = '/' + cmd.path; } // Add '/' to paths on non-windows
2109 + //console.log(objToString(cmd, 0, ' '));
2110 + switch (cmd.action) {
2111 + case 'ls': {
2112 + /*
2113 + // Close the watcher if required
2114 + var samepath = ((this.httprequest.watcher != undefined) && (cmd.path == this.httprequest.watcher.path));
2115 + if ((this.httprequest.watcher != undefined) && (samepath == false)) {
2116 + //console.log('Closing watcher: ' + this.httprequest.watcher.path);
2117 + //this.httprequest.watcher.close(); // TODO: This line causes the agent to crash!!!!
2118 + delete this.httprequest.watcher;
2119 + }
2120 + */
2121 +
2122 + // Send the folder content to the browser
2123 + var response = getDirectoryInfo(cmd.path);
2124 + if (cmd.reqid != undefined) { response.reqid = cmd.reqid; }
2125 + this.write(Buffer.from(JSON.stringify(response)));
2126 +
2127 + /*
2128 + // Start the directory watcher
2129 + if ((cmd.path != '') && (samepath == false)) {
2130 + var watcher = fs.watch(cmd.path, onFileWatcher);
2131 + watcher.tunnel = this.httprequest;
2132 + watcher.path = cmd.path;
2133 + this.httprequest.watcher = watcher;
2134 + //console.log('Starting watcher: ' + this.httprequest.watcher.path);
2135 + }
2136 + */
2137 + break;
2138 + }
2139 + case 'mkdir': {
2140 + // Create a new empty folder
2141 + fs.mkdirSync(cmd.path);
2142 + MeshServerLogEx(44, [cmd.path], "Create folder: \"" + cmd.path + "\"", this.httprequest);
2143 + break;
2144 + }
2145 + case 'rm': {
2146 + // Delete, possibly recursive delete
2147 + for (var i in cmd.delfiles) {
2148 + var p = obj.path.join(cmd.path, cmd.delfiles[i]), delcount = 0;
2149 + try { delcount = deleteFolderRecursive(p, cmd.rec); } catch (e) { }
2150 + if ((delcount == 1) && !cmd.rec) {
2151 + MeshServerLogEx(45, [p], "Delete: \"" + p + "\"", this.httprequest);
2152 + } else {
2153 + if (cmd.rec) {
2154 + MeshServerLogEx(46, [p, delcount], "Delete recursive: \"" + p + "\", " + delcount + " element(s) removed", this.httprequest);
2155 + } else {
2156 + MeshServerLogEx(47, [p, delcount], "Delete: \"" + p + "\", " + delcount + " element(s) removed", this.httprequest);
2157 + }
2158 + }
2159 + }
2160 + break;
2161 + }
2162 + case 'markcoredump': {
2163 + // If we are asking for the coredump file, set the right path.
2164 + var coreDumpPath = null;
2165 + if (process.platform == 'win32') {
2166 + if (fs.existsSync(process.coreDumpLocation)) { coreDumpPath = process.coreDumpLocation; }
2167 + } else {
2168 + if ((process.cwd() != '//') && fs.existsSync(process.cwd() + 'core')) { coreDumpPath = process.cwd() + 'core'; }
2169 + }
2170 + if (coreDumpPath != null) { db.Put('CoreDumpTime', require('fs').statSync(coreDumpPath).mtime); }
2171 + break;
2172 + }
2173 + case 'rename': {
2174 + // Rename a file or folder
2175 + var oldfullpath = obj.path.join(cmd.path, cmd.oldname);
2176 + var newfullpath = obj.path.join(cmd.path, cmd.newname);
2177 + MeshServerLogEx(48, [oldfullpath, cmd.newname], 'Rename: \"' + oldfullpath + '\" to \"' + cmd.newname + '\"', this.httprequest);
2178 + try { fs.renameSync(oldfullpath, newfullpath); } catch (e) { console.log(e); }
2179 + break;
2180 + }
2181 + case 'download': {
2182 + // Download a file
2183 + var sendNextBlock = 0;
2184 + if (cmd.sub == 'start') { // Setup the download
2185 + if ((cmd.path == null) && (cmd.ask == 'coredump')) { // If we are asking for the coredump file, set the right path.
2186 + if (process.platform == 'win32') {
2187 + if (fs.existsSync(process.coreDumpLocation)) { cmd.path = process.coreDumpLocation; }
2188 + } else {
2189 + if ((process.cwd() != '//') && fs.existsSync(process.cwd() + 'core')) { cmd.path = process.cwd() + 'core'; }
2190 + }
2191 + }
2192 + MeshServerLogEx(49, [cmd.path], 'Download: \"' + cmd.path + '\"', this.httprequest);
2193 + if ((cmd.path == null) || (this.filedownload != null)) { this.write({ action: 'download', sub: 'cancel', id: this.filedownload.id }); delete this.filedownload; }
2194 + this.filedownload = { id: cmd.id, path: cmd.path, ptr: 0 }
2195 + 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; }
2196 + if (this.filedownload) { this.write({ action: 'download', sub: 'start', id: cmd.id }); }
2197 + } else if ((this.filedownload != null) && (cmd.id == this.filedownload.id)) { // Download commands
2198 + 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; }
2199 + }
2200 + // Send the next download block(s)
2201 + while (sendNextBlock > 0) {
2202 + sendNextBlock--;
2203 + var buf = Buffer.alloc(16384);
2204 + var len = fs.readSync(this.filedownload.f, buf, 4, 16380, null);
2205 + this.filedownload.ptr += len;
2206 + if (len < 16380) { buf.writeInt32BE(0x01000001, 0); fs.closeSync(this.filedownload.f); delete this.filedownload; sendNextBlock = 0; } else { buf.writeInt32BE(0x01000000, 0); }
2207 + this.write(buf.slice(0, len + 4)); // Write as binary
2208 + }
2209 + break;
2210 + }
2211 + /*
2212 + case 'download': {
2213 + // Packet download of a file, agent to browser
2214 + if (cmd.path == undefined) break;
2215 + var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path;
2216 + //console.log('Download: ' + filepath);
2217 + try { this.httprequest.downloadFile = fs.openSync(filepath, 'rbN'); } catch (e) { this.write(Buffer.from(JSON.stringify({ action: 'downloaderror', reqid: cmd.reqid }))); break; }
2218 + this.httprequest.downloadFileId = cmd.reqid;
2219 + this.httprequest.downloadFilePtr = 0;
2220 + if (this.httprequest.downloadFile) { this.write(Buffer.from(JSON.stringify({ action: 'downloadstart', reqid: this.httprequest.downloadFileId }))); }
2221 + break;
2222 + }
2223 + case 'download2': {
2224 + // Stream download of a file, agent to browser
2225 + if (cmd.path == undefined) break;
2226 + var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path;
2227 + try { this.httprequest.downloadFile = fs.createReadStream(filepath, { flags: 'rbN' }); } catch (e) { console.log(e); }
2228 + this.httprequest.downloadFile.pipe(this);
2229 + this.httprequest.downloadFile.end = function () { }
2230 + break;
2231 + }
2232 + */
2233 + case 'upload': {
2234 + // Upload a file, browser to agent
2235 + if (this.httprequest.uploadFile != null) { fs.closeSync(this.httprequest.uploadFile); delete this.httprequest.uploadFile; }
2236 + if (cmd.path == undefined) break;
2237 + var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path;
2238 + this.httprequest.uploadFilePath = filepath;
2239 + MeshServerLogEx(50, [filepath], 'Upload: \"' + filepath + '\"', this.httprequest);
2240 + try { this.httprequest.uploadFile = fs.openSync(filepath, 'wbN'); } catch (e) { this.write(Buffer.from(JSON.stringify({ action: 'uploaderror', reqid: cmd.reqid }))); break; }
2241 + this.httprequest.uploadFileid = cmd.reqid;
2242 + if (this.httprequest.uploadFile) { this.write(Buffer.from(JSON.stringify({ action: 'uploadstart', reqid: this.httprequest.uploadFileid }))); }
2243 + break;
2244 + }
2245 + case 'uploaddone': {
2246 + // Indicates that an upload is done
2247 + if (this.httprequest.uploadFile) {
2248 + fs.closeSync(this.httprequest.uploadFile);
2249 + this.write(Buffer.from(JSON.stringify({ action: 'uploaddone', reqid: this.httprequest.uploadFileid }))); // Indicate that we closed the file.
2250 + delete this.httprequest.uploadFile;
2251 + delete this.httprequest.uploadFileid;
2252 + delete this.httprequest.uploadFilePath;
2253 + }
2254 + break;
2255 + }
2256 + case 'uploadcancel': {
2257 + // Indicates that an upload is canceled
2258 + if (this.httprequest.uploadFile) {
2259 + fs.closeSync(this.httprequest.uploadFile);
2260 + fs.unlinkSync(this.httprequest.uploadFilePath);
2261 + this.write(Buffer.from(JSON.stringify({ action: 'uploadcancel', reqid: this.httprequest.uploadFileid }))); // Indicate that we closed the file.
2262 + delete this.httprequest.uploadFile;
2263 + delete this.httprequest.uploadFileid;
2264 + delete this.httprequest.uploadFilePath;
2265 + }
2266 + break;
2267 + }
2268 + case 'copy': {
2269 + // Copy a bunch of files from scpath to dspath
2270 + for (var i in cmd.names) {
2271 + var sc = obj.path.join(cmd.scpath, cmd.names[i]), ds = obj.path.join(cmd.dspath, cmd.names[i]);
2272 + MeshServerLogEx(51, [sc, ds], 'Copy: \"' + sc + '\" to \"' + ds + '\"', this.httprequest);
2273 + if (sc != ds) { try { fs.copyFileSync(sc, ds); } catch (e) { } }
2274 + }
2275 + break;
2276 + }
2277 + case 'move': {
2278 + // Move a bunch of files from scpath to dspath
2279 + for (var i in cmd.names) {
2280 + var sc = obj.path.join(cmd.scpath, cmd.names[i]), ds = obj.path.join(cmd.dspath, cmd.names[i]);
2281 + MeshServerLogEx(52, [sc, ds], 'Move: \"' + sc + '\" to \"' + ds + '\"', this.httprequest);
2282 + if (sc != ds) { try { fs.copyFileSync(sc, ds); fs.unlinkSync(sc); } catch (e) { } }
2283 + }
2284 + break;
2285 + }
2286 + case 'zip':
2287 + // Zip a bunch of files
2288 + if (this.zip != null) return; // Zip operating is currently running, exit now.
2289 +
2290 + // Check that the specified files exist & build full paths
2291 + var fp, stat, p = [];
2292 + 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); } }
2293 + if (p.length == 0) return; // No files, quit now.
2294 +
2295 + // Setup file compression
2296 + var ofile = cmd.path + '/' + cmd.output;
2297 + this.write(Buffer.from(JSON.stringify({ action: 'dialogmessage', msg: 'zipping' })));
2298 + this.zipfile = ofile;
2299 + delete this.zipcancel;
2300 + var out = require('fs').createWriteStream(ofile, { flags: 'wb' });
2301 + out.xws = this;
2302 + out.on('close', function () {
2303 + this.xws.write(Buffer.from(JSON.stringify({ action: 'dialogmessage', msg: null })));
2304 + this.xws.write(Buffer.from(JSON.stringify({ action: 'refresh' })));
2305 + if (this.xws.zipcancel === true) { fs.unlinkSync(this.xws.zipfile); } // Delete the complete file.
2306 + delete this.xws.zipcancel;
2307 + delete this.xws.zipfile;
2308 + delete this.xws.zip;
2309 + });
2310 + this.zip = require('zip-writer').write({ files: p, basePath: cmd.path });
2311 + this.zip.xws = this;
2312 + 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));
2313 + this.zip.pipe(out);
2314 + break;
2315 + case 'cancel':
2316 + // Cancel zip operation if present
2317 + try { this.zipcancel = true; this.zip.cancel(function () { }); } catch (e) { }
2318 + this.zip = null;
2319 + break;
2320 + default:
2321 + // Unknown action, ignore it.
2322 + break;
2323 + }
2324 + } else if (this.httprequest.protocol == 7) { // Plugin data exchange
2325 + var cmd = null;
2326 + try { cmd = JSON.parse(data); } catch (e) { };
2327 + if (cmd == null) { return; }
2328 + if ((cmd.ctrlChannel == '102938') || ((cmd.type == 'offer') && (cmd.sdp != null))) { onTunnelControlData(cmd, this); return; } // If this is control data, handle it now.
2329 + if (cmd.action == undefined) return;
2330 +
2331 + switch (cmd.action) {
2332 + case 'plugin': {
2333 + try { require(cmd.plugin).consoleaction(cmd, null, null, this); } catch (e) { throw e; }
2334 + break;
2335 + }
2336 + default: {
2337 + // probably shouldn't happen, but just in case this feature is expanded
2338 + }
2339 + }
2340 +
2341 + }
2342 + //sendConsoleText("Got tunnel #" + this.httprequest.index + " data: " + data, this.httprequest.sessionid);
2343 + }
2344 + }
2345 +
2346 + // Delete a directory with a files and directories within it
2347 + function deleteFolderRecursive(path, rec) {
2348 + var count = 0;
2349 + if (fs.existsSync(path)) {
2350 + if (rec == true) {
2351 + fs.readdirSync(obj.path.join(path, '*')).forEach(function (file, index) {
2352 + var curPath = obj.path.join(path, file);
2353 + if (fs.statSync(curPath).isDirectory()) { // recurse
2354 + count += deleteFolderRecursive(curPath, true);
2355 + } else { // delete file
2356 + fs.unlinkSync(curPath);
2357 + count++;
2358 + }
2359 + });
2360 + }
2361 + fs.unlinkSync(path);
2362 + count++;
2363 + }
2364 + return count;
2365 + };
2366 +
2367 + // Called when receiving control data on WebRTC
2368 + function onTunnelWebRTCControlData(data) {
2369 + if (typeof data != 'string') return;
2370 + var obj;
2371 + try { obj = JSON.parse(data); } catch (e) { sendConsoleText('Invalid control JSON on WebRTC: ' + data); return; }
2372 + if (obj.type == 'close') {
2373 + //sendConsoleText('Tunnel #' + this.xrtc.websocket.tunnel.index + ' WebRTC control close');
2374 + try { this.close(); } catch (e) { }
2375 + try { this.xrtc.close(); } catch (e) { }
2376 + }
2377 + }
2378 +
2379 + // Called when receiving control data on websocket
2380 + function onTunnelControlData(data, ws) {
2381 + var obj;
2382 + if (ws == null) { ws = this; }
2383 + if (typeof data == 'string') { try { obj = JSON.parse(data); } catch (e) { sendConsoleText('Invalid control JSON: ' + data); return; } }
2384 + else if (typeof data == 'object') { obj = data; } else { return; }
2385 + //sendConsoleText('onTunnelControlData(' + ws.httprequest.protocol + '): ' + JSON.stringify(data));
2386 + //console.log('onTunnelControlData: ' + JSON.stringify(data));
2387 +
2388 + if (obj.action) {
2389 + switch (obj.action) {
2390 + case 'lock': {
2391 + // Lock the current user out of the desktop
2392 + try {
2393 + if (process.platform == 'win32') {
2394 + MeshServerLogEx(53, null, "Locking remote user out of desktop", ws.httprequest);
2395 + var child = require('child_process');
2396 + child.execFile(process.env['windir'] + '\\system32\\cmd.exe', ['/c', 'RunDll32.exe user32.dll,LockWorkStation'], { type: 1 });
2397 + }
2398 + } catch (e) { }
2399 + break;
2400 + }
2401 + default:
2402 + // Unknown action, ignore it.
2403 + break;
2404 + }
2405 + return;
2406 + }
2407 +
2408 + switch (obj.type) {
2409 + case 'options': {
2410 + // These are additional connection options passed in the control channel.
2411 + //sendConsoleText('options: ' + JSON.stringify(obj));
2412 + delete obj.type;
2413 + ws.httprequest.xoptions = obj;
2414 +
2415 + // Set additional user consent options if present
2416 + if ((obj != null) && (typeof obj.consent == 'number')) { ws.httprequest.consent |= obj.consent; }
2417 +
2418 + break;
2419 + }
2420 + case 'close': {
2421 + // We received the close on the websocket
2422 + //sendConsoleText('Tunnel #' + ws.tunnel.index + ' WebSocket control close');
2423 + try { ws.close(); } catch (e) { }
2424 + break;
2425 + }
2426 + case 'termsize': {
2427 + // Indicates a change in terminal size
2428 + if (process.platform == 'win32')
2429 + {
2430 + if (ws.httprequest._dispatcher == null) return;
2431 + //sendConsoleText('Win32-TermSize: ' + obj.cols + 'x' + obj.rows);
2432 + if (ws.httprequest._dispatcher.invoke) { ws.httprequest._dispatcher.invoke('resizeTerminal', [obj.cols, obj.rows]); }
2433 + } else
2434 + {
2435 + if (ws.httprequest.process == null || ws.httprequest.process.pty == 0) return;
2436 + //sendConsoleText('Linux Resize: ' + obj.cols + 'x' + obj.rows);
2437 +
2438 + if (ws.httprequest.process.tcsetsize) { ws.httprequest.process.tcsetsize(obj.rows, obj.cols); }
2439 + }
2440 + break;
2441 + }
2442 + case 'webrtc0': { // Browser indicates we can start WebRTC switch-over.
2443 + if (ws.httprequest.protocol == 1) { // Terminal
2444 + // 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
2445 + if (process.platform == 'win32') {
2446 + ws.httprequest._term.unpipe(ws);
2447 + } else {
2448 + ws.httprequest.process.stdout.unpipe(ws);
2449 + ws.httprequest.process.stderr.unpipe(ws);
2450 + }
2451 + } else if (ws.httprequest.protocol == 2) { // Desktop
2452 + // 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
2453 + ws.httprequest.desktop.kvm.unpipe(ws);
2454 + } else {
2455 + // Switch things around so all WebRTC data goes to onTunnelData().
2456 + ws.rtcchannel.httprequest = ws.httprequest;
2457 + ws.rtcchannel.removeAllListeners('data');
2458 + ws.rtcchannel.on('data', onTunnelData);
2459 + }
2460 + ws.write("{\"ctrlChannel\":\"102938\",\"type\":\"webrtc1\"}"); // End of data marker
2461 + break;
2462 + }
2463 + case 'webrtc1': {
2464 + if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) { // Terminal
2465 + // Switch the user input from websocket to webrtc at this point.
2466 + if (process.platform == 'win32') {
2467 + ws.unpipe(ws.httprequest._term);
2468 + ws.rtcchannel.pipe(ws.httprequest._term, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
2469 + } else {
2470 + ws.unpipe(ws.httprequest.process.stdin);
2471 + ws.rtcchannel.pipe(ws.httprequest.process.stdin, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
2472 + }
2473 + ws.resume(); // Resume the websocket to keep receiving control data
2474 + } else if (ws.httprequest.protocol == 2) { // Desktop
2475 + // Switch the user input from websocket to webrtc at this point.
2476 + ws.unpipe(ws.httprequest.desktop.kvm);
2477 + try { ws.webrtc.rtcchannel.pipe(ws.httprequest.desktop.kvm, { dataTypeSkip: 1, end: false }); } catch (e) { sendConsoleText('EX2'); } // 0 = Binary, 1 = Text.
2478 + ws.resume(); // Resume the websocket to keep receiving control data
2479 + }
2480 + ws.write('{\"ctrlChannel\":\"102938\",\"type\":\"webrtc2\"}'); // Indicates we will no longer get any data on websocket, switching to WebRTC at this point.
2481 + break;
2482 + }
2483 + case 'webrtc2': {
2484 + // Other side received websocket end of data marker, start sending data on WebRTC channel
2485 + if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) { // Terminal
2486 + if (process.platform == 'win32') {
2487 + ws.httprequest._term.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
2488 + } else {
2489 + ws.httprequest.process.stdout.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
2490 + ws.httprequest.process.stderr.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
2491 + }
2492 + } else if (ws.httprequest.protocol == 2) { // Desktop
2493 + ws.httprequest.desktop.kvm.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
2494 + }
2495 + break;
2496 + }
2497 + case 'offer': {
2498 + // This is a WebRTC offer.
2499 + if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) return; // TODO: Terminal is currently broken with WebRTC. Reject WebRTC upgrade for now.
2500 + ws.webrtc = rtc.createConnection();
2501 + ws.webrtc.websocket = ws;
2502 + ws.webrtc.on('connected', function () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC connected');*/ });
2503 + ws.webrtc.on('disconnected', function () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC disconnected');*/ });
2504 + ws.webrtc.on('dataChannel', function (rtcchannel) {
2505 + //sendConsoleText('WebRTC Datachannel open, protocol: ' + this.websocket.httprequest.protocol);
2506 + rtcchannel.maxFragmentSize = 32768;
2507 + rtcchannel.xrtc = this;
2508 + rtcchannel.websocket = this.websocket;
2509 + this.rtcchannel = rtcchannel;
2510 + this.websocket.rtcchannel = rtcchannel;
2511 + this.websocket.rtcchannel.on('data', onTunnelWebRTCControlData);
2512 + this.websocket.rtcchannel.on('end', function () {
2513 + // The WebRTC channel closed, unpipe the KVM now. This is also done when the web socket closes.
2514 + //sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC data channel closed');
2515 + if (this.websocket.desktop && this.websocket.desktop.kvm)
2516 + {
2517 + try
2518 + {
2519 + this.unpipe(this.websocket.desktop.kvm);
2520 + this.websocket.httprequest.desktop.kvm.unpipe(this);
2521 + }
2522 + catch (e) { }
2523 + }
2524 + });
2525 + this.websocket.write('{\"ctrlChannel\":\"102938\",\"type\":\"webrtc0\"}'); // Indicate we are ready for WebRTC switch-over.
2526 + });
2527 + var sdp = null;
2528 + try { sdp = ws.webrtc.setOffer(obj.sdp); } catch (e) { }
2529 + if (sdp != null) { ws.write({ type: 'answer', ctrlChannel: '102938', sdp: sdp }); }
2530 + break;
2531 + }
2532 + case 'ping': {
2533 + ws.write("{\"ctrlChannel\":\"102938\",\"type\":\"pong\"}"); // Send pong response
2534 + break;
2535 + }
2536 + case 'pong': { // NOP
2537 + break;
2538 + }
2539 + case 'rtt': {
2540 + ws.write({ type: 'rtt', ctrlChannel: '102938', time: obj.time });
2541 + break;
2542 + }
2543 + }
2544 + }
2545 +
2546 + // Console state
2547 + var consoleWebSockets = {};
2548 + var consoleHttpRequest = null;
2549 +
2550 + // Console HTTP response
2551 + function consoleHttpResponse(response) {
2552 + response.data = function (data) { sendConsoleText(rstr2hex(buf2rstr(data)), this.sessionid); consoleHttpRequest = null; }
2553 + response.close = function () { sendConsoleText('httprequest.response.close', this.sessionid); consoleHttpRequest = null; }
2554 + };
2555 +
2556 + // Open a web browser to a specified URL on current user's desktop
2557 + function openUserDesktopUrl(url) {
2558 + if ((url.toLowerCase().startsWith('http://') == false) && (url.toLowerCase().startsWith('https://') == false)) { return null; }
2559 + var child = null;
2560 + try {
2561 + switch (process.platform) {
2562 + case 'win32':
2563 + var user = require('user-sessions').getUsername(require('user-sessions').consoleUid());
2564 + child = require('child_process').execFile(process.env['windir'] + '\\system32\\cmd.exe', ['cmd']);
2565 + child.stderr.on('data', function () { });
2566 + child.stdout.on('data', function () { });
2567 + 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');
2568 + child.stdin.write('SCHTASKS /RUN /TN MeshChatTask\r\n');
2569 + child.stdin.write('SCHTASKS /DELETE /F /TN MeshChatTask\r\n');
2570 + child.stdin.write('exit\r\n');
2571 + child.waitExit();
2572 + break;
2573 + case 'linux':
2574 + child = require('child_process').execFile('/usr/bin/xdg-open', ['xdg-open', url], { uid: require('user-sessions').consoleUid() });
2575 + break;
2576 + case 'darwin':
2577 + child = require('child_process').execFile('/usr/bin/open', ['open', url], { uid: require('user-sessions').consoleUid() });
2578 + break;
2579 + default:
2580 + // Unknown platform, ignore this command.
2581 + break;
2582 + }
2583 + } catch (e) { }
2584 + return child;
2585 + }
2586 +
2587 + // Process a mesh agent console command
2588 + function processConsoleCommand(cmd, args, rights, sessionid) {
2589 + try {
2590 + var response = null;
2591 + switch (cmd) {
2592 + case 'help': { // Displays available commands
2593 + 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';
2594 + if (process.platform == 'win32') { availcommands += ',safemode,wpfhwacceleration,uac'; }
2595 + if (process.platform != 'freebsd') { availcommands += ',vm';}
2596 + if (require('MeshAgent').maxKvmTileSize != null) { availcommands += ',kvmmode'; }
2597 + try { require('zip-reader'); availcommands += ',zip,unzip'; } catch (e) { }
2598 +
2599 + availcommands = availcommands.split(',').sort();
2600 + while (availcommands.length > 0) {
2601 + if (f.length > 90) { fin += (f + ',\r\n'); f = ''; }
2602 + f += (((f != '') ? ', ' : ' ') + availcommands.shift());
2603 + }
2604 + if (f != '') { fin += f; }
2605 + response = "Available commands: \r\n" + fin + ".";
2606 + break;
2607 + }
2608 + case 'agentmsg': {
2609 + if (args['_'].length == 0) {
2610 + response = "Proper usage:\r\n agentmsg add \"[message]\" [iconIndex]\r\n agentmsg remove [index]\r\n agentmsg list"; // Display usage
2611 + } else {
2612 + if ((args['_'][0] == 'add') && (args['_'].length > 1)) {
2613 + var msgIndex = 1, iconIndex = 0;
2614 + while (tunnelUserCount.msg[msgIndex] != null) { msgIndex++; }
2615 + if (args['_'].length >= 3) { try { iconIndex = parseInt(args['_'][2]); } catch (e) { } }
2616 + if (typeof iconIndex != 'number') { iconIndex = 0; }
2617 + tunnelUserCount.msg[msgIndex] = { msg: args['_'][1], icon: iconIndex };
2618 + response = 'Agent message ' + msgIndex + ' added.';
2619 + } else if ((args['_'][0] == 'remove') && (args['_'].length > 1)) {
2620 + var msgIndex = 0;
2621 + try { msgIndex = parseInt(args['_'][1]); } catch (x) { }
2622 + if (tunnelUserCount.msg[msgIndex] == null) { response = "Message not found."; } else { delete tunnelUserCount.msg[msgIndex]; response = "Message removed."; }
2623 + } else if (args['_'][0] == 'list') {
2624 + response = JSON.stringify(tunnelUserCount.msg, null, 2);
2625 + }
2626 + try { mesh.SendCommand({ action: 'sessions', type: 'msg', value: tunnelUserCount.msg }); } catch (x) { }
2627 + broadcastSessionsToRegisteredApps();
2628 + }
2629 + break;
2630 + }
2631 + case 'clearagentmsg': {
2632 + tunnelUserCount.msg = {};
2633 + try { mesh.SendCommand({ action: 'sessions', type: 'msg', value: tunnelUserCount.msg }); } catch (x) { }
2634 + broadcastSessionsToRegisteredApps();
2635 + break;
2636 + }
2637 + case 'coredump':
2638 + if (args['_'].length != 1) {
2639 + response = "Proper usage: coredump on|off|status|clear"; // Display usage
2640 + } else {
2641 + switch (args['_'][0].toLowerCase())
2642 + {
2643 + case 'on':
2644 + process.coreDumpLocation = (process.platform == 'win32') ? (process.execPath.replace('.exe', '.dmp')) : (process.execPath + '.dmp');
2645 + response = 'coredump is now on';
2646 + break;
2647 + case 'off':
2648 + process.coreDumpLocation = null;
2649 + response = 'coredump is now off';
2650 + break;
2651 + case 'status':
2652 + response = 'coredump is: ' + ((process.coreDumpLocation == null) ? 'off' : 'on');
2653 + if (process.coreDumpLocation != null) {
2654 + if (process.platform == 'win32') {
2655 + if (fs.existsSync(process.coreDumpLocation)) { response += '\r\n CoreDump present at: ' + process.coreDumpLocation; }
2656 + } else {
2657 + if ((process.cwd() != '//') && fs.existsSync(process.cwd() + 'core')) { response += '\r\n CoreDump present at: ' + process.cwd() + 'core'; }
2658 + }
2659 + }
2660 + break;
2661 + case 'clear':
2662 + db.Put('CoreDumpTime', null);
2663 + response = 'coredump db cleared';
2664 + break;
2665 + default:
2666 + response = "Proper usage: coredump on|off|status"; // Display usage
2667 + break;
2668 + }
2669 + }
2670 + break;
2671 + case 'service':
2672 + if (args['_'].length != 1)
2673 + {
2674 + response = "Proper usage: service status|restart"; // Display usage
2675 + }
2676 + else
2677 + {
2678 + var s = require('service-manager').manager.getService(process.platform == 'win32' ? 'Mesh Agent' : 'meshagent');
2679 + switch(args['_'][0].toLowerCase())
2680 + {
2681 + case 'status':
2682 + response = 'Service ' + (s.isRunning() ? (s.isMe() ? '[SELF]' : '[RUNNING]') : ('[NOT RUNNING]'));
2683 + break;
2684 + case 'restart':
2685 + if (s.isMe())
2686 + {
2687 + s.restart();
2688 + }
2689 + else
2690 + {
2691 + response = 'Restarting another agent instance is not allowed';
2692 + }
2693 + break;
2694 + default:
2695 + response = "Proper usage: service status|restart"; // Display usage
2696 + break;
2697 + }
2698 + if (process.platform == 'win32') { s.close(); }
2699 + }
2700 + break;
2701 + case 'zip':
2702 + if (args['_'].length == 0) {
2703 + response = "Proper usage: zip (output file name), input1 [, input n]"; // Display usage
2704 + } else {
2705 + var p = args['_'].join(' ').split(',');
2706 + var ofile = p.shift();
2707 + sendConsoleText('Writing ' + ofile + '...');
2708 + var out = require('fs').createWriteStream(ofile, { flags: 'wb' });
2709 + out.fname = ofile;
2710 + out.sessionid = sessionid;
2711 + out.on('close', function () { sendConsoleText('DONE writing ' + this.fname, this.sessionid); });
2712 + var zip = require('zip-writer').write({ files: p });
2713 + zip.pipe(out);
2714 + }
2715 + break;
2716 + case 'unzip':
2717 + if (args['_'].length == 0) {
2718 + response = "Proper usage: unzip input, destination"; // Display usage
2719 + } else {
2720 + var p = args['_'].join(' ').split(',');
2721 + if (p.length != 2) { response = "Proper usage: unzip input, destination"; break; } // Display usage
2722 + var prom = require('zip-reader').read(p[0]);
2723 + prom._dest = p[1];
2724 + prom.self = this;
2725 + prom.sessionid = sessionid;
2726 + prom.then(function (zipped) {
2727 + sendConsoleText('Extracting to ' + this._dest + '...', this.sessionid);
2728 + zipped.extractAll(this._dest).then(function () { sendConsoleText('finished unzipping', this.sessionid); }, function (e) { sendConsoleText('Error unzipping: ' + e, this.sessionid); }).parentPromise.sessionid = this.sessionid;
2729 + }, function (e) { sendConsoleText('Error unzipping: ' + e, this.sessionid); });
2730 + }
2731 + break;
2732 + case 'setbattery':
2733 + // require('MeshAgent').SendCommand({ action: 'battery', state: 'dc', level: 55 });
2734 + if ((args['_'].length > 0) && ((args['_'][0] == 'ac') || (args['_'][0] == 'dc'))) {
2735 + var b = { action: 'battery', state: args['_'][0] };
2736 + if (args['_'].length == 2) { b.level = parseInt(args['_'][1]); }
2737 + require('MeshAgent').SendCommand(b);
2738 + } else {
2739 + require('MeshAgent').SendCommand({ action: 'battery' });
2740 + }
2741 + break;
2742 + case 'fdsnapshot':
2743 + require('ChainViewer').getSnapshot().then(function (c) { sendConsoleText(c, this.sessionid); }).parentPromise.sessionid = sessionid;
2744 + break;
2745 + case 'fdcount':
2746 + require('DescriptorEvents').getDescriptorCount().then(
2747 + function (c)
2748 + {
2749 + sendConsoleText('Descriptor Count: ' + c, this.sessionid);
2750 + }, function (e)
2751 + {
2752 + sendConsoleText('Error fetching descriptor count: ' + e, this.sessionid);
2753 + }).parentPromise.sessionid = sessionid;
2754 + break;
2755 + case 'uac':
2756 + if (process.platform != 'win32')
2757 + {
2758 + response = 'Unknown command "uac", type "help" for list of avaialble commands.';
2759 + break;
2760 + }
2761 + if (args['_'].length != 1)
2762 + {
2763 + response = 'Proper usage: uac [get|interactive|secure]';
2764 + }
2765 + else
2766 + {
2767 + switch(args['_'][0].toUpperCase())
2768 + {
2769 + case 'GET':
2770 + var secd = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System', 'PromptOnSecureDesktop');
2771 + response = "UAC mode: " + (secd == 0 ? "Interactive Desktop" : "Secure Desktop");
2772 + break;
2773 + case 'INTERACTIVE':
2774 + try
2775 + {
2776 + require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System', 'PromptOnSecureDesktop', 0);
2777 + response = 'UAC mode changed to: Interactive Desktop';
2778 + }
2779 + catch (e)
2780 + {
2781 + response = "Unable to change UAC Mode";
2782 + }
2783 + break;
2784 + case 'SECURE':
2785 + try
2786 + {
2787 + require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System', 'PromptOnSecureDesktop', 1);
2788 + response = 'UAC mode changed to: Secure Desktop';
2789 + }
2790 + catch(e)
2791 + {
2792 + response = "Unable to change UAC Mode";
2793 + }
2794 + break;
2795 + default:
2796 + response = 'Proper usage: uac [get|interactive|secure]';
2797 + break;
2798 + }
2799 + }
2800 + break;
2801 + case 'vm':
2802 + response = 'Virtual Machine = ' + require('identifiers').isVM();
2803 + break;
2804 + case 'startupoptions':
2805 + response = JSON.stringify(require('MeshAgent').getStartupOptions());
2806 + break;
2807 + case 'kvmmode':
2808 + if (require('MeshAgent').maxKvmTileSize == null)
2809 + {
2810 + response = "Unknown command \"kvmmode\", type \"help\" for list of avaialble commands.";
2811 + }
2812 + else
2813 + {
2814 + if(require('MeshAgent').maxKvmTileSize == 0)
2815 + {
2816 + response = 'KVM Mode: Full JUMBO';
2817 + }
2818 + else
2819 + {
2820 + response = 'KVM Mode: ' + (require('MeshAgent').maxKvmTileSize <= 65500 ? 'NO JUMBO' : 'Partial JUMBO');
2821 + response += (', TileLimit: ' + (require('MeshAgent').maxKvmTileSize < 1024 ? (require('MeshAgent').maxKvmTileSize + ' bytes') : (Math.round(require('MeshAgent').maxKvmTileSize/1024) + ' Kbytes')));
2822 + }
2823 + }
2824 + break;
2825 + case 'alert':
2826 + if (args['_'].length == 0)
2827 + {
2828 + response = "Proper usage: alert TITLE, CAPTION [, TIMEOUT]"; // Display usage
2829 + }
2830 + else
2831 + {
2832 + var p = args['_'].join(' ').split(',');
2833 + if(p.length<2)
2834 + {
2835 + response = "Proper usage: alert TITLE, CAPTION [, TIMEOUT]"; // Display usage
2836 + }
2837 + else
2838 + {
2839 + this._alert = require('message-box').create(p[0], p[1], p.length==3?parseInt(p[2]):9999,1);
2840 + }
2841 + }
2842 + break;
2843 + case 'agentsize':
2844 + var actualSize = Math.floor(require('fs').statSync(process.execPath).size / 1024);
2845 + if (process.platform == 'win32') {
2846 + // Check the Agent Uninstall MetaData for correctness, as the installer may have written an incorrect value
2847 + var writtenSize = 0;
2848 + try { writtenSize = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MeshCentralAgent', 'EstimatedSize'); } catch (e) { response = e; }
2849 + if (writtenSize != actualSize) {
2850 + response = "Size updated from: " + writtenSize + " to: " + actualSize;
2851 + try { require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MeshCentralAgent', 'EstimatedSize', actualSize); } catch (e) { response = e; }
2852 + } else { response = "Agent Size: " + actualSize + " kb"; }
2853 + } else { response = "Agent Size: " + actualSize + " kb"; }
2854 + break;
2855 + case 'versions':
2856 + response = JSON.stringify(process.versions, null, ' ');
2857 + break;
2858 + case 'wpfhwacceleration':
2859 + if (process.platform != 'win32') { throw ("wpfhwacceleration setting is only supported on Windows"); }
2860 + if (args['_'].length != 1) {
2861 + response = "Proper usage: wpfhwacceleration (ON|OFF|STATUS)"; // Display usage
2862 + }
2863 + else {
2864 + var reg = require('win-registry');
2865 + var uname = require('user-sessions').getUsername(require('user-sessions').consoleUid());
2866 + var key = reg.usernameToUserKey(uname);
2867 +
2868 + switch (args['_'][0].toUpperCase()) {
2869 + default:
2870 + response = "Proper usage: wpfhwacceleration (ON|OFF|STATUS|DEFAULT)"; // Display usage
2871 + break;
2872 + case 'ON':
2873 + try {
2874 + reg.WriteKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration', 0);
2875 + response = "OK";
2876 + } catch (e) { response = "FAILED"; }
2877 + break;
2878 + case 'OFF':
2879 + try {
2880 + reg.WriteKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration', 1);
2881 + response = 'OK';
2882 + } catch (e) { response = 'FAILED'; }
2883 + break;
2884 + case 'STATUS':
2885 + var s;
2886 + try { s = reg.QueryKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration') == 1 ? 'DISABLED' : 'ENABLED'; } catch (e) { s = 'DEFAULT'; }
2887 + response = "WPF Hardware Acceleration: " + s;
2888 + break;
2889 + case 'DEFAULT':
2890 + try { reg.DeleteKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration'); } catch (e) { }
2891 + response = 'OK';
2892 + break;
2893 + }
2894 + }
2895 + break;
2896 + case 'tsid':
2897 + if (process.platform == 'win32') {
2898 + if (args['_'].length != 1) {
2899 + response = "TSID: " + (require('MeshAgent')._tsid == null ? "console" : require('MeshAgent')._tsid);
2900 + } else {
2901 + var i = parseInt(args['_'][0]);
2902 + require('MeshAgent')._tsid = (isNaN(i) ? null : i);
2903 + response = "TSID set to: " + (require('MeshAgent')._tsid == null ? "console" : require('MeshAgent')._tsid);
2904 + }
2905 + } else { response = "TSID command only supported on Windows"; }
2906 + break;
2907 + case 'activeusers':
2908 + if (process.platform == 'win32') {
2909 + var p = require('user-sessions').enumerateUsers();
2910 + p.sessionid = sessionid;
2911 + p.then(function (u) {
2912 + var v = [];
2913 + for (var i in u) {
2914 + if (u[i].State == 'Active') { v.push({ tsid: i, type: u[i].StationName, user: u[i].Username, domain: u[i].Domain }); }
2915 + }
2916 + sendConsoleText(JSON.stringify(v, null, 1), this.sessionid);
2917 + });
2918 + } else { response = "activeusers command only supported on Windows"; }
2919 + break;
2920 + case 'wallpaper':
2921 + if (process.platform != 'win32' && !(process.platform == 'linux' && require('linux-gnome-helpers').available)) {
2922 + response = "wallpaper command not supported on this platform";
2923 + }
2924 + else {
2925 + if (args['_'].length != 1) {
2926 + response = 'Proper usage: wallpaper (GET|TOGGLE)'; // Display usage
2927 + }
2928 + else {
2929 + switch (args['_'][0].toUpperCase()) {
2930 + default:
2931 + response = 'Proper usage: wallpaper (GET|TOGGLE)'; // Display usage
2932 + break;
2933 + case 'GET':
2934 + case 'TOGGLE':
2935 + if (process.platform == 'win32') {
2936 + var id = require('user-sessions').getProcessOwnerName(process.pid).tsid == 0 ? 1 : 0;
2937 + var child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0='], { type: id });
2938 + child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
2939 + child.stderr.on('data', function () { });
2940 + child.waitExit();
2941 + var current = child.stdout.str.trim();
2942 + if (args['_'][0].toUpperCase() == 'GET') {
2943 + response = current;
2944 + break;
2945 + }
2946 + if (current != '') {
2947 + require('MeshAgent')._wallpaper = current;
2948 + response = 'Wallpaper cleared';
2949 + } else {
2950 + response = 'Wallpaper restored';
2951 + }
2952 + child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0=', current != '' ? '""' : require('MeshAgent')._wallpaper], { type: id });
2953 + child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
2954 + child.stderr.on('data', function () { });
2955 + child.waitExit();
2956 + }
2957 + else {
2958 + var id = require('user-sessions').consoleUid();
2959 + var current = require('linux-gnome-helpers').getDesktopWallpaper(id);
2960 + if (args['_'][0].toUpperCase() == 'GET') {
2961 + response = current;
2962 + break;
2963 + }
2964 + if (current != '/dev/null') {
2965 + require('MeshAgent')._wallpaper = current;
2966 + response = 'Wallpaper cleared';
2967 + } else {
2968 + response = 'Wallpaper restored';
2969 + }
2970 + require('linux-gnome-helpers').setDesktopWallpaper(id, current != '/dev/null' ? undefined : require('MeshAgent')._wallpaper);
2971 + }
2972 + break;
2973 + }
2974 + }
2975 + }
2976 + break;
2977 + case 'safemode':
2978 + if (process.platform != 'win32') {
2979 + response = 'safemode only supported on Windows Platforms'
2980 + }
2981 + else {
2982 + if (args['_'].length != 1) {
2983 + response = 'Proper usage: safemode (ON|OFF|STATUS)'; // Display usage
2984 + }
2985 + else {
2986 + switch (args['_'][0].toUpperCase()) {
2987 + default:
2988 + response = 'Proper usage: safemode (ON|OFF|STATUS)'; // Display usage
2989 + break;
2990 + case 'ON':
2991 + require('win-bcd').setKey('safeboot', 'Network');
2992 + require('win-bcd').enableSafeModeService('Mesh Agent');
2993 + break;
2994 + case 'OFF':
2995 + require('win-bcd').deleteKey('safeboot');
2996 + break;
2997 + case 'STATUS':
2998 + var nextboot = require('win-bcd').getKey('safeboot');
2999 + if (nextboot) {
3000 + switch (nextboot) {
3001 + case 'Network':
3002 + case 'network':
3003 + nextboot = 'SAFE_MODE_NETWORK';
3004 + break;
3005 + default:
3006 + nextboot = 'SAFE_MODE';
3007 + break;
3008 + }
3009 + }
3010 + response = 'Current: ' + require('win-bcd').bootMode + ', NextBoot: ' + (nextboot ? nextboot : 'NORMAL');
3011 + break;
3012 + }
3013 + }
3014 + }
3015 + break;
3016 + /*
3017 + case 'border':
3018 + {
3019 + if ((args['_'].length == 1) && (args['_'][0] == 'on')) {
3020 + if (meshCoreObj.users.length > 0) {
3021 + obj.borderManager.Start(meshCoreObj.users[0]);
3022 + response = 'Border blinking is on.';
3023 + } else {
3024 + response = 'Cannot turn on border blinking, no logged in users.';
3025 + }
3026 + } else if ((args['_'].length == 1) && (args['_'][0] == 'off')) {
3027 + obj.borderManager.Stop();
3028 + response = 'Border blinking is off.';
3029 + } else {
3030 + response = 'Proper usage: border "on|off"'; // Display correct command usage
3031 + }
3032 + }
3033 + break;
3034 + */
3035 + case 'av':
3036 + if (process.platform == 'win32') {
3037 + // Windows Command: "wmic /Namespace:\\root\SecurityCenter2 Path AntiVirusProduct get /FORMAT:CSV"
3038 + response = JSON.stringify(require('win-info').av(), null, 1);
3039 + } else {
3040 + response = 'Not supported on the platform';
3041 + }
3042 + break;
3043 + case 'log':
3044 + if (args['_'].length != 1) { response = 'Proper usage: log "sample text"'; } else { MeshServerLog(args['_'][0]); response = 'ok'; }
3045 + break;
3046 + case 'getclip':
3047 + if (require('MeshAgent').isService) {
3048 + require('clipboard').dispatchRead().then(function (str) { sendConsoleText(str, sessionid); });
3049 + } else {
3050 + require("clipboard").read().then(function (str) { sendConsoleText(str, sessionid); });
3051 + }
3052 + break;
3053 + case 'setclip': {
3054 + if (args['_'].length != 1) {
3055 + response = 'Proper usage: setclip "sample text"';
3056 + } else {
3057 + if (require('MeshAgent').isService) {
3058 + require('clipboard').dispatchWrite(args['_'][0]);
3059 + response = 'Setting clipboard to: "' + args['_'][0] + '"';
3060 + }
3061 + else {
3062 + require("clipboard")(args['_'][0]); response = 'Setting clipboard to: "' + args['_'][0] + '"';
3063 + }
3064 + }
3065 + break;
3066 + }
3067 + /*
3068 + case 'amtreset': {
3069 + if (amt != null) { amt.reset(); response = 'Done.'; }
3070 + break;
3071 + }
3072 + case 'amtlmsreset': {
3073 + if (amt != null) { amt.lmsreset(); response = 'Done.'; }
3074 + break;
3075 + }
3076 + case 'amtccm': {
3077 + if (amt == null) { response = 'Intel AMT not supported.'; } else {
3078 + if (args['_'].length != 1) { response = 'Proper usage: amtccm (adminPassword)'; } // Display usage
3079 + else { amt.setPolicy({ type: 0 }); amt.activeToCCM(args['_'][0]); }
3080 + }
3081 + break;
3082 + }
3083 + case 'amtacm': {
3084 + if (amt == null) { response = 'Intel AMT not supported.'; } else {
3085 + amt.setPolicy({ type: 0 });
3086 + amt.getAmtInfo(function (meinfo) { amt.activeToACM(meinfo); });
3087 + }
3088 + break;
3089 + }
3090 + case 'amtdeactivate': {
3091 + if (amt == null) { response = 'Intel AMT not supported.'; } else { amt.setPolicy({ type: 0 }); amt.deactivateCCM(); }
3092 + break;
3093 + }
3094 + */
3095 + case 'openurl': {
3096 + if (args['_'].length != 1) { response = 'Proper usage: openurl (url)'; } // Display usage
3097 + else { if (openUserDesktopUrl(args['_'][0]) == null) { response = 'Failed.'; } else { response = 'Success.'; } }
3098 + break;
3099 + }
3100 + case 'users': {
3101 + if (meshCoreObj.users == null) { response = 'Active users are unknown.'; } else { response = 'Active Users: ' + meshCoreObj.users.join(', ') + '.'; }
3102 + require('user-sessions').enumerateUsers().then(function (u) { for (var i in u) { sendConsoleText(u[i]); } });
3103 + break;
3104 + }
3105 + case 'toast': {
3106 + if (args['_'].length < 1) { response = 'Proper usage: toast "message"'; } else {
3107 + if (require('MeshAgent')._tsid == null) {
3108 + require('toaster').Toast('MeshCentral', args['_'][0]).then(sendConsoleText, sendConsoleText);
3109 + }
3110 + else {
3111 + require('toaster').Toast('MeshCentral', args['_'][0], require('MeshAgent')._tsid).then(sendConsoleText, sendConsoleText);
3112 + }
3113 + }
3114 + break;
3115 + }
3116 + case 'setdebug': {
3117 + if (args['_'].length < 1) { response = 'Proper usage: setdebug (target), 0 = Disabled, 1 = StdOut, 2 = This Console, * = All Consoles, 4 = WebLog, 8 = Logfile'; } // Display usage
3118 + else { if (args['_'][0] == '*') { console.setDestination(2); } else { console.setDestination(parseInt(args['_'][0]), sessionid); } }
3119 + break;
3120 + }
3121 + case 'ps': {
3122 + processManager.getProcesses(function (plist) {
3123 + var x = '';
3124 + for (var i in plist) { x += i + ((plist[i].user) ? (', ' + plist[i].user) : '') + ', ' + plist[i].cmd + '\r\n'; }
3125 + sendConsoleText(x, sessionid);
3126 + });
3127 + break;
3128 + }
3129 + case 'kill': {
3130 + if ((args['_'].length < 1)) {
3131 + response = 'Proper usage: kill [pid]'; // Display correct command usage
3132 + } else {
3133 + process.kill(parseInt(args['_'][0]));
3134 + response = 'Killed process ' + args['_'][0] + '.';
3135 + }
3136 + break;
3137 + }
3138 + case 'smbios': {
3139 + if (SMBiosTables == null) { response = 'SMBios tables not available.'; } else { response = objToString(SMBiosTables, 0, ' ', true); }
3140 + break;
3141 + }
3142 + case 'rawsmbios': {
3143 + if (SMBiosTablesRaw == null) { response = 'SMBios tables not available.'; } else {
3144 + response = '';
3145 + for (var i in SMBiosTablesRaw) {
3146 + var header = false;
3147 + for (var j in SMBiosTablesRaw[i]) {
3148 + if (SMBiosTablesRaw[i][j].length > 0) {
3149 + if (header == false) { response += ('Table type #' + i + ((require('smbios').smTableTypes[i] == null) ? '' : (', ' + require('smbios').smTableTypes[i]))) + '\r\n'; header = true; }
3150 + response += (' ' + SMBiosTablesRaw[i][j].toString('hex')) + '\r\n';
3151 + }
3152 + }
3153 + }
3154 + }
3155 + break;
3156 + }
3157 + case 'eval': { // Eval JavaScript
3158 + if (args['_'].length < 1) {
3159 + response = 'Proper usage: eval "JavaScript code"'; // Display correct command usage
3160 + } else {
3161 + response = JSON.stringify(mesh.eval(args['_'][0])); // This can only be run by trusted administrator.
3162 + }
3163 + break;
3164 + }
3165 + case 'uninstallagent': // Uninstall this agent
3166 + var agentName = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent';
3167 + if (!require('service-manager').manager.getService(agentName).isMe()) {
3168 + response = 'Uininstall failed, this instance is not the service instance';
3169 + } else {
3170 + try { diagnosticAgent_uninstall(); } catch (e) { }
3171 + var js = "require('service-manager').manager.getService('" + agentName + "').stop(); require('service-manager').manager.uninstallService('" + agentName + "'); process.exit();";
3172 + 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 });
3173 + }
3174 + break;
3175 + case 'notify': { // Send a notification message to the mesh
3176 + if (args['_'].length != 1) {
3177 + response = 'Proper usage: notify "message" [--session]'; // Display correct command usage
3178 + } else {
3179 + var notification = { action: 'msg', type: 'notify', value: args['_'][0], tag: 'console' };
3180 + if (args.session) { notification.sessionid = sessionid; } // If "--session" is specified, notify only this session, if not, the server will notify the mesh
3181 + mesh.SendCommand(notification); // no sessionid or userid specified, notification will go to the entire mesh
3182 + response = "ok";
3183 + }
3184 + break;
3185 + }
3186 + case 'cpuinfo': { // Return system information
3187 + // CPU & memory utilization
3188 + pr = require('sysinfo').cpuUtilization();
3189 + pr.sessionid = sessionid;
3190 + pr.then(function (data) {
3191 + sendConsoleText(JSON.stringify({ cpu: data, memory: require('sysinfo').memUtilization() }, null, 1), this.sessionid);
3192 + }, function (e) {
3193 + sendConsoleText(e);
3194 + });
3195 + break;
3196 + }
3197 + case 'sysinfo': { // Return system information
3198 + getSystemInformation(function (results, err) {
3199 + if (results == null) { sendConsoleText(err, this.sessionid); } else {
3200 + sendConsoleText(JSON.stringify(results, null, 1), this.sessionid);
3201 + }
3202 + });
3203 + break;
3204 + }
3205 + case 'info': { // Return information about the agent and agent core module
3206 + 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 + '.';
3207 + if (amt != null) { response += '\r\nBuilt-in LMS: ' + ['Disabled', 'Connecting..', 'Connected'][amt.lmsstate] + '.'; }
3208 + if (meshCoreObj.osdesc) { response += '\r\nOS: ' + meshCoreObj.osdesc + '.'; }
3209 + response += '\r\nModules: ' + addedModules.join(', ') + '.';
3210 + response += '\r\nServer Connection: ' + mesh.isControlChannelConnected + ', State: ' + meshServerConnectionState + '.';
3211 + response += '\r\lastMeInfo: ' + lastMeInfo + '.';
3212 + var oldNodeId = db.Get('OldNodeId');
3213 + if (oldNodeId != null) { response += '\r\nOldNodeID: ' + oldNodeId + '.'; }
3214 + if (process.platform == 'linux' || process.platform == 'freebsd') { response += '\r\nX11 support: ' + require('monitor-info').kvm_x11_support + '.'; }
3215 + break;
3216 + }
3217 + case 'osinfo': { // Return the operating system information
3218 + var i = 1;
3219 + if (args['_'].length > 0) { i = parseInt(args['_'][0]); if (i > 8) { i = 8; } response = 'Calling ' + i + ' times.'; }
3220 + for (var j = 0; j < i; j++) {
3221 + var pr = require('os').name();
3222 + pr.sessionid = sessionid;
3223 + pr.then(function (v) {
3224 + sendConsoleText("OS: " + v + (process.platform == 'win32' ? (require('win-virtual-terminal').supported ? ' [ConPTY: YES]' : ' [ConPTY: NO]') : ''), this.sessionid);
3225 + });
3226 + }
3227 + break;
3228 + }
3229 + case 'sendcaps': { // Send capability flags to the server
3230 + if (args['_'].length == 0) {
3231 + response = 'Proper usage: sendcaps (number)'; // Display correct command usage
3232 + } else {
3233 + meshCoreObj.caps = parseInt(args['_'][0]);
3234 + mesh.SendCommand(meshCoreObj);
3235 + response = JSON.stringify(meshCoreObj);
3236 + }
3237 + break;
3238 + }
3239 + case 'sendosdesc': { // Send OS description
3240 + if (args['_'].length > 0) {
3241 + meshCoreObj.osdesc = args['_'][0];
3242 + mesh.SendCommand(meshCoreObj);
3243 + response = JSON.stringify(meshCoreObj);
3244 + } else {
3245 + response = 'Proper usage: sendosdesc [os description]'; // Display correct command usage
3246 + }
3247 + break;
3248 + }
3249 + case 'args': { // Displays parsed command arguments
3250 + response = 'args ' + objToString(args, 0, ' ', true);
3251 + break;
3252 + }
3253 + case 'print': { // Print a message on the mesh agent console, does nothing when running in the background
3254 + var r = [];
3255 + for (var i in args['_']) { r.push(args['_'][i]); }
3256 + console.log(r.join(' '));
3257 + response = 'Message printed on agent console.';
3258 + break;
3259 + }
3260 + case 'type': { // Returns the content of a file
3261 + if (args['_'].length == 0) {
3262 + response = 'Proper usage: type (filepath) [maxlength]'; // Display correct command usage
3263 + } else {
3264 + var max = 4096;
3265 + if ((args['_'].length > 1) && (typeof args['_'][1] == 'number')) { max = args['_'][1]; }
3266 + if (max > 4096) max = 4096;
3267 + var buf = Buffer.alloc(max), fd = fs.openSync(args['_'][0], "r"), r = fs.readSync(fd, buf, 0, max); // Read the file content
3268 + response = buf.toString();
3269 + var i = response.indexOf('\n');
3270 + if ((i > 0) && (response[i - 1] != '\r')) { response = response.split('\n').join('\r\n'); }
3271 + if (r == max) response += '...';
3272 + fs.closeSync(fd);
3273 + }
3274 + break;
3275 + }
3276 + case 'dbkeys': { // Return all data store keys
3277 + response = JSON.stringify(db.Keys);
3278 + break;
3279 + }
3280 + case 'dbget': { // Return the data store value for a given key
3281 + if (db == null) { response = 'Database not accessible.'; break; }
3282 + if (args['_'].length != 1) {
3283 + response = 'Proper usage: dbget (key)'; // Display the value for a given database key
3284 + } else {
3285 + response = db.Get(args['_'][0]);
3286 + }
3287 + break;
3288 + }
3289 + case 'dbset': { // Set a data store key and value pair
3290 + if (db == null) { response = 'Database not accessible.'; break; }
3291 + if (args['_'].length != 2) {
3292 + response = 'Proper usage: dbset (key) (value)'; // Set a database key
3293 + } else {
3294 + var r = db.Put(args['_'][0], args['_'][1]);
3295 + response = 'Key set: ' + r;
3296 + }
3297 + break;
3298 + }
3299 + case 'dbcompact': { // Compact the data store
3300 + if (db == null) { response = 'Database not accessible.'; break; }
3301 + var r = db.Compact();
3302 + response = 'Database compacted: ' + r;
3303 + break;
3304 + }
3305 + case 'httpget': {
3306 + if (consoleHttpRequest != null) {
3307 + response = 'HTTP operation already in progress.';
3308 + } else {
3309 + if (args['_'].length != 1) {
3310 + response = 'Proper usage: httpget (url)';
3311 + } else {
3312 + var options = http.parseUri(args['_'][0]);
3313 + options.method = 'GET';
3314 + if (options == null) {
3315 + response = 'Invalid url.';
3316 + } else {
3317 + try { consoleHttpRequest = http.request(options, consoleHttpResponse); } catch (e) { response = 'Invalid HTTP GET request'; }
3318 + consoleHttpRequest.sessionid = sessionid;
3319 + if (consoleHttpRequest != null) {
3320 + consoleHttpRequest.end();
3321 + response = 'HTTPGET ' + options.protocol + '//' + options.host + ':' + options.port + options.path;
3322 + }
3323 + }
3324 + }
3325 + }
3326 + break;
3327 + }
3328 + case 'wslist': { // List all web sockets
3329 + response = '';
3330 + for (var i in consoleWebSockets) {
3331 + var httprequest = consoleWebSockets[i];
3332 + response += 'Websocket #' + i + ', ' + httprequest.url + '\r\n';
3333 + }
3334 + if (response == '') { response = 'no websocket sessions.'; }
3335 + break;
3336 + }
3337 + case 'wsconnect': { // Setup a web socket
3338 + if (args['_'].length == 0) {
3339 + response = 'Proper usage: wsconnect (url)\r\nFor example: wsconnect wss://localhost:443/meshrelay.ashx?id=abc'; // Display correct command usage
3340 + } else {
3341 + var httprequest = null;
3342 + try {
3343 + var options = http.parseUri(args['_'][0].split('$').join('%24').split('@').join('%40')); // Escape the $ and @ characters in the URL
3344 + options.rejectUnauthorized = 0;
3345 + httprequest = http.request(options);
3346 + } catch (e) { response = 'Invalid HTTP websocket request'; }
3347 + if (httprequest != null) {
3348 + httprequest.upgrade = onWebSocketUpgrade;
3349 + httprequest.on('error', function (e) { sendConsoleText("ERROR: Unable to connect to: " + this.url + ", " + JSON.stringify(e)); });
3350 +
3351 + var index = 1;
3352 + while (consoleWebSockets[index]) { index++; }
3353 + httprequest.sessionid = sessionid;
3354 + httprequest.index = index;
3355 + httprequest.url = args['_'][0];
3356 + consoleWebSockets[index] = httprequest;
3357 + response = 'New websocket session #' + index;
3358 + }
3359 + }
3360 + break;
3361 + }
3362 + case 'wssend': { // Send data on a web socket
3363 + if (args['_'].length == 0) {
3364 + response = 'Proper usage: wssend (socketnumber)\r\n'; // Display correct command usage
3365 + for (var i in consoleWebSockets) {
3366 + var httprequest = consoleWebSockets[i];
3367 + response += 'Websocket #' + i + ', ' + httprequest.url + '\r\n';
3368 + }
3369 + } else {
3370 + var i = parseInt(args['_'][0]);
3371 + var httprequest = consoleWebSockets[i];
3372 + if (httprequest != undefined) {
3373 + httprequest.s.write(args['_'][1]);
3374 + response = 'ok';
3375 + } else {
3376 + response = 'Invalid web socket number';
3377 + }
3378 + }
3379 + break;
3380 + }
3381 + case 'wsclose': { // Close a websocket
3382 + if (args['_'].length == 0) {
3383 + response = 'Proper usage: wsclose (socketnumber)'; // Display correct command usage
3384 + } else {
3385 + var i = parseInt(args['_'][0]);
3386 + var httprequest = consoleWebSockets[i];
3387 + if (httprequest != undefined) {
3388 + if (httprequest.s != null) { httprequest.s.end(); } else { httprequest.end(); }
3389 + response = 'ok';
3390 + } else {
3391 + response = 'Invalid web socket number';
3392 + }
3393 + }
3394 + break;
3395 + }
3396 + case 'tunnels': { // Show the list of current tunnels
3397 + response = '';
3398 + for (var i in tunnels) { response += 'Tunnel #' + i + ', ' + tunnels[i].url + '\r\n'; }
3399 + if (response == '') { response = 'No websocket sessions.'; }
3400 + break;
3401 + }
3402 + case 'ls': { // Show list of files and folders
3403 + response = '';
3404 + var xpath = '*';
3405 + if (args['_'].length > 0) { xpath = obj.path.join(args['_'][0], '*'); }
3406 + response = 'List of ' + xpath + '\r\n';
3407 + var results = fs.readdirSync(xpath);
3408 + for (var i = 0; i < results.length; ++i) {
3409 + var stat = null, p = obj.path.join(args['_'][0], results[i]);
3410 + try { stat = fs.statSync(p); } catch (e) { }
3411 + if ((stat == null) || (stat == undefined)) {
3412 + response += (results[i] + "\r\n");
3413 + } else {
3414 + response += (results[i] + " " + ((stat.isDirectory()) ? "(Folder)" : "(File)") + "\r\n");
3415 + }
3416 + }
3417 + break;
3418 + }
3419 + case 'lsx': { // Show list of files and folders
3420 + response = objToString(getDirectoryInfo(args['_'][0]), 0, ' ', true);
3421 + break;
3422 + }
3423 + case 'lock': { // Lock the current user out of the desktop
3424 + 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'; }
3425 + else { response = 'Not supported on the platform'; }
3426 + break;
3427 + }
3428 + case 'amt': { // Show Intel AMT status
3429 + if (amt != null) {
3430 + amt.getAmtInfo(function (state) {
3431 + var resp = 'Intel AMT not detected.';
3432 + if (state != null) { resp = objToString(state, 0, ' ', true); }
3433 + sendConsoleText(resp, sessionid);
3434 + });
3435 + } else {
3436 + response = 'Intel AMT not detected.';
3437 + }
3438 + break;
3439 + }
3440 + case 'netinfo': { // Show network interface information
3441 + var interfaces = require('os').networkInterfaces();
3442 + response = objToString(interfaces, 0, ' ', true);
3443 + break;
3444 + }
3445 + case 'wakeonlan': { // Send wake-on-lan
3446 + if ((args['_'].length != 1) || (args['_'][0].length != 12)) {
3447 + response = 'Proper usage: wakeonlan [mac], for example "wakeonlan 010203040506".';
3448 + } else {
3449 + var count = sendWakeOnLan(args['_'][0]);
3450 + response = 'Sent wake-on-lan on ' + count + ' interface(s).';
3451 + }
3452 + break;
3453 + }
3454 + case 'sendall': { // Send a message to all consoles on this mesh
3455 + sendConsoleText(args['_'].join(' '));
3456 + break;
3457 + }
3458 + case 'power': { // Execute a power action on this computer
3459 + if (mesh.ExecPowerState == undefined) {
3460 + response = 'Power command not supported on this agent.';
3461 + } else {
3462 + if ((args['_'].length == 0) || isNaN(Number(args['_'][0]))) {
3463 + 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
3464 + } else {
3465 + var r = mesh.ExecPowerState(Number(args['_'][0]), Number(args['_'][1]));
3466 + response = 'Power action executed with return code: ' + r + '.';
3467 + }
3468 + }
3469 + break;
3470 + }
3471 + case 'location': {
3472 + getIpLocationData(function (location) {
3473 + sendConsoleText(objToString({ action: 'iplocation', type: 'publicip', value: location }, 0, ' '));
3474 + });
3475 + break;
3476 + }
3477 + case 'parseuri': {
3478 + response = JSON.stringify(http.parseUri(args['_'][0]));
3479 + break;
3480 + }
3481 + case 'scanwifi': {
3482 + if (wifiScanner != null) {
3483 + var wifiPresent = wifiScanner.hasWireless;
3484 + if (wifiPresent) { response = "Perfoming Wifi scan..."; wifiScanner.Scan(); } else { response = "Wifi absent."; }
3485 + } else { response = "Wifi module not present."; }
3486 + break;
3487 + }
3488 + case 'scanamt': {
3489 + if (amtscanner != null) {
3490 + if (args['_'].length != 1) {
3491 + 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
3492 + } else {
3493 + response = 'Scanning: ' + args['_'][0] + '...';
3494 + amtscanner.scan(args['_'][0], 2000, function (data) {
3495 + if (data.length > 0) {
3496 + var r = '', pstates = ['NotActivated', 'InActivation', 'Activated'];
3497 + for (var i in data) {
3498 + var x = data[i];
3499 + if (r != '') { r += '\r\n'; }
3500 + r += x.address + ' - Intel AMT v' + x.majorVersion + '.' + x.minorVersion;
3501 + if (x.provisioningState < 3) { r += (', ' + pstates[x.provisioningState]); }
3502 + if (x.provisioningState == 2) { r += (', ' + x.openPorts.join(', ')); }
3503 + r += '.';
3504 + }
3505 + } else {
3506 + r = 'No Intel AMT found.';
3507 + }
3508 + sendConsoleText(r);
3509 + });
3510 + }
3511 + } else { response = "Intel AMT scanner module not present."; }
3512 + break;
3513 + }
3514 + case 'modules': {
3515 + response = JSON.stringify(addedModules);
3516 + break;
3517 + }
3518 + case 'listservices': {
3519 + var services = require('service-manager').manager.enumerateService();
3520 + response = JSON.stringify(services, null, 1);
3521 + break;
3522 + }
3523 + case 'getscript': {
3524 + if (args['_'].length != 1) {
3525 + response = "Proper usage: getscript [scriptNumber].";
3526 + } else {
3527 + mesh.SendCommand({ action: 'getScript', type: args['_'][0] });
3528 + }
3529 + break;
3530 + }
3531 + case 'diagnostic':
3532 + {
3533 + if (!mesh.DAIPC.listening) {
3534 + response = 'Unable to bind to Diagnostic IPC, most likely because the path (' + process.cwd() + ') is not on a local file system';
3535 + break;
3536 + }
3537 + var diag = diagnosticAgent_installCheck();
3538 + if (diag) {
3539 + if (args['_'].length == 1 && args['_'][0] == 'uninstall') {
3540 + diagnosticAgent_uninstall();
3541 + response = 'Diagnostic Agent uninstalled';
3542 + }
3543 + else {
3544 + response = 'Diagnostic Agent installed at: ' + diag.appLocation();
3545 + }
3546 + }
3547 + else {
3548 + if (args['_'].length == 1 && args['_'][0] == 'install') {
3549 + diag = diagnosticAgent_installCheck(true);
3550 + if (diag) {
3551 + response = 'Diagnostic agent was installed at: ' + diag.appLocation();
3552 + }
3553 + else {
3554 + response = 'Diagnostic agent installation failed';
3555 + }
3556 + }
3557 + else {
3558 + response = 'Diagnostic Agent Not installed. To install: diagnostic install';
3559 + }
3560 + }
3561 + if (diag) { diag.close(); diag = null; }
3562 + break;
3563 + }
3564 + case 'amtconfig': {
3565 + if (apftunnel != null) { response = "Intel AMT server tunnel already active"; break; }
3566 + if (amt == null) { response = "No Intel AMT support delected"; break; }
3567 + getMeiState(15, function (state) {
3568 + var rx = '';
3569 + if ((state == null) || (state.ProvisioningState == null)) { rx = "Intel AMT not ready for configuration."; } else {
3570 + var apfarg = {
3571 + mpsurl: mesh.ServerUrl.replace('agent.ashx', 'apf.ashx'),
3572 + mpsuser: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16),
3573 + mpspass: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16),
3574 + mpskeepalive: 60000,
3575 + clientname: state.OsHostname,
3576 + clientaddress: '127.0.0.1',
3577 + clientuuid: state.UUID,
3578 + conntype: 2, // 0 = CIRA, 1 = Relay, 2 = LMS. The correct value is 2 since we are performing an LMS relay, other values for testing.
3579 + meiState: state // MEI state will be passed to MPS server
3580 + };
3581 + if ((state.UUID == null) || (state.UUID.length != 36)) {
3582 + rx = "Unable to get Intel AMT UUID";
3583 + } else {
3584 + apftunnel = require('apfclient')({ debug: false }, apfarg);
3585 + apftunnel.onJsonControl = function (data) {
3586 + if (data.action == 'console') { require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: data.msg }); } // Display a console message
3587 + if (data.action == 'mestate') { getMeiState(15, function (state) { apftunnel.updateMeiState(state); }); } // Update the MEI state
3588 + if (data.action == 'deactivate') { // Request CCM deactivation
3589 + var amtMeiModule, amtMei;
3590 + try { amtMeiModule = require('amt-mei'); amtMei = new amtMeiModule(); } catch (ex) { apftunnel.sendMeiDeactivationState(1); break; }
3591 + amtMei.on('error', function (e) { apftunnel.sendMeiDeactivationState(1); });
3592 + amtMei.unprovision(1, function (status) { apftunnel.sendMeiDeactivationState(status); }); // 0 = Success
3593 + }
3594 + if (data.action == 'close') { try { apftunnel.disconnect(); } catch (e) { } apftunnel = null; } // Close the CIRA-LMS connection
3595 + }
3596 + apftunnel.onChannelClosed = function () { apftunnel = null; }
3597 + try {
3598 + apftunnel.connect();
3599 + rx = "Started Intel AMT configuration";
3600 + } catch (ex) {
3601 + rx = JSON.stringify(ex);
3602 + }
3603 + }
3604 + }
3605 + if (rx != '') { require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: rx }); }
3606 + });
3607 + break;
3608 + }
3609 + case 'apf': {
3610 + if (meshCoreObj.intelamt !== null) {
3611 + if (args['_'].length == 1) {
3612 + var connType = -1, connTypeStr = args['_'][0].toLowerCase();
3613 + if (connTypeStr == 'lms') { connType = 2; }
3614 + if (connTypeStr == 'relay') { connType = 1; }
3615 + if (connTypeStr == 'cira') { connType = 0; }
3616 + if (connTypeStr == 'off') { connType = -2; }
3617 + if (connType >= 0) { // Connect
3618 + var apfarg = {
3619 + mpsurl: mesh.ServerUrl.replace('agent.ashx', 'apf.ashx'),
3620 + mpsuser: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16),
3621 + mpspass: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16),
3622 + mpskeepalive: 60000,
3623 + clientname: require('os').hostname(),
3624 + clientaddress: '127.0.0.1',
3625 + clientuuid: meshCoreObj.intelamt.uuid,
3626 + conntype: connType // 0 = CIRA, 1 = Relay, 2 = LMS. The correct value is 2 since we are performing an LMS relay, other values for testing.
3627 + };
3628 + if ((apfarg.clientuuid == null) || (apfarg.clientuuid.length != 36)) {
3629 + response = "Unable to get Intel AMT UUID: " + apfarg.clientuuid;
3630 + } else {
3631 + apftunnel = require('apfclient')({ debug: false }, apfarg);
3632 + apftunnel.onJsonControl = function (data) {
3633 + if (data.action == 'console') { require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: data.msg }); }
3634 + if (data.action == 'close') { try { apftunnel.disconnect(); } catch (e) { } apftunnel = null; }
3635 + }
3636 + apftunnel.onChannelClosed = function () { apftunnel = null; }
3637 + try {
3638 + apftunnel.connect();
3639 + response = "Started APF tunnel";
3640 + } catch (e) {
3641 + response = JSON.stringify(e);
3642 + }
3643 + }
3644 + } else if (connType == -2) { // Disconnect
3645 + try {
3646 + apftunnel.disconnect();
3647 + response = "Stopped APF tunnel";
3648 + } catch (e) {
3649 + response = JSON.stringify(e);
3650 + }
3651 + apftunnel = null;
3652 + } else {
3653 + response = "Invalid command.\r\nUse: apf lms|relay|cira|off";
3654 + }
3655 + } else {
3656 + response = "APF tunnel is " + (apftunnel == null ? "off" : "on") + "\r\nUse: apf lms|relay|cira|off";
3657 + }
3658 + } else {
3659 + response = "APF tunnel requires Intel AMT";
3660 + }
3661 + break;
3662 + }
3663 + case 'plugin': {
3664 + if (typeof args['_'][0] == 'string') {
3665 + try {
3666 + // Pass off the action to the plugin
3667 + // for plugin creators, you'll want to have a plugindir/modules_meshcore/plugin.js
3668 + // to control the output / actions here.
3669 + response = require(args['_'][0]).consoleaction(args, rights, sessionid, mesh);
3670 + } catch (e) {
3671 + response = "There was an error in the plugin (" + e + ")";
3672 + }
3673 + } else {
3674 + response = "Proper usage: plugin [pluginName] [args].";
3675 + }
3676 + break;
3677 + }
3678 + default: { // This is an unknown command, return an error message
3679 + response = "Unknown command \"" + cmd + "\", type \"help\" for list of avaialble commands.";
3680 + break;
3681 + }
3682 + }
3683 + } catch (e) { response = "Command returned an exception error: " + e; console.log(e); }
3684 + if (response != null) { sendConsoleText(response, sessionid); }
3685 + }
3686 +
3687 + // Send a mesh agent console command
3688 + function sendConsoleText(text, sessionid) {
3689 + if (typeof text == 'object') { text = JSON.stringify(text); }
3690 + require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: text, sessionid: sessionid });
3691 + }
3692 +
3693 + // Called before the process exits
3694 + //process.exit = function (code) { console.log("Exit with code: " + code.toString()); }
3695 +
3696 + // Called when the server connection state changes
3697 + function handleServerConnection(state) {
3698 + meshServerConnectionState = state;
3699 + if (meshServerConnectionState == 0) {
3700 + // Server disconnected
3701 + if (selfInfoUpdateTimer != null) { clearInterval(selfInfoUpdateTimer); selfInfoUpdateTimer = null; }
3702 + lastSelfInfo = null;
3703 + } else {
3704 + // Server connected, send mesh core information
3705 + var oldNodeId = db.Get('OldNodeId');
3706 + if (oldNodeId != null) { mesh.SendCommand({ action: 'mc1migration', oldnodeid: oldNodeId }); }
3707 +
3708 + // Update the server with basic info, logged in users and more.
3709 + mesh.SendCommand(meshCoreObj);
3710 +
3711 + // Send SMBios tables if present
3712 + if (SMBiosTablesRaw != null) { mesh.SendCommand({ action: 'smbios', value: SMBiosTablesRaw }); }
3713 +
3714 + // Update the server on more advanced stuff, like Intel ME and Network Settings
3715 + meInfoStr = null;
3716 + sendPeriodicServerUpdate();
3717 + if (selfInfoUpdateTimer == null) { selfInfoUpdateTimer = setInterval(sendPeriodicServerUpdate, 1200000); } // 20 minutes
3718 +
3719 + // Send any state messages
3720 + if (Object.keys(tunnelUserCount.msg).length > 0) {
3721 + try { mesh.SendCommand({ action: 'sessions', type: 'msg', value: tunnelUserCount.msg }); } catch (e) { }
3722 + broadcastSessionsToRegisteredApps();
3723 + }
3724 +
3725 + // Send update of registered applications to the server
3726 + updateRegisteredAppsToServer();
3727 + }
3728 +
3729 + // Send server state update to registered applications
3730 + broadcastToRegisteredApps({ cmd: 'serverstate', value: meshServerConnectionState, url: require('MeshAgent').ConnectedServer });
3731 + }
3732 +
3733 + // Update the server with the latest network interface information
3734 + var sendNetworkUpdateNagleTimer = null;
3735 + function sendNetworkUpdateNagle() { if (sendNetworkUpdateNagleTimer != null) { clearTimeout(sendNetworkUpdateNagleTimer); sendNetworkUpdateNagleTimer = null; } sendNetworkUpdateNagleTimer = setTimeout(sendNetworkUpdate, 5000); }
3736 + function sendNetworkUpdate(force) {
3737 + sendNetworkUpdateNagleTimer = null;
3738 +
3739 + // Update the network interfaces information data
3740 + var netInfo = { netif2: require('os').networkInterfaces() };
3741 + if (netInfo.netif2) {
3742 + netInfo.action = 'netinfo';
3743 + var netInfoStr = JSON.stringify(netInfo);
3744 + if ((force == true) || (clearGatewayMac(netInfoStr) != clearGatewayMac(lastNetworkInfo))) { mesh.SendCommand(netInfo); lastNetworkInfo = netInfoStr; }
3745 + }
3746 + }
3747 +
3748 + // Called periodically to check if we need to send updates to the server
3749 + function sendPeriodicServerUpdate(flags) {
3750 + if (meshServerConnectionState == 0) return; // Not connected to server, do nothing.
3751 + if (!flags) { flags = 0xFFFFFFFF; }
3752 +
3753 + if ((flags & 1) && (amt != null)) {
3754 + // If we have a connected MEI, get Intel ME information
3755 + amt.getAmtInfo(function (meinfo) {
3756 + try {
3757 + if (meinfo == null) return;
3758 + var intelamt = {};
3759 + if (amt != null)
3760 + {
3761 + switch(amt.lmsstate)
3762 + {
3763 + case 0: intelamt.microlms = 'DISABLED'; break;
3764 + case 1: intelamt.microlms = 'CONNECTING'; break;
3765 + case 2: intelamt.microlms = 'CONNECTED'; break;
3766 + default: intelamt.microlms = 'unknown'; break;
3767 + }
3768 + }
3769 + var p = false;
3770 + 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); } }
3771 + if (meinfo.ProvisioningState != null) { intelamt.state = meinfo.ProvisioningState; p = true; }
3772 + if (meinfo.Flags != null) { intelamt.flags = meinfo.Flags; p = true; }
3773 + if (meinfo.OsHostname != null) { intelamt.host = meinfo.OsHostname; p = true; }
3774 + if (meinfo.UUID != null) { intelamt.uuid = meinfo.UUID; p = true; }
3775 + if ((meinfo.ProvisioningState == 0) && (meinfo.net0 != null) && (meinfo.net0.enabled == 1)) { // If not activated, look to see if we have wired net working.
3776 + // Not activated and we have wired ethernet, look for the trusted DNS
3777 + var dns = meinfo.DNS;
3778 + if (dns == null) {
3779 + // Trusted DNS not set, let's look for the OS network DNS suffix
3780 + var interfaces = require('os').networkInterfaces();
3781 + for (var i in interfaces) {
3782 + for (var j in interfaces[i]) {
3783 + if ((interfaces[i][j].mac == mestate.net0.mac) && (interfaces[i][j].fqdn != null) && (interfaces[i][j].fqdn != '')) { dns = interfaces[i][j].fqdn; }
3784 + }
3785 + }
3786 + }
3787 + if (intelamt.dns != dns) { intelamt.dns = dns; p = true; }
3788 + } else { if (intelamt.dns != null) { delete intelamt.dns; p = true; } }
3789 + if (p == true) {
3790 + var meInfoStr = JSON.stringify(intelamt);
3791 + if (meInfoStr != lastMeInfo) {
3792 + meshCoreObj.intelamt = intelamt;
3793 + mesh.SendCommand(meshCoreObj);
3794 + lastMeInfo = meInfoStr;
3795 + }
3796 + }
3797 + } catch (e) { }
3798 + });
3799 + }
3800 +
3801 + if (flags & 2) {
3802 + // Update network information
3803 + sendNetworkUpdateNagle(false);
3804 + }
3805 +
3806 + if ((flags & 4) && (process.platform == 'win32')) {
3807 + // Update anti-virus information
3808 + // Windows Command: "wmic /Namespace:\\root\SecurityCenter2 Path AntiVirusProduct get /FORMAT:CSV"
3809 + var av, pr;
3810 + try { av = require('win-info').av(); } catch (e) { av = null; } // Antivirus
3811 + //if (process.platform == 'win32') { try { pr = require('win-info').pendingReboot(); } catch (e) { pr = null; } } // Pending reboot
3812 + if ((meshCoreObj.av == null) || (JSON.stringify(meshCoreObj.av) != JSON.stringify(av))) { meshCoreObj.av = av; mesh.SendCommand(meshCoreObj); }
3813 + }
3814 + }
3815 +
3816 + // Starting function
3817 + obj.start = function () {
3818 + // Setup the mesh agent event handlers
3819 + mesh.AddCommandHandler(handleServerCommand);
3820 + mesh.AddConnectHandler(handleServerConnection);
3821 +
3822 + // Parse input arguments
3823 + //var args = parseArgs(process.argv);
3824 + //console.log(args);
3825 +
3826 + //resetMicroLms();
3827 +
3828 + // Setup logged in user monitoring (THIS IS BROKEN IN WIN7)
3829 + try {
3830 + var userSession = require('user-sessions');
3831 + userSession.on('changed', function onUserSessionChanged() {
3832 + userSession.enumerateUsers().then(function (users) {
3833 + var u = [], a = users.Active;
3834 + for (var i = 0; i < a.length; i++) {
3835 + var un = a[i].Domain ? (a[i].Domain + '\\' + a[i].Username) : (a[i].Username);
3836 + if (u.indexOf(un) == -1) { u.push(un); } // Only push users in the list once.
3837 + }
3838 + meshCoreObj.users = u;
3839 + mesh.SendCommand(meshCoreObj);
3840 + });
3841 + });
3842 + userSession.emit('changed');
3843 + //userSession.on('locked', function (user) { sendConsoleText('[' + (user.Domain ? user.Domain + '\\' : '') + user.Username + '] has LOCKED the desktop'); });
3844 + //userSession.on('unlocked', function (user) { sendConsoleText('[' + (user.Domain ? user.Domain + '\\' : '') + user.Username + '] has UNLOCKED the desktop'); });
3845 + } catch (e) { }
3846 + }
3847 +
3848 + obj.stop = function () {
3849 + mesh.AddCommandHandler(null);
3850 + mesh.AddConnectHandler(null);
3851 + }
3852 +
3853 + function onWebSocketClosed() { sendConsoleText("WebSocket #" + this.httprequest.index + " closed.", this.httprequest.sessionid); delete consoleWebSockets[this.httprequest.index]; }
3854 + function onWebSocketData(data) { sendConsoleText("Got WebSocket #" + this.httprequest.index + " data: " + data, this.httprequest.sessionid); }
3855 + function onWebSocketSendOk() { sendConsoleText("WebSocket #" + this.index + " SendOK.", this.sessionid); }
3856 +
3857 + function onWebSocketUpgrade(response, s, head) {
3858 + sendConsoleText("WebSocket #" + this.index + " connected.", this.sessionid);
3859 + this.s = s;
3860 + s.httprequest = this;
3861 + s.end = onWebSocketClosed;
3862 + s.data = onWebSocketData;
3863 + }
3864 +
3865 + // Get Intel MEI State in a flexible way
3866 + // Flags: 1 = Versions, 2 = OsAdmin, 4 = Hashes, 8 = Network
3867 + function getMeiState(flags, func) {
3868 + var amtMeiModule, amtMei;
3869 + try { amtMeiModule = require('amt-mei'); amtMei = new amtMeiModule(); } catch (ex) { func(null); return; }
3870 + amtMei.on('error', function (e) { func(null); return; });
3871 + try {
3872 + var amtMeiTmpState = { OsHostname: require('os').hostname(), Flags: 0 }; // Flags: 1=EHBC, 2=CCM, 4=ACM
3873 + amtMei.getProtocolVersion(function (result) { if (result != null) { amtMeiTmpState.MeiVersion = result; } });
3874 + 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; } } }); }
3875 + amtMei.getProvisioningMode(function (result) { if (result) { amtMeiTmpState.ProvisioningMode = result.mode; } });
3876 + amtMei.getProvisioningState(function (result) { if (result) { amtMeiTmpState.ProvisioningState = result.state; } }); // 0: "Not Activated (Pre)", 1: "Not Activated (In)", 2: "Activated"
3877 + amtMei.getEHBCState(function (result) { if ((result != null) && (result.EHBC == true)) { amtMeiTmpState.Flags += 1; } });
3878 + 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
3879 + //amtMei.getMACAddresses(function (result) { if (result) { amtMeiTmpState.mac = result; } });
3880 + if ((flags & 8) != 0) {
3881 + amtMei.getLanInterfaceSettings(0, function (result) {
3882 + if (result) {
3883 + amtMeiTmpState.net0 = result;
3884 + var fqdn = null, interfaces = require('os').networkInterfaces(); // Look for the DNS suffix for the Intel AMT Ethernet interface
3885 + 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; } } }
3886 + }
3887 + });
3888 + amtMei.getLanInterfaceSettings(1, function (result) { if (result) { amtMeiTmpState.net1 = result; } });
3889 + }
3890 + amtMei.getUuid(function (result) { if ((result != null) && (result.uuid != null)) { amtMeiTmpState.UUID = result.uuid; } });
3891 + if ((flags & 2) != 0) { amtMei.getLocalSystemAccount(function (x) { if ((x != null) && x.user && x.pass) { amtMeiTmpState.OsAdmin = { user: x.user, pass: x.pass }; } }); }
3892 + amtMei.getDnsSuffix(function (result) { if (result != null) { amtMeiTmpState.DnsSuffix = result; } if ((flags & 4) == 0) { if (func != null) { func(amtMeiTmpState); } } });
3893 + if ((flags & 4) != 0) {
3894 + amtMei.getHashHandles(function (handles) {
3895 + if ((handles != null) && (handles.length > 0)) { amtMeiTmpState.Hashes = []; } else { func(amtMeiTmpState); }
3896 + var exitOnCount = handles.length;
3897 + 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); } } }); }
3898 + });
3899 + }
3900 + } catch (e) { if (func != null) { func(null); } return; }
3901 + }
3902 +
3903 + return obj;
3904 +}
3905 +
3906 +//
3907 +// Module startup
3908 +//
3909 +
3910 +try {
3911 + var xexports = null, mainMeshCore = null;
3912 + try { xexports = module.exports; } catch (e) { }
3913 +
3914 + if (xexports != null) {
3915 + // If we are running within NodeJS, export the core
3916 + module.exports.createMeshCore = createMeshCore;
3917 + } else {
3918 + // If we are not running in NodeJS, launch the core
3919 + mainMeshCore = createMeshCore();
3920 + mainMeshCore.start(null);
3921 + }
3922 +} catch (e) {
3923 + require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: "uncaughtException2: " + ex });
3924 +}
agents/meshcore-old.js new
+3903
@@ -0,0 +1,3903 @@
1 +/*
2 +Copyright 2018-2020 Intel Corporation
3 +
4 +Licensed under the Apache License, Version 2.0 (the "License");
5 +you may not use this file except in compliance with the License.
6 +You may obtain a copy of the License at
7 +
8 + http://www.apache.org/licenses/LICENSE-2.0
9 +
10 +Unless required by applicable law or agreed to in writing, software
11 +distributed under the License is distributed on an "AS IS" BASIS,
12 +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 +See the License for the specific language governing permissions and
14 +limitations under the License.
15 +*/
16 +
17 +process.on('uncaughtException', function (ex) {
18 + require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: "uncaughtException1: " + ex });
19 +});
20 +
21 +// NOTE: This seems to cause big problems, don't enable the debugger in the server's meshcore.
22 +//attachDebugger({ webport: 9999, wait: 1 }).then(function (prt) { console.log('Point Browser for Debug to port: ' + prt); });
23 +
24 +// Mesh Rights
25 +var MNG_ERROR = 65;
26 +var MESHRIGHT_EDITMESH = 1;
27 +var MESHRIGHT_MANAGEUSERS = 2;
28 +var MESHRIGHT_MANAGECOMPUTERS = 4;
29 +var MESHRIGHT_REMOTECONTROL = 8;
30 +var MESHRIGHT_AGENTCONSOLE = 16;
31 +var MESHRIGHT_SERVERFILES = 32;
32 +var MESHRIGHT_WAKEDEVICE = 64;
33 +var MESHRIGHT_SETNOTES = 128;
34 +var MESHRIGHT_REMOTEVIEW = 256;
35 +var MESHRIGHT_NOTERMINAL = 512;
36 +var MESHRIGHT_NOFILES = 1024;
37 +var MESHRIGHT_NOAMT = 2048;
38 +var MESHRIGHT_LIMITEDINPUT = 4096;
39 +var MESHRIGHT_LIMITEVENTS = 8192;
40 +var MESHRIGHT_CHATNOTIFY = 16384;
41 +var MESHRIGHT_UNINSTALL = 32768;
42 +var MESHRIGHT_NODESKTOP = 65536;
43 +
44 +function createMeshCore(agent) {
45 + var obj = {};
46 + if (process.platform == 'win32' && require('user-sessions').isRoot()) {
47 + // Check the Agent Uninstall MetaData for correctness, as the installer may have written an incorrect value
48 + try {
49 + var writtenSize = 0, actualSize = Math.floor(require('fs').statSync(process.execPath).size / 1024);
50 + try { writtenSize = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MeshCentralAgent', 'EstimatedSize'); } catch (e) { }
51 + if (writtenSize != actualSize) { try { require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MeshCentralAgent', 'EstimatedSize', actualSize); } catch (e) { } }
52 + } catch (x) { }
53 +
54 + // Check to see if we are the Installed Mesh Agent Service, if we are, make sure we can run in Safe Mode
55 + try {
56 + var meshCheck = false;
57 + try { meshCheck = require('service-manager').manager.getService('Mesh Agent').isMe(); } catch (e) { }
58 + if (meshCheck && require('win-bcd').isSafeModeService && !require('win-bcd').isSafeModeService('Mesh Agent')) { require('win-bcd').enableSafeModeService('Mesh Agent'); }
59 + } catch (e) { }
60 + }
61 +
62 + if (process.platform == 'darwin' && !process.versions) {
63 + // This is an older MacOS Agent, so we'll need to check the service definition so that Auto-Update will function correctly
64 + var child = require('child_process').execFile('/bin/sh', ['sh']);
65 + child.stdout.str = '';
66 + child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });
67 + child.stdin.write("cat /Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist | tr '\n' '\.' | awk '{split($0, a, \"<key>KeepAlive</key>\"); split(a[2], b, \"<\"); split(b[2], c, \">\"); ");
68 + child.stdin.write(" if(c[1]==\"dict\"){ split(a[2], d, \"</dict>\"); if(split(d[1], truval, \"<true/>\")>1) { split(truval[1], kn1, \"<key>\"); split(kn1[2], kn2, \"</key>\"); print kn2[1]; } }");
69 + child.stdin.write(" else { split(c[1], ka, \"/\"); if(ka[1]==\"true\") {print \"ALWAYS\";} } }'\nexit\n");
70 + child.waitExit();
71 + if (child.stdout.str.trim() == 'Crashed') {
72 + child = require('child_process').execFile('/bin/sh', ['sh']);
73 + child.stdout.str = '';
74 + child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });
75 + child.stdin.write("launchctl list | grep 'meshagent' | awk '{ if($3==\"meshagent\"){print $1;}}'\nexit\n");
76 + child.waitExit();
77 +
78 + if (parseInt(child.stdout.str.trim()) == process.pid) {
79 + // The currently running MeshAgent is us, so we can continue with the update
80 + var plist = require('fs').readFileSync('/Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist').toString();
81 + var tokens = plist.split('<key>KeepAlive</key>');
82 + if (tokens[1].split('>')[0].split('<')[1] == 'dict') {
83 + var tmp = tokens[1].split('</dict>');
84 + tmp.shift();
85 + tokens[1] = '\n <true/>' + tmp.join('</dict>');
86 + tokens = tokens.join('<key>KeepAlive</key>');
87 +
88 + require('fs').writeFileSync('/Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist', tokens);
89 +
90 + var fix = '';
91 + fix += ("function macosRepair()\n");
92 + fix += ("{\n");
93 + fix += (" var child = require('child_process').execFile('/bin/sh', ['sh']);\n");
94 + fix += (" child.stdout.str = '';\n");
95 + fix += (" child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });\n");
96 + fix += (" child.stderr.on('data', function (chunk) { });\n");
97 + fix += (" child.stdin.write('launchctl unload /Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist\\n');\n");
98 + fix += (" child.stdin.write('launchctl load /Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist\\n');\n");
99 + fix += (" child.stdin.write('rm /Library/LaunchDaemons/meshagentRepair.plist\\n');\n");
100 + fix += (" child.stdin.write('rm " + process.cwd() + "/macosRepair.js\\n');\n");
101 + fix += (" child.stdin.write('launchctl stop meshagentRepair\\nexit\\n');\n");
102 + fix += (" child.waitExit();\n");
103 + fix += ("}\n");
104 + fix += ("macosRepair();\n");
105 + fix += ("process.exit();\n");
106 + require('fs').writeFileSync(process.cwd() + '/macosRepair.js', fix);
107 +
108 + var plist = '<?xml version="1.0" encoding="UTF-8"?>\n';
109 + plist += '<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n';
110 + plist += '<plist version="1.0">\n';
111 + plist += ' <dict>\n';
112 + plist += ' <key>Label</key>\n';
113 + plist += (' <string>meshagentRepair</string>\n');
114 + plist += ' <key>ProgramArguments</key>\n';
115 + plist += ' <array>\n';
116 + plist += (' <string>' + process.execPath + '</string>\n');
117 + plist += ' <string>macosRepair.js</string>\n';
118 + plist += ' </array>\n';
119 + plist += ' <key>WorkingDirectory</key>\n';
120 + plist += (' <string>' + process.cwd() + '</string>\n');
121 + plist += ' <key>RunAtLoad</key>\n';
122 + plist += ' <true/>\n';
123 + plist += ' </dict>\n';
124 + plist += '</plist>';
125 + require('fs').writeFileSync('/Library/LaunchDaemons/meshagentRepair.plist', plist);
126 +
127 + child = require('child_process').execFile('/bin/sh', ['sh']);
128 + child.stdout.str = '';
129 + child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });
130 + child.stdin.write("launchctl load /Library/LaunchDaemons/meshagentRepair.plist\nexit\n");
131 + child.waitExit();
132 + }
133 + }
134 + }
135 + }
136 +
137 + // Create Secure IPC for Diagnostic Agent Communications
138 + obj.DAIPC = require('net').createServer();
139 + if (process.platform != 'win32') { try { require('fs').unlinkSync(process.cwd() + '/DAIPC'); } catch (e) { } }
140 + obj.DAIPC.IPCPATH = process.platform == 'win32' ? ('\\\\.\\pipe\\' + require('_agentNodeId')() + '-DAIPC') : (process.cwd() + '/DAIPC');
141 + try { obj.DAIPC.listen({ path: obj.DAIPC.IPCPATH, writableAll: true, maxConnections: 5 }); } catch (e) { }
142 + obj.DAIPC._daipc = [];
143 + obj.DAIPC.on('connection', function (c) {
144 + c._send = function (j) {
145 + var data = JSON.stringify(j);
146 + var packet = Buffer.alloc(data.length + 4);
147 + packet.writeUInt32LE(data.length + 4, 0);
148 + Buffer.from(data).copy(packet, 4);
149 + this.write(packet);
150 + };
151 + this._daipc.push(c);
152 + c.parent = this;
153 + c.on('end', function () { removeRegisteredApp(this); });
154 + c.on('data', function (chunk) {
155 + if (chunk.length < 4) { this.unshift(chunk); return; }
156 + var len = chunk.readUInt32LE(0);
157 + if (len > 8192) { removeRegisteredApp(this); this.end(); return; }
158 + if (chunk.length < len) { this.unshift(chunk); return; }
159 +
160 + var data = chunk.slice(4, len);
161 + try { data = JSON.parse(data.toString()); } catch (e) { }
162 + if ((data == null) || (typeof data.cmd != 'string')) return;
163 +
164 + try {
165 + switch (data.cmd) {
166 + case 'requesthelp':
167 + if (this._registered == null) return;
168 + sendConsoleText('Request Help (' + this._registered + '): ' + data.value);
169 + var help = {};
170 + help[this._registered] = data.value;
171 + try { mesh.SendCommand({ action: 'sessions', type: 'help', value: help }); } catch (e) { }
172 + MeshServerLogEx(98, [this._registered, data.value], "Help Requested, user: " + this._registered + ", details: " + data.value, null);
173 + break;
174 + case 'cancelhelp':
175 + if (this._registered == null) return;
176 + sendConsoleText('Cancel Help (' + this._registered + ')');
177 + try { mesh.SendCommand({ action: 'sessions', type: 'help', value: {} }); } catch (e) { }
178 + break;
179 + case 'register':
180 + if (typeof data.value == 'string') {
181 + this._registered = data.value;
182 + var apps = {};
183 + apps[data.value] = 1;
184 + try { mesh.SendCommand({ action: 'sessions', type: 'app', value: apps }); } catch (e) { }
185 + this._send({ cmd: 'serverstate', value: meshServerConnectionState, url: require('MeshAgent').ConnectedServer, amt: (amt != null) });
186 + }
187 + break;
188 + case 'query':
189 + switch (data.value) {
190 + case 'connection':
191 + data.result = require('MeshAgent').ConnectedServer;
192 + this._send(data);
193 + break;
194 + case 'descriptors':
195 + require('ChainViewer').getSnapshot().then(function (f) {
196 + this.tag.payload.result = f;
197 + this.tag.ipc._send(this.tag.payload);
198 + }).parentPromise.tag = { ipc: this, payload: data };
199 + break;
200 + }
201 + break;
202 + case 'amtstate':
203 + if (amt == null) return;
204 + var func = function amtStateFunc(state) { if (state != null) { amtStateFunc.pipe._send({ cmd: 'amtstate', value: state }); } }
205 + func.pipe = this;
206 + amt.getAmtInfo(func);
207 + break;
208 + case 'sessions':
209 + this._send({ cmd: 'sessions', sessions: tunnelUserCount });
210 + break;
211 + }
212 + }
213 + catch (e) { removeRegisteredApp(this); this.end(); return; }
214 + });
215 + });
216 +
217 + // Send current sessions to registered apps
218 + function broadcastSessionsToRegisteredApps(x) {
219 + broadcastToRegisteredApps({ cmd: 'sessions', sessions: tunnelUserCount });
220 + }
221 +
222 + // Send this object to all registered local applications
223 + function broadcastToRegisteredApps(x) {
224 + if ((obj.DAIPC == null) || (obj.DAIPC._daipc == null)) return;
225 + for (var i in obj.DAIPC._daipc) { if (obj.DAIPC._daipc[i]._registered != null) { obj.DAIPC._daipc[i]._send(x); } }
226 + }
227 +
228 + // Send list of registered apps to the server
229 + function updateRegisteredAppsToServer() {
230 + if ((obj.DAIPC == null) || (obj.DAIPC._daipc == null)) return;
231 + var apps = {};
232 + 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]++; } }
233 + try { mesh.SendCommand({ action: 'sessions', type: 'app', value: apps }); } catch (e) { }
234 + }
235 +
236 + // Remove a registered app
237 + function removeRegisteredApp(pipe) {
238 + for (var i = obj.DAIPC._daipc.length - 1; i >= 0; i--) { if (obj.DAIPC._daipc[i] === pipe) { obj.DAIPC._daipc.splice(i, 1); } }
239 + if (pipe._registered != null) updateRegisteredAppsToServer();
240 + }
241 +
242 + function diagnosticAgent_uninstall() {
243 + require('service-manager').manager.uninstallService('meshagentDiagnostic');
244 + require('task-scheduler').delete('meshagentDiagnostic/periodicStart');
245 + };
246 + function diagnosticAgent_installCheck(install) {
247 + try {
248 + var diag = require('service-manager').manager.getService('meshagentDiagnostic');
249 + return (diag);
250 + }
251 + catch (e) {
252 + }
253 + if (!install) { return (null); }
254 +
255 + var svc = null;
256 + try {
257 + require('service-manager').manager.installService(
258 + {
259 + name: 'meshagentDiagnostic',
260 + displayName: "Mesh Agent Diagnostic Service",
261 + description: "Mesh Agent Diagnostic Service",
262 + servicePath: process.execPath,
263 + parameters: ['-recovery']
264 + //files: [{ newName: 'diagnostic.js', _buffer: Buffer.from('LyoNCkNvcHlyaWdodCAyMDE5IEludGVsIENvcnBvcmF0aW9uDQoNCkxpY2Vuc2VkIHVuZGVyIHRoZSBBcGFjaGUgTGljZW5zZSwgVmVyc2lvbiAyLjAgKHRoZSAiTGljZW5zZSIpOw0KeW91IG1heSBub3QgdXNlIHRoaXMgZmlsZSBleGNlcHQgaW4gY29tcGxpYW5jZSB3aXRoIHRoZSBMaWNlbnNlLg0KWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0DQoNCiAgICBodHRwOi8vd3d3LmFwYWNoZS5vcmcvbGljZW5zZXMvTElDRU5TRS0yLjANCg0KVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQ0KZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywNCldJVEhPVVQgV0FSUkFOVElFUyBPUiBDT05ESVRJT05TIE9GIEFOWSBLSU5ELCBlaXRoZXIgZXhwcmVzcyBvciBpbXBsaWVkLg0KU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZA0KbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuDQoqLw0KDQp2YXIgaG9zdCA9IHJlcXVpcmUoJ3NlcnZpY2UtaG9zdCcpLmNyZWF0ZSgnbWVzaGFnZW50RGlhZ25vc3RpYycpOw0KdmFyIFJlY292ZXJ5QWdlbnQgPSByZXF1aXJlKCdNZXNoQWdlbnQnKTsNCg0KaG9zdC5vbignc2VydmljZVN0YXJ0JywgZnVuY3Rpb24gKCkNCnsNCiAgICBjb25zb2xlLnNldERlc3RpbmF0aW9uKGNvbnNvbGUuRGVzdGluYXRpb25zLkxPR0ZJTEUpOw0KICAgIGhvc3Quc3RvcCA9IGZ1bmN0aW9uKCkNCiAgICB7DQogICAgICAgIHJlcXVpcmUoJ3NlcnZpY2UtbWFuYWdlcicpLm1hbmFnZXIuZ2V0U2VydmljZSgnbWVzaGFnZW50RGlhZ25vc3RpYycpLnN0b3AoKTsNCiAgICB9DQogICAgUmVjb3ZlcnlBZ2VudC5vbignQ29ubmVjdGVkJywgZnVuY3Rpb24gKHN0YXR1cykNCiAgICB7DQogICAgICAgIGlmIChzdGF0dXMgPT0gMCkNCiAgICAgICAgew0KICAgICAgICAgICAgY29uc29sZS5sb2coJ0RpYWdub3N0aWMgQWdlbnQ6IFNlcnZlciBjb25uZWN0aW9uIGxvc3QuLi4nKTsNCiAgICAgICAgICAgIHJldHVybjsNCiAgICAgICAgfQ0KICAgICAgICBjb25zb2xlLmxvZygnRGlhZ25vc3RpYyBBZ2VudDogQ29ubmVjdGlvbiBFc3RhYmxpc2hlZCB3aXRoIFNlcnZlcicpOw0KICAgICAgICBzdGFydCgpOw0KICAgIH0pOw0KfSk7DQpob3N0Lm9uKCdub3JtYWxTdGFydCcsIGZ1bmN0aW9uICgpDQp7DQogICAgaG9zdC5zdG9wID0gZnVuY3Rpb24gKCkNCiAgICB7DQogICAgICAgIHByb2Nlc3MuZXhpdCgpOw0KICAgIH0NCiAgICBjb25zb2xlLmxvZygnTm9uIFNlcnZpY2UgTW9kZScpOw0KICAgIFJlY292ZXJ5QWdlbnQub24oJ0Nvbm5lY3RlZCcsIGZ1bmN0aW9uIChzdGF0dXMpDQogICAgew0KICAgICAgICBpZiAoc3RhdHVzID09IDApDQogICAgICAgIHsNCiAgICAgICAgICAgIGNvbnNvbGUubG9nKCdEaWFnbm9zdGljIEFnZW50OiBTZXJ2ZXIgY29ubmVjdGlvbiBsb3N0Li4uJyk7DQogICAgICAgICAgICByZXR1cm47DQogICAgICAgIH0NCiAgICAgICAgY29uc29sZS5sb2coJ0RpYWdub3N0aWMgQWdlbnQ6IENvbm5lY3Rpb24gRXN0YWJsaXNoZWQgd2l0aCBTZXJ2ZXInKTsNCiAgICAgICAgc3RhcnQoKTsNCiAgICB9KTsNCn0pOw0KaG9zdC5vbignc2VydmljZVN0b3AnLCBmdW5jdGlvbiAoKSB7IHByb2Nlc3MuZXhpdCgpOyB9KTsNCmhvc3QucnVuKCk7DQoNCg0KZnVuY3Rpb24gc3RhcnQoKQ0Kew0KDQp9Ow0K', 'base64') }]
265 + });
266 + svc = require('service-manager').manager.getService('meshagentDiagnostic');
267 + }
268 + catch (e) {
269 + return (null);
270 + }
271 + var proxyConfig = require('global-tunnel').proxyConfig;
272 + var cert = require('MeshAgent').GenerateAgentCertificate('CN=MeshNodeDiagnosticCertificate');
273 + var nodeid = require('tls').loadCertificate(cert.root).getKeyHash().toString('base64');
274 + ddb = require('SimpleDataStore').Create(svc.appWorkingDirectory().replace('\\', '/') + '/meshagentDiagnostic.db');
275 + ddb.Put('disableUpdate', '1');
276 + ddb.Put('MeshID', Buffer.from(require('MeshAgent').ServerInfo.MeshID, 'hex'));
277 + ddb.Put('ServerID', require('MeshAgent').ServerInfo.ServerID);
278 + ddb.Put('MeshServer', require('MeshAgent').ServerInfo.ServerUri);
279 + if (cert.root.pfx) { ddb.Put('SelfNodeCert', cert.root.pfx); }
280 + if (cert.tls) { ddb.Put('SelfNodeTlsCert', cert.tls.pfx); }
281 + if (proxyConfig) {
282 + ddb.Put('WebProxy', proxyConfig.host + ':' + proxyConfig.port);
283 + } else {
284 + ddb.Put('ignoreProxyFile', '1');
285 + }
286 +
287 + require('MeshAgent').SendCommand({ action: 'diagnostic', value: { command: 'register', value: nodeid } });
288 + require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: "Diagnostic Agent Registered [" + nodeid.length + "/" + nodeid + "]" });
289 +
290 + delete ddb;
291 +
292 + // Set a recurrent task, to run the Diagnostic Agent every 2 days
293 + 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' });
294 + //require('task-scheduler').create({ name: 'meshagentDiagnostic/periodicStart', daily: '1', time: '17:16', service: 'meshagentDiagnostic' });
295 +
296 + return (svc);
297 + }
298 +
299 + // Monitor the file 'batterystate.txt' in the agent's folder and sends battery update when this file is changed.
300 + if ((require('fs').existsSync(process.cwd() + 'batterystate.txt')) && (require('fs').watch != null)) {
301 + // Setup manual battery monitoring
302 + require('MeshAgent')._batteryFileWatcher = require('fs').watch(process.cwd(), function () {
303 + if (require('MeshAgent')._batteryFileTimer != null) return;
304 + require('MeshAgent')._batteryFileTimer = setTimeout(function () {
305 + try {
306 + require('MeshAgent')._batteryFileTimer = null;
307 + var data = null;
308 + try { data = require('fs').readFileSync(process.cwd() + 'batterystate.txt').toString(); } catch (e) { }
309 + if ((data != null) && (data.length < 10)) {
310 + data = data.split(',');
311 + if ((data.length == 2) && ((data[0] == 'ac') || (data[0] == 'dc'))) {
312 + var level = parseInt(data[1]);
313 + if ((level >= 0) && (level <= 100)) { require('MeshAgent').SendCommand({ action: 'battery', state: data[0], level: level }); }
314 + }
315 + }
316 + } catch (e) { }
317 + }, 1000);
318 + });
319 + } else {
320 + // Setup normal battery monitoring
321 + if (require('identifiers').isBatteryPowered && require('identifiers').isBatteryPowered()) {
322 + require('MeshAgent')._battLevelChanged = function _battLevelChanged(val) {
323 + _battLevelChanged.self._currentBatteryLevel = val;
324 + _battLevelChanged.self.SendCommand({ action: 'battery', state: _battLevelChanged.self._currentPowerState, level: val });
325 + };
326 + require('MeshAgent')._battLevelChanged.self = require('MeshAgent');
327 + require('MeshAgent')._powerChanged = function _powerChanged(val) {
328 + _powerChanged.self._currentPowerState = (val == 'AC' ? 'ac' : 'dc');
329 + _powerChanged.self.SendCommand({ action: 'battery', state: (val == 'AC' ? 'ac' : 'dc'), level: _powerChanged.self._currentBatteryLevel });
330 + };
331 + require('MeshAgent')._powerChanged.self = require('MeshAgent');
332 + require('MeshAgent').on('Connected', function (status) {
333 + if (status == 0) {
334 + require('power-monitor').removeListener('acdc', this._powerChanged);
335 + require('power-monitor').removeListener('batteryLevel', this._battLevelChanged);
336 + } else {
337 + require('power-monitor').on('acdc', this._powerChanged);
338 + require('power-monitor').on('batteryLevel', this._battLevelChanged);
339 + }
340 + });
341 + }
342 + }
343 +
344 +
345 + /*
346 + function borderController() {
347 + this.container = null;
348 + this.Start = function Start(user) {
349 + if (this.container == null) {
350 + if (process.platform == 'win32') {
351 + try {
352 + this.container = require('ScriptContainer').Create({ processIsolation: 1, sessionId: user.SessionId });
353 + } catch (e) {
354 + this.container = require('ScriptContainer').Create({ processIsolation: 1 });
355 + }
356 + } else {
357 + this.container = require('ScriptContainer').Create({ processIsolation: 1, sessionId: user.uid });
358 + }
359 + this.container.parent = this;
360 + this.container.addModule('monitor-info', getJSModule('monitor-info'));
361 + this.container.addModule('monitor-border', getJSModule('monitor-border'));
362 + this.container.addModule('promise', getJSModule('promise'));
363 + this.container.once('exit', function (code) { sendConsoleText('Border Process Exited with code: ' + code); this.parent.container = this.parent._container = null; });
364 + this.container.ExecuteString("var border = require('monitor-border'); border.Start();");
365 + }
366 + }
367 + this.Stop = function Stop() {
368 + if (this.container != null) {
369 + this._container = this.container;
370 + this._container.parent = this;
371 + this.container = null;
372 + this._container.exit();
373 + }
374 + }
375 + }
376 + obj.borderManager = new borderController();
377 + */
378 +
379 + // MeshAgent JavaScript Core Module. This code is sent to and running on the mesh agent.
380 + 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
381 +
382 +
383 + // Get the operating system description string
384 + try { require('os').name().then(function (v) { meshCoreObj.osdesc = v; }); } catch (e) { }
385 +
386 + var meshServerConnectionState = 0;
387 + var tunnels = {};
388 + var lastMeInfo = null;
389 + var lastNetworkInfo = null;
390 + var lastPublicLocationInfo = null;
391 + var selfInfoUpdateTimer = null;
392 + var http = require('http');
393 + var net = require('net');
394 + var fs = require('fs');
395 + var rtc = require('ILibWebRTC');
396 + var amt = null;
397 + var processManager = require('process-manager');
398 + var wifiScannerLib = null;
399 + var wifiScanner = null;
400 + var networkMonitor = null;
401 + var amtscanner = null;
402 + var nextTunnelIndex = 1;
403 + var amtPolicy = null;
404 + var apftunnel = null;
405 + var tunnelUserCount = { terminal: {}, files: {}, tcp: {}, udp: {}, msg: {} }; // List of userid->count sessions for terminal, files and TCP/UDP routing
406 +
407 + // Add to the server event log
408 + function MeshServerLog(msg, state) {
409 + if (typeof msg == 'string') { msg = { action: 'log', msg: msg }; } else { msg.action = 'log'; }
410 + if (state) {
411 + if (state.userid) { msg.userid = state.userid; }
412 + if (state.username) { msg.username = state.username; }
413 + if (state.sessionid) { msg.sessionid = state.sessionid; }
414 + if (state.remoteaddr) { msg.remoteaddr = state.remoteaddr; }
415 + }
416 + mesh.SendCommand(msg);
417 + }
418 +
419 + // Add to the server event log, use internationalized events
420 + function MeshServerLogEx(id, args, msg, state) {
421 + var msg = { action: 'log', msgid: id, msgArgs: args, msg: msg };
422 + if (state) {
423 + if (state.userid) { msg.userid = state.userid; }
424 + if (state.username) { msg.username = state.username; }
425 + if (state.sessionid) { msg.sessionid = state.sessionid; }
426 + if (state.remoteaddr) { msg.remoteaddr = state.remoteaddr; }
427 + }
428 + mesh.SendCommand(msg);
429 + }
430 +
431 + // If we are running in Duktape, agent will be null
432 + if (agent == null) {
433 + // Running in native agent, Import libraries
434 + db = require('SimpleDataStore').Shared();
435 + sha = require('SHA256Stream');
436 + mesh = require('MeshAgent');
437 + childProcess = require('child_process');
438 + if (mesh.hasKVM == 1) { // if the agent is compiled with KVM support
439 + // Check if this computer supports a desktop
440 + try
441 + {
442 + if ((process.platform == 'win32') || (process.platform == 'darwin') || (require('monitor-info').kvm_x11_support))
443 + {
444 + meshCoreObj.caps |= 1;
445 + }
446 + else if(process.platform == 'linux' || process.platform == 'freebsd')
447 + {
448 + require('monitor-info').on('kvmSupportDetected', function (value)
449 + {
450 + meshCoreObj.caps |= 1;
451 + mesh.SendCommand(meshCoreObj);
452 + });
453 + }
454 + } catch (e) { }
455 + }
456 + } else {
457 + // Running in nodejs
458 + meshCoreObj.value += '-NodeJS';
459 + meshCoreObj.caps = 8;
460 + mesh = agent.getMeshApi();
461 + }
462 +
463 + mesh.DAIPC = obj.DAIPC;
464 +
465 + /*
466 + var AMTScanner = require("AMTScanner");
467 + var scan = new AMTScanner();
468 +
469 + scan.on("found", function (data) {
470 + if (typeof data === 'string') {
471 + console.log(data);
472 + } else {
473 + console.log(JSON.stringify(data, null, " "));
474 + }
475 + });
476 + scan.scan("10.2.55.140", 1000);
477 + scan.scan("10.2.55.139-10.2.55.145", 1000);
478 + scan.scan("10.2.55.128/25", 2000);
479 + */
480 +
481 + /*
482 + // Try to load up the network monitor
483 + try {
484 + networkMonitor = require('NetworkMonitor');
485 + networkMonitor.on('change', function () { sendNetworkUpdateNagle(); });
486 + networkMonitor.on('add', function (addr) { sendNetworkUpdateNagle(); });
487 + networkMonitor.on('remove', function (addr) { sendNetworkUpdateNagle(); });
488 + } catch (e) { networkMonitor = null; }
489 + */
490 +
491 + // Try to load up the Intel AMT scanner
492 + try {
493 + var AMTScannerModule = require('amt-scanner');
494 + amtscanner = new AMTScannerModule();
495 + //amtscanner.on('found', function (data) { if (typeof data != 'string') { data = JSON.stringify(data, null, " "); } sendConsoleText(data); });
496 + } catch (e) { amtscanner = null; }
497 +
498 + // Fetch the SMBios Tables
499 + var SMBiosTables = null;
500 + var SMBiosTablesRaw = null;
501 + try {
502 + var SMBiosModule = null;
503 + try { SMBiosModule = require('smbios'); } catch (e) { }
504 + if (SMBiosModule != null) {
505 + SMBiosModule.get(function (data) {
506 + if (data != null) {
507 + SMBiosTablesRaw = data;
508 + SMBiosTables = require('smbios').parse(data)
509 + if (mesh.isControlChannelConnected) { mesh.SendCommand({ action: 'smbios', value: SMBiosTablesRaw }); }
510 +
511 + // If SMBios tables say that Intel AMT is present, try to connect MEI
512 + if (SMBiosTables.amtInfo && (SMBiosTables.amtInfo.AMT == true))
513 + {
514 + var amtmodule = require('amt-manage');
515 + amt = new amtmodule(mesh, db, false);
516 + amt.on('portBinding_LMS', function (map)
517 + {
518 + var j = { action: 'lmsinfo', value: { ports: map.keys() } };
519 + mesh.SendCommand(j);
520 + });
521 + amt.on('stateChange_LMS', function (v)
522 + {
523 + if (!meshCoreObj.intelamt) { meshCoreObj.intelamt = {}; }
524 + switch(v)
525 + {
526 + case 0:
527 + meshCoreObj.intelamt.microlms = 'DISABLED';
528 + break;
529 + case 1:
530 + meshCoreObj.intelamt.microlms = 'CONNECTING';
531 + break;
532 + case 2:
533 + meshCoreObj.intelamt.microlms = 'CONNECTED';
534 + break;
535 + default:
536 + break;
537 + }
538 + mesh.SendCommand(meshCoreObj);
539 + });
540 + amt.onStateChange = function (state) { if (state == 2) { sendPeriodicServerUpdate(1); } }
541 + if (amtPolicy != null) { amt.setPolicy(amtPolicy); }
542 + amt.start();
543 + }
544 + }
545 + });
546 + }
547 + } catch (e) { sendConsoleText("ex1: " + e); }
548 +
549 + // Try to load up the WIFI scanner
550 + try {
551 + var wifiScannerLib = require('wifi-scanner');
552 + wifiScanner = new wifiScannerLib();
553 + wifiScanner.on('accessPoint', function (data) { sendConsoleText("wifiScanner: " + data); });
554 + } catch (e) { wifiScannerLib = null; wifiScanner = null; }
555 +
556 + // Get our location (lat/long) using our public IP address
557 + var getIpLocationDataExInProgress = false;
558 + var getIpLocationDataExCounts = [0, 0];
559 + function getIpLocationDataEx(func) {
560 + if (getIpLocationDataExInProgress == true) { return false; }
561 + try {
562 + getIpLocationDataExInProgress = true;
563 + getIpLocationDataExCounts[0]++;
564 + var options = http.parseUri("http://ipinfo.io/json");
565 + options.method = 'GET';
566 + http.request(options, function (resp) {
567 + if (resp.statusCode == 200) {
568 + var geoData = '';
569 + resp.data = function (geoipdata) { geoData += geoipdata; };
570 + resp.end = function () {
571 + var location = null;
572 + try {
573 + if (typeof geoData == 'string') {
574 + var result = JSON.parse(geoData);
575 + if (result.ip && result.loc) { location = result; }
576 + }
577 + } catch (e) { }
578 + if (func) { getIpLocationDataExCounts[1]++; func(location); }
579 + }
580 + } else { func(null); }
581 + getIpLocationDataExInProgress = false;
582 + }).end();
583 + return true;
584 + }
585 + catch (e) { return false; }
586 + }
587 +
588 + // Remove all Gateway MAC addresses for interface list. This is useful because the gateway MAC is not always populated reliably.
589 + function clearGatewayMac(str) {
590 + if (str == null) return null;
591 + var x = JSON.parse(str);
592 + for (var i in x.netif) { if (x.netif[i].gatewaymac) { delete x.netif[i].gatewaymac } }
593 + return JSON.stringify(x);
594 + }
595 +
596 + function getIpLocationData(func) {
597 + // Get the location information for the cache if possible
598 + var publicLocationInfo = db.Get('publicLocationInfo');
599 + if (publicLocationInfo != null) { publicLocationInfo = JSON.parse(publicLocationInfo); }
600 + if (publicLocationInfo == null) {
601 + // Nothing in the cache, fetch the data
602 + getIpLocationDataEx(function (locationData) {
603 + if (locationData != null) {
604 + publicLocationInfo = {};
605 + publicLocationInfo.netInfoStr = lastNetworkInfo;
606 + publicLocationInfo.locationData = locationData;
607 + var x = db.Put('publicLocationInfo', JSON.stringify(publicLocationInfo)); // Save to database
608 + if (func) func(locationData); // Report the new location
609 + } else {
610 + if (func) func(null); // Report no location
611 + }
612 + });
613 + } else {
614 + // Check the cache
615 + if (clearGatewayMac(publicLocationInfo.netInfoStr) == clearGatewayMac(lastNetworkInfo)) {
616 + // Cache match
617 + if (func) func(publicLocationInfo.locationData);
618 + } else {
619 + // Cache mismatch
620 + getIpLocationDataEx(function (locationData) {
621 + if (locationData != null) {
622 + publicLocationInfo = {};
623 + publicLocationInfo.netInfoStr = lastNetworkInfo;
624 + publicLocationInfo.locationData = locationData;
625 + var x = db.Put('publicLocationInfo', JSON.stringify(publicLocationInfo)); // Save to database
626 + if (func) func(locationData); // Report the new location
627 + } else {
628 + if (func) func(publicLocationInfo.locationData); // Can't get new location, report the old location
629 + }
630 + });
631 + }
632 + }
633 + }
634 +
635 + // Polyfill String.endsWith
636 + if (!String.prototype.endsWith) {
637 + String.prototype.endsWith = function (searchString, position) {
638 + var subjectString = this.toString();
639 + if (typeof position !== 'number' || !isFinite(position) || Math.floor(position) !== position || position > subjectString.length) { position = subjectString.length; }
640 + position -= searchString.length;
641 + var lastIndex = subjectString.lastIndexOf(searchString, position);
642 + return lastIndex !== -1 && lastIndex === position;
643 + };
644 + }
645 +
646 + // Polyfill path.join
647 + obj.path = {
648 + join: function () {
649 + var x = [];
650 + for (var i in arguments) {
651 + var w = arguments[i];
652 + if (w != null) {
653 + while (w.endsWith('/') || w.endsWith('\\')) { w = w.substring(0, w.length - 1); }
654 + if (i != 0) {
655 + while (w.startsWith('/') || w.startsWith('\\')) { w = w.substring(1); }
656 + }
657 + x.push(w);
658 + }
659 + }
660 + if (x.length == 0) return '/';
661 + return x.join('/');
662 + }
663 + };
664 +
665 + // Replace a string with a number if the string is an exact number
666 + function toNumberIfNumber(x) { if ((typeof x == 'string') && (+parseInt(x) === x)) { x = parseInt(x); } return x; }
667 +
668 + // Convert decimal to hex
669 + function char2hex(i) { return (i + 0x100).toString(16).substr(-2).toUpperCase(); }
670 +
671 + // Convert a raw string to a hex string
672 + function rstr2hex(input) { var r = '', i; for (i = 0; i < input.length; i++) { r += char2hex(input.charCodeAt(i)); } return r; }
673 +
674 + // Convert a buffer into a string
675 + function buf2rstr(buf) { var r = ''; for (var i = 0; i < buf.length; i++) { r += String.fromCharCode(buf[i]); } return r; }
676 +
677 + // Convert a hex string to a raw string // TODO: Do this using Buffer(), will be MUCH faster
678 + function hex2rstr(d) {
679 + if (typeof d != "string" || d.length == 0) return '';
680 + var r = '', m = ('' + d).match(/../g), t;
681 + while (t = m.shift()) r += String.fromCharCode('0x' + t);
682 + return r
683 + }
684 +
685 + // Convert an object to string with all functions
686 + function objToString(x, p, pad, ret) {
687 + if (ret == undefined) ret = '';
688 + if (p == undefined) p = 0;
689 + if (x == null) { return '[null]'; }
690 + if (p > 8) { return '[...]'; }
691 + if (x == undefined) { return '[undefined]'; }
692 + if (typeof x == 'string') { if (p == 0) return x; return '"' + x + '"'; }
693 + if (typeof x == 'buffer') { return '[buffer]'; }
694 + if (typeof x != 'object') { return x; }
695 + var r = '{' + (ret ? '\r\n' : ' ');
696 + for (var i in x) { if (i != '_ObjectID') { r += (addPad(p + 2, pad) + i + ': ' + objToString(x[i], p + 2, pad, ret) + (ret ? '\r\n' : ' ')); } }
697 + return r + addPad(p, pad) + '}';
698 + }
699 +
700 + // Return p number of spaces
701 + function addPad(p, ret) { var r = ''; for (var i = 0; i < p; i++) { r += ret; } return r; }
702 +
703 + // Split a string taking into account the quoats. Used for command line parsing
704 + function splitArgs(str) {
705 + var myArray = [], myRegexp = /[^\s"]+|"([^"]*)"/gi;
706 + do { var match = myRegexp.exec(str); if (match != null) { myArray.push(match[1] ? match[1] : match[0]); } } while (match != null);
707 + return myArray;
708 + }
709 +
710 + // Parse arguments string array into an object
711 + function parseArgs(argv) {
712 + var results = { '_': [] }, current = null;
713 + for (var i = 1, len = argv.length; i < len; i++) {
714 + var x = argv[i];
715 + if (x.length > 2 && x[0] == '-' && x[1] == '-') {
716 + if (current != null) { results[current] = true; }
717 + current = x.substring(2);
718 + } else {
719 + if (current != null) { results[current] = toNumberIfNumber(x); current = null; } else { results['_'].push(toNumberIfNumber(x)); }
720 + }
721 + }
722 + if (current != null) { results[current] = true; }
723 + return results;
724 + }
725 +
726 + // Get server target url with a custom path
727 + function getServerTargetUrl(path) {
728 + var x = mesh.ServerUrl;
729 + //sendConsoleText("mesh.ServerUrl: " + mesh.ServerUrl);
730 + if (x == null) { return null; }
731 + if (path == null) { path = ''; }
732 + x = http.parseUri(x);
733 + if (x == null) return null;
734 + return x.protocol + '//' + x.host + ':' + x.port + '/' + path;
735 + }
736 +
737 + // Get server url. If the url starts with "*/..." change it, it not use the url as is.
738 + function getServerTargetUrlEx(url) {
739 + if (url.substring(0, 2) == '*/') { return getServerTargetUrl(url.substring(2)); }
740 + return url;
741 + }
742 +
743 + // Send a wake-on-lan packet
744 + function sendWakeOnLan(hexMac) {
745 + hexMac = hexMac.split(':').join('');
746 + var count = 0;
747 + try {
748 + var interfaces = require('os').networkInterfaces();
749 + var magic = 'FFFFFFFFFFFF';
750 + for (var x = 1; x <= 16; ++x) { magic += hexMac; }
751 + var magicbin = Buffer.from(magic, 'hex');
752 +
753 + for (var adapter in interfaces) {
754 + if (interfaces.hasOwnProperty(adapter)) {
755 + for (var i = 0; i < interfaces[adapter].length; ++i) {
756 + var addr = interfaces[adapter][i];
757 + if ((addr.family == 'IPv4') && (addr.mac != '00:00:00:00:00:00')) {
758 + try {
759 + var socket = require('dgram').createSocket({ type: 'udp4' });
760 + socket.bind({ address: addr.address });
761 + socket.setBroadcast(true);
762 + socket.setMulticastInterface(addr.address);
763 + socket.setMulticastTTL(1);
764 + socket.send(magicbin, 7, '255.255.255.255');
765 + socket.descriptorMetadata = 'WoL (' + addr.address + ' => ' + hexMac + ')';
766 + count++;
767 + }
768 + catch (e) { }
769 + }
770 + }
771 + }
772 + }
773 + } catch (e) { }
774 + return count;
775 + }
776 +
777 + // Handle a mesh agent command
778 + function handleServerCommand(data) {
779 + if (typeof data == 'object') {
780 + // If this is a console command, parse it and call the console handler
781 + switch (data.action) {
782 + case 'msg': {
783 + switch (data.type) {
784 + case 'console': { // Process a console command
785 + if (data.value && data.sessionid) {
786 + MeshServerLogEx(17, [data.value], "Processing console command: " + data.value, data);
787 + var args = splitArgs(data.value);
788 + processConsoleCommand(args[0].toLowerCase(), parseArgs(args), data.rights, data.sessionid);
789 + }
790 + break;
791 + }
792 + case 'tunnel': {
793 + if (data.value != null) { // Process a new tunnel connection request
794 + // Create a new tunnel object
795 + var xurl = getServerTargetUrlEx(data.value);
796 + if (xurl != null) {
797 + xurl = xurl.split('$').join('%24').split('@').join('%40'); // Escape the $ and @ characters
798 + var woptions = http.parseUri(xurl);
799 + woptions.perMessageDeflate = false;
800 + if (typeof data.perMessageDeflate == 'boolean') { woptions.perMessageDeflate = data.perMessageDeflate; }
801 + woptions.rejectUnauthorized = 0;
802 + //sendConsoleText(JSON.stringify(woptions));
803 + //sendConsoleText('TUNNEL: ' + JSON.stringify(data));
804 + var tunnel = http.request(woptions);
805 + tunnel.upgrade = onTunnelUpgrade;
806 + tunnel.on('error', function (e) { sendConsoleText("ERROR: Unable to connect relay tunnel to: " + this.url + ", " + JSON.stringify(e)); });
807 + tunnel.sessionid = data.sessionid;
808 + tunnel.rights = data.rights;
809 + tunnel.consent = data.consent;
810 + tunnel.privacybartext = data.privacybartext ? data.privacybartext : "Sharing desktop with: {0}";
811 + tunnel.username = data.username + (data.guestname ? (' - ' + data.guestname) : '');
812 + tunnel.realname = (data.realname ? data.realname : data.username) + (data.guestname ? (' - ' + data.guestname) : '');
813 + tunnel.userid = data.userid;
814 + tunnel.remoteaddr = data.remoteaddr;
815 + tunnel.state = 0;
816 + tunnel.url = xurl;
817 + tunnel.protocol = 0;
818 + tunnel.soptions = data.soptions;
819 + tunnel.tcpaddr = data.tcpaddr;
820 + tunnel.tcpport = data.tcpport;
821 + tunnel.udpaddr = data.udpaddr;
822 + tunnel.udpport = data.udpport;
823 + tunnel.end();
824 + // Put the tunnel in the tunnels list
825 + var index = nextTunnelIndex++;
826 + tunnel.index = index;
827 + tunnels[index] = tunnel;
828 +
829 + //sendConsoleText('New tunnel connection #' + index + ': ' + tunnel.url + ', rights: ' + tunnel.rights, data.sessionid);
830 + }
831 + }
832 + break;
833 + }
834 + case 'messagebox': {
835 + // Display a message box
836 + if (data.title && data.msg) {
837 + MeshServerLogEx(18, [data.title, data.msg], "Displaying message box, title=" + data.title + ", message=" + data.msg, data);
838 + data.msg = data.msg.split('\r').join('\\r').split('\n').join('\\n');
839 + try { require('message-box').create(data.title, data.msg, 120); } catch (e) { }
840 + }
841 + break;
842 + }
843 + case 'ps': {
844 + // Return the list of running processes
845 + if (data.sessionid) {
846 + processManager.getProcesses(function (plist) {
847 + mesh.SendCommand({ action: 'msg', type: 'ps', value: JSON.stringify(plist), sessionid: data.sessionid });
848 + });
849 + }
850 + break;
851 + }
852 + case 'pskill': {
853 + // Kill a process
854 + if (data.value) {
855 + MeshServerLogEx(19, [data.value], "Killing process " + data.value, data);
856 + try { process.kill(data.value); } catch (e) { sendConsoleText("pskill: " + JSON.stringify(e)); }
857 + }
858 + break;
859 + }
860 + case 'services': {
861 + // Return the list of installed services
862 + var services = null;
863 + try { services = require('service-manager').manager.enumerateService(); } catch (e) { }
864 + if (services != null) { mesh.SendCommand({ action: 'msg', type: 'services', value: JSON.stringify(services), sessionid: data.sessionid }); }
865 + break;
866 + }
867 + case 'serviceStop': {
868 + // Stop a service
869 + try {
870 + var service = require('service-manager').manager.getService(data.serviceName);
871 + if (service != null) { service.stop(); }
872 + } catch (e) { }
873 + break;
874 + }
875 + case 'serviceStart': {
876 + // Start a service
877 + try {
878 + var service = require('service-manager').manager.getService(data.serviceName);
879 + if (service != null) { service.start(); }
880 + } catch (e) { }
881 + break;
882 + }
883 + case 'serviceRestart': {
884 + // Restart a service
885 + try {
886 + var service = require('service-manager').manager.getService(data.serviceName);
887 + if (service != null) { service.restart(); }
888 + } catch (e) { }
889 + break;
890 + }
891 + case 'deskBackground':
892 + {
893 + // Toggle desktop background
894 + try {
895 + if (process.platform == 'win32') {
896 + var stype = require('user-sessions').getProcessOwnerName(process.pid).tsid == 0 ? 1 : 0;
897 + var sid = undefined;
898 + if (stype == 1) {
899 + if (require('MeshAgent')._tsid != null) {
900 + stype = 5;
901 + sid = require('MeshAgent')._tsid;
902 + }
903 + }
904 + var id = require('user-sessions').getProcessOwnerName(process.pid).tsid == 0 ? 1 : 0;
905 + var child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0='], { type: stype, uid: sid });
906 + child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
907 + child.stderr.on('data', function () { });
908 + child.waitExit();
909 + var current = child.stdout.str.trim();
910 + if (current != '') { require('MeshAgent')._wallpaper = current; }
911 + child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0=', current != '' ? '""' : require('MeshAgent')._wallpaper], { type: stype, uid: sid });
912 + child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
913 + child.stderr.on('data', function () { });
914 + child.waitExit();
915 + } else {
916 + var id = require('user-sessions').consoleUid();
917 + var current = require('linux-gnome-helpers').getDesktopWallpaper(id);
918 + if (current != '/dev/null') { require('MeshAgent')._wallpaper = current; }
919 + require('linux-gnome-helpers').setDesktopWallpaper(id, current != '/dev/null' ? undefined : require('MeshAgent')._wallpaper);
920 + }
921 + } catch (e) {
922 + sendConsoleText(e);
923 + }
924 + break;
925 + }
926 + case 'openUrl': {
927 + // Open a local web browser and return success/fail
928 + MeshServerLogEx(20, [data.url], "Opening: " + data.url, data);
929 + sendConsoleText("OpenURL: " + data.url);
930 + if (data.url) { mesh.SendCommand({ action: 'msg', type: 'openUrl', url: data.url, sessionid: data.sessionid, success: (openUserDesktopUrl(data.url) != null) }); }
931 + break;
932 + }
933 + case 'getclip': {
934 + // Send the load clipboard back to the user
935 + //sendConsoleText('getClip: ' + JSON.stringify(data));
936 + if (require('MeshAgent').isService) {
937 + require('clipboard').dispatchRead().then(function (str) {
938 + if (str) {
939 + MeshServerLogEx(21, [str.length], "Getting clipboard content, " + str.length + " byte(s)", data);
940 + mesh.SendCommand({ action: 'msg', type: 'getclip', sessionid: data.sessionid, data: str, tag: data.tag });
941 + }
942 + });
943 + } else {
944 + require("clipboard").read().then(function (str) {
945 + if (str) {
946 + MeshServerLogEx(21, [str.length], "Getting clipboard content, " + str.length + " byte(s)", data);
947 + mesh.SendCommand({ action: 'msg', type: 'getclip', sessionid: data.sessionid, data: str, tag: data.tag });
948 + }
949 + });
950 + }
951 + break;
952 + }
953 + case 'setclip': {
954 + // Set the load clipboard to a user value
955 + //sendConsoleText('setClip: ' + JSON.stringify(data));
956 + if (typeof data.data == 'string') {
957 + MeshServerLogEx(22, [data.data.length], "Setting clipboard content, " + data.data.length + " byte(s)", data);
958 + if (require('MeshAgent').isService) { require('clipboard').dispatchWrite(data.data); } else { require("clipboard")(data.data); } // Set the clipboard
959 + mesh.SendCommand({ action: 'msg', type: 'setclip', sessionid: data.sessionid, success: true });
960 + }
961 + break;
962 + }
963 + case 'userSessions': {
964 + // Send back current user sessions list, this is Windows only.
965 + //sendConsoleText('userSessions: ' + JSON.stringify(data));
966 + if (process.platform != 'win32') break;
967 + var p = require('user-sessions').enumerateUsers();
968 + p.sessionid = data.sessionid;
969 + p.then(function (u) { mesh.SendCommand({ action: 'msg', type: 'userSessions', sessionid: data.sessionid, data: u, tag: data.tag }); });
970 + break;
971 + }
972 + default:
973 + // Unknown action, ignore it.
974 + break;
975 + }
976 + break;
977 + }
978 + case 'acmactivate': {
979 + if (amt != null) {
980 + MeshServerLogEx(23, null, "Attempting Intel AMT ACM mode activation", data);
981 + amt.setAcmResponse(data);
982 + }
983 + break;
984 + }
985 + case 'wakeonlan': {
986 + // Send wake-on-lan on all interfaces for all MAC addresses in data.macs array. The array is a list of HEX MAC addresses.
987 + sendConsoleText("Server requesting wake-on-lan for: " + data.macs.join(', '));
988 + for (var i in data.macs) { sendWakeOnLan(data.macs[i]); }
989 + break;
990 + }
991 + case 'runcommands': {
992 + if (mesh.cmdchild != null) { sendConsoleText("Run commands can't execute, already busy."); break; }
993 + sendConsoleText("Run commands (" + data.runAsUser + "): " + data.cmds);
994 +
995 + // data.runAsUser: 0=Agent,1=UserOrAgent,2=UserOnly
996 + var options = {};
997 + if (data.runAsUser > 0) {
998 + try { options.uid = require('user-sessions').consoleUid(); } catch (e) { }
999 + options.type = require('child_process').SpawnTypes.TERM;
1000 + }
1001 + if (data.runAsUser == 2) {
1002 + if (options.uid == null) break;
1003 + if (((require('user-sessions').minUid != null) && (options.uid < require('user-sessions').minUid()))) break; // This command can only run as user.
1004 + }
1005 +
1006 + if (process.platform == 'win32') {
1007 + if (data.type == 1) {
1008 + // Windows command shell
1009 + mesh.cmdchild = require('child_process').execFile(process.env['windir'] + '\\system32\\cmd.exe', ['cmd'], options);
1010 + mesh.cmdchild.descriptorMetadata = 'UserCommandsShell';
1011 + mesh.cmdchild.stdout.on('data', function (c) { sendConsoleText(c.toString()); });
1012 + mesh.cmdchild.stderr.on('data', function (c) { sendConsoleText(c.toString()); });
1013 + mesh.cmdchild.stdin.write(data.cmds + '\r\nexit\r\n');
1014 + mesh.cmdchild.on('exit', function () { sendConsoleText("Run commands completed."); delete mesh.cmdchild; });
1015 + } else if (data.type == 2) {
1016 + // Windows Powershell
1017 + mesh.cmdchild = require('child_process').execFile(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', ['powershell', '-noprofile', '-nologo', '-command', '-'], options);
1018 + mesh.cmdchild.descriptorMetadata = 'UserCommandsPowerShell';
1019 + mesh.cmdchild.stdout.on('data', function (c) { sendConsoleText(c.toString()); });
1020 + mesh.cmdchild.stderr.on('data', function (c) { sendConsoleText(c.toString()); });
1021 + mesh.cmdchild.stdin.write(data.cmds + '\r\nexit\r\n');
1022 + mesh.cmdchild.on('exit', function () { sendConsoleText("Run commands completed."); delete mesh.cmdchild; });
1023 + }
1024 + } else if (data.type == 3) {
1025 + // Linux shell
1026 + mesh.cmdchild = require('child_process').execFile('/bin/sh', ['sh'], options);
1027 + mesh.cmdchild.descriptorMetadata = 'UserCommandsShell';
1028 + mesh.cmdchild.stdout.on('data', function (c) { sendConsoleText(c.toString()); });
1029 + mesh.cmdchild.stderr.on('data', function (c) { sendConsoleText(c.toString()); });
1030 + mesh.cmdchild.stdin.write(data.cmds.split('\r').join('') + '\nexit\n');
1031 + mesh.cmdchild.on('exit', function () { sendConsoleText("Run commands completed."); delete mesh.cmdchild; });
1032 + }
1033 + break;
1034 + }
1035 + case 'uninstallagent':
1036 + // Uninstall this agent
1037 + var agentName = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent';
1038 + if (require('service-manager').manager.getService(agentName).isMe()) {
1039 + try { diagnosticAgent_uninstall(); } catch (e) { }
1040 + var js = "require('service-manager').manager.getService('" + agentName + "').stop(); require('service-manager').manager.uninstallService('" + agentName + "'); process.exit();";
1041 + 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 });
1042 + }
1043 + break;
1044 + case 'poweraction': {
1045 + // Server telling us to execute a power action
1046 + if ((mesh.ExecPowerState != undefined) && (data.actiontype)) {
1047 + var forced = 0;
1048 + if (data.forced == 1) { forced = 1; }
1049 + data.actiontype = parseInt(data.actiontype);
1050 + MeshServerLogEx(25, [data.actiontype, forced], "Performing power action=" + data.actiontype + ", forced=" + forced, data);
1051 + sendConsoleText("Performing power action=" + data.actiontype + ", forced=" + forced + '.');
1052 + var r = mesh.ExecPowerState(data.actiontype, forced);
1053 + sendConsoleText("ExecPowerState returned code: " + r);
1054 + }
1055 + break;
1056 + }
1057 + case 'iplocation': {
1058 + // 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
1059 + getIpLocationData(function (location) { mesh.SendCommand({ action: 'iplocation', type: 'publicip', value: location }); });
1060 + break;
1061 + }
1062 + case 'toast': {
1063 + // Display a toast message
1064 + if (data.title && data.msg) {
1065 + MeshServerLogEx(26, [data.title, data.msg], "Displaying toast message, title=" + data.title + ", message=" + data.msg, data);
1066 + data.msg = data.msg.split('\r').join('\\r').split('\n').join('\\n');
1067 + try { require('toaster').Toast(data.title, data.msg); } catch (e) { }
1068 + }
1069 + break;
1070 + }
1071 + case 'openUrl': {
1072 + // Open a local web browser and return success/fail
1073 + //sendConsoleText('OpenURL: ' + data.url);
1074 + MeshServerLogEx(20, [data.url], "Opening: " + data.url, data);
1075 + if (data.url) { mesh.SendCommand({ action: 'openUrl', url: data.url, sessionid: data.sessionid, success: (openUserDesktopUrl(data.url) != null) }); }
1076 + break;
1077 + }
1078 + case 'amtPolicy': {
1079 + // Store the latest Intel AMT policy
1080 + amtPolicy = data.amtPolicy;
1081 + if (data.amtPolicy != null) { db.Put('amtPolicy', JSON.stringify(data.amtPolicy)); } else { db.Put('amtPolicy', null); }
1082 + if (amt != null) { amt.setPolicy(amtPolicy, true); }
1083 + break;
1084 + }
1085 + case 'getScript': {
1086 + // Received a configuration script from the server
1087 + sendConsoleText('getScript: ' + JSON.stringify(data));
1088 + break;
1089 + }
1090 + case 'sysinfo': {
1091 + // Fetch system information
1092 + getSystemInformation(function (results) {
1093 + if ((results != null) && (data.hash != results.hash)) { mesh.SendCommand({ action: 'sysinfo', sessionid: this.sessionid, data: results }); }
1094 + });
1095 + break;
1096 + }
1097 + case 'ping': { mesh.SendCommand('{"action":"pong"}'); break; }
1098 + case 'pong': { break; }
1099 + case 'plugin': {
1100 + try { require(data.plugin).consoleaction(data, data.rights, data.sessionid, this); } catch (e) { throw e; }
1101 + break;
1102 + }
1103 + case 'coredump':
1104 + // Set the current agent coredump situation.
1105 + if (data.value === true) {
1106 + // TODO: This replace() below is not ideal, would be better to remove the .exe at the end instead of replace.
1107 + process.coreDumpLocation = (process.platform == 'win32') ? (process.execPath.replace('.exe', '.dmp')) : (process.execPath + '.dmp');
1108 + } else if (data.value === false) {
1109 + process.coreDumpLocation = null;
1110 + }
1111 + break;
1112 + case 'getcoredump':
1113 + // Ask the agent if a core dump is currently available, if yes, also return the hash of the agent.
1114 + var r = { action: 'getcoredump', value: (process.coreDumpLocation != null) };
1115 + var coreDumpPath = null;
1116 + if (process.platform == 'win32') { coreDumpPath = process.coreDumpLocation; } else { coreDumpPath = (process.cwd() != '//') ? fs.existsSync(process.cwd() + 'core') : null; }
1117 + if ((coreDumpPath != null) && (fs.existsSync(coreDumpPath))) { r.exists = (db.Get('CoreDumpTime') != require('fs').statSync(coreDumpPath).mtime); }
1118 + if (r.exists == true) { r.agenthashhex = getSHA384FileHash(process.execPath).toString('hex'); }
1119 + mesh.SendCommand(JSON.stringify(r));
1120 + default:
1121 + // Unknown action, ignore it.
1122 + break;
1123 + }
1124 + }
1125 + }
1126 +
1127 + // Called when a file changed in the file system
1128 + /*
1129 + function onFileWatcher(a, b) {
1130 + console.log('onFileWatcher', a, b, this.path);
1131 + var response = getDirectoryInfo(this.path);
1132 + if ((response != undefined) && (response != null)) { this.tunnel.s.write(JSON.stringify(response)); }
1133 + }
1134 + */
1135 +
1136 + function getSystemInformation(func) {
1137 + try {
1138 + var results = { hardware: require('identifiers').get() }; // Hardware info
1139 + if (results.hardware && results.hardware.windows) {
1140 + // Remove extra entries and things that change quickly
1141 + var x = results.hardware.windows.osinfo;
1142 + try { delete x.FreePhysicalMemory; } catch (e) { }
1143 + try { delete x.FreeSpaceInPagingFiles; } catch (e) { }
1144 + try { delete x.FreeVirtualMemory; } catch (e) { }
1145 + try { delete x.LocalDateTime; } catch (e) { }
1146 + try { delete x.MaxProcessMemorySize; } catch (e) { }
1147 + try { delete x.TotalVirtualMemorySize; } catch (e) { }
1148 + try { delete x.TotalVisibleMemorySize; } catch (e) { }
1149 + try {
1150 + if (results.hardware.windows.memory) { for (var i in results.hardware.windows.memory) { delete results.hardware.windows.memory[i].Node; } }
1151 + if (results.hardware.windows.osinfo) { delete results.hardware.windows.osinfo.Node; }
1152 + if (results.hardware.windows.partitions) { for (var i in results.hardware.windows.partitions) { delete results.hardware.windows.partitions[i].Node; } }
1153 + } catch (e) { }
1154 + }
1155 + if (process.platform == 'win32') { results.pendingReboot = require('win-info').pendingReboot(); } // Pending reboot
1156 + /*
1157 + if (process.platform == 'win32') {
1158 + var defragResult = function (r) {
1159 + if (typeof r == 'object') { results[this.callname] = r; }
1160 + if (this.callname == 'defrag') {
1161 + var pr = require('win-info').installedApps(); // Installed apps
1162 + pr.callname = 'installedApps';
1163 + pr.sessionid = data.sessionid;
1164 + pr.then(defragResult, defragResult);
1165 + }
1166 + else {
1167 + results.winpatches = require('win-info').qfe(); // Windows patches
1168 + results.hash = require('SHA384Stream').create().syncHash(JSON.stringify(results)).toString('hex');
1169 + func(results);
1170 + }
1171 + }
1172 + var pr = require('win-info').defrag({ volume: 'C:' }); // Defrag TODO
1173 + pr.callname = 'defrag';
1174 + pr.sessionid = data.sessionid;
1175 + pr.then(defragResult, defragResult);
1176 + } else {
1177 + */
1178 + results.hash = require('SHA384Stream').create().syncHash(JSON.stringify(results)).toString('hex');
1179 + func(results);
1180 + //}
1181 + } catch (e) { func(null, e); }
1182 + }
1183 +
1184 + // Get a formated response for a given directory path
1185 + function getDirectoryInfo(reqpath) {
1186 + var response = { path: reqpath, dir: [] };
1187 + if (((reqpath == undefined) || (reqpath == '')) && (process.platform == 'win32')) {
1188 + // List all the drives in the root, or the root itself
1189 + var results = null;
1190 + try { results = fs.readDrivesSync(); } catch (e) { } // TODO: Anyway to get drive total size and free space? Could draw a progress bar.
1191 + if (results != null) {
1192 + for (var i = 0; i < results.length; ++i) {
1193 + var drive = { n: results[i].name, t: 1 };
1194 + if (results[i].type == 'REMOVABLE') { drive.dt = 'removable'; } // TODO: See if this is USB/CDROM or something else, we can draw icons.
1195 + response.dir.push(drive);
1196 + }
1197 + }
1198 + } else {
1199 + // List all the files and folders in this path
1200 + if (reqpath == '') { reqpath = '/'; }
1201 + var results = null, xpath = obj.path.join(reqpath, '*');
1202 + //if (process.platform == "win32") { xpath = xpath.split('/').join('\\'); }
1203 + try { results = fs.readdirSync(xpath); } catch (e) { }
1204 + if (results != null) {
1205 + for (var i = 0; i < results.length; ++i) {
1206 + if ((results[i] != '.') && (results[i] != '..')) {
1207 + var stat = null, p = obj.path.join(reqpath, results[i]);
1208 + //if (process.platform == "win32") { p = p.split('/').join('\\'); }
1209 + try { stat = fs.statSync(p); } catch (e) { } // TODO: Get file size/date
1210 + if ((stat != null) && (stat != undefined)) {
1211 + if (stat.isDirectory() == true) {
1212 + response.dir.push({ n: results[i], t: 2, d: stat.mtime });
1213 + } else {
1214 + response.dir.push({ n: results[i], t: 3, s: stat.size, d: stat.mtime });
1215 + }
1216 + }
1217 + }
1218 + }
1219 + }
1220 + }
1221 + return response;
1222 + }
1223 +
1224 + // Tunnel callback operations
1225 + function onTunnelUpgrade(response, s, head) {
1226 + this.s = s;
1227 + s.httprequest = this;
1228 + s.end = onTunnelClosed;
1229 + s.tunnel = this;
1230 + s.descriptorMetadata = "MeshAgent_relayTunnel";
1231 +
1232 + if (require('MeshAgent').idleTimeout != null)
1233 + {
1234 + s.setTimeout(require('MeshAgent').idleTimeout * 1000);
1235 + s.on('timeout', function ()
1236 + {
1237 + this.ping();
1238 + this.setTimeout(require('MeshAgent').idleTimeout * 1000);
1239 + });
1240 + }
1241 +
1242 + //sendConsoleText('onTunnelUpgrade - ' + this.tcpport + ' - ' + this.udpport);
1243 +
1244 + if (this.tcpport != null) {
1245 + // This is a TCP relay connection, pause now and try to connect to the target.
1246 + s.pause();
1247 + s.data = onTcpRelayServerTunnelData;
1248 + var connectionOptions = { port: parseInt(this.tcpport) };
1249 + if (this.tcpaddr != null) { connectionOptions.host = this.tcpaddr; } else { connectionOptions.host = '127.0.0.1'; }
1250 + s.tcprelay = net.createConnection(connectionOptions, onTcpRelayTargetTunnelConnect);
1251 + s.tcprelay.peerindex = this.index;
1252 +
1253 + // Add the TCP session to the count and update the server
1254 + if (s.httprequest.userid != null) {
1255 + if (tunnelUserCount.tcp[s.httprequest.userid] == null) { tunnelUserCount.tcp[s.httprequest.userid] = 1; } else { tunnelUserCount.tcp[s.httprequest.userid]++; }
1256 + try { mesh.SendCommand({ action: 'sessions', type: 'tcp', value: tunnelUserCount.tcp }); } catch (e) { }
1257 + broadcastSessionsToRegisteredApps();
1258 + }
1259 + } if (this.udpport != null) {
1260 + // This is a UDP relay connection, get the UDP socket setup. // TODO: ***************
1261 + s.data = onUdpRelayServerTunnelData;
1262 + s.udprelay = require('dgram').createSocket({ type: 'udp4' });
1263 + s.udprelay.bind({ port: 0 });
1264 + s.udprelay.peerindex = this.index;
1265 + s.udprelay.on('message', onUdpRelayTargetTunnelConnect);
1266 + s.udprelay.udpport = this.udpport;
1267 + s.udprelay.udpaddr = this.udpaddr;
1268 + s.udprelay.first = true;
1269 +
1270 + // Add the UDP session to the count and update the server
1271 + if (s.httprequest.userid != null) {
1272 + if (tunnelUserCount.udp[s.httprequest.userid] == null) { tunnelUserCount.udp[s.httprequest.userid] = 1; } else { tunnelUserCount.udp[s.httprequest.userid]++; }
1273 + try { mesh.SendCommand({ action: 'sessions', type: 'udp', value: tunnelUserCount.tcp }); } catch (e) { }
1274 + broadcastSessionsToRegisteredApps();
1275 + }
1276 + } else {
1277 + // This is a normal connect for KVM/Terminal/Files
1278 + s.data = onTunnelData;
1279 + }
1280 + }
1281 +
1282 + // Called when UDP relay data is received // TODO****
1283 + function onUdpRelayTargetTunnelConnect(data) {
1284 + var peerTunnel = tunnels[this.peerindex];
1285 + peerTunnel.s.write(data);
1286 + }
1287 +
1288 + // Called when we get data from the server for a TCP relay (We have to skip the first received 'c' and pipe the rest)
1289 + function onUdpRelayServerTunnelData(data) {
1290 + if (this.udprelay.first === true) {
1291 + delete this.udprelay.first; // Skip the first 'c' that is received.
1292 + } else {
1293 + this.udprelay.send(data, parseInt(this.udprelay.udpport), this.udprelay.udpaddr ? this.udprelay.udpaddr : '127.0.0.1');
1294 + }
1295 + }
1296 +
1297 + // Called when the TCP relay target is connected
1298 + function onTcpRelayTargetTunnelConnect() {
1299 + var peerTunnel = tunnels[this.peerindex];
1300 + this.pipe(peerTunnel.s); // Pipe Target --> Server
1301 + peerTunnel.s.first = true;
1302 + peerTunnel.s.resume();
1303 + }
1304 +
1305 + // Called when we get data from the server for a TCP relay (We have to skip the first received 'c' and pipe the rest)
1306 + function onTcpRelayServerTunnelData(data) {
1307 + if (this.first == true) {
1308 + this.first = false;
1309 + this.pipe(this.tcprelay, { dataTypeSkip: 1 }); // Pipe Server --> Target (don't pipe text type websocket frames)
1310 + }
1311 + }
1312 +
1313 + function onTunnelClosed() {
1314 + var tunnel = tunnels[this.httprequest.index];
1315 + if (tunnel == null) return; // Stop duplicate calls.
1316 +
1317 + // If this is a routing session, clean up and send the new session counts.
1318 + if (this.httprequest.userid != null) {
1319 + if (this.httprequest.tcpport != null) {
1320 + 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]; } }
1321 + try { mesh.SendCommand({ action: 'sessions', type: 'tcp', value: tunnelUserCount.tcp }); } catch (e) { }
1322 + broadcastSessionsToRegisteredApps();
1323 + } else if (this.httprequest.udpport != null) {
1324 + 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]; } }
1325 + try { mesh.SendCommand({ action: 'sessions', type: 'udp', value: tunnelUserCount.udp }); } catch (e) { }
1326 + broadcastSessionsToRegisteredApps();
1327 + }
1328 + }
1329 +
1330 + // Sent tunnel statistics to the server, only send this if compression was used.
1331 + if ((this.bytesSent_uncompressed) && (this.bytesSent_uncompressed.toString() != this.bytesSent_actual.toString())) {
1332 + mesh.SendCommand({
1333 + action: 'tunnelCloseStats',
1334 + url: tunnel.url,
1335 + userid: tunnel.userid,
1336 + protocol: tunnel.protocol,
1337 + sessionid: tunnel.sessionid,
1338 + sent: this.bytesSent_uncompressed.toString(),
1339 + sentActual: this.bytesSent_actual.toString(),
1340 + sentRatio: this.bytesSent_ratio,
1341 + received: this.bytesReceived_uncompressed.toString(),
1342 + receivedActual: this.bytesReceived_actual.toString(),
1343 + receivedRatio: this.bytesReceived_ratio
1344 + });
1345 + }
1346 +
1347 + //sendConsoleText("Tunnel #" + this.httprequest.index + " closed. Sent -> " + this.bytesSent_uncompressed + ' bytes (uncompressed), ' + this.bytesSent_actual + ' bytes (actual), ' + this.bytesSent_ratio + '% compression', this.httprequest.sessionid);
1348 + if (this.httprequest.index) { delete tunnels[this.httprequest.index]; }
1349 +
1350 + /*
1351 + // Close the watcher if required
1352 + if (this.httprequest.watcher != undefined) {
1353 + //console.log('Closing watcher: ' + this.httprequest.watcher.path);
1354 + //this.httprequest.watcher.close(); // TODO: This line causes the agent to crash!!!!
1355 + delete this.httprequest.watcher;
1356 + }
1357 + */
1358 +
1359 + // If there is a upload or download active on this connection, close the file
1360 + if (this.httprequest.uploadFile) { fs.closeSync(this.httprequest.uploadFile); delete this.httprequest.uploadFile; delete this.httprequest.uploadFileid; delete this.httprequest.uploadFilePath; }
1361 + if (this.httprequest.downloadFile) { delete this.httprequest.downloadFile; }
1362 +
1363 + // Clean up WebRTC
1364 + if (this.webrtc != null) {
1365 + 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; }
1366 + if (this.webrtc.websocket) { delete this.webrtc.websocket; }
1367 + try { this.webrtc.close(); } catch (e) { }
1368 + this.webrtc.removeAllListeners('connected');
1369 + this.webrtc.removeAllListeners('disconnected');
1370 + this.webrtc.removeAllListeners('dataChannel');
1371 + delete this.webrtc;
1372 + }
1373 +
1374 + // Clean up WebSocket
1375 + this.removeAllListeners('data');
1376 + }
1377 + function onTunnelSendOk() { /*sendConsoleText("Tunnel #" + this.index + " SendOK.", this.sessionid);*/ }
1378 + function onTunnelData(data) {
1379 + //console.log("OnTunnelData");
1380 + //sendConsoleText('OnTunnelData, ' + data.length + ', ' + typeof data + ', ' + data);
1381 +
1382 + // If this is upload data, save it to file
1383 + if ((this.httprequest.uploadFile) && (typeof data == 'object') && (data[0] != 123)) {
1384 + // Save the data to file being uploaded.
1385 + if (data[0] == 0) {
1386 + // If data starts with zero, skip the first byte. This is used to escape binary file data from JSON.
1387 + 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.
1388 + } else {
1389 + // If data does not start with zero, save as-is.
1390 + 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.
1391 + }
1392 + this.write(Buffer.from(JSON.stringify({ action: 'uploadack', reqid: this.httprequest.uploadFileid }))); // Ask for more data.
1393 + return;
1394 + }
1395 +
1396 + if (this.httprequest.state == 0) {
1397 + // Check if this is a relay connection
1398 + if ((data == 'c') || (data == 'cr')) { this.httprequest.state = 1; /*sendConsoleText("Tunnel #" + this.httprequest.index + " now active", this.httprequest.sessionid);*/ }
1399 + }
1400 + else
1401 + {
1402 + // Handle tunnel data
1403 + 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
1404 + // Take a look at the protocol
1405 + if ((data.length > 3) && (data[0] == '{')) { onTunnelControlData(data, this); return; }
1406 + this.httprequest.protocol = parseInt(data);
1407 + if (typeof this.httprequest.protocol != 'number') { this.httprequest.protocol = 0; }
1408 + if (this.httprequest.protocol == 10) {
1409 + //
1410 + // Basic file transfer
1411 + //
1412 + var stats = null;
1413 + if ((process.platform != 'win32') && (this.httprequest.xoptions.file.startsWith('/') == false)) { this.httprequest.xoptions.file = '/' + this.httprequest.xoptions.file; }
1414 + try { stats = require('fs').statSync(this.httprequest.xoptions.file) } catch (e) { }
1415 + try { if (stats) { this.httprequest.downloadFile = fs.createReadStream(this.httprequest.xoptions.file, { flags: 'rbN' }); } } catch (e) { }
1416 + if (this.httprequest.downloadFile) {
1417 + //sendConsoleText('BasicFileTransfer, ok, ' + this.httprequest.xoptions.file + ', ' + JSON.stringify(stats));
1418 + this.write(JSON.stringify({ op: 'ok', size: stats.size }));
1419 + this.httprequest.downloadFile.pipe(this);
1420 + this.httprequest.downloadFile.end = function () { }
1421 + } else {
1422 + //sendConsoleText('BasicFileTransfer, cancel, ' + this.httprequest.xoptions.file);
1423 + this.write(JSON.stringify({ op: 'cancel' }));
1424 + }
1425 + }
1426 + else if ((this.httprequest.protocol == 1) || (this.httprequest.protocol == 6) || (this.httprequest.protocol == 8) || (this.httprequest.protocol == 9))
1427 + {
1428 + //
1429 + // Remote Terminal
1430 + //
1431 +
1432 + // Check user access rights for terminal
1433 + if (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) || ((this.httprequest.rights != 0xFFFFFFFF) && ((this.httprequest.rights & MESHRIGHT_NOTERMINAL) != 0))) {
1434 + // Disengage this tunnel, user does not have the rights to do this!!
1435 + this.httprequest.protocol = 999999;
1436 + this.httprequest.s.end();
1437 + sendConsoleText("Error: No Terminal Control Rights.");
1438 + return;
1439 + }
1440 +
1441 + this.descriptorMetadata = "Remote Terminal";
1442 +
1443 + if (process.platform == 'win32')
1444 + {
1445 + if (!require('win-terminal').PowerShellCapable() && (this.httprequest.protocol == 6 || this.httprequest.protocol == 9))
1446 + {
1447 + this.httprequest.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: 'PowerShell is not supported on this version of windows', msgid: 1 }));
1448 + this.httprequest.s.end();
1449 + return;
1450 + }
1451 + }
1452 +
1453 + var prom = require('promise');
1454 + this.httprequest.tpromise = new prom(function (res, rej) { this._res = res; this._rej = rej; });
1455 + this.httprequest.tpromise.that = this;
1456 + this.httprequest.tpromise.httprequest = this.httprequest;
1457 +
1458 + this.end = function ()
1459 + {
1460 + if (this.httprequest.tpromise._consent) { this.httprequest.tpromise._consent.close(); }
1461 + if (this.httprequest.connectionPromise) { this.httprequest.connectionPromise._rej('Closed'); }
1462 +
1463 + // Remove the terminal session to the count to update the server
1464 + if (this.httprequest.userid != null)
1465 + {
1466 + 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]; } }
1467 + try { mesh.SendCommand({ action: 'sessions', type: 'terminal', value: tunnelUserCount.terminal }); } catch (e) { }
1468 + broadcastSessionsToRegisteredApps();
1469 + }
1470 +
1471 + if (process.platform == 'win32')
1472 + {
1473 + // Unpipe the web socket
1474 + this.unpipe(this.httprequest._term);
1475 + if (this.httprequest._term) { this.httprequest._term.unpipe(this); }
1476 +
1477 + // Unpipe the WebRTC channel if needed (This will also be done when the WebRTC channel ends).
1478 + if (this.rtcchannel)
1479 + {
1480 + this.rtcchannel.unpipe(this.httprequest._term);
1481 + if (this.httprequest._term) { this.httprequest._term.unpipe(this.rtcchannel); }
1482 + }
1483 +
1484 + // Clean up
1485 + if (this.httprequest._term) { this.httprequest._term.end(); }
1486 + this.httprequest._term = null;
1487 + }
1488 + };
1489 +
1490 + // Perform User-Consent if needed.
1491 + if (this.httprequest.consent && (this.httprequest.consent & 16))
1492 + {
1493 + this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 }));
1494 + var consentMessage = this.httprequest.username + " requesting remote terminal access. Grant access?", consentTitle = 'MeshCentral';
1495 + if (this.httprequest.soptions != null) {
1496 + if (this.httprequest.soptions.consentTitle != null) { consentTitle = this.httprequest.soptions.consentTitle; }
1497 + if (this.httprequest.soptions.consentMsgTerminal != null) { consentMessage = this.httprequest.soptions.consentMsgTerminal.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); }
1498 + }
1499 + this.httprequest.tpromise._consent = require('message-box').create(consentTitle, consentMessage, 30);
1500 + this.httprequest.tpromise._consent.retPromise = this.httprequest.tpromise;
1501 + this.httprequest.tpromise._consent.then(
1502 + function ()
1503 + {
1504 + // Success
1505 + MeshServerLogEx(27, null, "Local user accepted remote terminal request (" + this.retPromise.httprequest.remoteaddr + ")", this.retPromise.that.httprequest);
1506 + this.retPromise.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null, msgid: 0 }));
1507 + this.retPromise._consent = null;
1508 + this.retPromise._res();
1509 + },
1510 + function (e)
1511 + {
1512 + // Denied
1513 + MeshServerLogEx(28, null, "Local user rejected remote terminal request (" + this.retPromise.that.httprequest.remoteaddr + ")", this.retPromise.that.httprequest);
1514 + this.retPromise.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
1515 + this.retPromise._rej(e.toString());
1516 + });
1517 + }
1518 + else
1519 + {
1520 + // User-Consent is not required, so just resolve this promise
1521 + this.httprequest.tpromise._res();
1522 + }
1523 +
1524 +
1525 + this.httprequest.tpromise.then(
1526 + function ()
1527 + {
1528 + this.httprequest.connectionPromise = new prom(function (res, rej) { this._res = res; this._rej = rej; });
1529 + this.httprequest.connectionPromise.ws = this.that;
1530 +
1531 + // Start Terminal
1532 + if(process.platform == 'win32')
1533 + {
1534 + try
1535 + {
1536 + var cols = 80, rows = 25;
1537 + if (this.httprequest.xoptions)
1538 + {
1539 + if (this.httprequest.xoptions.rows) { rows = this.httprequest.xoptions.rows; }
1540 + if (this.httprequest.xoptions.cols) { cols = this.httprequest.xoptions.cols; }
1541 + }
1542 +
1543 + if ((this.httprequest.protocol == 1) || (this.httprequest.protocol == 6))
1544 + {
1545 + // Admin Terminal
1546 + if (require('win-virtual-terminal').supported)
1547 + {
1548 + // ConPTY PseudoTerminal
1549 + // this.httprequest._term = require('win-virtual-terminal')[this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'](80, 25);
1550 +
1551 + // The above line is commented out, because there is a bug with ClosePseudoConsole() API, so this is the workaround
1552 + 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] } });
1553 + this.httprequest._dispatcher.httprequest = this.httprequest;
1554 + this.httprequest._dispatcher.on('connection', function (c)
1555 + {
1556 + if (this.httprequest.connectionPromise.completed)
1557 + {
1558 + c.end();
1559 + }
1560 + else
1561 + {
1562 + this.httprequest.connectionPromise._res(c);
1563 + }
1564 + });
1565 + }
1566 + else
1567 + {
1568 + // Legacy Terminal
1569 + this.httprequest.connectionPromise._res(require('win-terminal')[this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'](cols, rows));
1570 + }
1571 + }
1572 + else
1573 + {
1574 + // Logged in user
1575 + var userPromise = require('user-sessions').enumerateUsers();
1576 + userPromise.that = this;
1577 + userPromise.then(function (u)
1578 + {
1579 + var that = this.that;
1580 + if (u.Active.length > 0)
1581 + {
1582 + var username = u.Active[0].Username;
1583 + if (require('win-virtual-terminal').supported)
1584 + {
1585 + // ConPTY PseudoTerminal
1586 + 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] } });
1587 + }
1588 + else
1589 + {
1590 + // Legacy Terminal
1591 + 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] } });
1592 + }
1593 + that.httprequest._dispatcher.ws = that;
1594 + that.httprequest._dispatcher.on('connection', function (c)
1595 + {
1596 + if (this.ws.httprequest.connectionPromise.completed)
1597 + {
1598 + c.end();
1599 + }
1600 + else
1601 + {
1602 + this.ws.httprequest.connectionPromise._res(c);
1603 + }
1604 + });
1605 + }
1606 + });
1607 + }
1608 + }
1609 + catch (e)
1610 + {
1611 + this.httprequest.connectionPromise._rej('Failed to start remote terminal session, ' + e.toString());
1612 + }
1613 + }
1614 + else
1615 + {
1616 + try
1617 + {
1618 + var bash = fs.existsSync('/bin/bash') ? '/bin/bash' : false;
1619 + var sh = fs.existsSync('/bin/sh') ? '/bin/sh' : false;
1620 + var login = process.platform == 'linux' ? '/bin/login' : '/usr/bin/login';
1621 +
1622 + var env = { HISTCONTROL: 'ignoreboth' };
1623 + if (this.httprequest.xoptions)
1624 + {
1625 + if (this.httprequest.xoptions.rows) { env.LINES = ('' + this.httprequest.xoptions.rows); }
1626 + if (this.httprequest.xoptions.cols) { env.COLUMNS = ('' + this.httprequest.xoptions.cols); }
1627 + }
1628 + var options = { type: childProcess.SpawnTypes.TERM, uid: (this.httprequest.protocol == 8) ? require('user-sessions').consoleUid() : null, env: env };
1629 + if (this.httprequest.xoptions && this.httprequest.xoptions.requireLogin)
1630 + {
1631 + if (!require('fs').existsSync(login)) { throw ('Unable to spawn login process'); }
1632 + this.httprequest.connectionPromise._res(childProcess.execFile(login, ['login'], options)); // Start login shell
1633 + }
1634 + else if (bash)
1635 + {
1636 + var p = childProcess.execFile(bash, ['bash'], options); // Start bash
1637 + // Spaces at the beginning of lines are needed to hide commands from the command history
1638 + if (process.platform == 'linux') { p.stdin.write(' alias ls=\'ls --color=auto\';clear\n'); }
1639 + this.httprequest.connectionPromise._res(p);
1640 + }
1641 + else if (sh)
1642 + {
1643 + var p = childProcess.execFile(sh, ['sh'], options); // Start sh
1644 + // Spaces at the beginning of lines are needed to hide commands from the command history
1645 + if (process.platform == 'linux') { p.stdin.write(' alias ls=\'ls --color=auto\';clear\n'); }
1646 + this.httprequest.connectionPromise._res(p);
1647 + }
1648 + else
1649 + {
1650 + this.httprequest.connectionPromise._rej('Failed to start remote terminal session, no shell found');
1651 + }
1652 + }
1653 + catch (e)
1654 + {
1655 + this.httprequest.connectionPromise._rej('Failed to start remote terminal session, ' + e.toString());
1656 + }
1657 + }
1658 +
1659 + this.httprequest.connectionPromise.then(
1660 + function (term)
1661 + {
1662 + // SUCCESS
1663 + var stdoutstream;
1664 + var stdinstream;
1665 + if (process.platform == 'win32')
1666 + {
1667 + this.ws.httprequest._term = term;
1668 + this.ws.httprequest._term.tunnel = this.ws;
1669 + stdoutstream = stdinstream = term;
1670 + }
1671 + else
1672 + {
1673 + term.descriptorMetadata = 'Remote Terminal';
1674 + this.ws.httprequest.process = term;
1675 + this.ws.httprequest.process.tunnel = this.ws;
1676 + term.stderr.stdout = term.stdout;
1677 + term.stderr.on('data', function (c) { this.stdout.write(c); });
1678 + stdoutstream = term.stdout;
1679 + stdinstream = term.stdin;
1680 + this.ws.prependListener('end', function () { this.httprequest.process.kill(); });
1681 + term.prependListener('exit', function () { this.tunnel.end(); });
1682 + }
1683 +
1684 + this.ws.removeAllListeners('data');
1685 + this.ws.on('data', onTunnelControlData);
1686 +
1687 + stdoutstream.pipe(this.ws, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
1688 + this.ws.pipe(stdinstream, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
1689 +
1690 + // Add the terminal session to the count to update the server
1691 + if (this.ws.httprequest.userid != null)
1692 + {
1693 + if (tunnelUserCount.terminal[this.ws.httprequest.userid] == null) { tunnelUserCount.terminal[this.ws.httprequest.userid] = 1; } else { tunnelUserCount.terminal[this.ws.httprequest.userid]++; }
1694 + try { mesh.SendCommand({ action: 'sessions', type: 'terminal', value: tunnelUserCount.terminal }); } catch (e) { }
1695 + broadcastSessionsToRegisteredApps();
1696 + }
1697 +
1698 + // Toast Notification, if required
1699 + if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 2))
1700 + {
1701 + // User Notifications is required
1702 + var notifyMessage = this.ws.httprequest.username + " started a remote terminal session.", notifyTitle = "MeshCentral";
1703 + if (this.ws.httprequest.soptions != null) {
1704 + if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; }
1705 + 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); }
1706 + }
1707 + try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (e) { }
1708 + }
1709 + },
1710 + function (e)
1711 + {
1712 + // FAILED to connect terminal
1713 + this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
1714 + this.ws.end();
1715 + });
1716 + },
1717 + function (e)
1718 + {
1719 + // DO NOT start terminal
1720 + this.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
1721 + this.that.end();
1722 + });
1723 + }
1724 + else if (this.httprequest.protocol == 2)
1725 + {
1726 + //
1727 + // Remote KVM
1728 + //
1729 +
1730 + // Check user access rights for desktop
1731 + if ((((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) && ((this.httprequest.rights & MESHRIGHT_REMOTEVIEW) == 0)) || ((this.httprequest.rights != 0xFFFFFFFF) && ((this.httprequest.rights & MESHRIGHT_NODESKTOP) != 0))) {
1732 + // Disengage this tunnel, user does not have the rights to do this!!
1733 + this.httprequest.protocol = 999999;
1734 + this.httprequest.s.end();
1735 + sendConsoleText("Error: No Desktop Control Rights.");
1736 + return;
1737 + }
1738 +
1739 + this.descriptorMetadata = "Remote KVM";
1740 +
1741 + // Look for a TSID
1742 + var tsid = null;
1743 + if ((this.httprequest.xoptions != null) && (typeof this.httprequest.xoptions.tsid == 'number')) { tsid = this.httprequest.xoptions.tsid; }
1744 + require('MeshAgent')._tsid = tsid;
1745 +
1746 + // Remote desktop using native pipes
1747 + this.httprequest.desktop = { state: 0, kvm: mesh.getRemoteDesktopStream(tsid), tunnel: this };
1748 + this.httprequest.desktop.kvm.parent = this.httprequest.desktop;
1749 + this.desktop = this.httprequest.desktop;
1750 +
1751 + // Add ourself to the list of remote desktop sessions
1752 + if (this.httprequest.desktop.kvm.tunnels == null) { this.httprequest.desktop.kvm.tunnels = []; }
1753 + this.httprequest.desktop.kvm.tunnels.push(this);
1754 +
1755 + // Send a metadata update to all desktop sessions
1756 + var users = {};
1757 + if (this.httprequest.desktop.kvm.tunnels != null) {
1758 + 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) { } }
1759 + 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) { } }
1760 + tunnelUserCount.desktop = users;
1761 + try { mesh.SendCommand({ action: 'sessions', type: 'kvm', value: users }); } catch (e) { }
1762 + broadcastSessionsToRegisteredApps();
1763 + }
1764 +
1765 + this.end = function () {
1766 + --this.desktop.kvm.connectionCount;
1767 +
1768 + // Remove ourself from the list of remote desktop session
1769 + var i = this.desktop.kvm.tunnels.indexOf(this);
1770 + if (i >= 0) { this.desktop.kvm.tunnels.splice(i, 1); }
1771 +
1772 + // Send a metadata update to all desktop sessions
1773 + var users = {};
1774 + if (this.httprequest.desktop.kvm.tunnels != null) {
1775 + 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) { } }
1776 + 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) { } }
1777 + tunnelUserCount.desktop = users;
1778 + try { mesh.SendCommand({ action: 'sessions', type: 'kvm', value: users }); } catch (e) { }
1779 + broadcastSessionsToRegisteredApps();
1780 + }
1781 +
1782 + // Unpipe the web socket
1783 + try
1784 + {
1785 + this.unpipe(this.httprequest.desktop.kvm);
1786 + this.httprequest.desktop.kvm.unpipe(this);
1787 + }
1788 + catch(e) { }
1789 +
1790 + // Unpipe the WebRTC channel if needed (This will also be done when the WebRTC channel ends).
1791 + if (this.rtcchannel)
1792 + {
1793 + try
1794 + {
1795 + this.rtcchannel.unpipe(this.httprequest.desktop.kvm);
1796 + this.httprequest.desktop.kvm.unpipe(this.rtcchannel);
1797 + }
1798 + catch(e) { }
1799 + }
1800 +
1801 + // Place wallpaper back if needed
1802 + // TODO
1803 +
1804 + if (this.desktop.kvm.connectionCount == 0) {
1805 + // Display a toast message. This may not be supported on all platforms.
1806 + // try { require('toaster').Toast('MeshCentral', 'Remote Desktop Control Ended.'); } catch (e) { }
1807 +
1808 + this.httprequest.desktop.kvm.end();
1809 + if (this.httprequest.desktop.kvm.connectionBar) {
1810 + this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close');
1811 + this.httprequest.desktop.kvm.connectionBar.close();
1812 + this.httprequest.desktop.kvm.connectionBar = null;
1813 + }
1814 + } else {
1815 + for (var i in this.httprequest.desktop.kvm.users) {
1816 + if ((this.httprequest.desktop.kvm.users[i] == this.httprequest.username) && this.httprequest.desktop.kvm.connectionBar) {
1817 + 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; } }
1818 + this.httprequest.desktop.kvm.users.splice(i, 1);
1819 + this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close');
1820 + this.httprequest.desktop.kvm.connectionBar.close();
1821 + 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);
1822 + this.httprequest.desktop.kvm.connectionBar.httprequest = this.httprequest;
1823 + this.httprequest.desktop.kvm.connectionBar.on('close', function () {
1824 + MeshServerLogEx(29, null, "Remote Desktop Connection forcefully closed by local user (" + this.httprequest.remoteaddr + ")", this.httprequest);
1825 + for (var i in this.httprequest.desktop.kvm._pipedStreams) {
1826 + this.httprequest.desktop.kvm._pipedStreams[i].end();
1827 + }
1828 + this.httprequest.desktop.kvm.end();
1829 + });
1830 + break;
1831 + }
1832 + }
1833 + }
1834 + };
1835 + if (this.httprequest.desktop.kvm.hasOwnProperty('connectionCount')) {
1836 + this.httprequest.desktop.kvm.connectionCount++;
1837 + this.httprequest.desktop.kvm.rusers.push(this.httprequest.realname);
1838 + this.httprequest.desktop.kvm.users.push(this.httprequest.username);
1839 + this.httprequest.desktop.kvm.rusers.sort();
1840 + this.httprequest.desktop.kvm.users.sort();
1841 + } else {
1842 + this.httprequest.desktop.kvm.connectionCount = 1;
1843 + this.httprequest.desktop.kvm.rusers = [this.httprequest.realname];
1844 + this.httprequest.desktop.kvm.users = [this.httprequest.username];
1845 + }
1846 +
1847 + if ((this.httprequest.rights == 0xFFFFFFFF) || (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) != 0) && ((this.httprequest.rights & MESHRIGHT_REMOTEVIEW) == 0))) {
1848 + // If we have remote control rights, pipe the KVM input
1849 + this.pipe(this.httprequest.desktop.kvm, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. Pipe the Browser --> KVM input.
1850 + } else {
1851 + // We need to only pipe non-mouse & non-keyboard inputs.
1852 + //sendConsoleText('Warning: No Remote Desktop Input Rights.');
1853 + // TODO!!!
1854 + }
1855 +
1856 + // Perform notification if needed. Toast messages may not be supported on all platforms.
1857 + if (this.httprequest.consent && (this.httprequest.consent & 8))
1858 + {
1859 + // User Consent Prompt is required
1860 + // Send a console message back using the console channel, "\n" is supported.
1861 + this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 }));
1862 + var consentMessage = this.httprequest.realname + " requesting remote desktop access. Grant access?", consentTitle = 'MeshCentral';
1863 + if (this.httprequest.soptions != null) {
1864 + if (this.httprequest.soptions.consentTitle != null) { consentTitle = this.httprequest.soptions.consentTitle; }
1865 + if (this.httprequest.soptions.consentMsgDesktop != null) { consentMessage = this.httprequest.soptions.consentMsgDesktop.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); }
1866 + }
1867 + var pr = require('message-box').create(consentTitle, consentMessage, 30, null, tsid);
1868 + pr.ws = this;
1869 + this.pause();
1870 + this._consentpromise = pr;
1871 + this.prependOnceListener('end', function () { if (this._consentpromise && this._consentpromise.close) { this._consentpromise.close(); }});
1872 + pr.then(
1873 + function ()
1874 + {
1875 + // Success
1876 + this.ws._consentpromise = null;
1877 + MeshServerLogEx(30, null, "Starting remote desktop after local user accepted (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
1878 + this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null, msgid: 0 }));
1879 + if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 1)) {
1880 + // User Notifications is required
1881 + var notifyMessage = this.ws.httprequest.realname + " started a remote desktop session.", notifyTitle = "MeshCentral";
1882 + if (this.ws.httprequest.soptions != null) {
1883 + if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; }
1884 + 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); }
1885 + }
1886 + try { require('toaster').Toast(notifyTitle, notifyMessage, tsid); } catch (e) { }
1887 + }
1888 + if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 0x40)) {
1889 + // Connection Bar is required
1890 + if (this.ws.httprequest.desktop.kvm.connectionBar) {
1891 + this.ws.httprequest.desktop.kvm.connectionBar.removeAllListeners('close');
1892 + this.ws.httprequest.desktop.kvm.connectionBar.close();
1893 + }
1894 + try {
1895 + 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);
1896 + MeshServerLogEx(31, null, "Remote Desktop Connection Bar Activated/Updated (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
1897 + }
1898 + catch (e) {
1899 + if (process.platform != 'darwin') {
1900 + MeshServerLogEx(32, null, "Remote Desktop Connection Bar Failed or Not Supported (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
1901 + }
1902 + }
1903 + if (this.ws.httprequest.desktop.kvm.connectionBar) {
1904 + this.ws.httprequest.desktop.kvm.connectionBar.httprequest = this.ws.httprequest;
1905 + this.ws.httprequest.desktop.kvm.connectionBar.on('close', function () {
1906 + MeshServerLogEx(33, null, "Remote Desktop Connection forcefully closed by local user (" + this.httprequest.remoteaddr + ")", this.httprequest);
1907 + for (var i in this.httprequest.desktop.kvm._pipedStreams) {
1908 + this.httprequest.desktop.kvm._pipedStreams[i].end();
1909 + }
1910 + this.httprequest.desktop.kvm.end();
1911 + });
1912 + }
1913 + }
1914 + this.ws.httprequest.desktop.kvm.pipe(this.ws, { dataTypeSkip: 1 });
1915 + this.ws.resume();
1916 + },
1917 + function (e)
1918 + {
1919 + // User Consent Denied/Failed
1920 + this.ws._consentpromise = null;
1921 + MeshServerLogEx(34, null, "Failed to start remote desktop after local user rejected (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
1922 + this.ws.end(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
1923 + });
1924 + } else {
1925 + // User Consent Prompt is not required
1926 + if (this.httprequest.consent && (this.httprequest.consent & 1)) {
1927 + // User Notifications is required
1928 + MeshServerLogEx(35, null, "Started remote desktop with toast notification (" + this.httprequest.remoteaddr + ")", this.httprequest);
1929 + var notifyMessage = this.httprequest.realname + " started a remote desktop session.", notifyTitle = "MeshCentral";
1930 + if (this.httprequest.soptions != null) {
1931 + if (this.httprequest.soptions.notifyTitle != null) { notifyTitle = this.httprequest.soptions.notifyTitle; }
1932 + if (this.httprequest.soptions.notifyMsgDesktop != null) { notifyMessage = this.httprequest.soptions.notifyMsgDesktop.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); }
1933 + }
1934 + try { require('toaster').Toast(notifyTitle, notifyMessage, tsid); } catch (e) { }
1935 + } else {
1936 + MeshServerLogEx(36, null, "Started remote desktop without notification (" + this.httprequest.remoteaddr + ")", this.httprequest);
1937 + }
1938 + if (this.httprequest.consent && (this.httprequest.consent & 0x40)) {
1939 + // Connection Bar is required
1940 + if (this.httprequest.desktop.kvm.connectionBar) {
1941 + this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close');
1942 + this.httprequest.desktop.kvm.connectionBar.close();
1943 + }
1944 + try {
1945 + 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);
1946 + MeshServerLogEx(37, null, "Remote Desktop Connection Bar Activated/Updated (" + this.httprequest.remoteaddr + ")", this.httprequest);
1947 + }
1948 + catch (e) {
1949 + MeshServerLogEx(38, null, "Remote Desktop Connection Bar Failed or not Supported (" + this.httprequest.remoteaddr + ")", this.httprequest);
1950 + }
1951 + if (this.httprequest.desktop.kvm.connectionBar) {
1952 + this.httprequest.desktop.kvm.connectionBar.httprequest = this.httprequest;
1953 + this.httprequest.desktop.kvm.connectionBar.on('close', function () {
1954 + MeshServerLogEx(39, null, "Remote Desktop Connection forcefully closed by local user (" + this.httprequest.remoteaddr + ")", this.httprequest);
1955 + for (var i in this.httprequest.desktop.kvm._pipedStreams) {
1956 + this.httprequest.desktop.kvm._pipedStreams[i].end();
1957 + }
1958 + this.httprequest.desktop.kvm.end();
1959 + });
1960 + }
1961 + }
1962 + this.httprequest.desktop.kvm.pipe(this, { dataTypeSkip: 1 });
1963 + }
1964 +
1965 + this.removeAllListeners('data');
1966 + this.on('data', onTunnelControlData);
1967 + //this.write('MeshCore KVM Hello!1');
1968 +
1969 + } else if (this.httprequest.protocol == 5) {
1970 + //
1971 + // Remote Files
1972 + //
1973 +
1974 + // Check user access rights for files
1975 + if (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) || ((this.httprequest.rights != 0xFFFFFFFF) && ((this.httprequest.rights & MESHRIGHT_NOFILES) != 0))) {
1976 + // Disengage this tunnel, user does not have the rights to do this!!
1977 + this.httprequest.protocol = 999999;
1978 + this.httprequest.s.end();
1979 + sendConsoleText("Error: No files control rights.");
1980 + return;
1981 + }
1982 +
1983 + this.descriptorMetadata = "Remote Files";
1984 +
1985 + // Add the files session to the count to update the server
1986 + if (this.httprequest.userid != null) {
1987 + if (tunnelUserCount.files[this.httprequest.userid] == null) { tunnelUserCount.files[this.httprequest.userid] = 1; } else { tunnelUserCount.files[this.httprequest.userid]++; }
1988 + try { mesh.SendCommand({ action: 'sessions', type: 'files', value: tunnelUserCount.files }); } catch (e) { }
1989 + broadcastSessionsToRegisteredApps();
1990 + }
1991 +
1992 + this.end = function () {
1993 + // Remove the files session from the count to update the server
1994 + if (this.httprequest.userid != null) {
1995 + 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]; } }
1996 + try { mesh.SendCommand({ action: 'sessions', type: 'files', value: tunnelUserCount.files }); } catch (e) { }
1997 + broadcastSessionsToRegisteredApps();
1998 + }
1999 + };
2000 +
2001 + // Perform notification if needed. Toast messages may not be supported on all platforms.
2002 + if (this.httprequest.consent && (this.httprequest.consent & 32)) {
2003 + // User Consent Prompt is required
2004 + // Send a console message back using the console channel, "\n" is supported.
2005 + this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 }));
2006 + var consentMessage = this.httprequest.realname + " requesting remote file Access. Grant access?", consentTitle = 'MeshCentral';
2007 + if (this.httprequest.soptions != null) {
2008 + if (this.httprequest.soptions.consentTitle != null) { consentTitle = this.httprequest.soptions.consentTitle; }
2009 + if (this.httprequest.soptions.consentMsgFiles != null) { consentMessage = this.httprequest.soptions.consentMsgFiles.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); }
2010 + }
2011 + var pr = require('message-box').create(consentTitle, consentMessage, 30);
2012 + pr.ws = this;
2013 + this.pause();
2014 + this._consentpromise = pr;
2015 + this.prependOnceListener('end', function () { if (this._consentpromise && this._consentpromise.close) { this._consentpromise.close(); } });
2016 + pr.then(
2017 + function ()
2018 + {
2019 + // Success
2020 + this.ws._consentpromise = null;
2021 + MeshServerLogEx(40, null, "Starting remote files after local user accepted (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
2022 + this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null }));
2023 + if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 4)) {
2024 + // User Notifications is required
2025 + var notifyMessage = this.ws.httprequest.realname + " started a remote file session.", notifyTitle = "MeshCentral";
2026 + if (this.ws.httprequest.soptions != null) {
2027 + if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; }
2028 + 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); }
2029 + }
2030 + try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (e) { }
2031 + }
2032 + this.ws.resume();
2033 + },
2034 + function (e)
2035 + {
2036 + // User Consent Denied/Failed
2037 + this.ws._consentpromise = null;
2038 + MeshServerLogEx(41, null, "Failed to start remote files after local user rejected (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
2039 + this.ws.end(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
2040 + });
2041 + } else {
2042 + // User Consent Prompt is not required
2043 + if (this.httprequest.consent && (this.httprequest.consent & 4)) {
2044 + // User Notifications is required
2045 + MeshServerLogEx(42, null, "Started remote files with toast notification (" + this.httprequest.remoteaddr + ")", this.httprequest);
2046 + var notifyMessage = this.httprequest.realname + " started a remote file session.", notifyTitle = "MeshCentral";
2047 + if (this.httprequest.soptions != null) {
2048 + if (this.httprequest.soptions.notifyTitle != null) { notifyTitle = this.httprequest.soptions.notifyTitle; }
2049 + if (this.httprequest.soptions.notifyMsgFiles != null) { notifyMessage = this.httprequest.soptions.notifyMsgFiles.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); }
2050 + }
2051 + try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (e) { }
2052 + } else {
2053 + MeshServerLogEx(43, null, "Started remote files without notification (" + this.httprequest.remoteaddr + ")", this.httprequest);
2054 + }
2055 + this.resume();
2056 + }
2057 +
2058 + // Setup files
2059 + // NOP
2060 + }
2061 + } else if (this.httprequest.protocol == 1) {
2062 + // Send data into terminal stdin
2063 + //this.write(data); // Echo back the keys (Does not seem to be a good idea)
2064 + } else if (this.httprequest.protocol == 2) {
2065 + // Send data into remote desktop
2066 + if (this.httprequest.desktop.state == 0) {
2067 + this.write(Buffer.from(String.fromCharCode(0x11, 0xFE, 0x00, 0x00, 0x4D, 0x45, 0x53, 0x48, 0x00, 0x00, 0x00, 0x00, 0x02)));
2068 + this.httprequest.desktop.state = 1;
2069 + } else {
2070 + this.httprequest.desktop.write(data);
2071 + }
2072 + } else if (this.httprequest.protocol == 5) {
2073 + // Process files commands
2074 + var cmd = null;
2075 + try { cmd = JSON.parse(data); } catch (e) { };
2076 + if (cmd == null) { return; }
2077 + if ((cmd.ctrlChannel == '102938') || ((cmd.type == 'offer') && (cmd.sdp != null))) { onTunnelControlData(cmd, this); return; } // If this is control data, handle it now.
2078 + if (cmd.action == undefined) { return; }
2079 + //sendConsoleText('CMD: ' + JSON.stringify(cmd));
2080 +
2081 + if ((cmd.path != null) && (process.platform != 'win32') && (cmd.path[0] != '/')) { cmd.path = '/' + cmd.path; } // Add '/' to paths on non-windows
2082 + //console.log(objToString(cmd, 0, ' '));
2083 + switch (cmd.action) {
2084 + case 'ls': {
2085 + /*
2086 + // Close the watcher if required
2087 + var samepath = ((this.httprequest.watcher != undefined) && (cmd.path == this.httprequest.watcher.path));
2088 + if ((this.httprequest.watcher != undefined) && (samepath == false)) {
2089 + //console.log('Closing watcher: ' + this.httprequest.watcher.path);
2090 + //this.httprequest.watcher.close(); // TODO: This line causes the agent to crash!!!!
2091 + delete this.httprequest.watcher;
2092 + }
2093 + */
2094 +
2095 + // Send the folder content to the browser
2096 + var response = getDirectoryInfo(cmd.path);
2097 + if (cmd.reqid != undefined) { response.reqid = cmd.reqid; }
2098 + this.write(Buffer.from(JSON.stringify(response)));
2099 +
2100 + /*
2101 + // Start the directory watcher
2102 + if ((cmd.path != '') && (samepath == false)) {
2103 + var watcher = fs.watch(cmd.path, onFileWatcher);
2104 + watcher.tunnel = this.httprequest;
2105 + watcher.path = cmd.path;
2106 + this.httprequest.watcher = watcher;
2107 + //console.log('Starting watcher: ' + this.httprequest.watcher.path);
2108 + }
2109 + */
2110 + break;
2111 + }
2112 + case 'mkdir': {
2113 + // Create a new empty folder
2114 + fs.mkdirSync(cmd.path);
2115 + MeshServerLogEx(44, [cmd.path], "Create folder: \"" + cmd.path + "\"", this.httprequest);
2116 + break;
2117 + }
2118 + case 'rm': {
2119 + // Delete, possibly recursive delete
2120 + for (var i in cmd.delfiles) {
2121 + var p = obj.path.join(cmd.path, cmd.delfiles[i]), delcount = 0;
2122 + try { delcount = deleteFolderRecursive(p, cmd.rec); } catch (e) { }
2123 + if ((delcount == 1) && !cmd.rec) {
2124 + MeshServerLogEx(45, [p], "Delete: \"" + p + "\"", this.httprequest);
2125 + } else {
2126 + if (cmd.rec) {
2127 + MeshServerLogEx(46, [p, delcount], "Delete recursive: \"" + p + "\", " + delcount + " element(s) removed", this.httprequest);
2128 + } else {
2129 + MeshServerLogEx(47, [p, delcount], "Delete: \"" + p + "\", " + delcount + " element(s) removed", this.httprequest);
2130 + }
2131 + }
2132 + }
2133 + break;
2134 + }
2135 + case 'markcoredump': {
2136 + // If we are asking for the coredump file, set the right path.
2137 + var coreDumpPath = null;
2138 + if (process.platform == 'win32') {
2139 + if (fs.existsSync(process.coreDumpLocation)) { coreDumpPath = process.coreDumpLocation; }
2140 + } else {
2141 + if ((process.cwd() != '//') && fs.existsSync(process.cwd() + 'core')) { coreDumpPath = process.cwd() + 'core'; }
2142 + }
2143 + if (coreDumpPath != null) { db.Put('CoreDumpTime', require('fs').statSync(coreDumpPath).mtime); }
2144 + break;
2145 + }
2146 + case 'rename': {
2147 + // Rename a file or folder
2148 + var oldfullpath = obj.path.join(cmd.path, cmd.oldname);
2149 + var newfullpath = obj.path.join(cmd.path, cmd.newname);
2150 + MeshServerLogEx(48, [oldfullpath, cmd.newname], 'Rename: \"' + oldfullpath + '\" to \"' + cmd.newname + '\"', this.httprequest);
2151 + try { fs.renameSync(oldfullpath, newfullpath); } catch (e) { console.log(e); }
2152 + break;
2153 + }
2154 + case 'download': {
2155 + // Download a file
2156 + var sendNextBlock = 0;
2157 + if (cmd.sub == 'start') { // Setup the download
2158 + if ((cmd.path == null) && (cmd.ask == 'coredump')) { // If we are asking for the coredump file, set the right path.
2159 + if (process.platform == 'win32') {
2160 + if (fs.existsSync(process.coreDumpLocation)) { cmd.path = process.coreDumpLocation; }
2161 + } else {
2162 + if ((process.cwd() != '//') && fs.existsSync(process.cwd() + 'core')) { cmd.path = process.cwd() + 'core'; }
2163 + }
2164 + }
2165 + MeshServerLogEx(49, [cmd.path], 'Download: \"' + cmd.path + '\"', this.httprequest);
2166 + if ((cmd.path == null) || (this.filedownload != null)) { this.write({ action: 'download', sub: 'cancel', id: this.filedownload.id }); delete this.filedownload; }
2167 + this.filedownload = { id: cmd.id, path: cmd.path, ptr: 0 }
2168 + 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; }
2169 + if (this.filedownload) { this.write({ action: 'download', sub: 'start', id: cmd.id }); }
2170 + } else if ((this.filedownload != null) && (cmd.id == this.filedownload.id)) { // Download commands
2171 + 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; }
2172 + }
2173 + // Send the next download block(s)
2174 + while (sendNextBlock > 0) {
2175 + sendNextBlock--;
2176 + var buf = Buffer.alloc(16384);
2177 + var len = fs.readSync(this.filedownload.f, buf, 4, 16380, null);
2178 + this.filedownload.ptr += len;
2179 + if (len < 16380) { buf.writeInt32BE(0x01000001, 0); fs.closeSync(this.filedownload.f); delete this.filedownload; sendNextBlock = 0; } else { buf.writeInt32BE(0x01000000, 0); }
2180 + this.write(buf.slice(0, len + 4)); // Write as binary
2181 + }
2182 + break;
2183 + }
2184 + /*
2185 + case 'download': {
2186 + // Packet download of a file, agent to browser
2187 + if (cmd.path == undefined) break;
2188 + var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path;
2189 + //console.log('Download: ' + filepath);
2190 + try { this.httprequest.downloadFile = fs.openSync(filepath, 'rbN'); } catch (e) { this.write(Buffer.from(JSON.stringify({ action: 'downloaderror', reqid: cmd.reqid }))); break; }
2191 + this.httprequest.downloadFileId = cmd.reqid;
2192 + this.httprequest.downloadFilePtr = 0;
2193 + if (this.httprequest.downloadFile) { this.write(Buffer.from(JSON.stringify({ action: 'downloadstart', reqid: this.httprequest.downloadFileId }))); }
2194 + break;
2195 + }
2196 + case 'download2': {
2197 + // Stream download of a file, agent to browser
2198 + if (cmd.path == undefined) break;
2199 + var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path;
2200 + try { this.httprequest.downloadFile = fs.createReadStream(filepath, { flags: 'rbN' }); } catch (e) { console.log(e); }
2201 + this.httprequest.downloadFile.pipe(this);
2202 + this.httprequest.downloadFile.end = function () { }
2203 + break;
2204 + }
2205 + */
2206 + case 'upload': {
2207 + // Upload a file, browser to agent
2208 + if (this.httprequest.uploadFile != null) { fs.closeSync(this.httprequest.uploadFile); delete this.httprequest.uploadFile; }
2209 + if (cmd.path == undefined) break;
2210 + var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path;
2211 + this.httprequest.uploadFilePath = filepath;
2212 + MeshServerLogEx(50, [filepath], 'Upload: \"' + filepath + '\"', this.httprequest);
2213 + try { this.httprequest.uploadFile = fs.openSync(filepath, 'wbN'); } catch (e) { this.write(Buffer.from(JSON.stringify({ action: 'uploaderror', reqid: cmd.reqid }))); break; }
2214 + this.httprequest.uploadFileid = cmd.reqid;
2215 + if (this.httprequest.uploadFile) { this.write(Buffer.from(JSON.stringify({ action: 'uploadstart', reqid: this.httprequest.uploadFileid }))); }
2216 + break;
2217 + }
2218 + case 'uploaddone': {
2219 + // Indicates that an upload is done
2220 + if (this.httprequest.uploadFile) {
2221 + fs.closeSync(this.httprequest.uploadFile);
2222 + this.write(Buffer.from(JSON.stringify({ action: 'uploaddone', reqid: this.httprequest.uploadFileid }))); // Indicate that we closed the file.
2223 + delete this.httprequest.uploadFile;
2224 + delete this.httprequest.uploadFileid;
2225 + delete this.httprequest.uploadFilePath;
2226 + }
2227 + break;
2228 + }
2229 + case 'uploadcancel': {
2230 + // Indicates that an upload is canceled
2231 + if (this.httprequest.uploadFile) {
2232 + fs.closeSync(this.httprequest.uploadFile);
2233 + fs.unlinkSync(this.httprequest.uploadFilePath);
2234 + this.write(Buffer.from(JSON.stringify({ action: 'uploadcancel', reqid: this.httprequest.uploadFileid }))); // Indicate that we closed the file.
2235 + delete this.httprequest.uploadFile;
2236 + delete this.httprequest.uploadFileid;
2237 + delete this.httprequest.uploadFilePath;
2238 + }
2239 + break;
2240 + }
2241 + case 'copy': {
2242 + // Copy a bunch of files from scpath to dspath
2243 + for (var i in cmd.names) {
2244 + var sc = obj.path.join(cmd.scpath, cmd.names[i]), ds = obj.path.join(cmd.dspath, cmd.names[i]);
2245 + MeshServerLogEx(51, [sc, ds], 'Copy: \"' + sc + '\" to \"' + ds + '\"', this.httprequest);
2246 + if (sc != ds) { try { fs.copyFileSync(sc, ds); } catch (e) { } }
2247 + }
2248 + break;
2249 + }
2250 + case 'move': {
2251 + // Move a bunch of files from scpath to dspath
2252 + for (var i in cmd.names) {
2253 + var sc = obj.path.join(cmd.scpath, cmd.names[i]), ds = obj.path.join(cmd.dspath, cmd.names[i]);
2254 + MeshServerLogEx(52, [sc, ds], 'Move: \"' + sc + '\" to \"' + ds + '\"', this.httprequest);
2255 + if (sc != ds) { try { fs.copyFileSync(sc, ds); fs.unlinkSync(sc); } catch (e) { } }
2256 + }
2257 + break;
2258 + }
2259 + case 'zip':
2260 + // Zip a bunch of files
2261 + if (this.zip != null) return; // Zip operating is currently running, exit now.
2262 +
2263 + // Check that the specified files exist & build full paths
2264 + var fp, stat, p = [];
2265 + 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); } }
2266 + if (p.length == 0) return; // No files, quit now.
2267 +
2268 + // Setup file compression
2269 + var ofile = cmd.path + '/' + cmd.output;
2270 + this.write(Buffer.from(JSON.stringify({ action: 'dialogmessage', msg: 'zipping' })));
2271 + this.zipfile = ofile;
2272 + delete this.zipcancel;
2273 + var out = require('fs').createWriteStream(ofile, { flags: 'wb' });
2274 + out.xws = this;
2275 + out.on('close', function () {
2276 + this.xws.write(Buffer.from(JSON.stringify({ action: 'dialogmessage', msg: null })));
2277 + this.xws.write(Buffer.from(JSON.stringify({ action: 'refresh' })));
2278 + if (this.xws.zipcancel === true) { fs.unlinkSync(this.xws.zipfile); } // Delete the complete file.
2279 + delete this.xws.zipcancel;
2280 + delete this.xws.zipfile;
2281 + delete this.xws.zip;
2282 + });
2283 + this.zip = require('zip-writer').write({ files: p, basePath: cmd.path });
2284 + this.zip.xws = this;
2285 + 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));
2286 + this.zip.pipe(out);
2287 + break;
2288 + case 'cancel':
2289 + // Cancel zip operation if present
2290 + try { this.zipcancel = true; this.zip.cancel(function () { }); } catch (e) { }
2291 + this.zip = null;
2292 + break;
2293 + default:
2294 + // Unknown action, ignore it.
2295 + break;
2296 + }
2297 + } else if (this.httprequest.protocol == 7) { // Plugin data exchange
2298 + var cmd = null;
2299 + try { cmd = JSON.parse(data); } catch (e) { };
2300 + if (cmd == null) { return; }
2301 + if ((cmd.ctrlChannel == '102938') || ((cmd.type == 'offer') && (cmd.sdp != null))) { onTunnelControlData(cmd, this); return; } // If this is control data, handle it now.
2302 + if (cmd.action == undefined) return;
2303 +
2304 + switch (cmd.action) {
2305 + case 'plugin': {
2306 + try { require(cmd.plugin).consoleaction(cmd, null, null, this); } catch (e) { throw e; }
2307 + break;
2308 + }
2309 + default: {
2310 + // probably shouldn't happen, but just in case this feature is expanded
2311 + }
2312 + }
2313 +
2314 + }
2315 + //sendConsoleText("Got tunnel #" + this.httprequest.index + " data: " + data, this.httprequest.sessionid);
2316 + }
2317 + }
2318 +
2319 + // Delete a directory with a files and directories within it
2320 + function deleteFolderRecursive(path, rec) {
2321 + var count = 0;
2322 + if (fs.existsSync(path)) {
2323 + if (rec == true) {
2324 + fs.readdirSync(obj.path.join(path, '*')).forEach(function (file, index) {
2325 + var curPath = obj.path.join(path, file);
2326 + if (fs.statSync(curPath).isDirectory()) { // recurse
2327 + count += deleteFolderRecursive(curPath, true);
2328 + } else { // delete file
2329 + fs.unlinkSync(curPath);
2330 + count++;
2331 + }
2332 + });
2333 + }
2334 + fs.unlinkSync(path);
2335 + count++;
2336 + }
2337 + return count;
2338 + };
2339 +
2340 + // Called when receiving control data on WebRTC
2341 + function onTunnelWebRTCControlData(data) {
2342 + if (typeof data != 'string') return;
2343 + var obj;
2344 + try { obj = JSON.parse(data); } catch (e) { sendConsoleText('Invalid control JSON on WebRTC: ' + data); return; }
2345 + if (obj.type == 'close') {
2346 + //sendConsoleText('Tunnel #' + this.xrtc.websocket.tunnel.index + ' WebRTC control close');
2347 + try { this.close(); } catch (e) { }
2348 + try { this.xrtc.close(); } catch (e) { }
2349 + }
2350 + }
2351 +
2352 + // Called when receiving control data on websocket
2353 + function onTunnelControlData(data, ws) {
2354 + var obj;
2355 + if (ws == null) { ws = this; }
2356 + if (typeof data == 'string') { try { obj = JSON.parse(data); } catch (e) { sendConsoleText('Invalid control JSON: ' + data); return; } }
2357 + else if (typeof data == 'object') { obj = data; } else { return; }
2358 + //sendConsoleText('onTunnelControlData(' + ws.httprequest.protocol + '): ' + JSON.stringify(data));
2359 + //console.log('onTunnelControlData: ' + JSON.stringify(data));
2360 +
2361 + if (obj.action) {
2362 + switch (obj.action) {
2363 + case 'lock': {
2364 + // Lock the current user out of the desktop
2365 + try {
2366 + if (process.platform == 'win32') {
2367 + MeshServerLogEx(53, null, "Locking remote user out of desktop", ws.httprequest);
2368 + var child = require('child_process');
2369 + child.execFile(process.env['windir'] + '\\system32\\cmd.exe', ['/c', 'RunDll32.exe user32.dll,LockWorkStation'], { type: 1 });
2370 + }
2371 + } catch (e) { }
2372 + break;
2373 + }
2374 + default:
2375 + // Unknown action, ignore it.
2376 + break;
2377 + }
2378 + return;
2379 + }
2380 +
2381 + switch (obj.type) {
2382 + case 'options': {
2383 + // These are additional connection options passed in the control channel.
2384 + //sendConsoleText('options: ' + JSON.stringify(obj));
2385 + delete obj.type;
2386 + ws.httprequest.xoptions = obj;
2387 +
2388 + // Set additional user consent options if present
2389 + if ((obj != null) && (typeof obj.consent == 'number')) { ws.httprequest.consent |= obj.consent; }
2390 +
2391 + break;
2392 + }
2393 + case 'close': {
2394 + // We received the close on the websocket
2395 + //sendConsoleText('Tunnel #' + ws.tunnel.index + ' WebSocket control close');
2396 + try { ws.close(); } catch (e) { }
2397 + break;
2398 + }
2399 + case 'termsize': {
2400 + // Indicates a change in terminal size
2401 + if (process.platform == 'win32')
2402 + {
2403 + if (ws.httprequest._dispatcher == null) return;
2404 + //sendConsoleText('Win32-TermSize: ' + obj.cols + 'x' + obj.rows);
2405 + if (ws.httprequest._dispatcher.invoke) { ws.httprequest._dispatcher.invoke('resizeTerminal', [obj.cols, obj.rows]); }
2406 + } else
2407 + {
2408 + if (ws.httprequest.process == null || ws.httprequest.process.pty == 0) return;
2409 + //sendConsoleText('Linux Resize: ' + obj.cols + 'x' + obj.rows);
2410 +
2411 + if (ws.httprequest.process.tcsetsize) { ws.httprequest.process.tcsetsize(obj.rows, obj.cols); }
2412 + }
2413 + break;
2414 + }
2415 + case 'webrtc0': { // Browser indicates we can start WebRTC switch-over.
2416 + if (ws.httprequest.protocol == 1) { // Terminal
2417 + // 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
2418 + if (process.platform == 'win32') {
2419 + ws.httprequest._term.unpipe(ws);
2420 + } else {
2421 + ws.httprequest.process.stdout.unpipe(ws);
2422 + ws.httprequest.process.stderr.unpipe(ws);
2423 + }
2424 + } else if (ws.httprequest.protocol == 2) { // Desktop
2425 + // 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
2426 + ws.httprequest.desktop.kvm.unpipe(ws);
2427 + } else {
2428 + // Switch things around so all WebRTC data goes to onTunnelData().
2429 + ws.rtcchannel.httprequest = ws.httprequest;
2430 + ws.rtcchannel.removeAllListeners('data');
2431 + ws.rtcchannel.on('data', onTunnelData);
2432 + }
2433 + ws.write("{\"ctrlChannel\":\"102938\",\"type\":\"webrtc1\"}"); // End of data marker
2434 + break;
2435 + }
2436 + case 'webrtc1': {
2437 + if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) { // Terminal
2438 + // Switch the user input from websocket to webrtc at this point.
2439 + if (process.platform == 'win32') {
2440 + ws.unpipe(ws.httprequest._term);
2441 + ws.rtcchannel.pipe(ws.httprequest._term, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
2442 + } else {
2443 + ws.unpipe(ws.httprequest.process.stdin);
2444 + ws.rtcchannel.pipe(ws.httprequest.process.stdin, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
2445 + }
2446 + ws.resume(); // Resume the websocket to keep receiving control data
2447 + } else if (ws.httprequest.protocol == 2) { // Desktop
2448 + // Switch the user input from websocket to webrtc at this point.
2449 + ws.unpipe(ws.httprequest.desktop.kvm);
2450 + try { ws.webrtc.rtcchannel.pipe(ws.httprequest.desktop.kvm, { dataTypeSkip: 1, end: false }); } catch (e) { sendConsoleText('EX2'); } // 0 = Binary, 1 = Text.
2451 + ws.resume(); // Resume the websocket to keep receiving control data
2452 + }
2453 + ws.write('{\"ctrlChannel\":\"102938\",\"type\":\"webrtc2\"}'); // Indicates we will no longer get any data on websocket, switching to WebRTC at this point.
2454 + break;
2455 + }
2456 + case 'webrtc2': {
2457 + // Other side received websocket end of data marker, start sending data on WebRTC channel
2458 + if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) { // Terminal
2459 + if (process.platform == 'win32') {
2460 + ws.httprequest._term.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
2461 + } else {
2462 + ws.httprequest.process.stdout.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
2463 + ws.httprequest.process.stderr.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
2464 + }
2465 + } else if (ws.httprequest.protocol == 2) { // Desktop
2466 + ws.httprequest.desktop.kvm.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
2467 + }
2468 + break;
2469 + }
2470 + case 'offer': {
2471 + // This is a WebRTC offer.
2472 + if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) return; // TODO: Terminal is currently broken with WebRTC. Reject WebRTC upgrade for now.
2473 + ws.webrtc = rtc.createConnection();
2474 + ws.webrtc.websocket = ws;
2475 + ws.webrtc.on('connected', function () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC connected');*/ });
2476 + ws.webrtc.on('disconnected', function () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC disconnected');*/ });
2477 + ws.webrtc.on('dataChannel', function (rtcchannel) {
2478 + //sendConsoleText('WebRTC Datachannel open, protocol: ' + this.websocket.httprequest.protocol);
2479 + rtcchannel.maxFragmentSize = 32768;
2480 + rtcchannel.xrtc = this;
2481 + rtcchannel.websocket = this.websocket;
2482 + this.rtcchannel = rtcchannel;
2483 + this.websocket.rtcchannel = rtcchannel;
2484 + this.websocket.rtcchannel.on('data', onTunnelWebRTCControlData);
2485 + this.websocket.rtcchannel.on('end', function () {
2486 + // The WebRTC channel closed, unpipe the KVM now. This is also done when the web socket closes.
2487 + //sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC data channel closed');
2488 + if (this.websocket.desktop && this.websocket.desktop.kvm)
2489 + {
2490 + try
2491 + {
2492 + this.unpipe(this.websocket.desktop.kvm);
2493 + this.websocket.httprequest.desktop.kvm.unpipe(this);
2494 + }
2495 + catch (e) { }
2496 + }
2497 + });
2498 + this.websocket.write('{\"ctrlChannel\":\"102938\",\"type\":\"webrtc0\"}'); // Indicate we are ready for WebRTC switch-over.
2499 + });
2500 + var sdp = null;
2501 + try { sdp = ws.webrtc.setOffer(obj.sdp); } catch (e) { }
2502 + if (sdp != null) { ws.write({ type: 'answer', ctrlChannel: '102938', sdp: sdp }); }
2503 + break;
2504 + }
2505 + case 'ping': {
2506 + ws.write("{\"ctrlChannel\":\"102938\",\"type\":\"pong\"}"); // Send pong response
2507 + break;
2508 + }
2509 + case 'pong': { // NOP
2510 + break;
2511 + }
2512 + case 'rtt': {
2513 + ws.write({ type: 'rtt', ctrlChannel: '102938', time: obj.time });
2514 + break;
2515 + }
2516 + }
2517 + }
2518 +
2519 + // Console state
2520 + var consoleWebSockets = {};
2521 + var consoleHttpRequest = null;
2522 +
2523 + // Console HTTP response
2524 + function consoleHttpResponse(response) {
2525 + response.data = function (data) { sendConsoleText(rstr2hex(buf2rstr(data)), this.sessionid); consoleHttpRequest = null; }
2526 + response.close = function () { sendConsoleText('httprequest.response.close', this.sessionid); consoleHttpRequest = null; }
2527 + };
2528 +
2529 + // Open a web browser to a specified URL on current user's desktop
2530 + function openUserDesktopUrl(url) {
2531 + if ((url.toLowerCase().startsWith('http://') == false) && (url.toLowerCase().startsWith('https://') == false)) { return null; }
2532 + var child = null;
2533 + try {
2534 + switch (process.platform) {
2535 + case 'win32':
2536 + var user = require('user-sessions').getUsername(require('user-sessions').consoleUid());
2537 + child = require('child_process').execFile(process.env['windir'] + '\\system32\\cmd.exe', ['cmd']);
2538 + child.stderr.on('data', function () { });
2539 + child.stdout.on('data', function () { });
2540 + 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');
2541 + child.stdin.write('SCHTASKS /RUN /TN MeshChatTask\r\n');
2542 + child.stdin.write('SCHTASKS /DELETE /F /TN MeshChatTask\r\n');
2543 + child.stdin.write('exit\r\n');
2544 + child.waitExit();
2545 + break;
2546 + case 'linux':
2547 + child = require('child_process').execFile('/usr/bin/xdg-open', ['xdg-open', url], { uid: require('user-sessions').consoleUid() });
2548 + break;
2549 + case 'darwin':
2550 + child = require('child_process').execFile('/usr/bin/open', ['open', url], { uid: require('user-sessions').consoleUid() });
2551 + break;
2552 + default:
2553 + // Unknown platform, ignore this command.
2554 + break;
2555 + }
2556 + } catch (e) { }
2557 + return child;
2558 + }
2559 +
2560 + // Process a mesh agent console command
2561 + function processConsoleCommand(cmd, args, rights, sessionid) {
2562 + try {
2563 + var response = null;
2564 + switch (cmd) {
2565 + case 'help': { // Displays available commands
2566 + 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';
2567 + if (process.platform == 'win32') { availcommands += ',safemode,wpfhwacceleration,uac'; }
2568 + if (process.platform != 'freebsd') { availcommands += ',vm';}
2569 + if (require('MeshAgent').maxKvmTileSize != null) { availcommands += ',kvmmode'; }
2570 + try { require('zip-reader'); availcommands += ',zip,unzip'; } catch (e) { }
2571 +
2572 + availcommands = availcommands.split(',').sort();
2573 + while (availcommands.length > 0) {
2574 + if (f.length > 90) { fin += (f + ',\r\n'); f = ''; }
2575 + f += (((f != '') ? ', ' : ' ') + availcommands.shift());
2576 + }
2577 + if (f != '') { fin += f; }
2578 + response = "Available commands: \r\n" + fin + ".";
2579 + break;
2580 + }
2581 + case 'agentmsg': {
2582 + if (args['_'].length == 0) {
2583 + response = "Proper usage:\r\n agentmsg add \"[message]\" [iconIndex]\r\n agentmsg remove [index]\r\n agentmsg list"; // Display usage
2584 + } else {
2585 + if ((args['_'][0] == 'add') && (args['_'].length > 1)) {
2586 + var msgIndex = 1, iconIndex = 0;
2587 + while (tunnelUserCount.msg[msgIndex] != null) { msgIndex++; }
2588 + if (args['_'].length >= 3) { try { iconIndex = parseInt(args['_'][2]); } catch (e) { } }
2589 + if (typeof iconIndex != 'number') { iconIndex = 0; }
2590 + tunnelUserCount.msg[msgIndex] = { msg: args['_'][1], icon: iconIndex };
2591 + response = 'Agent message ' + msgIndex + ' added.';
2592 + } else if ((args['_'][0] == 'remove') && (args['_'].length > 1)) {
2593 + var msgIndex = 0;
2594 + try { msgIndex = parseInt(args['_'][1]); } catch (x) { }
2595 + if (tunnelUserCount.msg[msgIndex] == null) { response = "Message not found."; } else { delete tunnelUserCount.msg[msgIndex]; response = "Message removed."; }
2596 + } else if (args['_'][0] == 'list') {
2597 + response = JSON.stringify(tunnelUserCount.msg, null, 2);
2598 + }
2599 + try { mesh.SendCommand({ action: 'sessions', type: 'msg', value: tunnelUserCount.msg }); } catch (x) { }
2600 + broadcastSessionsToRegisteredApps();
2601 + }
2602 + break;
2603 + }
2604 + case 'clearagentmsg': {
2605 + tunnelUserCount.msg = {};
2606 + try { mesh.SendCommand({ action: 'sessions', type: 'msg', value: tunnelUserCount.msg }); } catch (x) { }
2607 + broadcastSessionsToRegisteredApps();
2608 + break;
2609 + }
2610 + case 'coredump':
2611 + if (args['_'].length != 1) {
2612 + response = "Proper usage: coredump on|off|status|clear"; // Display usage
2613 + } else {
2614 + switch (args['_'][0].toLowerCase())
2615 + {
2616 + case 'on':
2617 + process.coreDumpLocation = (process.platform == 'win32') ? (process.execPath.replace('.exe', '.dmp')) : (process.execPath + '.dmp');
2618 + response = 'coredump is now on';
2619 + break;
2620 + case 'off':
2621 + process.coreDumpLocation = null;
2622 + response = 'coredump is now off';
2623 + break;
2624 + case 'status':
2625 + response = 'coredump is: ' + ((process.coreDumpLocation == null) ? 'off' : 'on');
2626 + if (process.coreDumpLocation != null) {
2627 + if (process.platform == 'win32') {
2628 + if (fs.existsSync(process.coreDumpLocation)) { response += '\r\n CoreDump present at: ' + process.coreDumpLocation; }
2629 + } else {
2630 + if ((process.cwd() != '//') && fs.existsSync(process.cwd() + 'core')) { response += '\r\n CoreDump present at: ' + process.cwd() + 'core'; }
2631 + }
2632 + }
2633 + break;
2634 + case 'clear':
2635 + db.Put('CoreDumpTime', null);
2636 + response = 'coredump db cleared';
2637 + break;
2638 + default:
2639 + response = "Proper usage: coredump on|off|status"; // Display usage
2640 + break;
2641 + }
2642 + }
2643 + break;
2644 + case 'service':
2645 + if (args['_'].length != 1)
2646 + {
2647 + response = "Proper usage: service status|restart"; // Display usage
2648 + }
2649 + else
2650 + {
2651 + var s = require('service-manager').manager.getService(process.platform == 'win32' ? 'Mesh Agent' : 'meshagent');
2652 + switch(args['_'][0].toLowerCase())
2653 + {
2654 + case 'status':
2655 + response = 'Service ' + (s.isRunning() ? (s.isMe() ? '[SELF]' : '[RUNNING]') : ('[NOT RUNNING]'));
2656 + break;
2657 + case 'restart':
2658 + if (s.isMe())
2659 + {
2660 + s.restart();
2661 + }
2662 + else
2663 + {
2664 + response = 'Restarting another agent instance is not allowed';
2665 + }
2666 + break;
2667 + default:
2668 + response = "Proper usage: service status|restart"; // Display usage
2669 + break;
2670 + }
2671 + if (process.platform == 'win32') { s.close(); }
2672 + }
2673 + break;
2674 + case 'zip':
2675 + if (args['_'].length == 0) {
2676 + response = "Proper usage: zip (output file name), input1 [, input n]"; // Display usage
2677 + } else {
2678 + var p = args['_'].join(' ').split(',');
2679 + var ofile = p.shift();
2680 + sendConsoleText('Writing ' + ofile + '...');
2681 + var out = require('fs').createWriteStream(ofile, { flags: 'wb' });
2682 + out.fname = ofile;
2683 + out.sessionid = sessionid;
2684 + out.on('close', function () { sendConsoleText('DONE writing ' + this.fname, this.sessionid); });
2685 + var zip = require('zip-writer').write({ files: p });
2686 + zip.pipe(out);
2687 + }
2688 + break;
2689 + case 'unzip':
2690 + if (args['_'].length == 0) {
2691 + response = "Proper usage: unzip input, destination"; // Display usage
2692 + } else {
2693 + var p = args['_'].join(' ').split(',');
2694 + if (p.length != 2) { response = "Proper usage: unzip input, destination"; break; } // Display usage
2695 + var prom = require('zip-reader').read(p[0]);
2696 + prom._dest = p[1];
2697 + prom.self = this;
2698 + prom.sessionid = sessionid;
2699 + prom.then(function (zipped) {
2700 + sendConsoleText('Extracting to ' + this._dest + '...', this.sessionid);
2701 + zipped.extractAll(this._dest).then(function () { sendConsoleText('finished unzipping', this.sessionid); }, function (e) { sendConsoleText('Error unzipping: ' + e, this.sessionid); }).parentPromise.sessionid = this.sessionid;
2702 + }, function (e) { sendConsoleText('Error unzipping: ' + e, this.sessionid); });
2703 + }
2704 + break;
2705 + case 'setbattery':
2706 + // require('MeshAgent').SendCommand({ action: 'battery', state: 'dc', level: 55 });
2707 + if ((args['_'].length > 0) && ((args['_'][0] == 'ac') || (args['_'][0] == 'dc'))) {
2708 + var b = { action: 'battery', state: args['_'][0] };
2709 + if (args['_'].length == 2) { b.level = parseInt(args['_'][1]); }
2710 + require('MeshAgent').SendCommand(b);
2711 + } else {
2712 + require('MeshAgent').SendCommand({ action: 'battery' });
2713 + }
2714 + break;
2715 + case 'fdsnapshot':
2716 + require('ChainViewer').getSnapshot().then(function (c) { sendConsoleText(c, this.sessionid); }).parentPromise.sessionid = sessionid;
2717 + break;
2718 + case 'fdcount':
2719 + require('DescriptorEvents').getDescriptorCount().then(
2720 + function (c)
2721 + {
2722 + sendConsoleText('Descriptor Count: ' + c, this.sessionid);
2723 + }, function (e)
2724 + {
2725 + sendConsoleText('Error fetching descriptor count: ' + e, this.sessionid);
2726 + }).parentPromise.sessionid = sessionid;
2727 + break;
2728 + case 'uac':
2729 + if (process.platform != 'win32')
2730 + {
2731 + response = 'Unknown command "uac", type "help" for list of avaialble commands.';
2732 + break;
2733 + }
2734 + if (args['_'].length != 1)
2735 + {
2736 + response = 'Proper usage: uac [get|interactive|secure]';
2737 + }
2738 + else
2739 + {
2740 + switch(args['_'][0].toUpperCase())
2741 + {
2742 + case 'GET':
2743 + var secd = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System', 'PromptOnSecureDesktop');
2744 + response = "UAC mode: " + (secd == 0 ? "Interactive Desktop" : "Secure Desktop");
2745 + break;
2746 + case 'INTERACTIVE':
2747 + try
2748 + {
2749 + require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System', 'PromptOnSecureDesktop', 0);
2750 + response = 'UAC mode changed to: Interactive Desktop';
2751 + }
2752 + catch (e)
2753 + {
2754 + response = "Unable to change UAC Mode";
2755 + }
2756 + break;
2757 + case 'SECURE':
2758 + try
2759 + {
2760 + require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System', 'PromptOnSecureDesktop', 1);
2761 + response = 'UAC mode changed to: Secure Desktop';
2762 + }
2763 + catch(e)
2764 + {
2765 + response = "Unable to change UAC Mode";
2766 + }
2767 + break;
2768 + default:
2769 + response = 'Proper usage: uac [get|interactive|secure]';
2770 + break;
2771 + }
2772 + }
2773 + break;
2774 + case 'vm':
2775 + response = 'Virtual Machine = ' + require('identifiers').isVM();
2776 + break;
2777 + case 'startupoptions':
2778 + response = JSON.stringify(require('MeshAgent').getStartupOptions());
2779 + break;
2780 + case 'kvmmode':
2781 + if (require('MeshAgent').maxKvmTileSize == null)
2782 + {
2783 + response = "Unknown command \"kvmmode\", type \"help\" for list of avaialble commands.";
2784 + }
2785 + else
2786 + {
2787 + if(require('MeshAgent').maxKvmTileSize == 0)
2788 + {
2789 + response = 'KVM Mode: Full JUMBO';
2790 + }
2791 + else
2792 + {
2793 + response = 'KVM Mode: ' + (require('MeshAgent').maxKvmTileSize <= 65500 ? 'NO JUMBO' : 'Partial JUMBO');
2794 + response += (', TileLimit: ' + (require('MeshAgent').maxKvmTileSize < 1024 ? (require('MeshAgent').maxKvmTileSize + ' bytes') : (Math.round(require('MeshAgent').maxKvmTileSize/1024) + ' Kbytes')));
2795 + }
2796 + }
2797 + break;
2798 + case 'alert':
2799 + if (args['_'].length == 0)
2800 + {
2801 + response = "Proper usage: alert TITLE, CAPTION [, TIMEOUT]"; // Display usage
2802 + }
2803 + else
2804 + {
2805 + var p = args['_'].join(' ').split(',');
2806 + if(p.length<2)
2807 + {
2808 + response = "Proper usage: alert TITLE, CAPTION [, TIMEOUT]"; // Display usage
2809 + }
2810 + else
2811 + {
2812 + this._alert = require('message-box').create(p[0], p[1], p.length==3?parseInt(p[2]):9999,1);
2813 + }
2814 + }
2815 + break;
2816 + case 'agentsize':
2817 + var actualSize = Math.floor(require('fs').statSync(process.execPath).size / 1024);
2818 + if (process.platform == 'win32') {
2819 + // Check the Agent Uninstall MetaData for correctness, as the installer may have written an incorrect value
2820 + var writtenSize = 0;
2821 + try { writtenSize = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MeshCentralAgent', 'EstimatedSize'); } catch (e) { response = e; }
2822 + if (writtenSize != actualSize) {
2823 + response = "Size updated from: " + writtenSize + " to: " + actualSize;
2824 + try { require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MeshCentralAgent', 'EstimatedSize', actualSize); } catch (e) { response = e; }
2825 + } else { response = "Agent Size: " + actualSize + " kb"; }
2826 + } else { response = "Agent Size: " + actualSize + " kb"; }
2827 + break;
2828 + case 'versions':
2829 + response = JSON.stringify(process.versions, null, ' ');
2830 + break;
2831 + case 'wpfhwacceleration':
2832 + if (process.platform != 'win32') { throw ("wpfhwacceleration setting is only supported on Windows"); }
2833 + if (args['_'].length != 1) {
2834 + response = "Proper usage: wpfhwacceleration (ON|OFF|STATUS)"; // Display usage
2835 + }
2836 + else {
2837 + var reg = require('win-registry');
2838 + var uname = require('user-sessions').getUsername(require('user-sessions').consoleUid());
2839 + var key = reg.usernameToUserKey(uname);
2840 +
2841 + switch (args['_'][0].toUpperCase()) {
2842 + default:
2843 + response = "Proper usage: wpfhwacceleration (ON|OFF|STATUS|DEFAULT)"; // Display usage
2844 + break;
2845 + case 'ON':
2846 + try {
2847 + reg.WriteKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration', 0);
2848 + response = "OK";
2849 + } catch (e) { response = "FAILED"; }
2850 + break;
2851 + case 'OFF':
2852 + try {
2853 + reg.WriteKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration', 1);
2854 + response = 'OK';
2855 + } catch (e) { response = 'FAILED'; }
2856 + break;
2857 + case 'STATUS':
2858 + var s;
2859 + try { s = reg.QueryKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration') == 1 ? 'DISABLED' : 'ENABLED'; } catch (e) { s = 'DEFAULT'; }
2860 + response = "WPF Hardware Acceleration: " + s;
2861 + break;
2862 + case 'DEFAULT':
2863 + try { reg.DeleteKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration'); } catch (e) { }
2864 + response = 'OK';
2865 + break;
2866 + }
2867 + }
2868 + break;
2869 + case 'tsid':
2870 + if (process.platform == 'win32') {
2871 + if (args['_'].length != 1) {
2872 + response = "TSID: " + (require('MeshAgent')._tsid == null ? "console" : require('MeshAgent')._tsid);
2873 + } else {
2874 + var i = parseInt(args['_'][0]);
2875 + require('MeshAgent')._tsid = (isNaN(i) ? null : i);
2876 + response = "TSID set to: " + (require('MeshAgent')._tsid == null ? "console" : require('MeshAgent')._tsid);
2877 + }
2878 + } else { response = "TSID command only supported on Windows"; }
2879 + break;
2880 + case 'activeusers':
2881 + if (process.platform == 'win32') {
2882 + var p = require('user-sessions').enumerateUsers();
2883 + p.sessionid = sessionid;
2884 + p.then(function (u) {
2885 + var v = [];
2886 + for (var i in u) {
2887 + if (u[i].State == 'Active') { v.push({ tsid: i, type: u[i].StationName, user: u[i].Username, domain: u[i].Domain }); }
2888 + }
2889 + sendConsoleText(JSON.stringify(v, null, 1), this.sessionid);
2890 + });
2891 + } else { response = "activeusers command only supported on Windows"; }
2892 + break;
2893 + case 'wallpaper':
2894 + if (process.platform != 'win32' && !(process.platform == 'linux' && require('linux-gnome-helpers').available)) {
2895 + response = "wallpaper command not supported on this platform";
2896 + }
2897 + else {
2898 + if (args['_'].length != 1) {
2899 + response = 'Proper usage: wallpaper (GET|TOGGLE)'; // Display usage
2900 + }
2901 + else {
2902 + switch (args['_'][0].toUpperCase()) {
2903 + default:
2904 + response = 'Proper usage: wallpaper (GET|TOGGLE)'; // Display usage
2905 + break;
2906 + case 'GET':
2907 + case 'TOGGLE':
2908 + if (process.platform == 'win32') {
2909 + var id = require('user-sessions').getProcessOwnerName(process.pid).tsid == 0 ? 1 : 0;
2910 + var child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0='], { type: id });
2911 + child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
2912 + child.stderr.on('data', function () { });
2913 + child.waitExit();
2914 + var current = child.stdout.str.trim();
2915 + if (args['_'][0].toUpperCase() == 'GET') {
2916 + response = current;
2917 + break;
2918 + }
2919 + if (current != '') {
2920 + require('MeshAgent')._wallpaper = current;
2921 + response = 'Wallpaper cleared';
2922 + } else {
2923 + response = 'Wallpaper restored';
2924 + }
2925 + child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0=', current != '' ? '""' : require('MeshAgent')._wallpaper], { type: id });
2926 + child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
2927 + child.stderr.on('data', function () { });
2928 + child.waitExit();
2929 + }
2930 + else {
2931 + var id = require('user-sessions').consoleUid();
2932 + var current = require('linux-gnome-helpers').getDesktopWallpaper(id);
2933 + if (args['_'][0].toUpperCase() == 'GET') {
2934 + response = current;
2935 + break;
2936 + }
2937 + if (current != '/dev/null') {
2938 + require('MeshAgent')._wallpaper = current;
2939 + response = 'Wallpaper cleared';
2940 + } else {
2941 + response = 'Wallpaper restored';
2942 + }
2943 + require('linux-gnome-helpers').setDesktopWallpaper(id, current != '/dev/null' ? undefined : require('MeshAgent')._wallpaper);
2944 + }
2945 + break;
2946 + }
2947 + }
2948 + }
2949 + break;
2950 + case 'safemode':
2951 + if (process.platform != 'win32') {
2952 + response = 'safemode only supported on Windows Platforms'
2953 + }
2954 + else {
2955 + if (args['_'].length != 1) {
2956 + response = 'Proper usage: safemode (ON|OFF|STATUS)'; // Display usage
2957 + }
2958 + else {
2959 + switch (args['_'][0].toUpperCase()) {
2960 + default:
2961 + response = 'Proper usage: safemode (ON|OFF|STATUS)'; // Display usage
2962 + break;
2963 + case 'ON':
2964 + require('win-bcd').setKey('safeboot', 'Network');
2965 + require('win-bcd').enableSafeModeService('Mesh Agent');
2966 + break;
2967 + case 'OFF':
2968 + require('win-bcd').deleteKey('safeboot');
2969 + break;
2970 + case 'STATUS':
2971 + var nextboot = require('win-bcd').getKey('safeboot');
2972 + if (nextboot) {
2973 + switch (nextboot) {
2974 + case 'Network':
2975 + case 'network':
2976 + nextboot = 'SAFE_MODE_NETWORK';
2977 + break;
2978 + default:
2979 + nextboot = 'SAFE_MODE';
2980 + break;
2981 + }
2982 + }
2983 + response = 'Current: ' + require('win-bcd').bootMode + ', NextBoot: ' + (nextboot ? nextboot : 'NORMAL');
2984 + break;
2985 + }
2986 + }
2987 + }
2988 + break;
2989 + /*
2990 + case 'border':
2991 + {
2992 + if ((args['_'].length == 1) && (args['_'][0] == 'on')) {
2993 + if (meshCoreObj.users.length > 0) {
2994 + obj.borderManager.Start(meshCoreObj.users[0]);
2995 + response = 'Border blinking is on.';
2996 + } else {
2997 + response = 'Cannot turn on border blinking, no logged in users.';
2998 + }
2999 + } else if ((args['_'].length == 1) && (args['_'][0] == 'off')) {
3000 + obj.borderManager.Stop();
3001 + response = 'Border blinking is off.';
3002 + } else {
3003 + response = 'Proper usage: border "on|off"'; // Display correct command usage
3004 + }
3005 + }
3006 + break;
3007 + */
3008 + case 'av':
3009 + if (process.platform == 'win32') {
3010 + // Windows Command: "wmic /Namespace:\\root\SecurityCenter2 Path AntiVirusProduct get /FORMAT:CSV"
3011 + response = JSON.stringify(require('win-info').av(), null, 1);
3012 + } else {
3013 + response = 'Not supported on the platform';
3014 + }
3015 + break;
3016 + case 'log':
3017 + if (args['_'].length != 1) { response = 'Proper usage: log "sample text"'; } else { MeshServerLog(args['_'][0]); response = 'ok'; }
3018 + break;
3019 + case 'getclip':
3020 + if (require('MeshAgent').isService) {
3021 + require('clipboard').dispatchRead().then(function (str) { sendConsoleText(str, sessionid); });
3022 + } else {
3023 + require("clipboard").read().then(function (str) { sendConsoleText(str, sessionid); });
3024 + }
3025 + break;
3026 + case 'setclip': {
3027 + if (args['_'].length != 1) {
3028 + response = 'Proper usage: setclip "sample text"';
3029 + } else {
3030 + if (require('MeshAgent').isService) {
3031 + require('clipboard').dispatchWrite(args['_'][0]);
3032 + response = 'Setting clipboard to: "' + args['_'][0] + '"';
3033 + }
3034 + else {
3035 + require("clipboard")(args['_'][0]); response = 'Setting clipboard to: "' + args['_'][0] + '"';
3036 + }
3037 + }
3038 + break;
3039 + }
3040 + case 'amtreset': {
3041 + if (amt != null) { amt.reset(); response = 'Done.'; }
3042 + break;
3043 + }
3044 + case 'amtlmsreset': {
3045 + if (amt != null) { amt.lmsreset(); response = 'Done.'; }
3046 + break;
3047 + }
3048 + case 'amtccm': {
3049 + if (amt == null) { response = 'Intel AMT not supported.'; } else {
3050 + if (args['_'].length != 1) { response = 'Proper usage: amtccm (adminPassword)'; } // Display usage
3051 + else { amt.setPolicy({ type: 0 }); amt.activeToCCM(args['_'][0]); }
3052 + }
3053 + break;
3054 + }
3055 + case 'amtacm': {
3056 + if (amt == null) { response = 'Intel AMT not supported.'; } else {
3057 + amt.setPolicy({ type: 0 });
3058 + amt.getAmtInfo(function (meinfo) { amt.activeToACM(meinfo); });
3059 + }
3060 + break;
3061 + }
3062 + case 'amtdeactivate': {
3063 + if (amt == null) { response = 'Intel AMT not supported.'; } else { amt.setPolicy({ type: 0 }); amt.deactivateCCM(); }
3064 + break;
3065 + }
3066 + case 'amtpolicy': {
3067 + if (amtPolicy == null) {
3068 + response = 'No Intel(R) AMT policy.';
3069 + } else {
3070 + response = JSON.stringify(amtPolicy);
3071 + }
3072 + break;
3073 + }
3074 + case 'openurl': {
3075 + if (args['_'].length != 1) { response = 'Proper usage: openurl (url)'; } // Display usage
3076 + else { if (openUserDesktopUrl(args['_'][0]) == null) { response = 'Failed.'; } else { response = 'Success.'; } }
3077 + break;
3078 + }
3079 + case 'users': {
3080 + if (meshCoreObj.users == null) { response = 'Active users are unknown.'; } else { response = 'Active Users: ' + meshCoreObj.users.join(', ') + '.'; }
3081 + require('user-sessions').enumerateUsers().then(function (u) { for (var i in u) { sendConsoleText(u[i]); } });
3082 + break;
3083 + }
3084 + case 'toast': {
3085 + if (args['_'].length < 1) { response = 'Proper usage: toast "message"'; } else {
3086 + if (require('MeshAgent')._tsid == null) {
3087 + require('toaster').Toast('MeshCentral', args['_'][0]).then(sendConsoleText, sendConsoleText);
3088 + }
3089 + else {
3090 + require('toaster').Toast('MeshCentral', args['_'][0], require('MeshAgent')._tsid).then(sendConsoleText, sendConsoleText);
3091 + }
3092 + }
3093 + break;
3094 + }
3095 + case 'setdebug': {
3096 + if (args['_'].length < 1) { response = 'Proper usage: setdebug (target), 0 = Disabled, 1 = StdOut, 2 = This Console, * = All Consoles, 4 = WebLog, 8 = Logfile'; } // Display usage
3097 + else { if (args['_'][0] == '*') { console.setDestination(2); } else { console.setDestination(parseInt(args['_'][0]), sessionid); } }
3098 + break;
3099 + }
3100 + case 'ps': {
3101 + processManager.getProcesses(function (plist) {
3102 + var x = '';
3103 + for (var i in plist) { x += i + ((plist[i].user) ? (', ' + plist[i].user) : '') + ', ' + plist[i].cmd + '\r\n'; }
3104 + sendConsoleText(x, sessionid);
3105 + });
3106 + break;
3107 + }
3108 + case 'kill': {
3109 + if ((args['_'].length < 1)) {
3110 + response = 'Proper usage: kill [pid]'; // Display correct command usage
3111 + } else {
3112 + process.kill(parseInt(args['_'][0]));
3113 + response = 'Killed process ' + args['_'][0] + '.';
3114 + }
3115 + break;
3116 + }
3117 + case 'smbios': {
3118 + if (SMBiosTables == null) { response = 'SMBios tables not available.'; } else { response = objToString(SMBiosTables, 0, ' ', true); }
3119 + break;
3120 + }
3121 + case 'rawsmbios': {
3122 + if (SMBiosTablesRaw == null) { response = 'SMBios tables not available.'; } else {
3123 + response = '';
3124 + for (var i in SMBiosTablesRaw) {
3125 + var header = false;
3126 + for (var j in SMBiosTablesRaw[i]) {
3127 + if (SMBiosTablesRaw[i][j].length > 0) {
3128 + if (header == false) { response += ('Table type #' + i + ((require('smbios').smTableTypes[i] == null) ? '' : (', ' + require('smbios').smTableTypes[i]))) + '\r\n'; header = true; }
3129 + response += (' ' + SMBiosTablesRaw[i][j].toString('hex')) + '\r\n';
3130 + }
3131 + }
3132 + }
3133 + }
3134 + break;
3135 + }
3136 + case 'eval': { // Eval JavaScript
3137 + if (args['_'].length < 1) {
3138 + response = 'Proper usage: eval "JavaScript code"'; // Display correct command usage
3139 + } else {
3140 + response = JSON.stringify(mesh.eval(args['_'][0])); // This can only be run by trusted administrator.
3141 + }
3142 + break;
3143 + }
3144 + case 'uninstallagent': // Uninstall this agent
3145 + var agentName = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent';
3146 + if (!require('service-manager').manager.getService(agentName).isMe()) {
3147 + response = 'Uininstall failed, this instance is not the service instance';
3148 + } else {
3149 + try { diagnosticAgent_uninstall(); } catch (e) { }
3150 + var js = "require('service-manager').manager.getService('" + agentName + "').stop(); require('service-manager').manager.uninstallService('" + agentName + "'); process.exit();";
3151 + 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 });
3152 + }
3153 + break;
3154 + case 'notify': { // Send a notification message to the mesh
3155 + if (args['_'].length != 1) {
3156 + response = 'Proper usage: notify "message" [--session]'; // Display correct command usage
3157 + } else {
3158 + var notification = { action: 'msg', type: 'notify', value: args['_'][0], tag: 'console' };
3159 + if (args.session) { notification.sessionid = sessionid; } // If "--session" is specified, notify only this session, if not, the server will notify the mesh
3160 + mesh.SendCommand(notification); // no sessionid or userid specified, notification will go to the entire mesh
3161 + response = "ok";
3162 + }
3163 + break;
3164 + }
3165 + case 'cpuinfo': { // Return system information
3166 + // CPU & memory utilization
3167 + pr = require('sysinfo').cpuUtilization();
3168 + pr.sessionid = sessionid;
3169 + pr.then(function (data) {
3170 + sendConsoleText(JSON.stringify({ cpu: data, memory: require('sysinfo').memUtilization() }, null, 1), this.sessionid);
3171 + }, function (e) {
3172 + sendConsoleText(e);
3173 + });
3174 + break;
3175 + }
3176 + case 'sysinfo': { // Return system information
3177 + getSystemInformation(function (results, err) {
3178 + if (results == null) { sendConsoleText(err, this.sessionid); } else {
3179 + sendConsoleText(JSON.stringify(results, null, 1), this.sessionid);
3180 + }
3181 + });
3182 + break;
3183 + }
3184 + case 'info': { // Return information about the agent and agent core module
3185 + 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 + '.';
3186 + if (amt != null) { response += '\r\nBuilt-in LMS: ' + ['Disabled', 'Connecting..', 'Connected'][amt.lmsstate] + '.'; }
3187 + if (meshCoreObj.osdesc) { response += '\r\nOS: ' + meshCoreObj.osdesc + '.'; }
3188 + response += '\r\nModules: ' + addedModules.join(', ') + '.';
3189 + response += '\r\nServer Connection: ' + mesh.isControlChannelConnected + ', State: ' + meshServerConnectionState + '.';
3190 + response += '\r\lastMeInfo: ' + lastMeInfo + '.';
3191 + var oldNodeId = db.Get('OldNodeId');
3192 + if (oldNodeId != null) { response += '\r\nOldNodeID: ' + oldNodeId + '.'; }
3193 + if (process.platform == 'linux' || process.platform == 'freebsd') { response += '\r\nX11 support: ' + require('monitor-info').kvm_x11_support + '.'; }
3194 + break;
3195 + }
3196 + case 'osinfo': { // Return the operating system information
3197 + var i = 1;
3198 + if (args['_'].length > 0) { i = parseInt(args['_'][0]); if (i > 8) { i = 8; } response = 'Calling ' + i + ' times.'; }
3199 + for (var j = 0; j < i; j++) {
3200 + var pr = require('os').name();
3201 + pr.sessionid = sessionid;
3202 + pr.then(function (v) {
3203 + sendConsoleText("OS: " + v + (process.platform == 'win32' ? (require('win-virtual-terminal').supported ? ' [ConPTY: YES]' : ' [ConPTY: NO]') : ''), this.sessionid);
3204 + });
3205 + }
3206 + break;
3207 + }
3208 + case 'sendcaps': { // Send capability flags to the server
3209 + if (args['_'].length == 0) {
3210 + response = 'Proper usage: sendcaps (number)'; // Display correct command usage
3211 + } else {
3212 + meshCoreObj.caps = parseInt(args['_'][0]);
3213 + mesh.SendCommand(meshCoreObj);
3214 + response = JSON.stringify(meshCoreObj);
3215 + }
3216 + break;
3217 + }
3218 + case 'sendosdesc': { // Send OS description
3219 + if (args['_'].length > 0) {
3220 + meshCoreObj.osdesc = args['_'][0];
3221 + mesh.SendCommand(meshCoreObj);
3222 + response = JSON.stringify(meshCoreObj);
3223 + } else {
3224 + response = 'Proper usage: sendosdesc [os description]'; // Display correct command usage
3225 + }
3226 + break;
3227 + }
3228 + case 'args': { // Displays parsed command arguments
3229 + response = 'args ' + objToString(args, 0, ' ', true);
3230 + break;
3231 + }
3232 + case 'print': { // Print a message on the mesh agent console, does nothing when running in the background
3233 + var r = [];
3234 + for (var i in args['_']) { r.push(args['_'][i]); }
3235 + console.log(r.join(' '));
3236 + response = 'Message printed on agent console.';
3237 + break;
3238 + }
3239 + case 'type': { // Returns the content of a file
3240 + if (args['_'].length == 0) {
3241 + response = 'Proper usage: type (filepath) [maxlength]'; // Display correct command usage
3242 + } else {
3243 + var max = 4096;
3244 + if ((args['_'].length > 1) && (typeof args['_'][1] == 'number')) { max = args['_'][1]; }
3245 + if (max > 4096) max = 4096;
3246 + var buf = Buffer.alloc(max), fd = fs.openSync(args['_'][0], "r"), r = fs.readSync(fd, buf, 0, max); // Read the file content
3247 + response = buf.toString();
3248 + var i = response.indexOf('\n');
3249 + if ((i > 0) && (response[i - 1] != '\r')) { response = response.split('\n').join('\r\n'); }
3250 + if (r == max) response += '...';
3251 + fs.closeSync(fd);
3252 + }
3253 + break;
3254 + }
3255 + case 'dbkeys': { // Return all data store keys
3256 + response = JSON.stringify(db.Keys);
3257 + break;
3258 + }
3259 + case 'dbget': { // Return the data store value for a given key
3260 + if (db == null) { response = 'Database not accessible.'; break; }
3261 + if (args['_'].length != 1) {
3262 + response = 'Proper usage: dbget (key)'; // Display the value for a given database key
3263 + } else {
3264 + response = db.Get(args['_'][0]);
3265 + }
3266 + break;
3267 + }
3268 + case 'dbset': { // Set a data store key and value pair
3269 + if (db == null) { response = 'Database not accessible.'; break; }
3270 + if (args['_'].length != 2) {
3271 + response = 'Proper usage: dbset (key) (value)'; // Set a database key
3272 + } else {
3273 + var r = db.Put(args['_'][0], args['_'][1]);
3274 + response = 'Key set: ' + r;
3275 + }
3276 + break;
3277 + }
3278 + case 'dbcompact': { // Compact the data store
3279 + if (db == null) { response = 'Database not accessible.'; break; }
3280 + var r = db.Compact();
3281 + response = 'Database compacted: ' + r;
3282 + break;
3283 + }
3284 + case 'httpget': {
3285 + if (consoleHttpRequest != null) {
3286 + response = 'HTTP operation already in progress.';
3287 + } else {
3288 + if (args['_'].length != 1) {
3289 + response = 'Proper usage: httpget (url)';
3290 + } else {
3291 + var options = http.parseUri(args['_'][0]);
3292 + options.method = 'GET';
3293 + if (options == null) {
3294 + response = 'Invalid url.';
3295 + } else {
3296 + try { consoleHttpRequest = http.request(options, consoleHttpResponse); } catch (e) { response = 'Invalid HTTP GET request'; }
3297 + consoleHttpRequest.sessionid = sessionid;
3298 + if (consoleHttpRequest != null) {
3299 + consoleHttpRequest.end();
3300 + response = 'HTTPGET ' + options.protocol + '//' + options.host + ':' + options.port + options.path;
3301 + }
3302 + }
3303 + }
3304 + }
3305 + break;
3306 + }
3307 + case 'wslist': { // List all web sockets
3308 + response = '';
3309 + for (var i in consoleWebSockets) {
3310 + var httprequest = consoleWebSockets[i];
3311 + response += 'Websocket #' + i + ', ' + httprequest.url + '\r\n';
3312 + }
3313 + if (response == '') { response = 'no websocket sessions.'; }
3314 + break;
3315 + }
3316 + case 'wsconnect': { // Setup a web socket
3317 + if (args['_'].length == 0) {
3318 + response = 'Proper usage: wsconnect (url)\r\nFor example: wsconnect wss://localhost:443/meshrelay.ashx?id=abc'; // Display correct command usage
3319 + } else {
3320 + var httprequest = null;
3321 + try {
3322 + var options = http.parseUri(args['_'][0].split('$').join('%24').split('@').join('%40')); // Escape the $ and @ characters in the URL
3323 + options.rejectUnauthorized = 0;
3324 + httprequest = http.request(options);
3325 + } catch (e) { response = 'Invalid HTTP websocket request'; }
3326 + if (httprequest != null) {
3327 + httprequest.upgrade = onWebSocketUpgrade;
3328 + httprequest.on('error', function (e) { sendConsoleText("ERROR: Unable to connect to: " + this.url + ", " + JSON.stringify(e)); });
3329 +
3330 + var index = 1;
3331 + while (consoleWebSockets[index]) { index++; }
3332 + httprequest.sessionid = sessionid;
3333 + httprequest.index = index;
3334 + httprequest.url = args['_'][0];
3335 + consoleWebSockets[index] = httprequest;
3336 + response = 'New websocket session #' + index;
3337 + }
3338 + }
3339 + break;
3340 + }
3341 + case 'wssend': { // Send data on a web socket
3342 + if (args['_'].length == 0) {
3343 + response = 'Proper usage: wssend (socketnumber)\r\n'; // Display correct command usage
3344 + for (var i in consoleWebSockets) {
3345 + var httprequest = consoleWebSockets[i];
3346 + response += 'Websocket #' + i + ', ' + httprequest.url + '\r\n';
3347 + }
3348 + } else {
3349 + var i = parseInt(args['_'][0]);
3350 + var httprequest = consoleWebSockets[i];
3351 + if (httprequest != undefined) {
3352 + httprequest.s.write(args['_'][1]);
3353 + response = 'ok';
3354 + } else {
3355 + response = 'Invalid web socket number';
3356 + }
3357 + }
3358 + break;
3359 + }
3360 + case 'wsclose': { // Close a websocket
3361 + if (args['_'].length == 0) {
3362 + response = 'Proper usage: wsclose (socketnumber)'; // Display correct command usage
3363 + } else {
3364 + var i = parseInt(args['_'][0]);
3365 + var httprequest = consoleWebSockets[i];
3366 + if (httprequest != undefined) {
3367 + if (httprequest.s != null) { httprequest.s.end(); } else { httprequest.end(); }
3368 + response = 'ok';
3369 + } else {
3370 + response = 'Invalid web socket number';
3371 + }
3372 + }
3373 + break;
3374 + }
3375 + case 'tunnels': { // Show the list of current tunnels
3376 + response = '';
3377 + for (var i in tunnels) { response += 'Tunnel #' + i + ', ' + tunnels[i].url + '\r\n'; }
3378 + if (response == '') { response = 'No websocket sessions.'; }
3379 + break;
3380 + }
3381 + case 'ls': { // Show list of files and folders
3382 + response = '';
3383 + var xpath = '*';
3384 + if (args['_'].length > 0) { xpath = obj.path.join(args['_'][0], '*'); }
3385 + response = 'List of ' + xpath + '\r\n';
3386 + var results = fs.readdirSync(xpath);
3387 + for (var i = 0; i < results.length; ++i) {
3388 + var stat = null, p = obj.path.join(args['_'][0], results[i]);
3389 + try { stat = fs.statSync(p); } catch (e) { }
3390 + if ((stat == null) || (stat == undefined)) {
3391 + response += (results[i] + "\r\n");
3392 + } else {
3393 + response += (results[i] + " " + ((stat.isDirectory()) ? "(Folder)" : "(File)") + "\r\n");
3394 + }
3395 + }
3396 + break;
3397 + }
3398 + case 'lsx': { // Show list of files and folders
3399 + response = objToString(getDirectoryInfo(args['_'][0]), 0, ' ', true);
3400 + break;
3401 + }
3402 + case 'lock': { // Lock the current user out of the desktop
3403 + 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'; }
3404 + else { response = 'Not supported on the platform'; }
3405 + break;
3406 + }
3407 + case 'amt': { // Show Intel AMT status
3408 + if (amt != null) {
3409 + amt.getAmtInfo(function (state) {
3410 + var resp = 'Intel AMT not detected.';
3411 + if (state != null) { resp = objToString(state, 0, ' ', true); }
3412 + sendConsoleText(resp, sessionid);
3413 + });
3414 + } else {
3415 + response = 'Intel AMT not detected.';
3416 + }
3417 + break;
3418 + }
3419 + case 'netinfo': { // Show network interface information
3420 + var interfaces = require('os').networkInterfaces();
3421 + response = objToString(interfaces, 0, ' ', true);
3422 + break;
3423 + }
3424 + case 'wakeonlan': { // Send wake-on-lan
3425 + if ((args['_'].length != 1) || (args['_'][0].length != 12)) {
3426 + response = 'Proper usage: wakeonlan [mac], for example "wakeonlan 010203040506".';
3427 + } else {
3428 + var count = sendWakeOnLan(args['_'][0]);
3429 + response = 'Sent wake-on-lan on ' + count + ' interface(s).';
3430 + }
3431 + break;
3432 + }
3433 + case 'sendall': { // Send a message to all consoles on this mesh
3434 + sendConsoleText(args['_'].join(' '));
3435 + break;
3436 + }
3437 + case 'power': { // Execute a power action on this computer
3438 + if (mesh.ExecPowerState == undefined) {
3439 + response = 'Power command not supported on this agent.';
3440 + } else {
3441 + if ((args['_'].length == 0) || isNaN(Number(args['_'][0]))) {
3442 + 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
3443 + } else {
3444 + var r = mesh.ExecPowerState(Number(args['_'][0]), Number(args['_'][1]));
3445 + response = 'Power action executed with return code: ' + r + '.';
3446 + }
3447 + }
3448 + break;
3449 + }
3450 + case 'location': {
3451 + getIpLocationData(function (location) {
3452 + sendConsoleText(objToString({ action: 'iplocation', type: 'publicip', value: location }, 0, ' '));
3453 + });
3454 + break;
3455 + }
3456 + case 'parseuri': {
3457 + response = JSON.stringify(http.parseUri(args['_'][0]));
3458 + break;
3459 + }
3460 + case 'scanwifi': {
3461 + if (wifiScanner != null) {
3462 + var wifiPresent = wifiScanner.hasWireless;
3463 + if (wifiPresent) { response = "Perfoming Wifi scan..."; wifiScanner.Scan(); } else { response = "Wifi absent."; }
3464 + } else { response = "Wifi module not present."; }
3465 + break;
3466 + }
3467 + case 'scanamt': {
3468 + if (amtscanner != null) {
3469 + if (args['_'].length != 1) {
3470 + 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
3471 + } else {
3472 + response = 'Scanning: ' + args['_'][0] + '...';
3473 + amtscanner.scan(args['_'][0], 2000, function (data) {
3474 + if (data.length > 0) {
3475 + var r = '', pstates = ['NotActivated', 'InActivation', 'Activated'];
3476 + for (var i in data) {
3477 + var x = data[i];
3478 + if (r != '') { r += '\r\n'; }
3479 + r += x.address + ' - Intel AMT v' + x.majorVersion + '.' + x.minorVersion;
3480 + if (x.provisioningState < 3) { r += (', ' + pstates[x.provisioningState]); }
3481 + if (x.provisioningState == 2) { r += (', ' + x.openPorts.join(', ')); }
3482 + r += '.';
3483 + }
3484 + } else {
3485 + r = 'No Intel AMT found.';
3486 + }
3487 + sendConsoleText(r);
3488 + });
3489 + }
3490 + } else { response = "Intel AMT scanner module not present."; }
3491 + break;
3492 + }
3493 + case 'modules': {
3494 + response = JSON.stringify(addedModules);
3495 + break;
3496 + }
3497 + case 'listservices': {
3498 + var services = require('service-manager').manager.enumerateService();
3499 + response = JSON.stringify(services, null, 1);
3500 + break;
3501 + }
3502 + case 'getscript': {
3503 + if (args['_'].length != 1) {
3504 + response = "Proper usage: getscript [scriptNumber].";
3505 + } else {
3506 + mesh.SendCommand({ action: 'getScript', type: args['_'][0] });
3507 + }
3508 + break;
3509 + }
3510 + case 'diagnostic':
3511 + {
3512 + if (!mesh.DAIPC.listening) {
3513 + response = 'Unable to bind to Diagnostic IPC, most likely because the path (' + process.cwd() + ') is not on a local file system';
3514 + break;
3515 + }
3516 + var diag = diagnosticAgent_installCheck();
3517 + if (diag) {
3518 + if (args['_'].length == 1 && args['_'][0] == 'uninstall') {
3519 + diagnosticAgent_uninstall();
3520 + response = 'Diagnostic Agent uninstalled';
3521 + }
3522 + else {
3523 + response = 'Diagnostic Agent installed at: ' + diag.appLocation();
3524 + }
3525 + }
3526 + else {
3527 + if (args['_'].length == 1 && args['_'][0] == 'install') {
3528 + diag = diagnosticAgent_installCheck(true);
3529 + if (diag) {
3530 + response = 'Diagnostic agent was installed at: ' + diag.appLocation();
3531 + }
3532 + else {
3533 + response = 'Diagnostic agent installation failed';
3534 + }
3535 + }
3536 + else {
3537 + response = 'Diagnostic Agent Not installed. To install: diagnostic install';
3538 + }
3539 + }
3540 + if (diag) { diag.close(); diag = null; }
3541 + break;
3542 + }
3543 + case 'amtconfig': {
3544 + if (apftunnel != null) { response = "Intel AMT server tunnel already active"; break; }
3545 + if (amt == null) { response = "No Intel AMT support delected"; break; }
3546 + getMeiState(15, function (state) {
3547 + var rx = '';
3548 + if ((state == null) || (state.ProvisioningState == null)) { rx = "Intel AMT not ready for configuration."; } else {
3549 + var apfarg = {
3550 + mpsurl: mesh.ServerUrl.replace('agent.ashx', 'apf.ashx'),
3551 + mpsuser: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16),
3552 + mpspass: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16),
3553 + mpskeepalive: 60000,
3554 + clientname: state.OsHostname,
3555 + clientaddress: '127.0.0.1',
3556 + clientuuid: state.UUID,
3557 + conntype: 2, // 0 = CIRA, 1 = Relay, 2 = LMS. The correct value is 2 since we are performing an LMS relay, other values for testing.
3558 + meiState: state // MEI state will be passed to MPS server
3559 + };
3560 + if ((state.UUID == null) || (state.UUID.length != 36)) {
3561 + rx = "Unable to get Intel AMT UUID";
3562 + } else {
3563 + apftunnel = require('apfclient')({ debug: false }, apfarg);
3564 + apftunnel.onJsonControl = function (data) {
3565 + if (data.action == 'console') { require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: data.msg }); } // Display a console message
3566 + if (data.action == 'mestate') { getMeiState(15, function (state) { apftunnel.updateMeiState(state); }); } // Update the MEI state
3567 + if (data.action == 'deactivate') { // Request CCM deactivation
3568 + var amtMeiModule, amtMei;
3569 + try { amtMeiModule = require('amt-mei'); amtMei = new amtMeiModule(); } catch (ex) { apftunnel.sendMeiDeactivationState(1); return; }
3570 + amtMei.on('error', function (e) { apftunnel.sendMeiDeactivationState(1); });
3571 + amtMei.unprovision(1, function (status) { apftunnel.sendMeiDeactivationState(status); }); // 0 = Success
3572 + }
3573 + if (data.action == 'close') { try { apftunnel.disconnect(); } catch (e) { } apftunnel = null; } // Close the CIRA-LMS connection
3574 + }
3575 + apftunnel.onChannelClosed = function () { apftunnel = null; }
3576 + try {
3577 + apftunnel.connect();
3578 + rx = "Started Intel AMT configuration";
3579 + } catch (ex) {
3580 + rx = JSON.stringify(ex);
3581 + }
3582 + }
3583 + }
3584 + if (rx != '') { require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: rx }); }
3585 + });
3586 + break;
3587 + }
3588 + case 'apf': {
3589 + if (meshCoreObj.intelamt !== null) {
3590 + if (args['_'].length == 1) {
3591 + var connType = -1, connTypeStr = args['_'][0].toLowerCase();
3592 + if (connTypeStr == 'lms') { connType = 2; }
3593 + if (connTypeStr == 'relay') { connType = 1; }
3594 + if (connTypeStr == 'cira') { connType = 0; }
3595 + if (connTypeStr == 'off') { connType = -2; }
3596 + if (connType >= 0) { // Connect
3597 + var apfarg = {
3598 + mpsurl: mesh.ServerUrl.replace('agent.ashx', 'apf.ashx'),
3599 + mpsuser: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16),
3600 + mpspass: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16),
3601 + mpskeepalive: 60000,
3602 + clientname: require('os').hostname(),
3603 + clientaddress: '127.0.0.1',
3604 + clientuuid: meshCoreObj.intelamt.uuid,
3605 + conntype: connType // 0 = CIRA, 1 = Relay, 2 = LMS. The correct value is 2 since we are performing an LMS relay, other values for testing.
3606 + };
3607 + if ((apfarg.clientuuid == null) || (apfarg.clientuuid.length != 36)) {
3608 + response = "Unable to get Intel AMT UUID: " + apfarg.clientuuid;
3609 + } else {
3610 + apftunnel = require('apfclient')({ debug: false }, apfarg);
3611 + apftunnel.onJsonControl = function (data) {
3612 + if (data.action == 'console') { require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: data.msg }); }
3613 + if (data.action == 'close') { try { apftunnel.disconnect(); } catch (e) { } apftunnel = null; }
3614 + }
3615 + apftunnel.onChannelClosed = function () { apftunnel = null; }
3616 + try {
3617 + apftunnel.connect();
3618 + response = "Started APF tunnel";
3619 + } catch (e) {
3620 + response = JSON.stringify(e);
3621 + }
3622 + }
3623 + } else if (connType == -2) { // Disconnect
3624 + try {
3625 + apftunnel.disconnect();
3626 + response = "Stopped APF tunnel";
3627 + } catch (e) {
3628 + response = JSON.stringify(e);
3629 + }
3630 + apftunnel = null;
3631 + } else {
3632 + response = "Invalid command.\r\nUse: apf lms|relay|cira|off";
3633 + }
3634 + } else {
3635 + response = "APF tunnel is " + (apftunnel == null ? "off" : "on") + "\r\nUse: apf lms|relay|cira|off";
3636 + }
3637 + } else {
3638 + response = "APF tunnel requires Intel AMT";
3639 + }
3640 + break;
3641 + }
3642 + case 'plugin': {
3643 + if (typeof args['_'][0] == 'string') {
3644 + try {
3645 + // Pass off the action to the plugin
3646 + // for plugin creators, you'll want to have a plugindir/modules_meshcore/plugin.js
3647 + // to control the output / actions here.
3648 + response = require(args['_'][0]).consoleaction(args, rights, sessionid, mesh);
3649 + } catch (e) {
3650 + response = "There was an error in the plugin (" + e + ")";
3651 + }
3652 + } else {
3653 + response = "Proper usage: plugin [pluginName] [args].";
3654 + }
3655 + break;
3656 + }
3657 + default: { // This is an unknown command, return an error message
3658 + response = "Unknown command \"" + cmd + "\", type \"help\" for list of avaialble commands.";
3659 + break;
3660 + }
3661 + }
3662 + } catch (e) { response = "Command returned an exception error: " + e; console.log(e); }
3663 + if (response != null) { sendConsoleText(response, sessionid); }
3664 + }
3665 +
3666 + // Send a mesh agent console command
3667 + function sendConsoleText(text, sessionid) {
3668 + if (typeof text == 'object') { text = JSON.stringify(text); }
3669 + require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: text, sessionid: sessionid });
3670 + }
3671 +
3672 + // Called before the process exits
3673 + //process.exit = function (code) { console.log("Exit with code: " + code.toString()); }
3674 +
3675 + // Called when the server connection state changes
3676 + function handleServerConnection(state) {
3677 + meshServerConnectionState = state;
3678 + if (meshServerConnectionState == 0) {
3679 + // Server disconnected
3680 + if (selfInfoUpdateTimer != null) { clearInterval(selfInfoUpdateTimer); selfInfoUpdateTimer = null; }
3681 + lastSelfInfo = null;
3682 + } else {
3683 + // Server connected, send mesh core information
3684 + var oldNodeId = db.Get('OldNodeId');
3685 + if (oldNodeId != null) { mesh.SendCommand({ action: 'mc1migration', oldnodeid: oldNodeId }); }
3686 +
3687 + // Update the server with basic info, logged in users and more.
3688 + mesh.SendCommand(meshCoreObj);
3689 +
3690 + // Send SMBios tables if present
3691 + if (SMBiosTablesRaw != null) { mesh.SendCommand({ action: 'smbios', value: SMBiosTablesRaw }); }
3692 +
3693 + // Update the server on more advanced stuff, like Intel ME and Network Settings
3694 + meInfoStr = null;
3695 + sendPeriodicServerUpdate();
3696 + if (selfInfoUpdateTimer == null) { selfInfoUpdateTimer = setInterval(sendPeriodicServerUpdate, 1200000); } // 20 minutes
3697 +
3698 + // Send any state messages
3699 + if (Object.keys(tunnelUserCount.msg).length > 0) {
3700 + try { mesh.SendCommand({ action: 'sessions', type: 'msg', value: tunnelUserCount.msg }); } catch (e) { }
3701 + broadcastSessionsToRegisteredApps();
3702 + }
3703 +
3704 + // Send update of registered applications to the server
3705 + updateRegisteredAppsToServer();
3706 + }
3707 +
3708 + // Send server state update to registered applications
3709 + broadcastToRegisteredApps({ cmd: 'serverstate', value: meshServerConnectionState, url: require('MeshAgent').ConnectedServer });
3710 + }
3711 +
3712 + // Update the server with the latest network interface information
3713 + var sendNetworkUpdateNagleTimer = null;
3714 + function sendNetworkUpdateNagle() { if (sendNetworkUpdateNagleTimer != null) { clearTimeout(sendNetworkUpdateNagleTimer); sendNetworkUpdateNagleTimer = null; } sendNetworkUpdateNagleTimer = setTimeout(sendNetworkUpdate, 5000); }
3715 + function sendNetworkUpdate(force) {
3716 + sendNetworkUpdateNagleTimer = null;
3717 +
3718 + // Update the network interfaces information data
3719 + var netInfo = { netif2: require('os').networkInterfaces() };
3720 + if (netInfo.netif2) {
3721 + netInfo.action = 'netinfo';
3722 + var netInfoStr = JSON.stringify(netInfo);
3723 + if ((force == true) || (clearGatewayMac(netInfoStr) != clearGatewayMac(lastNetworkInfo))) { mesh.SendCommand(netInfo); lastNetworkInfo = netInfoStr; }
3724 + }
3725 + }
3726 +
3727 + // Called periodically to check if we need to send updates to the server
3728 + function sendPeriodicServerUpdate(flags) {
3729 + if (meshServerConnectionState == 0) return; // Not connected to server, do nothing.
3730 + if (!flags) { flags = 0xFFFFFFFF; }
3731 +
3732 + if ((flags & 1) && (amt != null)) {
3733 + // If we have a connected MEI, get Intel ME information
3734 + amt.getAmtInfo(function (meinfo) {
3735 + try {
3736 + if (meinfo == null) return;
3737 + var intelamt = {};
3738 + if (amt != null)
3739 + {
3740 + switch(amt.lmsstate)
3741 + {
3742 + case 0: intelamt.microlms = 'DISABLED'; break;
3743 + case 1: intelamt.microlms = 'CONNECTING'; break;
3744 + case 2: intelamt.microlms = 'CONNECTED'; break;
3745 + default: intelamt.microlms = 'unknown'; break;
3746 + }
3747 + }
3748 + var p = false;
3749 + 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); } }
3750 + if (meinfo.ProvisioningState != null) { intelamt.state = meinfo.ProvisioningState; p = true; }
3751 + if (meinfo.Flags != null) { intelamt.flags = meinfo.Flags; p = true; }
3752 + if (meinfo.OsHostname != null) { intelamt.host = meinfo.OsHostname; p = true; }
3753 + if (meinfo.UUID != null) { intelamt.uuid = meinfo.UUID; p = true; }
3754 + if ((meinfo.ProvisioningState == 0) && (meinfo.net0 != null) && (meinfo.net0.enabled == 1)) { // If not activated, look to see if we have wired net working.
3755 + // Not activated and we have wired ethernet, look for the trusted DNS
3756 + var dns = meinfo.DNS;
3757 + if (dns == null) {
3758 + // Trusted DNS not set, let's look for the OS network DNS suffix
3759 + var interfaces = require('os').networkInterfaces();
3760 + for (var i in interfaces) {
3761 + for (var j in interfaces[i]) {
3762 + if ((interfaces[i][j].mac == mestate.net0.mac) && (interfaces[i][j].fqdn != null) && (interfaces[i][j].fqdn != '')) { dns = interfaces[i][j].fqdn; }
3763 + }
3764 + }
3765 + }
3766 + if (intelamt.dns != dns) { intelamt.dns = dns; p = true; }
3767 + } else { if (intelamt.dns != null) { delete intelamt.dns; p = true; } }
3768 + if (p == true) {
3769 + var meInfoStr = JSON.stringify(intelamt);
3770 + if (meInfoStr != lastMeInfo) {
3771 + meshCoreObj.intelamt = intelamt;
3772 + mesh.SendCommand(meshCoreObj);
3773 + lastMeInfo = meInfoStr;
3774 + }
3775 + }
3776 + } catch (e) { }
3777 + });
3778 + }
3779 +
3780 + if (flags & 2) {
3781 + // Update network information
3782 + sendNetworkUpdateNagle(false);
3783 + }
3784 +
3785 + if ((flags & 4) && (process.platform == 'win32')) {
3786 + // Update anti-virus information
3787 + // Windows Command: "wmic /Namespace:\\root\SecurityCenter2 Path AntiVirusProduct get /FORMAT:CSV"
3788 + var av, pr;
3789 + try { av = require('win-info').av(); } catch (e) { av = null; } // Antivirus
3790 + //if (process.platform == 'win32') { try { pr = require('win-info').pendingReboot(); } catch (e) { pr = null; } } // Pending reboot
3791 + if ((meshCoreObj.av == null) || (JSON.stringify(meshCoreObj.av) != JSON.stringify(av))) { meshCoreObj.av = av; mesh.SendCommand(meshCoreObj); }
3792 + }
3793 + }
3794 +
3795 + // Starting function
3796 + obj.start = function () {
3797 + // Setup the mesh agent event handlers
3798 + mesh.AddCommandHandler(handleServerCommand);
3799 + mesh.AddConnectHandler(handleServerConnection);
3800 +
3801 + // Parse input arguments
3802 + //var args = parseArgs(process.argv);
3803 + //console.log(args);
3804 +
3805 + //resetMicroLms();
3806 +
3807 + // Setup logged in user monitoring (THIS IS BROKEN IN WIN7)
3808 + try {
3809 + var userSession = require('user-sessions');
3810 + userSession.on('changed', function onUserSessionChanged() {
3811 + userSession.enumerateUsers().then(function (users) {
3812 + var u = [], a = users.Active;
3813 + for (var i = 0; i < a.length; i++) {
3814 + var un = a[i].Domain ? (a[i].Domain + '\\' + a[i].Username) : (a[i].Username);
3815 + if (u.indexOf(un) == -1) { u.push(un); } // Only push users in the list once.
3816 + }
3817 + meshCoreObj.users = u;
3818 + mesh.SendCommand(meshCoreObj);
3819 + });
3820 + });
3821 + userSession.emit('changed');
3822 + //userSession.on('locked', function (user) { sendConsoleText('[' + (user.Domain ? user.Domain + '\\' : '') + user.Username + '] has LOCKED the desktop'); });
3823 + //userSession.on('unlocked', function (user) { sendConsoleText('[' + (user.Domain ? user.Domain + '\\' : '') + user.Username + '] has UNLOCKED the desktop'); });
3824 + } catch (e) { }
3825 + }
3826 +
3827 + obj.stop = function () {
3828 + mesh.AddCommandHandler(null);
3829 + mesh.AddConnectHandler(null);
3830 + }
3831 +
3832 + function onWebSocketClosed() { sendConsoleText("WebSocket #" + this.httprequest.index + " closed.", this.httprequest.sessionid); delete consoleWebSockets[this.httprequest.index]; }
3833 + function onWebSocketData(data) { sendConsoleText("Got WebSocket #" + this.httprequest.index + " data: " + data, this.httprequest.sessionid); }
3834 + function onWebSocketSendOk() { sendConsoleText("WebSocket #" + this.index + " SendOK.", this.sessionid); }
3835 +
3836 + function onWebSocketUpgrade(response, s, head) {
3837 + sendConsoleText("WebSocket #" + this.index + " connected.", this.sessionid);
3838 + this.s = s;
3839 + s.httprequest = this;
3840 + s.end = onWebSocketClosed;
3841 + s.data = onWebSocketData;
3842 + }
3843 +
3844 + // Get Intel MEI State in a flexible way
3845 + // Flags: 1 = Versions, 2 = OsAdmin, 4 = Hashes, 8 = Network
3846 + function getMeiState(flags, func) {
3847 + var amtMeiModule, amtMei;
3848 + try { amtMeiModule = require('amt-mei'); amtMei = new amtMeiModule(); } catch (ex) { func(null); return; }
3849 + amtMei.on('error', function (e) { func(null); return; });
3850 + try {
3851 + var amtMeiTmpState = { OsHostname: require('os').hostname(), Flags: 0 }; // Flags: 1=EHBC, 2=CCM, 4=ACM
3852 + amtMei.getProtocolVersion(function (result) { if (result != null) { amtMeiTmpState.MeiVersion = result; } });
3853 + 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; } } }); }
3854 + amtMei.getProvisioningMode(function (result) { if (result) { amtMeiTmpState.ProvisioningMode = result.mode; } });
3855 + amtMei.getProvisioningState(function (result) { if (result) { amtMeiTmpState.ProvisioningState = result.state; } }); // 0: "Not Activated (Pre)", 1: "Not Activated (In)", 2: "Activated"
3856 + amtMei.getEHBCState(function (result) { if ((result != null) && (result.EHBC == true)) { amtMeiTmpState.Flags += 1; } });
3857 + 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
3858 + //amtMei.getMACAddresses(function (result) { if (result) { amtMeiTmpState.mac = result; } });
3859 + if ((flags & 8) != 0) {
3860 + amtMei.getLanInterfaceSettings(0, function (result) {
3861 + if (result) {
3862 + amtMeiTmpState.net0 = result;
3863 + var fqdn = null, interfaces = require('os').networkInterfaces(); // Look for the DNS suffix for the Intel AMT Ethernet interface
3864 + 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; } } }
3865 + }
3866 + });
3867 + amtMei.getLanInterfaceSettings(1, function (result) { if (result) { amtMeiTmpState.net1 = result; } });
3868 + }
3869 + amtMei.getUuid(function (result) { if ((result != null) && (result.uuid != null)) { amtMeiTmpState.UUID = result.uuid; } });
3870 + if ((flags & 2) != 0) { amtMei.getLocalSystemAccount(function (x) { if ((x != null) && x.user && x.pass) { amtMeiTmpState.OsAdmin = { user: x.user, pass: x.pass }; } }); }
3871 + amtMei.getDnsSuffix(function (result) { if (result != null) { amtMeiTmpState.DnsSuffix = result; } if ((flags & 4) == 0) { if (func != null) { func(amtMeiTmpState); } } });
3872 + if ((flags & 4) != 0) {
3873 + amtMei.getHashHandles(function (handles) {
3874 + if ((handles != null) && (handles.length > 0)) { amtMeiTmpState.Hashes = []; } else { func(amtMeiTmpState); }
3875 + var exitOnCount = handles.length;
3876 + 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); } } }); }
3877 + });
3878 + }
3879 + } catch (e) { if (func != null) { func(null); } return; }
3880 + }
3881 +
3882 + return obj;
3883 +}
3884 +
3885 +//
3886 +// Module startup
3887 +//
3888 +
3889 +try {
3890 + var xexports = null, mainMeshCore = null;
3891 + try { xexports = module.exports; } catch (e) { }
3892 +
3893 + if (xexports != null) {
3894 + // If we are running within NodeJS, export the core
3895 + module.exports.createMeshCore = createMeshCore;
3896 + } else {
3897 + // If we are not running in NodeJS, launch the core
3898 + mainMeshCore = createMeshCore();
3899 + mainMeshCore.start(null);
3900 + }
3901 +} catch (e) {
3902 + require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: "uncaughtException2: " + ex });
3903 +}
agents/meshcore.js
+36 -44
@@ -400,7 +400,6 @@ function createMeshCore(agent) {
400 var networkMonitor = null;
401 var amtscanner = null;
402 var nextTunnelIndex = 1;
403 - var amtPolicy = null;
403 var apftunnel = null;
404 var tunnelUserCount = { terminal: {}, files: {}, tcp: {}, udp: {}, msg: {} }; // List of userid->count sessions for terminal, files and TCP/UDP routing
405
@@ -538,7 +537,6 @@ function createMeshCore(agent) {
537 mesh.SendCommand(meshCoreObj);
538 });
539 amt.onStateChange = function (state) { if (state == 2) { sendPeriodicServerUpdate(1); } }
541 - if (amtPolicy != null) { amt.setPolicy(amtPolicy); }
540 amt.start();
541 }
542 }
@@ -1075,11 +1073,39 @@ function createMeshCore(agent) {
1073 if (data.url) { mesh.SendCommand({ action: 'openUrl', url: data.url, sessionid: data.sessionid, success: (openUserDesktopUrl(data.url) != null) }); }
1074 break;
1075 }
1078 - case 'amtPolicy': {
1079 - // Store the latest Intel AMT policy
1080 - amtPolicy = data.amtPolicy;
1081 - if (data.amtPolicy != null) { db.Put('amtPolicy', JSON.stringify(data.amtPolicy)); } else { db.Put('amtPolicy', null); }
1082 - if (amt != null) { amt.setPolicy(amtPolicy, true); }
1076 + case 'amtconfig': {
1077 + // Perform Intel AMT activation and/or configuration
1078 + if ((apftunnel != null) || (amt == null)) break;
1079 + getMeiState(15, function (state) {
1080 + if ((apftunnel != null) || (amt == null)) return;
1081 + if ((state == null) || (state.ProvisioningState == null)) return;
1082 + if ((state.UUID == null) || (state.UUID.length != 36)) return; // Bad UUID
1083 + var apfarg = {
1084 + mpsurl: mesh.ServerUrl.replace('agent.ashx', 'apf.ashx'),
1085 + mpsuser: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16), // TODO: User a server provided encrypted cookie for CIRA-LMS login
1086 + mpspass: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16),
1087 + mpskeepalive: 60000,
1088 + clientname: state.OsHostname,
1089 + clientaddress: '127.0.0.1',
1090 + clientuuid: state.UUID,
1091 + conntype: 2, // 0 = CIRA, 1 = Relay, 2 = LMS. The correct value is 2 since we are performing an LMS relay, other values for testing.
1092 + meiState: state // MEI state will be passed to MPS server
1093 + };
1094 + apftunnel = require('apfclient')({ debug: false }, apfarg);
1095 + apftunnel.onJsonControl = function (data) {
1096 + //if (data.action == 'console') { require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: data.msg }); } // Display a console message (DEBUG)
1097 + if (data.action == 'mestate') { getMeiState(15, function (state) { apftunnel.updateMeiState(state); }); } // Update the MEI state
1098 + if (data.action == 'deactivate') { // Request CCM deactivation
1099 + var amtMeiModule, amtMei;
1100 + try { amtMeiModule = require('amt-mei'); amtMei = new amtMeiModule(); } catch (ex) { if (apftunnel) apftunnel.sendMeiDeactivationState(1); return; }
1101 + amtMei.on('error', function (e) { if (apftunnel) apftunnel.sendMeiDeactivationState(1); });
1102 + amtMei.unprovision(1, function (status) { if (apftunnel) apftunnel.sendMeiDeactivationState(status); }); // 0 = Success
1103 + }
1104 + if (data.action == 'close') { try { apftunnel.disconnect(); } catch (e) { } apftunnel = null; } // Close the CIRA-LMS connection
1105 + }
1106 + apftunnel.onChannelClosed = function () { apftunnel = null; }
1107 + try { apftunnel.connect(); } catch (ex) { }
1108 + });
1109 break;
1110 }
1111 case 'getScript': {
@@ -2563,7 +2589,7 @@ function createMeshCore(agent) {
2589 var response = null;
2590 switch (cmd) {
2591 case 'help': { // Displays available commands
2566 - 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';
2592 + 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';
2593 if (process.platform == 'win32') { availcommands += ',safemode,wpfhwacceleration,uac'; }
2594 if (process.platform != 'freebsd') { availcommands += ',vm';}
2595 if (require('MeshAgent').maxKvmTileSize != null) { availcommands += ',kvmmode'; }
@@ -3037,40 +3063,6 @@ function createMeshCore(agent) {
3063 }
3064 break;
3065 }
3040 - case 'amtreset': {
3041 - if (amt != null) { amt.reset(); response = 'Done.'; }
3042 - break;
3043 - }
3044 - case 'amtlmsreset': {
3045 - if (amt != null) { amt.lmsreset(); response = 'Done.'; }
3046 - break;
3047 - }
3048 - case 'amtccm': {
3049 - if (amt == null) { response = 'Intel AMT not supported.'; } else {
3050 - if (args['_'].length != 1) { response = 'Proper usage: amtccm (adminPassword)'; } // Display usage
3051 - else { amt.setPolicy({ type: 0 }); amt.activeToCCM(args['_'][0]); }
3052 - }
3053 - break;
3054 - }
3055 - case 'amtacm': {
3056 - if (amt == null) { response = 'Intel AMT not supported.'; } else {
3057 - amt.setPolicy({ type: 0 });
3058 - amt.getAmtInfo(function (meinfo) { amt.activeToACM(meinfo); });
3059 - }
3060 - break;
3061 - }
3062 - case 'amtdeactivate': {
3063 - if (amt == null) { response = 'Intel AMT not supported.'; } else { amt.setPolicy({ type: 0 }); amt.deactivateCCM(); }
3064 - break;
3065 - }
3066 - case 'amtpolicy': {
3067 - if (amtPolicy == null) {
3068 - response = 'No Intel(R) AMT policy.';
3069 - } else {
3070 - response = JSON.stringify(amtPolicy);
3071 - }
3072 - break;
3073 - }
3066 case 'openurl': {
3067 if (args['_'].length != 1) { response = 'Proper usage: openurl (url)'; } // Display usage
3068 else { if (openUserDesktopUrl(args['_'][0]) == null) { response = 'Failed.'; } else { response = 'Success.'; } }
@@ -3566,7 +3558,7 @@ function createMeshCore(agent) {
3558 if (data.action == 'mestate') { getMeiState(15, function (state) { apftunnel.updateMeiState(state); }); } // Update the MEI state
3559 if (data.action == 'deactivate') { // Request CCM deactivation
3560 var amtMeiModule, amtMei;
3569 - try { amtMeiModule = require('amt-mei'); amtMei = new amtMeiModule(); } catch (ex) { apftunnel.sendMeiDeactivationState(1); break; }
3561 + try { amtMeiModule = require('amt-mei'); amtMei = new amtMeiModule(); } catch (ex) { apftunnel.sendMeiDeactivationState(1); return; }
3562 amtMei.on('error', function (e) { apftunnel.sendMeiDeactivationState(1); });
3563 amtMei.unprovision(1, function (status) { apftunnel.sendMeiDeactivationState(status); }); // 0 = Success
3564 }
@@ -3861,7 +3853,7 @@ function createMeshCore(agent) {
3853 if (result) {
3854 amtMeiTmpState.net0 = result;
3855 var fqdn = null, interfaces = require('os').networkInterfaces(); // Look for the DNS suffix for the Intel AMT Ethernet interface
3864 - 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; } } }
3856 + 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; } } }
3857 }
3858 });
3859 amtMei.getLanInterfaceSettings(1, function (result) { if (result) { amtMeiTmpState.net1 = result; } });
amtmanager.js
+42 -24
@@ -259,14 +259,29 @@ module.exports.CreateAmtManager = function(parent) {
259 if (amtPolicy < 2) { ciraPolicy = 0; }
260 dev.policy = { amtPolicy: amtPolicy, ciraPolicy: ciraPolicy, badPass: badPass, password: password };
261
262 - // If there is no Intel AMT policy for this device, stop here.
263 - if (amtPolicy == 0) { dev.consoleMsg("Done."); removeAmtDevice(dev); return; }
264 -
262 // Setup the monitored device
263 dev.name = node.name;
267 - //if (node.host) { dev.host = node.host.toLowerCase(); }
264 dev.meshid = node.meshid;
265 dev.intelamt = node.intelamt;
266 +
267 + // Check if the status of Intel AMT sent by the agents matched what we have in the database
268 + if ((dev.connType == 2) && (dev.mpsConnection != null) && (dev.mpsConnection.tag != null) && (dev.mpsConnection.tag.meiState != null)) {
269 + dev.aquired = {};
270 + if (typeof dev.mpsConnection.tag.meiState['ProvisioningState'] == 'number') {
271 + dev.intelamt.state = dev.aquired.state = dev.mpsConnection.tag.meiState['ProvisioningState'];
272 + }
273 + if (typeof dev.mpsConnection.tag.meiState['Flags'] == 'number') {
274 + const flags = dev.intelamt.flags = dev.mpsConnection.tag.meiState['Flags'];
275 + if (flags & 2) { dev.aquired.controlMode = 1; } // CCM
276 + if (flags & 4) { dev.aquired.controlMode = 2; } // ACM
277 + }
278 + UpdateDevice(dev);
279 + }
280 +
281 + // If there is no Intel AMT policy for this device, stop here.
282 + if (amtPolicy == 0) { dev.consoleMsg("Done."); removeAmtDevice(dev); return; }
283 +
284 + // Initiate the communication to Intel AMT
285 dev.consoleMsg("Checking Intel AMT state...");
286 attemptInitialContact(dev);
287 });
@@ -435,8 +450,8 @@ module.exports.CreateAmtManager = function(parent) {
450 var verSplit = stack.wsman.comm.amtVersion.split('.');
451 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]); }
452 dev.aquired.realm = stack.wsman.comm.digestRealm;
438 - dev.aquired.user = stack.wsman.comm.user;
439 - dev.aquired.pass = stack.wsman.comm.pass;
453 + dev.aquired.user = dev.intelamt.user = stack.wsman.comm.user;
454 + dev.aquired.pass = dev.intelamt.pass = stack.wsman.comm.pass;
455 dev.aquired.lastContact = Date.now();
456 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.
457 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) {
527
528 // Change the current core information string and event it
529 function UpdateDevice(dev) {
515 - if (isAmtDeviceValid(dev) == false) return; // Device no longer exists, ignore this request.
516 -
530 // Check that the mesh exists
531 const mesh = parent.webserver.meshes[dev.meshid];
519 - if (mesh == null) { removeAmtDevice(dev); return false; }
532 + if (mesh == null) { removeAmtDevice(dev); console.log('y3'); return false; }
533
534 // Get the node and change it if needed
535 parent.db.Get(dev.nodeid, function (err, nodes) {
523 - if ((nodes == null) || (nodes.length != 1)) { return false; }
536 + if ((nodes == null) || (nodes.length != 1)) { console.log('y1'); return false; }
537 const device = nodes[0];
538 var changes = [], change = 0, log = 0;
539 var domain = parent.config.domains[device.domain];
527 - if (domain == null) { return false; }
540 + if (domain == null) { console.log('y2'); return false; }
541
542 // Check if anything changes
543 if (device.intelamt == null) { device.intelamt = {}; }
@@ -535,7 +548,7 @@ module.exports.CreateAmtManager = function(parent) {
548 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'); }
549 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'); }
550 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'); }
538 - if (device.intelamt.state != 2) { change = 1; log = 1; device.intelamt.state = 2; changes.push('AMT state'); }
551 + 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'); }
552
553 // Update Intel AMT flags if needed
554 // dev.aquired.controlMode // 1 = CCM, 2 = ACM
@@ -544,7 +557,10 @@ module.exports.CreateAmtManager = function(parent) {
557 if (typeof device.intelamt.flags == 'number') { flags = device.intelamt.flags; }
558 if (dev.aquired.controlMode == 1) { if ((flags & 4) != 0) { flags -= 4; } if ((flags & 2) == 0) { flags += 2; } } // CCM
559 if (dev.aquired.controlMode == 2) { if ((flags & 4) == 0) { flags += 4; } if ((flags & 2) != 0) { flags -= 2; } } // ACM
547 - if (device.intelamt.flags != flags) { change = 1; log = 1; device.intelamt.flags = flags; changes.push('AMT flags'); }
560 + if (device.intelamt.flags != flags) {
561 + console.log('ChangeFlags', flags);
562 + change = 1; log = 1; device.intelamt.flags = flags; changes.push('AMT flags');
563 + }
564
565 // If there are changes, event the new device
566 if (change == 1) {
@@ -1104,13 +1120,8 @@ module.exports.CreateAmtManager = function(parent) {
1120 // If this device does not have KVM, ignore the response. This can happen for Intel Standard Manageability (Intel(R) SM).
1121 if ((responses['CIM_KVMRedirectionSAP'] == null) || (responses['CIM_KVMRedirectionSAP'].status == 400)) { responses['CIM_KVMRedirectionSAP'] = null; }
1122
1107 - // Check redirection services
1108 - var redir = (responses['AMT_RedirectionService'].response['ListenerEnabled'] == true);
1109 - var sol = ((responses['AMT_RedirectionService'].response['EnabledState'] & 2) != 0);
1110 - var ider = ((responses['AMT_RedirectionService'].response['EnabledState'] & 1) != 0);
1111 -
1123 // Enable SOL & IDER
1113 - if (responses['AMT_RedirectionService'].response['EnabledState'] != 32771) {
1124 + if ((responses['AMT_RedirectionService'].response['EnabledState'] != 32771) || (responses['AMT_RedirectionService'].response['ListenerEnabled'] == false)) {
1125 dev.redirObj = responses['AMT_RedirectionService'].response;
1126 dev.redirObj['ListenerEnabled'] = true;
1127 dev.redirObj['EnabledState'] = 32771;
@@ -1402,12 +1413,11 @@ module.exports.CreateAmtManager = function(parent) {
1413 dev.aquired.host = dev.mpsConnection.tag.meiState.OsHostname + '.' + dev.mpsConnection.tag.meiState.OsDnsSuffix;
1414 }
1415 dev.aquired.realm = dev.amtstack.wsman.comm.digestRealm;
1405 - dev.aquired.user = 'admin';
1406 - dev.aquired.pass = dev.temp.pass;
1416 + dev.intelamt.user = dev.aquired.user = 'admin';
1417 + dev.intelamt.pass = dev.aquired.pass = dev.temp.pass;
1418 + dev.intelamt.tls = dev.aquired.tls = 0;
1419 dev.aquired.lastContact = Date.now();
1408 - dev.aquired.tls = 0;
1409 - dev.intelamt.user = 'admin';
1410 - dev.intelamt.pass = dev.temp.pass;
1420 + dev.aquired.state = 2; // Activated
1421 delete dev.acctry;
1422 UpdateDevice(dev);
1423
@@ -1438,6 +1448,14 @@ module.exports.CreateAmtManager = function(parent) {
1448 dev.consoleMsg("Failed to deactivate Intel AMT CCM.");
1449 removeAmtDevice(dev);
1450 } else {
1451 + // Update the device
1452 + dev.aquired = {};
1453 + dev.aquired.controlMode = 0; // 1 = CCM, 2 = ACM
1454 + dev.aquired.state = 0; // Not activated
1455 + delete dev.acctry;
1456 + delete dev.amtstack;
1457 + UpdateDevice(dev);
1458 +
1459 if (dev.policy.amtPolicy == 1) { // CCM deactivation policy, we are done.
1460 dev.consoleMsg("Deactivation successful.");
1461 dev.consoleMsg("Done.");
meshagent.js
+6 -38
@@ -192,7 +192,6 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
192 obj.sendBinary(common.ShortToStr(10) + common.ShortToStr(0) + argument.hash + argument.core, function () { parent.parent.taskLimiter.completed(taskid); }); // MeshCommand_CoreModule, start core update
193 parent.agentStats.updatingCoreCount++;
194 parent.parent.debug('agent', "Updating core " + argument.name);
195 - agentCoreIsStable();
195 } else {
196 // This agent is probably disconnected, nothing to do.
197 parent.parent.taskLimiter.completed(taskid);
@@ -898,40 +897,11 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
897 }
898 }
899
901 - // Take a basic Intel AMT policy and add all server information to it, making it ready to send to this agent.
902 - /*
903 - function completeIntelAmtPolicy(amtPolicy) {
904 - var r = amtPolicy;
905 - if (amtPolicy == null) return null;
906 - if (amtPolicy.type == 2) {
907 - // CCM - Add server root certificate
908 - 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(''); }
909 - r.rootcert = parent.parent.certificates.rootex;
910 - if ((amtPolicy.cirasetup == 2) && (parent.parent.mpsserver != null) && (parent.parent.certificates.AmtMpsName != null) && (args.lanonly != true) && (args.mpsport != 0)) {
911 - // Add server CIRA settings
912 - r.ciraserver = {
913 - name: parent.parent.certificates.AmtMpsName,
914 - port: (typeof args.mpsaliasport == 'number' ? args.mpsaliasport : args.mpsport),
915 - user: obj.meshid.replace(/\@/g, 'X').replace(/\$/g, 'X').substring(0, 16),
916 - 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?
917 - home: ['sdlwerulis3wpj95dfj'] // Use a random FQDN to not have any home network.
918 - };
919 - if (Array.isArray(args.ciralocalfqdn)) { r.ciraserver.home = args.ciralocalfqdn; }
920 - }
921 - } else if ((amtPolicy.type == 3) && (domain.amtacmactivation.acmmatch)) {
922 - // ACM - In this mode, don't send much to Intel AMT. Just indicate ACM policy and let the agent try activation when possible.
923 - r = { type: 3, match: domain.amtacmactivation.acmmatch };
924 - }
925 - return r;
926 - }
927 - */
928 -
929 - // Send Intel AMT policy
900 + // Indicate to the agent that we want to reconfigure Intel AMT
901 obj.sendUpdatedIntelAmtPolicy = function (policy) {
902 if (obj.agentExeInfo && (obj.agentExeInfo.amt == true)) { // Only send Intel AMT policy to agents what could have AMT.
932 - // TODO
933 - //if (policy == null) { var mesh = parent.meshes[obj.dbMeshKey]; if (mesh == null) return; policy = mesh.amt; }
934 - //if (policy != null) { try { obj.send(JSON.stringify({ action: 'amtPolicy', amtPolicy: completeIntelAmtPolicy(common.Clone(policy)) })); } catch (ex) { } }
903 + if (policy == null) { var mesh = parent.meshes[obj.dbMeshKey]; if (mesh == null) return; policy = mesh.amt; }
904 + if ((policy != null) && (policy.type != 0)) { try { obj.send(JSON.stringify({ action: 'amtconfig' })); } catch (ex) { } }
905 }
906 }
907
@@ -983,12 +953,10 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
953 }
954 });
955
986 - // Send Intel AMT policy
987 - /*
988 - if (obj.agentExeInfo && (obj.agentExeInfo.amt == true) && (mesh.amt != null)) { // Only send Intel AMT policy to agents what could have AMT.
989 - try { obj.send(JSON.stringify({ action: 'amtPolicy', amtPolicy: completeIntelAmtPolicy(common.Clone(mesh.amt)) })); } catch (ex) { }
956 + // Indicate that we want to check the Intel AMT configuration
957 + 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.
958 + try { obj.send(JSON.stringify({ action: 'amtconfig' })); } catch (ex) { }
959 }
991 - */
960
961 // Fetch system information
962 db.GetHash('si' + obj.dbNodeKey, function (err, results) {
translate/translate.json
+739 -746
@@ -224,8 +224,8 @@
224 "zh-chs": " 用户需要先登录到该服务器一次,然后才能将其添加到设备组。",
225 "zh-cht": " 用戶需要先登入到該伺服器一次,然後才能將其新增到裝置群。",
226 "xloc": [
227 - "default.handlebars->29->1435",
228 - "default.handlebars->29->1895"
227 + "default.handlebars->29->1428",
228 + "default.handlebars->29->1888"
229 ]
230 },
231 {
@@ -458,7 +458,7 @@
458 "zh-chs": "*保留空白可为每个设备分配一个随机密码。",
459 "zh-cht": "*保留空白可為每個裝置分配一個隨機密碼。",
460 "xloc": [
461 - "default.handlebars->29->1405"
461 + "default.handlebars->29->1398"
462 ]
463 },
464 {
@@ -496,7 +496,7 @@
496 "zh-cht": ",",
497 "xloc": [
498 "default-mobile.handlebars->9->461",
499 - "default.handlebars->29->1505"
499 + "default.handlebars->29->1498"
500 ]
501 },
502 {
@@ -768,8 +768,8 @@
768 "xloc": [
769 "default-mobile.handlebars->9->108",
770 "default-mobile.handlebars->9->301",
771 - "default.handlebars->29->1546",
772 - "default.handlebars->29->2050",
771 + "default.handlebars->29->1539",
772 + "default.handlebars->29->2043",
773 "default.handlebars->29->894"
774 ]
775 },
@@ -826,7 +826,7 @@
826 "zh-chs": "1个活跃时段",
827 "zh-cht": "1個活躍時段",
828 "xloc": [
829 - "default.handlebars->29->1964"
829 + "default.handlebars->29->1957"
830 ]
831 },
832 {
@@ -848,7 +848,7 @@
848 "xloc": [
849 "default-mobile.handlebars->9->118",
850 "default-mobile.handlebars->9->465",
851 - "default.handlebars->29->1570",
851 + "default.handlebars->29->1563",
852 "download.handlebars->3->1",
853 "download2.handlebars->5->1"
854 ]
@@ -913,7 +913,7 @@
913 "zh-chs": "1组",
914 "zh-cht": "1群",
915 "xloc": [
916 - "default.handlebars->29->1929"
916 + "default.handlebars->29->1922"
917 ]
918 },
919 {
@@ -1017,7 +1017,7 @@
1017 "zh-chs": "有1个用户没有显示,请使用搜索框查找用户...",
1018 "zh-cht": "有1個用戶沒有顯示,請使用搜尋框搜尋用戶...",
1019 "xloc": [
1020 - "default.handlebars->29->1719"
1020 + "default.handlebars->29->1712"
1021 ]
1022 },
1023 {
@@ -1083,7 +1083,7 @@
1083 "default-mobile.handlebars->9->172",
1084 "default-mobile.handlebars->9->175",
1085 "default-mobile.handlebars->9->178",
1086 - "default.handlebars->29->1723",
1086 + "default.handlebars->29->1716",
1087 "default.handlebars->29->250",
1088 "default.handlebars->29->253",
1089 "default.handlebars->29->256",
@@ -1500,7 +1500,7 @@
1500 "zh-chs": "2FA备份代码已清除",
1501 "zh-cht": "2FA備份代碼已清除",
1502 "xloc": [
1503 - "default.handlebars->29->1681"
1503 + "default.handlebars->29->1674"
1504 ]
1505 },
1506 {
@@ -1520,8 +1520,8 @@
1520 "zh-chs": "启用第二因素身份验证",
1521 "zh-cht": "啟用第二因素身份驗證",
1522 "xloc": [
1523 - "default.handlebars->29->1736",
1524 - "default.handlebars->29->1951"
1523 + "default.handlebars->29->1729",
1524 + "default.handlebars->29->1944"
1525 ]
1526 },
1527 {
@@ -2503,7 +2503,7 @@
2503 "zh-chs": "访问服务器档案",
2504 "zh-cht": "存取伺服器檔案",
2505 "xloc": [
2506 - "default.handlebars->29->1901"
2506 + "default.handlebars->29->1894"
2507 ]
2508 },
2509 {
@@ -2656,7 +2656,7 @@
2656 "zh-chs": "帐户已更改:{0}",
2657 "zh-cht": "帳戶已更改:{0}",
2658 "xloc": [
2659 - "default.handlebars->29->1654"
2659 + "default.handlebars->29->1647"
2660 ]
2661 },
2662 {
@@ -2676,7 +2676,7 @@
2676 "zh-chs": "创建帐户,电子邮件为{0}",
2677 "zh-cht": "創建帳戶,電子郵件為{0}",
2678 "xloc": [
2679 - "default.handlebars->29->1653"
2679 + "default.handlebars->29->1646"
2680 ]
2681 },
2682 {
@@ -2696,7 +2696,7 @@
2696 "zh-chs": "创建帐户,用户名是{0}",
2697 "zh-cht": "帳戶已創建,用戶名是{0}",
2698 "xloc": [
2699 - "default.handlebars->29->1652"
2699 + "default.handlebars->29->1645"
2700 ]
2701 },
2702 {
@@ -2716,8 +2716,8 @@
2716 "zh-chs": "帐户已被锁定",
2717 "zh-cht": "帳戶已被鎖定",
2718 "xloc": [
2719 - "default.handlebars->29->1738",
2720 - "default.handlebars->29->1898"
2719 + "default.handlebars->29->1731",
2720 + "default.handlebars->29->1891"
2721 ]
2722 },
2723 {
@@ -2781,7 +2781,7 @@
2781 "zh-chs": "帐号登录",
2782 "zh-cht": "帳號登錄",
2783 "xloc": [
2784 - "default.handlebars->29->1589"
2784 + "default.handlebars->29->1582"
2785 ]
2786 },
2787 {
@@ -2801,7 +2801,7 @@
2801 "zh-chs": "帐户登出",
2802 "zh-cht": "帳戶登出",
2803 "xloc": [
2804 - "default.handlebars->29->1590"
2804 + "default.handlebars->29->1583"
2805 ]
2806 },
2807 {
@@ -2843,7 +2843,7 @@
2843 "zh-chs": "帐户密码已更改:{0}",
2844 "zh-cht": "帳戶密碼已更改:{0}",
2845 "xloc": [
2846 - "default.handlebars->29->1662"
2846 + "default.handlebars->29->1655"
2847 ]
2848 },
2849 {
@@ -2863,7 +2863,7 @@
2863 "zh-chs": "帐户已删除",
2864 "zh-cht": "帳戶已刪除",
2865 "xloc": [
2866 - "default.handlebars->29->1651"
2866 + "default.handlebars->29->1644"
2867 ]
2868 },
2869 {
@@ -3121,8 +3121,8 @@
3121 "zh-chs": "添加设备",
3122 "zh-cht": "新增裝置",
3123 "xloc": [
3124 - "default.handlebars->29->1875",
3125 - "default.handlebars->29->1999"
3124 + "default.handlebars->29->1868",
3125 + "default.handlebars->29->1992"
3126 ]
3127 },
3128 {
@@ -3162,9 +3162,9 @@
3162 "zh-chs": "添加设备组",
3163 "zh-cht": "新增裝置群",
3164 "xloc": [
3165 - "default.handlebars->29->1469",
3166 - "default.handlebars->29->1869",
3167 - "default.handlebars->29->1987",
3165 + "default.handlebars->29->1462",
3166 + "default.handlebars->29->1862",
3167 + "default.handlebars->29->1980",
3168 "default.handlebars->29->237"
3169 ]
3170 },
@@ -3185,7 +3185,7 @@
3185 "zh-chs": "添加设备组权限",
3186 "zh-cht": "新增裝置群權限",
3187 "xloc": [
3188 - "default.handlebars->29->1466"
3188 + "default.handlebars->29->1459"
3189 ]
3190 },
3191 {
@@ -3205,8 +3205,8 @@
3205 "zh-chs": "添加设备权限",
3206 "zh-cht": "新增裝置權限",
3207 "xloc": [
3208 - "default.handlebars->29->1471",
3209 - "default.handlebars->29->1473"
3208 + "default.handlebars->29->1464",
3209 + "default.handlebars->29->1466"
3210 ]
3211 },
3212 {
@@ -3306,7 +3306,7 @@
3306 "zh-chs": "添加成员身份",
3307 "zh-cht": "新增成員身份",
3308 "xloc": [
3309 - "default.handlebars->29->2017"
3309 + "default.handlebars->29->2010"
3310 ]
3311 },
3312 {
@@ -3392,7 +3392,7 @@
3392 "zh-chs": "添加用户设备权限",
3393 "zh-cht": "新增用戶裝置權限",
3394 "xloc": [
3395 - "default.handlebars->29->1476"
3395 + "default.handlebars->29->1469"
3396 ]
3397 },
3398 {
@@ -3413,8 +3413,8 @@
3413 "zh-cht": "新增用戶群",
3414 "xloc": [
3415 "default.handlebars->29->1365",
3416 - "default.handlebars->29->1468",
3417 - "default.handlebars->29->1993",
3416 + "default.handlebars->29->1461",
3417 + "default.handlebars->29->1986",
3418 "default.handlebars->29->673"
3419 ]
3420 },
@@ -3435,7 +3435,7 @@
3435 "zh-chs": "添加用户组设备权限",
3436 "zh-cht": "新增用戶群裝置權限",
3437 "xloc": [
3438 - "default.handlebars->29->1478"
3438 + "default.handlebars->29->1471"
3439 ]
3440 },
3441 {
@@ -3493,7 +3493,7 @@
3493 "zh-cht": "新增用戶",
3494 "xloc": [
3495 "default.handlebars->29->1364",
3496 - "default.handlebars->29->1864"
3496 + "default.handlebars->29->1857"
3497 ]
3498 },
3499 {
@@ -3513,7 +3513,7 @@
3513 "zh-chs": "将用户添加到设备组",
3514 "zh-cht": "將用戶新增到裝置群",
3515 "xloc": [
3516 - "default.handlebars->29->1465"
3516 + "default.handlebars->29->1458"
3517 ]
3518 },
3519 {
@@ -3533,7 +3533,7 @@
3533 "zh-chs": "将用户添加到用户组",
3534 "zh-cht": "將用戶新增到用戶群",
3535 "xloc": [
3536 - "default.handlebars->29->1897"
3536 + "default.handlebars->29->1890"
3537 ]
3538 },
3539 {
@@ -3696,7 +3696,7 @@
3696 "zh-chs": "添加了身份验证应用程序",
3697 "zh-cht": "添加了身份驗證應用程序",
3698 "xloc": [
3699 - "default.handlebars->29->1678"
3699 + "default.handlebars->29->1671"
3700 ]
3701 },
3702 {
@@ -3716,8 +3716,8 @@
3716 "zh-chs": "已将设备{0}添加到设备组{1}",
3717 "zh-cht": "已將設備{0}添加到設備組{1}",
3718 "xloc": [
3719 - "default.handlebars->29->1645",
3720 - "default.handlebars->29->1672"
3719 + "default.handlebars->29->1638",
3720 + "default.handlebars->29->1665"
3721 ]
3722 },
3723 {
@@ -3737,7 +3737,7 @@
3737 "zh-chs": "添加了安全密钥",
3738 "zh-cht": "添加了安全密鑰",
3739 "xloc": [
3740 - "default.handlebars->29->1683"
3740 + "default.handlebars->29->1676"
3741 ]
3742 },
3743 {
@@ -3757,7 +3757,7 @@
3757 "zh-chs": "已将用户组{0}添加到设备组{1}",
3758 "zh-cht": "已將用戶組{0}添加到設備組{1}",
3759 "xloc": [
3760 - "default.handlebars->29->1656"
3760 + "default.handlebars->29->1649"
3761 ]
3762 },
3763 {
@@ -3777,8 +3777,8 @@
3777 "zh-chs": "已将用户{0}添加到用户组{1}",
3778 "zh-cht": "已將用戶{0}添加到用戶組{1}",
3779 "xloc": [
3780 - "default.handlebars->29->1659",
3781 - "default.handlebars->29->1668"
3780 + "default.handlebars->29->1652",
3781 + "default.handlebars->29->1661"
3782 ]
3783 },
3784 {
@@ -3901,7 +3901,7 @@
3901 "zh-chs": "管理领域",
3902 "zh-cht": "管理領域",
3903 "xloc": [
3904 - "default.handlebars->29->1933"
3904 + "default.handlebars->29->1926"
3905 ]
3906 },
3907 {
@@ -3942,7 +3942,7 @@
3942 "zh-chs": "管理领域",
3943 "zh-cht": "管理領域",
3944 "xloc": [
3945 - "default.handlebars->29->1801"
3945 + "default.handlebars->29->1794"
3946 ]
3947 },
3948 {
@@ -3962,7 +3962,7 @@
3962 "zh-chs": "管理员",
3963 "zh-cht": "管理員",
3964 "xloc": [
3965 - "default.handlebars->29->1730"
3965 + "default.handlebars->29->1723"
3966 ]
3967 },
3968 {
@@ -4005,8 +4005,8 @@
4005 "default-mobile.handlebars->9->203",
4006 "default-mobile.handlebars->9->228",
4007 "default-mobile.handlebars->9->244",
4008 - "default.handlebars->29->1532",
4009 - "default.handlebars->29->1540",
4008 + "default.handlebars->29->1525",
4009 + "default.handlebars->29->1533",
4010 "default.handlebars->29->213",
4011 "default.handlebars->29->444",
4012 "default.handlebars->container->column_l->p15->consoleTable->1->6->1->1->1->0->p15outputselecttd->p15outputselect->1"
@@ -4029,8 +4029,8 @@
4029 "zh-chs": "代理+英特尔AMT",
4030 "zh-cht": "代理+Intel&reg; AMT",
4031 "xloc": [
4032 - "default.handlebars->29->1534",
4033 - "default.handlebars->29->1542"
4032 + "default.handlebars->29->1527",
4033 + "default.handlebars->29->1535"
4034 ]
4035 },
4036 {
@@ -4071,7 +4071,7 @@
4071 "zh-cht": "代理控制台",
4072 "xloc": [
4073 "default-mobile.handlebars->9->443",
4074 - "default.handlebars->29->1486"
4074 + "default.handlebars->29->1479"
4075 ]
4076 },
4077 {
@@ -4091,7 +4091,7 @@
4091 "zh-chs": "代理错误计数器",
4092 "zh-cht": "代理錯誤計數器",
4093 "xloc": [
4094 - "default.handlebars->29->2060"
4094 + "default.handlebars->29->2053"
4095 ]
4096 },
4097 {
@@ -4194,7 +4194,7 @@
4194 "zh-chs": "代理时段",
4195 "zh-cht": "代理時段",
4196 "xloc": [
4197 - "default.handlebars->29->2076"
4197 + "default.handlebars->29->2069"
4198 ]
4199 },
4200 {
@@ -4235,7 +4235,7 @@
4235 "zh-chs": "代理类型",
4236 "zh-cht": "代理類型",
4237 "xloc": [
4238 - "default.handlebars->29->1538",
4238 + "default.handlebars->29->1531",
4239 "default.handlebars->container->column_l->p21->3->1->meshOsChartDiv->1"
4240 ]
4241 },
@@ -4256,7 +4256,7 @@
4256 "zh-chs": "代理关闭了与服务器压缩的{0}%代理会话。已发送:{1},已压缩:{2}",
4257 "zh-cht": "代理關閉了與{0}%代理到服務器壓縮的會話。已發送:{1},已壓縮:{2}",
4258 "xloc": [
4259 - "default.handlebars->29->1642"
4259 + "default.handlebars->29->1635"
4260 ]
4261 },
4262 {
@@ -4365,7 +4365,7 @@
4365 "zh-chs": "代理",
4366 "zh-cht": "代理",
4367 "xloc": [
4368 - "default.handlebars->29->2092"
4368 + "default.handlebars->29->2085"
4369 ]
4370 },
4371 {
@@ -4428,7 +4428,7 @@
4428 "zh-chs": "全部可用",
4429 "zh-cht": "全部可用",
4430 "xloc": [
4431 - "default.handlebars->29->1693"
4431 + "default.handlebars->29->1686"
4432 ]
4433 },
4434 {
@@ -4465,7 +4465,7 @@
4465 "zh-chs": "所有事件",
4466 "zh-cht": "所有事件",
4467 "xloc": [
4468 - "default.handlebars->29->1691"
4468 + "default.handlebars->29->1684"
4469 ]
4470 },
4471 {
@@ -4507,8 +4507,8 @@
4507 "zh-chs": "允许用户管理此设备组和该组中的设备。",
4508 "zh-cht": "允許用戶管理此裝置群和該群中的裝置。",
4509 "xloc": [
4510 - "default.handlebars->29->1433",
4511 - "default.handlebars->29->1894"
4510 + "default.handlebars->29->1426",
4511 + "default.handlebars->29->1887"
4512 ]
4513 },
4514 {
@@ -4528,7 +4528,7 @@
4528 "zh-chs": "允许用户管理此设备。",
4529 "zh-cht": "允許用戶管理此裝置。",
4530 "xloc": [
4531 - "default.handlebars->29->1434"
4531 + "default.handlebars->29->1427"
4532 ]
4533 },
4534 {
@@ -4634,8 +4634,8 @@
4634 "zh-cht": "一直通知",
4635 "xloc": [
4636 "default.handlebars->29->1345",
4637 - "default.handlebars->29->1855",
4638 - "default.handlebars->29->1942",
4637 + "default.handlebars->29->1848",
4638 + "default.handlebars->29->1935",
4639 "default.handlebars->29->611"
4640 ]
4641 },
@@ -4657,8 +4657,8 @@
4657 "zh-cht": "一直提示",
4658 "xloc": [
4659 "default.handlebars->29->1346",
4660 - "default.handlebars->29->1856",
4661 - "default.handlebars->29->1943",
4660 + "default.handlebars->29->1849",
4661 + "default.handlebars->29->1936",
4662 "default.handlebars->29->612"
4663 ]
4664 },
@@ -5284,7 +5284,7 @@
5284 "zh-cht": "你確定要刪除群{0}嗎?刪除裝置群還將刪除該群中有關裝置的所有訊息。",
5285 "xloc": [
5286 "default-mobile.handlebars->9->412",
5287 - "default.handlebars->29->1409"
5287 + "default.handlebars->29->1402"
5288 ]
5289 },
5290 {
@@ -5364,7 +5364,7 @@
5364 "zh-chs": "您确定要{0}插件吗:{1}",
5365 "zh-cht": "你確定要{0}外掛嗎:{1}",
5366 "xloc": [
5367 - "default.handlebars->29->2132"
5367 + "default.handlebars->29->2125"
5368 ]
5369 },
5370 {
@@ -5484,7 +5484,7 @@
5484 "zh-chs": "尝试激活英特尔(R)AMT ACM模式",
5485 "zh-cht": "嘗試激活英特爾(R)AMT ACM模式",
5486 "xloc": [
5487 - "default.handlebars->29->1611"
5487 + "default.handlebars->29->1604"
5488 ]
5489 },
5490 {
@@ -5504,7 +5504,7 @@
5504 "zh-chs": "认证软件",
5505 "zh-cht": "認證軟體",
5506 "xloc": [
5507 - "default.handlebars->29->1946"
5507 + "default.handlebars->29->1939"
5508 ]
5509 },
5510 {
@@ -5675,7 +5675,7 @@
5675 "zh-chs": "可用內存",
5676 "zh-cht": "可用內存",
5677 "xloc": [
5678 - "default.handlebars->29->2085"
5678 + "default.handlebars->29->2078"
5679 ]
5680 },
5681 {
@@ -5831,8 +5831,8 @@
5831 "zh-chs": "背景与互动",
5832 "zh-cht": "背景與互動",
5833 "xloc": [
5834 + "default.handlebars->29->1508",
5835 "default.handlebars->29->1515",
5835 - "default.handlebars->29->1522",
5836 "default.handlebars->29->357",
5837 "default.handlebars->29->371"
5838 ]
@@ -5854,8 +5854,8 @@
5854 "zh-chs": "仅背景",
5855 "zh-cht": "僅背景",
5856 "xloc": [
5857 + "default.handlebars->29->1509",
5858 "default.handlebars->29->1516",
5858 - "default.handlebars->29->1523",
5859 "default.handlebars->29->358",
5860 "default.handlebars->29->372",
5861 "default.handlebars->29->386"
@@ -5898,7 +5898,7 @@
5898 "zh-chs": "备用码",
5899 "zh-cht": "備用碼",
5900 "xloc": [
5901 - "default.handlebars->29->1948"
5901 + "default.handlebars->29->1941"
5902 ]
5903 },
5904 {
@@ -5918,7 +5918,7 @@
5918 "zh-chs": "错误的签名",
5919 "zh-cht": "錯誤的簽名",
5920 "xloc": [
5921 - "default.handlebars->29->2067"
5921 + "default.handlebars->29->2060"
5922 ]
5923 },
5924 {
@@ -5938,7 +5938,7 @@
5938 "zh-chs": "错误的网络证书",
5939 "zh-cht": "錯誤的網絡憑證",
5940 "xloc": [
5941 - "default.handlebars->29->2066"
5941 + "default.handlebars->29->2059"
5942 ]
5943 },
5944 {
@@ -6101,7 +6101,7 @@
6101 "zh-chs": "广播",
6102 "zh-cht": "廣播",
6103 "xloc": [
6104 - "default.handlebars->29->1862",
6104 + "default.handlebars->29->1855",
6105 "default.handlebars->container->column_l->p4->3->1->0->3->1"
6106 ]
6107 },
@@ -6122,7 +6122,7 @@
6122 "zh-chs": "广播消息",
6123 "zh-cht": "廣播消息",
6124 "xloc": [
6125 - "default.handlebars->29->1783"
6125 + "default.handlebars->29->1776"
6126 ]
6127 },
6128 {
@@ -6142,7 +6142,7 @@
6142 "zh-chs": "向所有连接的用户广播消息。",
6143 "zh-cht": "向所有連接的用戶廣播消息。",
6144 "xloc": [
6145 - "default.handlebars->29->1778"
6145 + "default.handlebars->29->1771"
6146 ]
6147 },
6148 {
@@ -6224,8 +6224,7 @@
6224 "zh-cht": "CIRA",
6225 "xloc": [
6226 "default-mobile.handlebars->9->204",
6227 - "default.handlebars->29->1397",
6228 - "default.handlebars->29->1402",
6227 + "default.handlebars->29->1394",
6228 "default.handlebars->29->215",
6229 "default.handlebars->29->446"
6230 ]
@@ -6247,7 +6246,7 @@
6246 "zh-chs": "CIRA服务器",
6247 "zh-cht": "CIRA伺服器",
6248 "xloc": [
6250 - "default.handlebars->29->2120"
6249 + "default.handlebars->29->2113"
6250 ]
6251 },
6252 {
@@ -6267,7 +6266,7 @@
6266 "zh-chs": "CIRA服务器命令",
6267 "zh-cht": "CIRA伺服器指令",
6268 "xloc": [
6270 - "default.handlebars->29->2121"
6269 + "default.handlebars->29->2114"
6270 ]
6271 },
6272 {
@@ -6308,7 +6307,7 @@
6307 "zh-chs": "CPU负载",
6308 "zh-cht": "CPU負載",
6309 "xloc": [
6311 - "default.handlebars->29->2081"
6310 + "default.handlebars->29->2074"
6311 ]
6312 },
6313 {
@@ -6328,7 +6327,7 @@
6327 "zh-chs": "最近15分钟的CPU负载",
6328 "zh-cht": "最近15分鐘的CPU負載",
6329 "xloc": [
6331 - "default.handlebars->29->2084"
6330 + "default.handlebars->29->2077"
6331 ]
6332 },
6333 {
@@ -6348,7 +6347,7 @@
6347 "zh-chs": "最近5分钟的CPU负载",
6348 "zh-cht": "最近5分鐘的CPU負載",
6349 "xloc": [
6351 - "default.handlebars->29->2083"
6350 + "default.handlebars->29->2076"
6351 ]
6352 },
6353 {
@@ -6368,7 +6367,7 @@
6367 "zh-chs": "最近一分钟的CPU负载",
6368 "zh-cht": "最近一分鐘的CPU負載",
6369 "xloc": [
6371 - "default.handlebars->29->2082"
6370 + "default.handlebars->29->2075"
6371 ]
6372 },
6373 {
@@ -6410,7 +6409,7 @@
6409 "zh-chs": "CSV",
6410 "zh-cht": "CSV",
6411 "xloc": [
6413 - "default.handlebars->29->1701"
6412 + "default.handlebars->29->1694"
6413 ]
6414 },
6415 {
@@ -6430,8 +6429,8 @@
6429 "zh-chs": "CSV格式",
6430 "zh-cht": "CSV格式",
6431 "xloc": [
6433 - "default.handlebars->29->1705",
6434 - "default.handlebars->29->1770",
6432 + "default.handlebars->29->1698",
6433 + "default.handlebars->29->1763",
6434 "default.handlebars->29->496"
6435 ]
6436 },
@@ -6452,7 +6451,7 @@
6451 "zh-chs": "呼叫错误",
6452 "zh-cht": "呼叫錯誤",
6453 "xloc": [
6455 - "default.handlebars->29->2133"
6454 + "default.handlebars->29->2126"
6455 ]
6456 },
6457 {
@@ -6629,7 +6628,7 @@
6628 "zh-chs": "更改{0}的电邮",
6629 "zh-cht": "更改{0}的電郵",
6630 "xloc": [
6632 - "default.handlebars->29->1976"
6631 + "default.handlebars->29->1969"
6632 ]
6633 },
6634 {
@@ -6673,7 +6672,7 @@
6672 "xloc": [
6673 "default-mobile.handlebars->9->90",
6674 "default.handlebars->29->1277",
6676 - "default.handlebars->29->1963"
6675 + "default.handlebars->29->1956"
6676 ]
6677 },
6678 {
@@ -6693,7 +6692,7 @@
6692 "zh-chs": "更改{0}的密码",
6693 "zh-cht": "更改{0}的密碼",
6694 "xloc": [
6696 - "default.handlebars->29->1983"
6695 + "default.handlebars->29->1976"
6696 ]
6697 },
6698 {
@@ -6713,7 +6712,7 @@
6712 "zh-chs": "更改{0}的真实名称",
6713 "zh-cht": "更改{0}的真實名稱",
6714 "xloc": [
6716 - "default.handlebars->29->1971"
6715 + "default.handlebars->29->1964"
6716 ]
6717 },
6718 {
@@ -6795,7 +6794,7 @@
6794 "zh-chs": "更改该用户的密码",
6795 "zh-cht": "更改該用戶的密碼",
6796 "xloc": [
6798 - "default.handlebars->29->1962"
6797 + "default.handlebars->29->1955"
6798 ]
6799 },
6800 {
@@ -6875,7 +6874,7 @@
6874 "zh-chs": "更改帐户凭据",
6875 "zh-cht": "帳戶憑證已更改",
6876 "xloc": [
6878 - "default.handlebars->29->1663"
6877 + "default.handlebars->29->1656"
6878 ]
6879 },
6880 {
@@ -6895,7 +6894,7 @@
6894 "zh-chs": "{1}组中的设备{0}已更改:{2}",
6895 "zh-cht": "{1}組中的設備{0}已更改:{2}",
6896 "xloc": [
6898 - "default.handlebars->29->1647"
6897 + "default.handlebars->29->1640"
6898 ]
6899 },
6900 {
@@ -6915,7 +6914,7 @@
6914 "zh-chs": "语言从{1}更改为{2}",
6915 "zh-cht": "語言從{1}更改為{2}",
6916 "xloc": [
6918 - "default.handlebars->29->1591"
6917 + "default.handlebars->29->1584"
6918 ]
6919 },
6920 {
@@ -6935,8 +6934,8 @@
6934 "zh-chs": "已更改{0}的用户设备权限",
6935 "zh-cht": "已更改{0}的用戶設備權限",
6936 "xloc": [
6938 - "default.handlebars->29->1649",
6939 - "default.handlebars->29->1670"
6937 + "default.handlebars->29->1642",
6938 + "default.handlebars->29->1663"
6939 ]
6940 },
6941 {
@@ -6976,7 +6975,7 @@
6975 "zh-chs": "聊天",
6976 "zh-cht": "聊天",
6977 "xloc": [
6979 - "default.handlebars->29->1722",
6978 + "default.handlebars->29->1715",
6979 "default.handlebars->29->693",
6980 "default.handlebars->29->714"
6981 ]
@@ -7000,8 +6999,8 @@
6999 "xloc": [
7000 "default-mobile.handlebars->9->433",
7001 "default-mobile.handlebars->9->453",
7003 - "default.handlebars->29->1461",
7004 - "default.handlebars->29->1497"
7002 + "default.handlebars->29->1454",
7003 + "default.handlebars->29->1490"
7004 ]
7005 },
7006 {
@@ -7142,7 +7141,7 @@
7141 "zh-cht": "檢查...",
7142 "xloc": [
7143 "default.handlebars->29->1049",
7145 - "default.handlebars->29->2127"
7144 + "default.handlebars->29->2120"
7145 ]
7146 },
7147 {
@@ -7369,7 +7368,7 @@
7368 "default-mobile.handlebars->9->324",
7369 "default-mobile.handlebars->9->326",
7370 "default-mobile.handlebars->9->59",
7372 - "default.handlebars->29->1585",
7371 + "default.handlebars->29->1578",
7372 "default.handlebars->29->914",
7373 "default.handlebars->29->916",
7374 "default.handlebars->29->918",
@@ -7416,7 +7415,7 @@
7415 "zh-chs": "全部清除",
7416 "zh-cht": "全部清除",
7417 "xloc": [
7419 - "default.handlebars->29->2054"
7418 + "default.handlebars->29->2047"
7419 ]
7420 },
7421 {
@@ -7484,7 +7483,7 @@
7483 "zh-chs": "清除此通知",
7484 "zh-cht": "清除此通知",
7485 "xloc": [
7487 - "default.handlebars->29->2053"
7486 + "default.handlebars->29->2046"
7487 ]
7488 },
7489 {
@@ -7545,7 +7544,7 @@
7544 "zh-cht": "單擊此處編輯裝置群名稱",
7545 "xloc": [
7546 "default.handlebars->29->1321",
7548 - "default.handlebars->29->1536"
7547 + "default.handlebars->29->1529"
7548 ]
7549 },
7550 {
@@ -7585,7 +7584,7 @@
7584 "zh-chs": "单击此处编辑用户组名称",
7585 "zh-cht": "單擊此處編輯用戶群名稱",
7586 "xloc": [
7588 - "default.handlebars->29->1839"
7587 + "default.handlebars->29->1832"
7588 ]
7589 },
7590 {
@@ -7727,8 +7726,7 @@
7726 "zh-chs": "客户端启动的远程访问",
7727 "zh-cht": "客戶端啟動的遠程訪問",
7728 "xloc": [
7730 - "default.handlebars->29->1396",
7731 - "default.handlebars->29->1401"
7729 + "default.handlebars->29->1393"
7730 ]
7731 },
7732 {
@@ -7812,7 +7810,7 @@
7810 "zh-chs": "封闭式桌面多路复用会话,{0}秒",
7811 "zh-cht": "封閉式桌面多路復用會話,{0}秒",
7812 "xloc": [
7815 - "default.handlebars->29->1596"
7813 + "default.handlebars->29->1589"
7814 ]
7815 },
7816 {
@@ -7895,7 +7893,7 @@
7893 "nl": "Opdrachten",
7894 "xloc": [
7895 "default-mobile.handlebars->9->455",
7898 - "default.handlebars->29->1499",
7896 + "default.handlebars->29->1492",
7897 "default.handlebars->29->695",
7898 "default.handlebars->29->716"
7899 ]
@@ -7917,8 +7915,8 @@
7915 "zh-chs": "通用设备组",
7916 "zh-cht": "通用裝置群",
7917 "xloc": [
7920 - "default.handlebars->29->1870",
7921 - "default.handlebars->29->1988"
7918 + "default.handlebars->29->1863",
7919 + "default.handlebars->29->1981"
7920 ]
7921 },
7922 {
@@ -7938,8 +7936,8 @@
7936 "zh-chs": "通用设备",
7937 "zh-cht": "通用裝置",
7938 "xloc": [
7941 - "default.handlebars->29->1876",
7942 - "default.handlebars->29->2000"
7939 + "default.handlebars->29->1869",
7940 + "default.handlebars->29->1993"
7941 ]
7942 },
7943 {
@@ -7980,7 +7978,7 @@
7978 "zh-cht": "將{1}入口{2}中的{0}限製到此位置?",
7979 "xloc": [
7980 "default-mobile.handlebars->9->127",
7983 - "default.handlebars->29->1580"
7981 + "default.handlebars->29->1573"
7982 ]
7983 },
7984 {
@@ -8002,11 +8000,11 @@
8000 "xloc": [
8001 "default-mobile.handlebars->9->282",
8002 "default-mobile.handlebars->9->413",
8005 - "default.handlebars->29->1410",
8006 - "default.handlebars->29->1750",
8007 - "default.handlebars->29->1829",
8008 - "default.handlebars->29->1890",
8009 - "default.handlebars->29->1986",
8003 + "default.handlebars->29->1403",
8004 + "default.handlebars->29->1743",
8005 + "default.handlebars->29->1822",
8006 + "default.handlebars->29->1883",
8007 + "default.handlebars->29->1979",
8008 "default.handlebars->29->472",
8009 "default.handlebars->29->784",
8010 "default.handlebars->29->793"
@@ -8090,7 +8088,7 @@
8088 "zh-chs": "确认删除选定的帐户?",
8089 "zh-cht": "確認刪除所選帳戶?",
8090 "xloc": [
8093 - "default.handlebars->29->1749"
8091 + "default.handlebars->29->1742"
8092 ]
8093 },
8094 {
@@ -8130,7 +8128,7 @@
8128 "zh-chs": "确认删除选定的用户组?",
8129 "zh-cht": "確認刪除所選用戶群?",
8130 "xloc": [
8133 - "default.handlebars->29->1828"
8131 + "default.handlebars->29->1821"
8132 ]
8133 },
8134 {
@@ -8150,7 +8148,7 @@
8148 "zh-chs": "确认删除用户{0}?",
8149 "zh-cht": "確認刪除用戶{0}?",
8150 "xloc": [
8153 - "default.handlebars->29->1985"
8151 + "default.handlebars->29->1978"
8152 ]
8153 },
8154 {
@@ -8170,7 +8168,7 @@
8168 "zh-chs": "确认删除用户“ {0} ”的成员身份?",
8169 "zh-cht": "確認刪除用戶“ {0} ”的成員身份?",
8170 "xloc": [
8173 - "default.handlebars->29->1893"
8171 + "default.handlebars->29->1886"
8172 ]
8173 },
8174 {
@@ -8190,7 +8188,7 @@
8188 "zh-chs": "确认删除用户组“ {0} ”的成员身份?",
8189 "zh-cht": "確認刪除用戶群“ {0} ”的成員身份?",
8190 "xloc": [
8193 - "default.handlebars->29->2015"
8191 + "default.handlebars->29->2008"
8192 ]
8193 },
8194 {
@@ -8271,7 +8269,7 @@
8269 "zh-chs": "确认覆盖?",
8270 "zh-cht": "確認覆蓋?",
8271 "xloc": [
8274 - "default.handlebars->29->1579"
8272 + "default.handlebars->29->1572"
8273 ]
8274 },
8275 {
@@ -8291,8 +8289,8 @@
8289 "zh-chs": "确认删除设备“ {0} ”的访问权限?",
8290 "zh-cht": "確認刪除裝置“ {0} ”的訪問權限?",
8291 "xloc": [
8294 - "default.handlebars->29->1883",
8295 - "default.handlebars->29->2006"
8292 + "default.handlebars->29->1876",
8293 + "default.handlebars->29->1999"
8294 ]
8295 },
8296 {
@@ -8312,8 +8310,8 @@
8310 "zh-chs": "确认删除设备组“ {0} ”的访问权限?",
8311 "zh-cht": "確認刪除裝置群“ {0} ”的訪問權限?",
8312 "xloc": [
8315 - "default.handlebars->29->1885",
8316 - "default.handlebars->29->2019"
8313 + "default.handlebars->29->1878",
8314 + "default.handlebars->29->2012"
8315 ]
8316 },
8317 {
@@ -8333,7 +8331,7 @@
8331 "zh-chs": "确认删除用户“ {0} ”的访问权限?",
8332 "zh-cht": "確認刪除用戶“ {0} ”的訪問權限?",
8333 "xloc": [
8336 - "default.handlebars->29->2008"
8334 + "default.handlebars->29->2001"
8335 ]
8336 },
8337 {
@@ -8353,7 +8351,7 @@
8351 "zh-chs": "确认删除用户组“ {0} ”的访问权限?",
8352 "zh-cht": "確認刪除用戶群“ {0} ”的訪問權限?",
8353 "xloc": [
8356 - "default.handlebars->29->2011"
8354 + "default.handlebars->29->2004"
8355 ]
8356 },
8357 {
@@ -8373,8 +8371,8 @@
8371 "zh-chs": "确认删除访问权限?",
8372 "zh-cht": "確認刪除訪問權限?",
8373 "xloc": [
8376 - "default.handlebars->29->2009",
8377 - "default.handlebars->29->2012"
8374 + "default.handlebars->29->2002",
8375 + "default.handlebars->29->2005"
8376 ]
8377 },
8378 {
@@ -8466,7 +8464,7 @@
8464 "zh-chs": "确认删除用户“ {0} ”的权限?",
8465 "zh-cht": "確認刪除用戶“ {0} ”的權限?",
8466 "xloc": [
8469 - "default.handlebars->29->1508"
8467 + "default.handlebars->29->1501"
8468 ]
8469 },
8470 {
@@ -8486,7 +8484,7 @@
8484 "zh-chs": "确认删除用户组“ {0} ”的权限?",
8485 "zh-cht": "確認刪除用戶群“ {0} ”的權限?",
8486 "xloc": [
8489 - "default.handlebars->29->1510"
8487 + "default.handlebars->29->1503"
8488 ]
8489 },
8490 {
@@ -8594,8 +8592,7 @@
8592 "zh-chs": "连接到服务器",
8593 "zh-cht": "連接到伺服器",
8594 "xloc": [
8597 - "default.handlebars->29->1400",
8598 - "default.handlebars->29->1404"
8595 + "default.handlebars->29->1397"
8596 ]
8597 },
8598 {
@@ -8716,7 +8713,7 @@
8713 "zh-chs": "已连接的英特尔&reg;AMT",
8714 "zh-cht": "已連接的Intel&reg; AMT",
8715 "xloc": [
8719 - "default.handlebars->29->2072"
8716 + "default.handlebars->29->2065"
8717 ]
8718 },
8719 {
@@ -8736,7 +8733,7 @@
8733 "zh-chs": "已连接的用户",
8734 "zh-cht": "已连接的用户",
8735 "xloc": [
8739 - "default.handlebars->29->2077"
8736 + "default.handlebars->29->2070"
8737 ]
8738 },
8739 {
@@ -8826,7 +8823,7 @@
8823 "zh-chs": "连接数量",
8824 "zh-cht": "連接數量",
8825 "xloc": [
8829 - "default.handlebars->29->2091"
8826 + "default.handlebars->29->2084"
8827 ]
8828 },
8829 {
@@ -8846,7 +8843,7 @@
8843 "zh-chs": "连接转发器",
8844 "zh-cht": "連接轉發器",
8845 "xloc": [
8849 - "default.handlebars->29->2119"
8846 + "default.handlebars->29->2112"
8847 ]
8848 },
8849 {
@@ -8907,7 +8904,7 @@
8904 "zh-cht": "連接性",
8905 "xloc": [
8906 "default-mobile.handlebars->9->249",
8910 - "default.handlebars->29->1543",
8907 + "default.handlebars->29->1536",
8908 "default.handlebars->29->234",
8909 "default.handlebars->29->625",
8910 "default.handlebars->container->column_l->p21->3->1->meshConnChartDiv->1"
@@ -8996,7 +8993,7 @@
8993 "zh-chs": "Cookie编码器",
8994 "zh-cht": "Cookie編碼器",
8995 "xloc": [
8999 - "default.handlebars->29->2105"
8996 + "default.handlebars->29->2098"
8997 ]
8998 },
8999 {
@@ -9153,8 +9150,8 @@
9150 "zh-chs": "复制连结到剪贴板",
9151 "zh-cht": "複製連結到剪貼板",
9152 "xloc": [
9156 - "default.handlebars->29->1548",
9157 - "default.handlebars->29->1567",
9153 + "default.handlebars->29->1541",
9154 + "default.handlebars->29->1560",
9155 "default.handlebars->29->195",
9156 "default.handlebars->29->374"
9157 ]
@@ -9257,7 +9254,7 @@
9254 "zh-chs": "复制:“{0}”到“{1}”",
9255 "zh-cht": "複製:“{0}”到“{1}”",
9256 "xloc": [
9260 - "default.handlebars->29->1639"
9257 + "default.handlebars->29->1632"
9258 ]
9259 },
9260 {
@@ -9403,7 +9400,7 @@
9400 "zh-chs": "核心服务器",
9401 "zh-cht": "核心伺服器",
9402 "xloc": [
9406 - "default.handlebars->29->2104"
9403 + "default.handlebars->29->2097"
9404 ]
9405 },
9406 {
@@ -9443,7 +9440,7 @@
9440 "zh-chs": "创建帐号",
9441 "zh-cht": "創建帳號",
9442 "xloc": [
9446 - "default.handlebars->29->1797",
9443 + "default.handlebars->29->1790",
9444 "login-mobile.handlebars->container->page_content->column_l->1->1->0->1->createpanel->1->1->9->1->12->1->1",
9445 "login.handlebars->container->column_l->centralTable->1->0->logincell->createpanel->1->9->1->12->1->1",
9446 "login2.handlebars->centralTable->1->0->logincell->createpanel->1->9->1->12->1->1"
@@ -9486,7 +9483,7 @@
9483 "zh-chs": "创建用户组",
9484 "zh-cht": "創建用戶群",
9485 "xloc": [
9489 - "default.handlebars->29->1836"
9486 + "default.handlebars->29->1829"
9487 ]
9488 },
9489 {
@@ -9566,7 +9563,7 @@
9563 "zh-chs": "创建文件夹:“{0}”",
9564 "zh-cht": "創建文件夾:“{0}”",
9565 "xloc": [
9569 - "default.handlebars->29->1632"
9566 + "default.handlebars->29->1625"
9567 ]
9568 },
9569 {
@@ -9586,7 +9583,7 @@
9583 "zh-chs": "通过导入以下格式的JSON档案一次创建多个帐户:",
9584 "zh-cht": "通過導入以下格式的JSON檔案一次創建多個帳戶:",
9585 "xloc": [
9589 - "default.handlebars->29->1761"
9586 + "default.handlebars->29->1754"
9587 ]
9588 },
9589 {
@@ -9628,7 +9625,7 @@
9625 "zh-chs": "创建的设备组:{0}",
9626 "zh-cht": "創建的設備組:{0}",
9627 "xloc": [
9631 - "default.handlebars->29->1643"
9628 + "default.handlebars->29->1636"
9629 ]
9630 },
9631 {
@@ -9668,7 +9665,7 @@
9665 "zh-chs": "创建",
9666 "zh-cht": "創建",
9667 "xloc": [
9671 - "default.handlebars->29->1922"
9668 + "default.handlebars->29->1915"
9669 ]
9670 },
9671 {
@@ -10080,7 +10077,7 @@
10077 "zh-chs": "停用客户端控制模式(CCM)",
10078 "zh-cht": "停用客戶端控制模式(CCM)",
10079 "xloc": [
10083 - "default.handlebars->29->1388"
10080 + "default.handlebars->29->1385"
10081 ]
10082 },
10083 {
@@ -10121,10 +10118,10 @@
10118 "zh-chs": "默认",
10119 "zh-cht": "默認",
10120 "xloc": [
10124 - "default.handlebars->29->1784",
10125 - "default.handlebars->29->1832",
10126 - "default.handlebars->29->1843",
10127 - "default.handlebars->29->1911"
10121 + "default.handlebars->29->1777",
10122 + "default.handlebars->29->1825",
10123 + "default.handlebars->29->1836",
10124 + "default.handlebars->29->1904"
10125 ]
10126 },
10127 {
@@ -10148,7 +10145,7 @@
10145 "default-mobile.handlebars->9->307",
10146 "default-mobile.handlebars->container->page_content->column_l->p10->p10files->p13toolbar->1->2->1->1",
10147 "default-mobile.handlebars->container->page_content->column_l->p5->p5myfiles->p5toolbar->1->0->1->1",
10151 - "default.handlebars->29->1574",
10148 + "default.handlebars->29->1567",
10149 "default.handlebars->29->529",
10150 "default.handlebars->29->900",
10151 "default.handlebars->container->column_l->p13->p13toolbar->1->2->1->3",
@@ -10198,7 +10195,7 @@
10195 "zh-chs": "删除帐户",
10196 "zh-cht": "刪除帳戶",
10197 "xloc": [
10201 - "default.handlebars->29->1751"
10198 + "default.handlebars->29->1744"
10199 ]
10200 },
10201 {
@@ -10242,7 +10239,7 @@
10239 "default-mobile.handlebars->9->411",
10240 "default-mobile.handlebars->9->414",
10241 "default.handlebars->29->1381",
10245 - "default.handlebars->29->1411"
10242 + "default.handlebars->29->1404"
10243 ]
10244 },
10245 {
@@ -10303,7 +10300,7 @@
10300 "zh-chs": "删除用户",
10301 "zh-cht": "刪除用戶",
10302 "xloc": [
10306 - "default.handlebars->29->1961"
10303 + "default.handlebars->29->1954"
10304 ]
10305 },
10306 {
@@ -10323,8 +10320,8 @@
10320 "zh-chs": "删除用户群组",
10321 "zh-cht": "刪除用戶群組",
10322 "xloc": [
10326 - "default.handlebars->29->1881",
10327 - "default.handlebars->29->1891"
10323 + "default.handlebars->29->1874",
10324 + "default.handlebars->29->1884"
10325 ]
10326 },
10327 {
@@ -10344,7 +10341,7 @@
10341 "zh-chs": "删除用户群组",
10342 "zh-cht": "刪除用戶群組",
10343 "xloc": [
10347 - "default.handlebars->29->1830"
10344 + "default.handlebars->29->1823"
10345 ]
10346 },
10347 {
@@ -10364,7 +10361,7 @@
10361 "zh-chs": "删除用户{0}",
10362 "zh-cht": "刪除用戶{0}",
10363 "xloc": [
10367 - "default.handlebars->29->1984"
10364 + "default.handlebars->29->1977"
10365 ]
10366 },
10367 {
@@ -10385,7 +10382,7 @@
10382 "zh-cht": "刪除帳戶",
10383 "xloc": [
10384 "default-mobile.handlebars->container->page_content->column_l->p3->p3info->1->p3AccountActions->p2AccountActions->3->9->0",
10388 - "default.handlebars->29->1747",
10385 + "default.handlebars->29->1740",
10386 "default.handlebars->container->column_l->p2->p2info->p2AccountActions->3->p2AccountPassActions->7"
10387 ]
10388 },
@@ -10426,7 +10423,7 @@
10423 "zh-chs": "删除群组",
10424 "zh-cht": "刪除群組",
10425 "xloc": [
10429 - "default.handlebars->29->1826"
10426 + "default.handlebars->29->1819"
10427 ]
10428 },
10429 {
@@ -10466,7 +10463,7 @@
10463 "zh-chs": "递归删除:“{0}”,{1}个元素已删除",
10464 "zh-cht": "遞歸刪除:“{0}”,{1}個元素已刪除",
10465 "xloc": [
10469 - "default.handlebars->29->1634"
10466 + "default.handlebars->29->1627"
10467 ]
10468 },
10469 {
@@ -10488,7 +10485,7 @@
10485 "xloc": [
10486 "default-mobile.handlebars->9->124",
10487 "default-mobile.handlebars->9->309",
10491 - "default.handlebars->29->1576",
10488 + "default.handlebars->29->1569",
10489 "default.handlebars->29->902"
10490 ]
10491 },
@@ -10509,7 +10506,7 @@
10506 "zh-chs": "删除用户群组{0}?",
10507 "zh-cht": "刪除用戶群組{0}?",
10508 "xloc": [
10512 - "default.handlebars->29->1889"
10509 + "default.handlebars->29->1882"
10510 ]
10511 },
10512 {
@@ -10531,7 +10528,7 @@
10528 "xloc": [
10529 "default-mobile.handlebars->9->123",
10530 "default-mobile.handlebars->9->308",
10534 - "default.handlebars->29->1575",
10531 + "default.handlebars->29->1568",
10532 "default.handlebars->29->901"
10533 ]
10534 },
@@ -10572,7 +10569,7 @@
10569 "zh-chs": "删除:“{0}”",
10570 "zh-cht": "刪除:“{0}”",
10571 "xloc": [
10575 - "default.handlebars->29->1633"
10572 + "default.handlebars->29->1626"
10573 ]
10574 },
10575 {
@@ -10592,7 +10589,7 @@
10589 "zh-chs": "删除:“{0}”,{1}个元素已删除",
10590 "zh-cht": "刪除:“{0}”,已刪除{1}個元素",
10591 "xloc": [
10595 - "default.handlebars->29->1635"
10592 + "default.handlebars->29->1628"
10593 ]
10594 },
10595 {
@@ -10711,11 +10708,11 @@
10708 "default-mobile.handlebars->9->416",
10709 "default.handlebars->29->1289",
10710 "default.handlebars->29->1326",
10714 - "default.handlebars->29->1413",
10715 - "default.handlebars->29->1835",
10716 - "default.handlebars->29->1845",
10717 - "default.handlebars->29->1846",
10718 - "default.handlebars->29->1887",
10711 + "default.handlebars->29->1406",
10712 + "default.handlebars->29->1828",
10713 + "default.handlebars->29->1838",
10714 + "default.handlebars->29->1839",
10715 + "default.handlebars->29->1880",
10716 "default.handlebars->29->570",
10717 "default.handlebars->29->571",
10718 "default.handlebars->29->77",
@@ -10760,8 +10757,8 @@
10757 "zh-cht": "桌面",
10758 "xloc": [
10759 "default-mobile.handlebars->9->261",
10763 - "default.handlebars->29->1419",
10764 - "default.handlebars->29->2031",
10760 + "default.handlebars->29->1412",
10761 + "default.handlebars->29->2024",
10762 "default.handlebars->29->535",
10763 "default.handlebars->29->872",
10764 "default.handlebars->container->topbar->1->1->MainSubMenuSpan->MainSubMenu->1->0->MainDevDesktop",
@@ -10807,8 +10804,8 @@
10804 "zh-cht": "桌面通知",
10805 "xloc": [
10806 "default.handlebars->29->1340",
10810 - "default.handlebars->29->1850",
10811 - "default.handlebars->29->1937",
10807 + "default.handlebars->29->1843",
10808 + "default.handlebars->29->1930",
10809 "default.handlebars->29->606"
10810 ]
10811 },
@@ -10830,8 +10827,8 @@
10827 "zh-cht": "桌面提示",
10828 "xloc": [
10829 "default.handlebars->29->1339",
10833 - "default.handlebars->29->1849",
10834 - "default.handlebars->29->1936",
10830 + "default.handlebars->29->1842",
10831 + "default.handlebars->29->1929",
10832 "default.handlebars->29->605"
10833 ]
10834 },
@@ -10853,8 +10850,8 @@
10850 "zh-cht": "桌面提示+工具欄",
10851 "xloc": [
10852 "default.handlebars->29->1337",
10856 - "default.handlebars->29->1847",
10857 - "default.handlebars->29->1934",
10853 + "default.handlebars->29->1840",
10854 + "default.handlebars->29->1927",
10855 "default.handlebars->29->603"
10856 ]
10857 },
@@ -10897,8 +10894,8 @@
10894 "zh-cht": "桌面工具欄",
10895 "xloc": [
10896 "default.handlebars->29->1338",
10900 - "default.handlebars->29->1848",
10901 - "default.handlebars->29->1935",
10897 + "default.handlebars->29->1841",
10898 + "default.handlebars->29->1928",
10899 "default.handlebars->29->604"
10900 ]
10901 },
@@ -11002,9 +10999,9 @@
10999 "zh-chs": "设备",
11000 "zh-cht": "裝置",
11001 "xloc": [
11005 - "default.handlebars->29->1442",
11002 + "default.handlebars->29->1435",
11003 "default.handlebars->29->186",
11007 - "default.handlebars->29->2003",
11004 + "default.handlebars->29->1996",
11005 "default.handlebars->container->column_l->p1->devListToolbarSpan->1->0->9->devListToolbarSort->sortselect->5"
11006 ]
11007 },
@@ -11053,14 +11050,14 @@
11050 "zh-chs": "设备组",
11051 "zh-cht": "裝置群",
11052 "xloc": [
11056 - "default.handlebars->29->1437",
11057 - "default.handlebars->29->1440",
11058 - "default.handlebars->29->1441",
11059 - "default.handlebars->29->1698",
11060 - "default.handlebars->29->1873",
11061 - "default.handlebars->29->1879",
11062 - "default.handlebars->29->1991",
11063 - "default.handlebars->29->2040"
11053 + "default.handlebars->29->1430",
11054 + "default.handlebars->29->1433",
11055 + "default.handlebars->29->1434",
11056 + "default.handlebars->29->1691",
11057 + "default.handlebars->29->1866",
11058 + "default.handlebars->29->1872",
11059 + "default.handlebars->29->1984",
11060 + "default.handlebars->29->2033"
11061 ]
11062 },
11063 {
@@ -11081,7 +11078,7 @@
11078 "zh-cht": "裝置群用戶",
11079 "xloc": [
11080 "default-mobile.handlebars->9->462",
11084 - "default.handlebars->29->1506"
11081 + "default.handlebars->29->1499"
11082 ]
11083 },
11084 {
@@ -11102,11 +11099,11 @@
11099 "zh-cht": "裝置群",
11100 "xloc": [
11101 "default-mobile.handlebars->container->page_content->column_l->p3->p3info->1->3",
11105 - "default.handlebars->29->1714",
11106 - "default.handlebars->29->1820",
11107 - "default.handlebars->29->1860",
11108 - "default.handlebars->29->1931",
11109 - "default.handlebars->29->2075",
11102 + "default.handlebars->29->1707",
11103 + "default.handlebars->29->1813",
11104 + "default.handlebars->29->1853",
11105 + "default.handlebars->29->1924",
11106 + "default.handlebars->29->2068",
11107 "default.handlebars->container->column_l->p2->p2info->7"
11108 ]
11109 },
@@ -11188,7 +11185,7 @@
11185 "zh-cht": "裝置名稱",
11186 "xloc": [
11187 "default-mobile.handlebars->9->284",
11191 - "default.handlebars->29->2039",
11188 + "default.handlebars->29->2032",
11189 "default.handlebars->29->291",
11190 "default.handlebars->29->825",
11191 "player.handlebars->3->9"
@@ -11253,7 +11250,7 @@
11250 "zh-cht": "裝置連接。",
11251 "xloc": [
11252 "default.handlebars->29->1257",
11256 - "default.handlebars->29->1527"
11253 + "default.handlebars->29->1520"
11254 ]
11255 },
11256 {
@@ -11274,7 +11271,7 @@
11271 "zh-cht": "裝置斷開連接。",
11272 "xloc": [
11273 "default.handlebars->29->1258",
11277 - "default.handlebars->29->1528"
11274 + "default.handlebars->29->1521"
11275 ]
11276 },
11277 {
@@ -11294,7 +11291,7 @@
11291 "zh-chs": "创建的设备组:{0}",
11292 "zh-cht": "設備組已創建:{0}",
11293 "xloc": [
11297 - "default.handlebars->29->1664"
11294 + "default.handlebars->29->1657"
11295 ]
11296 },
11297 {
@@ -11314,7 +11311,7 @@
11311 "zh-chs": "设备组已删除:{0}",
11312 "zh-cht": "設備組已刪除:{0}",
11313 "xloc": [
11317 - "default.handlebars->29->1665"
11314 + "default.handlebars->29->1658"
11315 ]
11316 },
11317 {
@@ -11334,7 +11331,7 @@
11331 "zh-chs": "设备组成员身份已更改:{0}",
11332 "zh-cht": "設備組成員身份已更改:{0}",
11333 "xloc": [
11337 - "default.handlebars->29->1666"
11334 + "default.handlebars->29->1659"
11335 ]
11336 },
11337 {
@@ -11374,7 +11371,7 @@
11371 "zh-chs": "设备组通知已更改",
11372 "zh-cht": "設備組通知已更改",
11373 "xloc": [
11377 - "default.handlebars->29->1661"
11374 + "default.handlebars->29->1654"
11375 ]
11376 },
11377 {
@@ -11394,7 +11391,7 @@
11391 "zh-chs": "未删除的设备组:{0}",
11392 "zh-cht": "未刪除的設備組:{0}",
11393 "xloc": [
11397 - "default.handlebars->29->1644"
11394 + "default.handlebars->29->1637"
11395 ]
11396 },
11397 {
@@ -11787,7 +11784,7 @@
11784 "zh-chs": "设备请求激活Intel(R)AMT ACM,FQDN:{0}",
11785 "zh-cht": "設備請求激活Intel(R)AMT ACM,FQDN:{0}",
11786 "xloc": [
11790 - "default.handlebars->29->1646"
11787 + "default.handlebars->29->1639"
11788 ]
11789 },
11790 {
@@ -11824,8 +11821,8 @@
11821 "zh-chs": "设备",
11822 "zh-cht": "裝置",
11823 "xloc": [
11827 - "default.handlebars->29->1821",
11828 - "default.handlebars->29->1861"
11824 + "default.handlebars->29->1814",
11825 + "default.handlebars->29->1854"
11826 ]
11827 },
11828 {
@@ -11865,7 +11862,7 @@
11862 "zh-chs": "禁用的电子邮件两因素身份验证",
11863 "zh-cht": "禁用的電子郵件兩因素身份驗證",
11864 "xloc": [
11868 - "default.handlebars->29->1677"
11865 + "default.handlebars->29->1670"
11866 ]
11867 },
11868 {
@@ -12065,7 +12062,7 @@
12062 "zh-chs": "显示公共连结",
12063 "zh-cht": "顯示公共鏈結",
12064 "xloc": [
12068 - "default.handlebars->29->1547"
12065 + "default.handlebars->29->1540"
12066 ]
12067 },
12068 {
@@ -12085,7 +12082,7 @@
12082 "zh-chs": "显示消息框,标题= “{0}”,消息= “{1}”",
12083 "zh-cht": "顯示消息框,標題= “{0}”,消息= “{1}”",
12084 "xloc": [
12088 - "default.handlebars->29->1606"
12085 + "default.handlebars->29->1599"
12086 ]
12087 },
12088 {
@@ -12105,7 +12102,7 @@
12102 "zh-chs": "显示吐司消息,标题= “{0}”,消息= “{1}”",
12103 "zh-cht": "顯示吐司消息,標題= “{0}”,消息= “{1}”",
12104 "xloc": [
12108 - "default.handlebars->29->1614"
12105 + "default.handlebars->29->1607"
12106 ]
12107 },
12108 {
@@ -12125,7 +12122,7 @@
12122 "zh-chs": "什么都不做",
12123 "zh-cht": "什麼都不做",
12124 "xloc": [
12128 - "default.handlebars->29->1394"
12125 + "default.handlebars->29->1391"
12126 ]
12127 },
12128 {
@@ -12145,10 +12142,10 @@
12142 "zh-chs": "域",
12143 "zh-cht": "域",
12144 "xloc": [
12148 - "default.handlebars->29->1785",
12149 - "default.handlebars->29->1833",
12150 - "default.handlebars->29->1842",
12151 - "default.handlebars->29->1910",
12145 + "default.handlebars->29->1778",
12146 + "default.handlebars->29->1826",
12147 + "default.handlebars->29->1835",
12148 + "default.handlebars->29->1903",
12149 "mstsc.handlebars->main->1->3->1->2->1->0",
12150 "mstsc.handlebars->main->1->3->1->2->3"
12151 ]
@@ -12170,8 +12167,7 @@
12167 "zh-chs": "不要配置",
12168 "zh-cht": "不要配置",
12169 "xloc": [
12173 - "default.handlebars->29->1398",
12174 - "default.handlebars->29->1403"
12170 + "default.handlebars->29->1395"
12171 ]
12172 },
12173 {
@@ -12191,7 +12187,7 @@
12187 "zh-chs": "不要连接到服务器",
12188 "zh-cht": "不要連接到伺服器",
12189 "xloc": [
12194 - "default.handlebars->29->1399"
12190 + "default.handlebars->29->1396"
12191 ]
12192 },
12193 {
@@ -12411,7 +12407,7 @@
12407 "zh-chs": "下载报告",
12408 "zh-cht": "下載報告",
12409 "xloc": [
12414 - "default.handlebars->29->1703",
12410 + "default.handlebars->29->1696",
12411 "default.handlebars->container->column_l->p3->3->1->0->3"
12412 ]
12413 },
@@ -12612,7 +12608,7 @@
12608 "zh-chs": "使用以下一种档案格式下载事件列表。",
12609 "zh-cht": "使用以下一種檔案格式下載事件列表。",
12610 "xloc": [
12615 - "default.handlebars->29->1704"
12611 + "default.handlebars->29->1697"
12612 ]
12613 },
12614 {
@@ -12632,7 +12628,7 @@
12628 "zh-chs": "使用以下一种档案格式下载用户列表。",
12629 "zh-cht": "使用以下一種檔案格式下載用戶列表。",
12630 "xloc": [
12635 - "default.handlebars->29->1769"
12631 + "default.handlebars->29->1762"
12632 ]
12633 },
12634 {
@@ -12713,7 +12709,7 @@
12709 "zh-chs": "下载:“{0}”",
12710 "zh-cht": "下載:“{0}”",
12711 "xloc": [
12716 - "default.handlebars->29->1637"
12712 + "default.handlebars->29->1630"
12713 ]
12714 },
12715 {
@@ -12753,7 +12749,7 @@
12749 "zh-chs": "代理重复",
12750 "zh-cht": "代理重複",
12751 "xloc": [
12756 - "default.handlebars->29->2071"
12752 + "default.handlebars->29->2064"
12753 ]
12754 },
12755 {
@@ -12793,7 +12789,7 @@
12789 "zh-chs": "复制用户组",
12790 "zh-cht": "複製用戶群",
12791 "xloc": [
12796 - "default.handlebars->29->1837"
12792 + "default.handlebars->29->1830"
12793 ]
12794 },
12795 {
@@ -12830,8 +12826,8 @@
12826 "zh-chs": "持续时间",
12827 "zh-cht": "持續時間",
12828 "xloc": [
12833 - "default.handlebars->29->2025",
12834 - "default.handlebars->29->2045",
12829 + "default.handlebars->29->2018",
12830 + "default.handlebars->29->2038",
12831 "player.handlebars->3->2"
12832 ]
12833 },
@@ -12852,7 +12848,7 @@
12848 "zh-chs": "在激活期间,代理将有权取得管理员密码信息。",
12849 "zh-cht": "在啟動期間,代理將有權取得管理員密碼訊息。",
12850 "xloc": [
12855 - "default.handlebars->29->1408"
12851 + "default.handlebars->29->1401"
12852 ]
12853 },
12854 {
@@ -13185,10 +13181,10 @@
13181 "default-mobile.handlebars->9->417",
13182 "default-mobile.handlebars->9->419",
13183 "default-mobile.handlebars->9->439",
13188 - "default.handlebars->29->1414",
13189 - "default.handlebars->29->1446",
13190 - "default.handlebars->29->1470",
13191 - "default.handlebars->29->1482"
13184 + "default.handlebars->29->1407",
13185 + "default.handlebars->29->1439",
13186 + "default.handlebars->29->1463",
13187 + "default.handlebars->29->1475"
13188 ]
13189 },
13190 {
@@ -13208,7 +13204,7 @@
13204 "zh-chs": "编辑设备组功能",
13205 "zh-cht": "編輯裝置群功能",
13206 "xloc": [
13211 - "default.handlebars->29->1432"
13207 + "default.handlebars->29->1425"
13208 ]
13209 },
13210 {
@@ -13228,8 +13224,8 @@
13224 "zh-chs": "编辑设备组权限",
13225 "zh-cht": "編輯裝置群權限",
13226 "xloc": [
13231 - "default.handlebars->29->1467",
13232 - "default.handlebars->29->1479"
13227 + "default.handlebars->29->1460",
13228 + "default.handlebars->29->1472"
13229 ]
13230 },
13231 {
@@ -13249,7 +13245,7 @@
13245 "zh-chs": "编辑设备组用户同意",
13246 "zh-cht": "編輯裝置群用戶同意",
13247 "xloc": [
13252 - "default.handlebars->29->1415"
13248 + "default.handlebars->29->1408"
13249 ]
13250 },
13251 {
@@ -13270,7 +13266,7 @@
13266 "zh-cht": "編輯裝置筆記",
13267 "xloc": [
13268 "default-mobile.handlebars->9->431",
13273 - "default.handlebars->29->1459"
13269 + "default.handlebars->29->1452"
13270 ]
13271 },
13272 {
@@ -13290,8 +13286,8 @@
13286 "zh-chs": "编辑设备权限",
13287 "zh-cht": "編輯裝置權限",
13288 "xloc": [
13293 - "default.handlebars->29->1472",
13294 - "default.handlebars->29->1474"
13289 + "default.handlebars->29->1465",
13290 + "default.handlebars->29->1467"
13291 ]
13292 },
13293 {
@@ -13331,7 +13327,7 @@
13327 "zh-chs": "编辑设备用户同意",
13328 "zh-cht": "編輯裝置用戶同意",
13329 "xloc": [
13334 - "default.handlebars->29->1417"
13330 + "default.handlebars->29->1410"
13331 ]
13332 },
13333 {
@@ -13395,7 +13391,7 @@
13391 "zh-cht": "編輯筆記",
13392 "xloc": [
13393 "default-mobile.handlebars->9->446",
13398 - "default.handlebars->29->1489"
13394 + "default.handlebars->29->1482"
13395 ]
13396 },
13397 {
@@ -13415,7 +13411,7 @@
13411 "zh-chs": "编辑用户同意",
13412 "zh-cht": "編輯用戶同意",
13413 "xloc": [
13418 - "default.handlebars->29->1416"
13414 + "default.handlebars->29->1409"
13415 ]
13416 },
13417 {
@@ -13435,7 +13431,7 @@
13431 "zh-chs": "编辑用户设备组权限",
13432 "zh-cht": "編輯用戶裝置群權限",
13433 "xloc": [
13438 - "default.handlebars->29->1480"
13434 + "default.handlebars->29->1473"
13435 ]
13436 },
13437 {
@@ -13455,7 +13451,7 @@
13451 "zh-chs": "编辑用户设备权限",
13452 "zh-cht": "編輯用戶裝置權限",
13453 "xloc": [
13458 - "default.handlebars->29->1475"
13454 + "default.handlebars->29->1468"
13455 ]
13456 },
13457 {
@@ -13475,7 +13471,7 @@
13471 "zh-chs": "编辑用户组",
13472 "zh-cht": "編輯用戶群",
13473 "xloc": [
13478 - "default.handlebars->29->1888"
13474 + "default.handlebars->29->1881"
13475 ]
13476 },
13477 {
@@ -13495,14 +13491,14 @@
13491 "zh-chs": "编辑用户组设备权限",
13492 "zh-cht": "編輯用戶群裝置權限",
13493 "xloc": [
13498 - "default.handlebars->29->1477"
13494 + "default.handlebars->29->1470"
13495 ]
13496 },
13497 {
13498 "en": "Edit User Group User Consent",
13499 "nl": "Bewerk groepsgebruikerstoestemming",
13500 "xloc": [
13505 - "default.handlebars->29->1418"
13501 + "default.handlebars->29->1411"
13502 ]
13503 },
13504 {
@@ -13564,11 +13560,11 @@
13560 "zh-cht": "電郵",
13561 "xloc": [
13562 "default-mobile.handlebars->9->78",
13567 - "default.handlebars->29->1787",
13568 - "default.handlebars->29->1914",
13569 - "default.handlebars->29->1916",
13570 - "default.handlebars->29->1956",
13571 - "default.handlebars->29->1972",
13563 + "default.handlebars->29->1780",
13564 + "default.handlebars->29->1907",
13565 + "default.handlebars->29->1909",
13566 + "default.handlebars->29->1949",
13567 + "default.handlebars->29->1965",
13568 "default.handlebars->29->341",
13569 "login-mobile.handlebars->5->42",
13570 "login-mobile.handlebars->container->page_content->column_l->1->1->0->1->tokenpanel->1->7->1->4->1->3",
@@ -13722,7 +13718,7 @@
13718 "zh-chs": "电邮未验证",
13719 "zh-cht": "電郵未驗證",
13720 "xloc": [
13725 - "default.handlebars->29->1733"
13721 + "default.handlebars->29->1726"
13722 ]
13723 },
13724 {
@@ -13742,8 +13738,8 @@
13738 "zh-chs": "电邮已验证",
13739 "zh-cht": "電郵已驗證",
13740 "xloc": [
13745 - "default.handlebars->29->1734",
13746 - "default.handlebars->29->1908"
13741 + "default.handlebars->29->1727",
13742 + "default.handlebars->29->1901"
13743 ]
13744 },
13745 {
@@ -13763,7 +13759,7 @@
13759 "zh-chs": "电邮已验证。",
13760 "zh-cht": "電郵已驗證。",
13761 "xloc": [
13766 - "default.handlebars->29->1793"
13762 + "default.handlebars->29->1786"
13763 ]
13764 },
13765 {
@@ -13783,7 +13779,7 @@
13779 "zh-chs": "电邮未验证",
13780 "zh-cht": "電郵未驗證",
13781 "xloc": [
13786 - "default.handlebars->29->1909"
13782 + "default.handlebars->29->1902"
13783 ]
13784 },
13785 {
@@ -13847,7 +13843,7 @@
13843 "zh-chs": "已通过电邮验证,并且需要重置密码。",
13844 "zh-cht": "已通過電郵驗證,並且需要重置密碼。",
13845 "xloc": [
13850 - "default.handlebars->29->1794"
13846 + "default.handlebars->29->1787"
13847 ]
13848 },
13849 {
@@ -13867,7 +13863,7 @@
13863 "zh-chs": "电邮/短信流量",
13864 "zh-cht": "電郵/短信流量",
13865 "xloc": [
13870 - "default.handlebars->29->2113"
13866 + "default.handlebars->29->2106"
13867 ]
13868 },
13869 {
@@ -13913,7 +13909,7 @@
13909 "zh-chs": "启用邀请代码",
13910 "zh-cht": "啟用邀請代碼",
13911 "xloc": [
13916 - "default.handlebars->29->1512"
13912 + "default.handlebars->29->1505"
13913 ]
13914 },
13915 {
@@ -13994,7 +13990,7 @@
13990 "zh-chs": "已启用",
13991 "zh-cht": "已啟用",
13992 "xloc": [
13997 - "default.handlebars->29->2047"
13993 + "default.handlebars->29->2040"
13994 ]
13995 },
13996 {
@@ -14014,7 +14010,7 @@
14010 "zh-chs": "启用电子邮件两因素身份验证",
14011 "zh-cht": "啟用電子郵件兩因素身份驗證",
14012 "xloc": [
14017 - "default.handlebars->29->1676"
14013 + "default.handlebars->29->1669"
14014 ]
14015 },
14016 {
@@ -14054,7 +14050,7 @@
14050 "zh-chs": "时间结束",
14051 "zh-cht": "時間結束",
14052 "xloc": [
14057 - "default.handlebars->29->2044"
14053 + "default.handlebars->29->2037"
14054 ]
14055 },
14056 {
@@ -14074,7 +14070,7 @@
14070 "zh-chs": "从{1}到{2},{3}秒结束了桌面会话“{0}”",
14071 "zh-cht": "從{1}到{2},{3}秒結束了桌面會話“{0}”",
14072 "xloc": [
14077 - "default.handlebars->29->1599"
14073 + "default.handlebars->29->1592"
14074 ]
14075 },
14076 {
@@ -14094,7 +14090,7 @@
14090 "zh-chs": "从{1}到{2},{3}秒结束了文件管理会话“{0}”",
14091 "zh-cht": "從{1}到{2},{3}秒結束了文件管理會話“{0}”",
14092 "xloc": [
14097 - "default.handlebars->29->1600"
14093 + "default.handlebars->29->1593"
14094 ]
14095 },
14096 {
@@ -14114,7 +14110,7 @@
14110 "zh-chs": "从{1}到{2},{3}秒结束了中继会话“{0}”",
14111 "zh-cht": "從{1}到{2},{3}秒結束了中繼會話“{0}”",
14112 "xloc": [
14117 - "default.handlebars->29->1597"
14113 + "default.handlebars->29->1590"
14114 ]
14115 },
14116 {
@@ -14134,7 +14130,7 @@
14130 "zh-chs": "从{1}到{2},{3}秒结束了终端会话“{0}”",
14131 "zh-cht": "從{1}到{2},{3}秒結束了終端會話“{0}”",
14132 "xloc": [
14137 - "default.handlebars->29->1598"
14133 + "default.handlebars->29->1591"
14134 ]
14135 },
14136 {
@@ -14435,7 +14431,7 @@
14431 "zh-chs": "输入管理领域名称的逗号分隔列表。",
14432 "zh-cht": "輸入管理領域名稱的逗號分隔列表。",
14433 "xloc": [
14438 - "default.handlebars->29->1798"
14434 + "default.handlebars->29->1791"
14435 ]
14436 },
14437 {
@@ -14658,7 +14654,7 @@
14654 "zh-chs": "事件列表输出",
14655 "zh-cht": "事件列表輸出",
14656 "xloc": [
14661 - "default.handlebars->29->1709"
14657 + "default.handlebars->29->1702"
14658 ]
14659 },
14660 {
@@ -14850,7 +14846,7 @@
14846 "zh-chs": "外部",
14847 "zh-cht": "外部",
14848 "xloc": [
14853 - "default.handlebars->29->2098"
14849 + "default.handlebars->29->2091"
14850 ]
14851 },
14852 {
@@ -14950,7 +14946,7 @@
14946 "zh-chs": "本地用户拒绝后无法启动远程桌面",
14947 "zh-cht": "本地用戶拒絕後無法啟動遠程桌面",
14948 "xloc": [
14953 - "default.handlebars->29->1622"
14949 + "default.handlebars->29->1615"
14950 ]
14951 },
14952 {
@@ -14970,7 +14966,7 @@
14966 "zh-chs": "本地用户拒绝后无法启动远程文件",
14967 "zh-cht": "本地用戶拒絕後無法啟動遠程文件",
14968 "xloc": [
14973 - "default.handlebars->29->1629"
14969 + "default.handlebars->29->1622"
14970 ]
14971 },
14972 {
@@ -15198,8 +15194,8 @@
15194 "xloc": [
15195 "default-mobile.handlebars->9->171",
15196 "default-mobile.handlebars->9->262",
15201 - "default.handlebars->29->1426",
15202 - "default.handlebars->29->2032",
15197 + "default.handlebars->29->1419",
15198 + "default.handlebars->29->2025",
15199 "default.handlebars->29->258",
15200 "default.handlebars->container->topbar->1->1->MainSubMenuSpan->MainSubMenu->1->0->MainDevFiles",
15201 "default.handlebars->contextMenu->cxfiles"
@@ -15243,8 +15239,8 @@
15239 "zh-cht": "檔案通知",
15240 "xloc": [
15241 "default.handlebars->29->1344",
15246 - "default.handlebars->29->1854",
15247 - "default.handlebars->29->1941",
15242 + "default.handlebars->29->1847",
15243 + "default.handlebars->29->1934",
15244 "default.handlebars->29->610"
15245 ]
15246 },
@@ -15266,8 +15262,8 @@
15262 "zh-cht": "檔案提示",
15263 "xloc": [
15264 "default.handlebars->29->1343",
15269 - "default.handlebars->29->1853",
15270 - "default.handlebars->29->1940",
15265 + "default.handlebars->29->1846",
15266 + "default.handlebars->29->1933",
15267 "default.handlebars->29->609"
15268 ]
15269 },
@@ -15311,7 +15307,7 @@
15307 "zh-chs": "录制会话已完成,{0}秒",
15308 "zh-cht": "錄製會話已完成,{0}秒",
15309 "xloc": [
15314 - "default.handlebars->29->1595"
15310 + "default.handlebars->29->1588"
15311 ]
15312 },
15313 {
@@ -15478,8 +15474,8 @@
15474 "zh-chs": "下次登录时强制重置密码。",
15475 "zh-cht": "下次登入時強制重置密碼。",
15476 "xloc": [
15481 - "default.handlebars->29->1792",
15482 - "default.handlebars->29->1981"
15477 + "default.handlebars->29->1785",
15478 + "default.handlebars->29->1974"
15479 ]
15480 },
15481 {
@@ -15562,7 +15558,7 @@
15558 "zh-chs": "格式化",
15559 "zh-cht": "格式化",
15560 "xloc": [
15565 - "default.handlebars->29->1700"
15561 + "default.handlebars->29->1693"
15562 ]
15563 },
15564 {
@@ -15603,8 +15599,8 @@
15599 "zh-chs": "自由",
15600 "zh-cht": "自由",
15601 "xloc": [
15606 - "default.handlebars->29->2056",
15607 - "default.handlebars->29->2058"
15602 + "default.handlebars->29->2049",
15603 + "default.handlebars->29->2051"
15604 ]
15605 },
15606 {
@@ -15830,8 +15826,8 @@
15826 "default-mobile.handlebars->9->418",
15827 "default-mobile.handlebars->9->438",
15828 "default.handlebars->29->1298",
15833 - "default.handlebars->29->1445",
15834 - "default.handlebars->29->1804"
15829 + "default.handlebars->29->1438",
15830 + "default.handlebars->29->1797"
15831 ]
15832 },
15833 {
@@ -15851,7 +15847,7 @@
15847 "zh-chs": "完整管理员(保留所有权利)",
15848 "zh-cht": "完整管理員(保留所有權利)",
15849 "xloc": [
15854 - "default.handlebars->29->1481"
15850 + "default.handlebars->29->1474"
15851 ]
15852 },
15853 {
@@ -15950,7 +15946,7 @@
15946 "zh-chs": "完整管理员",
15947 "zh-cht": "完整管理員",
15948 "xloc": [
15953 - "default.handlebars->29->1902"
15949 + "default.handlebars->29->1895"
15950 ]
15951 },
15952 {
@@ -16338,7 +16334,7 @@
16334 "zh-chs": "正在获取剪贴板内容,{0}个字节",
16335 "zh-cht": "正在獲取剪貼板內容,{0}個字節",
16336 "xloc": [
16341 - "default.handlebars->29->1609"
16337 + "default.handlebars->29->1602"
16338 ]
16339 },
16340 {
@@ -16569,8 +16565,8 @@
16565 "zh-chs": "集体指令",
16566 "zh-cht": "集體指令",
16567 "xloc": [
16572 - "default.handlebars->29->1748",
16573 - "default.handlebars->29->1827",
16568 + "default.handlebars->29->1741",
16569 + "default.handlebars->29->1820",
16570 "default.handlebars->29->470",
16571 "default.handlebars->container->column_l->p1->devListToolbarSpan->1->0->devListToolbar",
16572 "default.handlebars->container->column_l->p4->3->1->0->3->3",
@@ -16594,7 +16590,7 @@
16590 "zh-chs": "通过...分组",
16591 "zh-cht": "通過...分群",
16592 "xloc": [
16597 - "default.handlebars->29->1696"
16593 + "default.handlebars->29->1689"
16594 ]
16595 },
16596 {
@@ -16614,7 +16610,7 @@
16610 "zh-chs": "组标识符",
16611 "zh-cht": "群標識符",
16612 "xloc": [
16617 - "default.handlebars->29->1844"
16613 + "default.handlebars->29->1837"
16614 ]
16615 },
16616 {
@@ -16634,7 +16630,7 @@
16630 "zh-chs": "群组成员",
16631 "zh-cht": "群組成員",
16632 "xloc": [
16637 - "default.handlebars->29->1865"
16633 + "default.handlebars->29->1858"
16634 ]
16635 },
16636 {
@@ -16654,7 +16650,7 @@
16650 "zh-chs": "用户{0}的群组权限。",
16651 "zh-cht": "用戶{0}的群組權限。",
16652 "xloc": [
16657 - "default.handlebars->29->1444"
16653 + "default.handlebars->29->1437"
16654 ]
16655 },
16656 {
@@ -16674,7 +16670,7 @@
16670 "zh-chs": "{0}的群组权限。",
16671 "zh-cht": "{0}的群組權限。",
16672 "xloc": [
16677 - "default.handlebars->29->1443"
16673 + "default.handlebars->29->1436"
16674 ]
16675 },
16676 {
@@ -16865,7 +16861,7 @@
16861 "zh-chs": "堆总数",
16862 "zh-cht": "堆總數",
16863 "xloc": [
16868 - "default.handlebars->29->2100"
16864 + "default.handlebars->29->2093"
16865 ]
16866 },
16867 {
@@ -16885,7 +16881,7 @@
16881 "zh-chs": "堆使用",
16882 "zh-cht": "堆使用",
16883 "xloc": [
16888 - "default.handlebars->29->2099"
16884 + "default.handlebars->29->2092"
16885 ]
16886 },
16887 {
@@ -16940,7 +16936,7 @@
16936 "en": "Help Requested, user: {0}, details: {1}",
16937 "nl": "Hulp verzoek van, user: {0}, details: {1}",
16938 "xloc": [
16943 - "default.handlebars->29->1686"
16939 + "default.handlebars->29->1679"
16940 ]
16941 },
16942 {
@@ -17201,7 +17197,7 @@
17197 "zh-cht": "保存{0}項目{1}給{2}",
17198 "xloc": [
17199 "default-mobile.handlebars->9->129",
17204 - "default.handlebars->29->1582"
17200 + "default.handlebars->29->1575"
17201 ]
17202 },
17203 {
@@ -17918,8 +17914,8 @@
17914 "zh-chs": "安装类型",
17915 "zh-cht": "安裝方式",
17916 "xloc": [
17917 + "default.handlebars->29->1507",
17918 "default.handlebars->29->1514",
17922 - "default.handlebars->29->1521",
17919 "default.handlebars->29->356",
17920 "default.handlebars->29->370",
17921 "default.handlebars->29->384"
@@ -17963,7 +17959,7 @@
17959 "zh-chs": "英特尔AMT",
17960 "zh-cht": "英特爾AMT",
17961 "xloc": [
17966 - "default.handlebars->29->2096"
17962 + "default.handlebars->29->2089"
17963 ]
17964 },
17965 {
@@ -18008,9 +18004,9 @@
18004 "default-mobile.handlebars->9->246",
18005 "default.handlebars->29->1351",
18006 "default.handlebars->29->1361",
18011 - "default.handlebars->29->1533",
18012 - "default.handlebars->29->1541",
18013 - "default.handlebars->29->2118",
18007 + "default.handlebars->29->1526",
18008 + "default.handlebars->29->1534",
18009 + "default.handlebars->29->2111",
18010 "default.handlebars->29->537",
18011 "default.handlebars->29->592",
18012 "default.handlebars->29->620"
@@ -18159,7 +18155,7 @@
18155 "zh-chs": "英特尔&reg;AMT政策",
18156 "zh-cht": "Intel&reg; AMT政策",
18157 "xloc": [
18162 - "default.handlebars->29->1390"
18158 + "default.handlebars->29->1387"
18159 ]
18160 },
18161 {
@@ -18179,7 +18175,7 @@
18175 "zh-chs": "英特尔&reg;AMT重定向",
18176 "zh-cht": "Intel&reg; AMT重定向",
18177 "xloc": [
18182 - "default.handlebars->29->2034",
18178 + "default.handlebars->29->2027",
18179 "player.handlebars->3->14"
18180 ]
18181 },
@@ -18220,7 +18216,7 @@
18216 "zh-chs": "英特尔&reg;AMT WSMAN",
18217 "zh-cht": "Intle&reg; AMT WSMAN",
18218 "xloc": [
18223 - "default.handlebars->29->2033",
18219 + "default.handlebars->29->2026",
18220 "player.handlebars->3->13"
18221 ]
18222 },
@@ -18285,7 +18281,7 @@
18281 "zh-cht": "Intel&reg; AMT桌面和串行事件。",
18282 "xloc": [
18283 "default.handlebars->29->1259",
18288 - "default.handlebars->29->1529"
18284 + "default.handlebars->29->1522"
18285 ]
18286 },
18287 {
@@ -18757,8 +18753,8 @@
18753 "zh-chs": "仅限互动",
18754 "zh-cht": "僅限互動",
18755 "xloc": [
18756 + "default.handlebars->29->1510",
18757 "default.handlebars->29->1517",
18761 - "default.handlebars->29->1524",
18758 "default.handlebars->29->359",
18759 "default.handlebars->29->373",
18760 "default.handlebars->29->387"
@@ -18821,7 +18817,7 @@
18817 "zh-chs": "无效的设备组类型",
18818 "zh-cht": "無效的裝置群類型",
18819 "xloc": [
18824 - "default.handlebars->29->2070"
18820 + "default.handlebars->29->2063"
18821 ]
18822 },
18823 {
@@ -18841,7 +18837,7 @@
18837 "zh-chs": "无效的JSON",
18838 "zh-cht": "無效的JSON",
18839 "xloc": [
18844 - "default.handlebars->29->2064"
18840 + "default.handlebars->29->2057"
18841 ]
18842 },
18843 {
@@ -18861,8 +18857,8 @@
18857 "zh-chs": "无效的JSON档案格式。",
18858 "zh-cht": "無效的JSON檔案格式。",
18859 "xloc": [
18864 - "default.handlebars->29->1766",
18865 - "default.handlebars->29->1768"
18860 + "default.handlebars->29->1759",
18861 + "default.handlebars->29->1761"
18862 ]
18863 },
18864 {
@@ -18882,7 +18878,7 @@
18878 "zh-chs": "无效的JSON档案:{0}。",
18879 "zh-cht": "無效的JSON檔案:{0}。",
18880 "xloc": [
18885 - "default.handlebars->29->1764"
18881 + "default.handlebars->29->1757"
18882 ]
18883 },
18884 {
@@ -18902,7 +18898,7 @@
18898 "zh-chs": "无效的PKCS签名",
18899 "zh-cht": "無效的PKCS簽名",
18900 "xloc": [
18905 - "default.handlebars->29->2062"
18901 + "default.handlebars->29->2055"
18902 ]
18903 },
18904 {
@@ -18922,7 +18918,7 @@
18918 "zh-chs": "無效的RSA密碼",
18919 "zh-cht": "無效的RSA密碼",
18920 "xloc": [
18925 - "default.handlebars->29->2063"
18921 + "default.handlebars->29->2056"
18922 ]
18923 },
18924 {
@@ -19049,7 +19045,7 @@
19045 "zh-chs": "使电邮无效",
19046 "zh-cht": "使電郵無效",
19047 "xloc": [
19052 - "default.handlebars->29->1742"
19048 + "default.handlebars->29->1735"
19049 ]
19050 },
19051 {
@@ -19126,7 +19122,7 @@
19122 "zh-chs": "任何人都可以使用邀请代码通过以下公共连接将设备加入该设备组:",
19123 "zh-cht": "任何人都可以使用邀請代碼通過以下公共鏈結將裝置加入該裝置群:",
19124 "xloc": [
19129 - "default.handlebars->29->1519"
19125 + "default.handlebars->29->1512"
19126 ]
19127 },
19128 {
@@ -19189,10 +19185,10 @@
19185 "zh-cht": "邀請碼",
19186 "xloc": [
19187 "default.handlebars->29->1355",
19188 + "default.handlebars->29->1506",
19189 + "default.handlebars->29->1511",
19190 "default.handlebars->29->1513",
19193 - "default.handlebars->29->1518",
19194 - "default.handlebars->29->1520",
19195 - "default.handlebars->29->1525"
19191 + "default.handlebars->29->1518"
19192 ]
19193 },
19194 {
@@ -19333,7 +19329,7 @@
19329 "zh-chs": "JSON",
19330 "zh-cht": "JSON",
19331 "xloc": [
19336 - "default.handlebars->29->1702"
19332 + "default.handlebars->29->1695"
19333 ]
19334 },
19335 {
@@ -19353,8 +19349,8 @@
19349 "zh-chs": "JSON格式",
19350 "zh-cht": "JSON格式",
19351 "xloc": [
19356 - "default.handlebars->29->1707",
19357 - "default.handlebars->29->1772",
19352 + "default.handlebars->29->1700",
19353 + "default.handlebars->29->1765",
19354 "default.handlebars->29->498"
19355 ]
19356 },
@@ -19395,7 +19391,7 @@
19391 "zh-chs": "已加入桌面Multiplex会话",
19392 "zh-cht": "已加入桌面Multiplex會話",
19393 "xloc": [
19398 - "default.handlebars->29->1592"
19394 + "default.handlebars->29->1585"
19395 ]
19396 },
19397 {
@@ -19576,7 +19572,7 @@
19572 "zh-chs": "杀死进程{0}",
19573 "zh-cht": "殺死進程{0}",
19574 "xloc": [
19579 - "default.handlebars->29->1607"
19575 + "default.handlebars->29->1600"
19576 ]
19577 },
19578 {
@@ -19991,7 +19987,7 @@
19987 "zh-chs": "最后访问",
19988 "zh-cht": "最後訪問",
19989 "xloc": [
19994 - "default.handlebars->29->1715"
19990 + "default.handlebars->29->1708"
19991 ]
19992 },
19993 {
@@ -20011,7 +20007,7 @@
20007 "zh-chs": "上次登录",
20008 "zh-cht": "上次登入",
20009 "xloc": [
20014 - "default.handlebars->29->1923"
20010 + "default.handlebars->29->1916"
20011 ]
20012 },
20013 {
@@ -20083,7 +20079,7 @@
20079 "zh-chs": "上次更改:{0}",
20080 "zh-cht": "上次更改:{0}",
20081 "xloc": [
20086 - "default.handlebars->29->1927"
20082 + "default.handlebars->29->1920"
20083 ]
20084 },
20085 {
@@ -20143,7 +20139,7 @@
20139 "zh-chs": "上次登录:{0}",
20140 "zh-cht": "上次登入:{0}",
20141 "xloc": [
20146 - "default.handlebars->29->1725"
20142 + "default.handlebars->29->1718"
20143 ]
20144 },
20145 {
@@ -20344,7 +20340,7 @@
20340 "zh-chs": "如没有请留空。",
20341 "zh-cht": "如沒有請留空。",
20342 "xloc": [
20347 - "default.handlebars->29->1967"
20343 + "default.handlebars->29->1960"
20344 ]
20345 },
20346 {
@@ -20389,7 +20385,7 @@
20385 "zh-chs": "离开桌面多路复用会话",
20386 "zh-cht": "離開桌面多路復用會話",
20387 "xloc": [
20392 - "default.handlebars->29->1593"
20388 + "default.handlebars->29->1586"
20389 ]
20390 },
20391 {
@@ -20409,7 +20405,7 @@
20405 "zh-chs": "减",
20406 "zh-cht": "減",
20407 "xloc": [
20412 - "default.handlebars->29->2135"
20408 + "default.handlebars->29->2128"
20409 ]
20410 },
20411 {
@@ -20472,7 +20468,7 @@
20468 "zh-cht": "有限輸入",
20469 "xloc": [
20470 "default-mobile.handlebars->9->451",
20475 - "default.handlebars->29->1495",
20471 + "default.handlebars->29->1488",
20472 "default.handlebars->29->685",
20473 "default.handlebars->29->706"
20474 ]
@@ -20495,7 +20491,7 @@
20491 "zh-cht": "僅有限輸入",
20492 "xloc": [
20493 "default-mobile.handlebars->9->424",
20498 - "default.handlebars->29->1451"
20494 + "default.handlebars->29->1444"
20495 ]
20496 },
20497 {
@@ -20968,7 +20964,7 @@
20964 "default.handlebars->29->1037",
20965 "default.handlebars->29->1314",
20966 "default.handlebars->29->1318",
20971 - "default.handlebars->29->2021",
20967 + "default.handlebars->29->2014",
20968 "default.handlebars->29->797"
20969 ]
20970 },
@@ -21051,7 +21047,7 @@
21047 "zh-chs": "本地用户接受的远程终端请求",
21048 "zh-cht": "本地用戶接受的遠程終端請求",
21049 "xloc": [
21054 - "default.handlebars->29->1615"
21050 + "default.handlebars->29->1608"
21051 ]
21052 },
21053 {
@@ -21071,7 +21067,7 @@
21067 "zh-chs": "本地用户拒绝了远程终端请求",
21068 "zh-cht": "本地用戶拒絕了遠程終端請求",
21069 "xloc": [
21074 - "default.handlebars->29->1616"
21070 + "default.handlebars->29->1609"
21071 ]
21072 },
21073 {
@@ -21152,7 +21148,7 @@
21148 "zh-chs": "锁定账户",
21149 "zh-cht": "鎖定賬戶",
21150 "xloc": [
21155 - "default.handlebars->29->1812"
21151 + "default.handlebars->29->1805"
21152 ]
21153 },
21154 {
@@ -21172,7 +21168,7 @@
21168 "zh-chs": "锁定帐户设置",
21169 "zh-cht": "鎖定帳戶設置",
21170 "xloc": [
21175 - "default.handlebars->29->1815"
21171 + "default.handlebars->29->1808"
21172 ]
21173 },
21174 {
@@ -21192,7 +21188,7 @@
21188 "zh-chs": "锁定账户",
21189 "zh-cht": "鎖定賬戶",
21190 "xloc": [
21195 - "default.handlebars->29->1745"
21191 + "default.handlebars->29->1738"
21192 ]
21193 },
21194 {
@@ -21212,7 +21208,7 @@
21208 "zh-chs": "已锁定",
21209 "zh-cht": "已鎖定",
21210 "xloc": [
21215 - "default.handlebars->29->1726"
21211 + "default.handlebars->29->1719"
21212 ]
21213 },
21214 {
@@ -21232,7 +21228,7 @@
21228 "zh-chs": "被锁定账户",
21229 "zh-cht": "被鎖定賬戶",
21230 "xloc": [
21235 - "default.handlebars->29->1899"
21231 + "default.handlebars->29->1892"
21232 ]
21233 },
21234 {
@@ -21252,7 +21248,7 @@
21248 "zh-chs": "将远程用户锁定在桌面之外",
21249 "zh-cht": "將遠程用戶鎖定在桌面之外",
21250 "xloc": [
21255 - "default.handlebars->29->1641"
21251 + "default.handlebars->29->1634"
21252 ]
21253 },
21254 {
@@ -21876,7 +21872,7 @@
21872 "zh-chs": "主服务器信息",
21873 "zh-cht": "主伺服器訊息",
21874 "xloc": [
21879 - "default.handlebars->29->2107"
21875 + "default.handlebars->29->2100"
21876 ]
21877 },
21878 {
@@ -21979,8 +21975,8 @@
21975 "xloc": [
21976 "default-mobile.handlebars->9->421",
21977 "default-mobile.handlebars->9->441",
21982 - "default.handlebars->29->1448",
21983 - "default.handlebars->29->1484"
21978 + "default.handlebars->29->1441",
21979 + "default.handlebars->29->1477"
21980 ]
21981 },
21982 {
@@ -22002,8 +21998,8 @@
21998 "xloc": [
21999 "default-mobile.handlebars->9->420",
22000 "default-mobile.handlebars->9->440",
22005 - "default.handlebars->29->1447",
22006 - "default.handlebars->29->1483"
22001 + "default.handlebars->29->1440",
22002 + "default.handlebars->29->1476"
22003 ]
22004 },
22005 {
@@ -22043,7 +22039,7 @@
22039 "zh-chs": "管理录音",
22040 "zh-cht": "管理錄音",
22041 "xloc": [
22046 - "default.handlebars->29->1810"
22042 + "default.handlebars->29->1803"
22043 ]
22044 },
22045 {
@@ -22083,7 +22079,7 @@
22079 "zh-chs": "管理用户组",
22080 "zh-cht": "管理用戶群",
22081 "xloc": [
22086 - "default.handlebars->29->1809"
22082 + "default.handlebars->29->1802"
22083 ]
22084 },
22085 {
@@ -22103,7 +22099,7 @@
22099 "zh-chs": "管理用户",
22100 "zh-cht": "管理用戶",
22101 "xloc": [
22106 - "default.handlebars->29->1808",
22102 + "default.handlebars->29->1801",
22103 "default.handlebars->29->700"
22104 ]
22105 },
@@ -22271,7 +22267,7 @@
22267 "zh-chs": "经理",
22268 "zh-cht": "經理",
22269 "xloc": [
22274 - "default.handlebars->29->1731"
22270 + "default.handlebars->29->1724"
22271 ]
22272 },
22273 {
@@ -22392,7 +22388,7 @@
22388 "zh-chs": "达到连接数量上限",
22389 "zh-cht": "達到連接數量上限",
22390 "xloc": [
22395 - "default.handlebars->29->2068"
22391 + "default.handlebars->29->2061"
22392 ]
22393 },
22394 {
@@ -22457,7 +22453,7 @@
22453 "zh-chs": "Megabyte",
22454 "zh-cht": "Megabyte",
22455 "xloc": [
22460 - "default.handlebars->29->2097"
22456 + "default.handlebars->29->2090"
22457 ]
22458 },
22459 {
@@ -22479,7 +22475,7 @@
22475 "xloc": [
22476 "default-mobile.handlebars->9->392",
22477 "default.handlebars->29->1003",
22482 - "default.handlebars->29->2088",
22478 + "default.handlebars->29->2081",
22479 "default.handlebars->container->column_l->p40->3->1->p40type->3"
22480 ]
22481 },
@@ -22533,7 +22529,7 @@
22529 "zh-cht": "網格代理控制台",
22530 "xloc": [
22531 "default-mobile.handlebars->9->428",
22536 - "default.handlebars->29->1456"
22532 + "default.handlebars->29->1449"
22533 ]
22534 },
22535 {
@@ -22638,7 +22634,7 @@
22634 "zh-chs": "MeshAgent流量",
22635 "zh-cht": "MeshAgent流量",
22636 "xloc": [
22641 - "default.handlebars->29->2109"
22637 + "default.handlebars->29->2102"
22638 ]
22639 },
22640 {
@@ -22658,7 +22654,7 @@
22654 "zh-chs": "MeshAgent更新",
22655 "zh-cht": "MeshAgent更新",
22656 "xloc": [
22661 - "default.handlebars->29->2110"
22657 + "default.handlebars->29->2103"
22658 ]
22659 },
22660 {
@@ -22779,7 +22775,7 @@
22775 "zh-chs": "MeshCentral服务器同级化",
22776 "zh-cht": "MeshCentral伺服器同級化",
22777 "xloc": [
22782 - "default.handlebars->29->2108"
22778 + "default.handlebars->29->2101"
22779 ]
22780 },
22781 {
@@ -23108,7 +23104,7 @@
23104 "zh-chs": "消息调度器",
23105 "zh-cht": "電郵調度器",
23106 "xloc": [
23111 - "default.handlebars->29->2106"
23107 + "default.handlebars->29->2099"
23108 ]
23109 },
23110 {
@@ -23291,7 +23287,7 @@
23287 "zh-chs": "更多",
23288 "zh-cht": "更多",
23289 "xloc": [
23294 - "default.handlebars->29->2134"
23290 + "default.handlebars->29->2127"
23291 ]
23292 },
23293 {
@@ -23372,7 +23368,7 @@
23368 "zh-chs": "移动:“{0}”到“{1}”",
23369 "zh-cht": "移動:“{0}”到“{1}”",
23370 "xloc": [
23375 - "default.handlebars->29->1640"
23371 + "default.handlebars->29->1633"
23372 ]
23373 },
23374 {
@@ -23392,7 +23388,7 @@
23388 "zh-chs": "将设备{0}移动到组{1}",
23389 "zh-cht": "將設備{0}移動到組{1}",
23390 "xloc": [
23395 - "default.handlebars->29->1673"
23391 + "default.handlebars->29->1666"
23392 ]
23393 },
23394 {
@@ -23432,7 +23428,7 @@
23428 "zh-chs": "多路复用器",
23429 "zh-cht": "多工器",
23430 "xloc": [
23435 - "default.handlebars->29->2046"
23431 + "default.handlebars->29->2039"
23432 ]
23433 },
23434 {
@@ -23785,13 +23781,13 @@
23781 "default-mobile.handlebars->container->page_content->column_l->p10->p10desktop->deskarea3->deskarea3x->DeskTools->5->1->1",
23782 "default.handlebars->29->1285",
23783 "default.handlebars->29->1325",
23788 - "default.handlebars->29->1412",
23789 - "default.handlebars->29->1713",
23790 - "default.handlebars->29->1818",
23784 + "default.handlebars->29->1405",
23785 + "default.handlebars->29->1706",
23786 + "default.handlebars->29->1811",
23787 + "default.handlebars->29->1827",
23788 "default.handlebars->29->1834",
23792 - "default.handlebars->29->1841",
23793 - "default.handlebars->29->1886",
23794 - "default.handlebars->29->1905",
23789 + "default.handlebars->29->1879",
23790 + "default.handlebars->29->1898",
23791 "default.handlebars->29->560",
23792 "default.handlebars->29->76",
23793 "default.handlebars->29->856",
@@ -23840,7 +23836,7 @@
23836 "zh-chs": "名称1,名称2,名称3",
23837 "zh-cht": "名稱1,名稱2,名稱3",
23838 "xloc": [
23843 - "default.handlebars->29->1800"
23839 + "default.handlebars->29->1793"
23840 ]
23841 },
23842 {
@@ -24000,7 +23996,7 @@
23996 "zh-chs": "生成新的2FA备份代码",
23997 "zh-cht": "生成新的2FA備份代碼",
23998 "xloc": [
24003 - "default.handlebars->29->1680"
23999 + "default.handlebars->29->1673"
24000 ]
24001 },
24002 {
@@ -24065,7 +24061,7 @@
24061 "xloc": [
24062 "default-mobile.handlebars->9->120",
24063 "default-mobile.handlebars->9->305",
24068 - "default.handlebars->29->1572",
24064 + "default.handlebars->29->1565",
24065 "default.handlebars->29->898",
24066 "default.handlebars->container->column_l->p13->p13toolbar->1->2->1->3",
24067 "default.handlebars->container->column_l->p5->p5toolbar->1->0->p5filehead->3"
@@ -24233,7 +24229,7 @@
24229 "zh-chs": "没有桌面",
24230 "zh-cht": "沒有桌面",
24231 "xloc": [
24236 - "default.handlebars->29->1491",
24232 + "default.handlebars->29->1484",
24233 "default.handlebars->29->686",
24234 "default.handlebars->29->707"
24235 ]
@@ -24255,7 +24251,7 @@
24251 "zh-chs": "不能访问桌面",
24252 "zh-cht": "不能訪問桌面",
24253 "xloc": [
24258 - "default.handlebars->29->1452"
24254 + "default.handlebars->29->1445"
24255 ]
24256 },
24257 {
@@ -24275,8 +24271,8 @@
24271 "zh-chs": "找不到事件",
24272 "zh-cht": "找不到事件",
24273 "xloc": [
24278 - "default.handlebars->29->1689",
24279 - "default.handlebars->29->2020",
24274 + "default.handlebars->29->1682",
24275 + "default.handlebars->29->2013",
24276 "default.handlebars->29->933"
24277 ]
24278 },
@@ -24298,7 +24294,7 @@
24294 "zh-cht": "不能存取檔案",
24295 "xloc": [
24296 "default-mobile.handlebars->9->426",
24301 - "default.handlebars->29->1454"
24297 + "default.handlebars->29->1447"
24298 ]
24299 },
24300 {
@@ -24319,7 +24315,7 @@
24315 "zh-cht": "沒有檔案",
24316 "xloc": [
24317 "default-mobile.handlebars->9->449",
24322 - "default.handlebars->29->1493",
24318 + "default.handlebars->29->1486",
24319 "default.handlebars->29->683",
24320 "default.handlebars->29->704"
24321 ]
@@ -24364,8 +24360,8 @@
24360 "xloc": [
24361 "default-mobile.handlebars->9->427",
24362 "default-mobile.handlebars->9->450",
24367 - "default.handlebars->29->1455",
24368 - "default.handlebars->29->1494"
24363 + "default.handlebars->29->1448",
24364 + "default.handlebars->29->1487"
24365 ]
24366 },
24367 {
@@ -24445,7 +24441,7 @@
24441 "zh-chs": "没有成员",
24442 "zh-cht": "沒有成員",
24443 "xloc": [
24448 - "default.handlebars->29->1868"
24444 + "default.handlebars->29->1861"
24445 ]
24446 },
24447 {
@@ -24465,7 +24461,7 @@
24461 "zh-chs": "没有新的设备组",
24462 "zh-cht": "沒有新的裝置群",
24463 "xloc": [
24468 - "default.handlebars->29->1813"
24464 + "default.handlebars->29->1806"
24465 ]
24466 },
24467 {
@@ -24486,8 +24482,7 @@
24482 "zh-cht": "沒有政策",
24483 "xloc": [
24484 "default.handlebars->29->1356",
24489 - "default.handlebars->29->1384",
24490 - "default.handlebars->29->1387"
24485 + "default.handlebars->29->1384"
24486 ]
24487 },
24488 {
@@ -24511,7 +24506,7 @@
24506 "default-mobile.handlebars->9->410",
24507 "default-mobile.handlebars->9->457",
24508 "default.handlebars->29->1299",
24514 - "default.handlebars->29->1501",
24509 + "default.handlebars->29->1494",
24510 "default.handlebars->29->697",
24511 "default.handlebars->29->718"
24512 ]
@@ -24556,7 +24551,7 @@
24551 "zh-cht": "沒有終端",
24552 "xloc": [
24553 "default-mobile.handlebars->9->448",
24559 - "default.handlebars->29->1492",
24554 + "default.handlebars->29->1485",
24555 "default.handlebars->29->682",
24556 "default.handlebars->29->703"
24557 ]
@@ -24579,7 +24574,7 @@
24574 "zh-cht": "不能訪問終端",
24575 "xloc": [
24576 "default-mobile.handlebars->9->425",
24582 - "default.handlebars->29->1453"
24577 + "default.handlebars->29->1446"
24578 ]
24579 },
24580 {
@@ -24599,7 +24594,7 @@
24594 "zh-chs": "没有工具(MeshCmd /路由器)",
24595 "zh-cht": "沒有工具(MeshCmd /路由器)",
24596 "xloc": [
24602 - "default.handlebars->29->1814"
24597 + "default.handlebars->29->1807"
24598 ]
24599 },
24600 {
@@ -24627,8 +24622,8 @@
24622 "zh-chs": "没有共同的设备组",
24623 "zh-cht": "沒有共同的裝置群",
24624 "xloc": [
24630 - "default.handlebars->29->1874",
24631 - "default.handlebars->29->1992"
24625 + "default.handlebars->29->1867",
24626 + "default.handlebars->29->1985"
24627 ]
24628 },
24629 {
@@ -24748,8 +24743,8 @@
24743 "zh-chs": "没有共同的设备",
24744 "zh-cht": "沒有共同的裝置",
24745 "xloc": [
24751 - "default.handlebars->29->1880",
24752 - "default.handlebars->29->2004"
24746 + "default.handlebars->29->1873",
24747 + "default.handlebars->29->1997"
24748 ]
24749 },
24750 {
@@ -24769,7 +24764,7 @@
24764 "zh-chs": "该设备组中没有设备。",
24765 "zh-cht": "該裝置群中沒有裝置。",
24766 "xloc": [
24772 - "default.handlebars->29->1544"
24767 + "default.handlebars->29->1537"
24768 ]
24769 },
24770 {
@@ -24852,7 +24847,7 @@
24847 "zh-chs": "找不到群组。",
24848 "zh-cht": "找不到群組。",
24849 "xloc": [
24855 - "default.handlebars->29->1817"
24850 + "default.handlebars->29->1810"
24851 ]
24852 },
24853 {
@@ -24973,7 +24968,7 @@
24968 "zh-chs": "没有录音。",
24969 "zh-cht": "沒有錄音。",
24970 "xloc": [
24976 - "default.handlebars->29->2022"
24971 + "default.handlebars->29->2015"
24972 ]
24973 },
24974 {
@@ -24993,7 +24988,7 @@
24988 "zh-chs": "没有服务器权限",
24989 "zh-cht": "沒有伺服器權限",
24990 "xloc": [
24996 - "default.handlebars->29->1900"
24991 + "default.handlebars->29->1893"
24992 ]
24993 },
24994 {
@@ -25013,7 +25008,7 @@
25008 "zh-chs": "没有用户组成员身份",
25009 "zh-cht": "沒有用戶群成員身份",
25010 "xloc": [
25016 - "default.handlebars->29->1998"
25011 + "default.handlebars->29->1991"
25012 ]
25013 },
25014 {
@@ -25033,7 +25028,7 @@
25028 "zh-chs": "未找到相应的用户。",
25029 "zh-cht": "未找到相應的用戶。",
25030 "xloc": [
25036 - "default.handlebars->29->1721"
25031 + "default.handlebars->29->1714"
25032 ]
25033 },
25034 {
@@ -25129,19 +25124,19 @@
25124 "default.handlebars->29->1347",
25125 "default.handlebars->29->1352",
25126 "default.handlebars->29->1354",
25132 - "default.handlebars->29->1535",
25133 - "default.handlebars->29->1554",
25134 - "default.handlebars->29->1559",
25135 - "default.handlebars->29->1697",
25127 + "default.handlebars->29->1528",
25128 + "default.handlebars->29->1547",
25129 + "default.handlebars->29->1552",
25130 + "default.handlebars->29->1690",
25131 "default.handlebars->29->177",
25137 - "default.handlebars->29->1838",
25138 - "default.handlebars->29->1840",
25139 - "default.handlebars->29->1857",
25140 - "default.handlebars->29->1919",
25132 + "default.handlebars->29->1831",
25133 + "default.handlebars->29->1833",
25134 + "default.handlebars->29->1850",
25135 + "default.handlebars->29->1912",
25136 "default.handlebars->29->192",
25142 - "default.handlebars->29->1928",
25143 - "default.handlebars->29->1932",
25144 - "default.handlebars->29->1944",
25137 + "default.handlebars->29->1921",
25138 + "default.handlebars->29->1925",
25139 + "default.handlebars->29->1937",
25140 "default.handlebars->29->208",
25141 "default.handlebars->29->209",
25142 "default.handlebars->29->557",
@@ -25296,8 +25291,8 @@
25291 "zh-chs": "未连接",
25292 "zh-cht": "未連接",
25293 "xloc": [
25299 - "default.handlebars->29->1531",
25300 - "default.handlebars->29->1539"
25294 + "default.handlebars->29->1524",
25295 + "default.handlebars->29->1532"
25296 ]
25297 },
25298 {
@@ -25338,7 +25333,7 @@
25333 "zh-chs": "不在服务器上",
25334 "zh-cht": "不在伺服器上",
25335 "xloc": [
25341 - "default.handlebars->29->2038"
25336 + "default.handlebars->29->2031"
25337 ]
25338 },
25339 {
@@ -25358,8 +25353,8 @@
25353 "zh-chs": "没有设置",
25354 "zh-cht": "沒有設置",
25355 "xloc": [
25361 - "default.handlebars->29->1906",
25362 - "default.handlebars->29->1907"
25356 + "default.handlebars->29->1899",
25357 + "default.handlebars->29->1900"
25358 ]
25359 },
25360 {
@@ -25379,7 +25374,7 @@
25374 "zh-chs": "未经审核的",
25375 "zh-cht": "未經審核的",
25376 "xloc": [
25382 - "default.handlebars->29->1974"
25377 + "default.handlebars->29->1967"
25378 ]
25379 },
25380 {
@@ -25400,7 +25395,7 @@
25395 "zh-cht": "筆記",
25396 "xloc": [
25397 "default.handlebars->29->1362",
25403 - "default.handlebars->29->1952",
25398 + "default.handlebars->29->1945",
25399 "default.handlebars->29->629",
25400 "default.handlebars->29->691",
25401 "default.handlebars->29->712",
@@ -25445,7 +25440,7 @@
25440 "zh-cht": "通知設定",
25441 "xloc": [
25442 "default.handlebars->29->1260",
25448 - "default.handlebars->29->1530",
25443 + "default.handlebars->29->1523",
25444 "default.handlebars->container->column_l->p2->p2info->p2AccountActions->3->10"
25445 ]
25446 },
@@ -25466,7 +25461,7 @@
25461 "zh-chs": "通知设置还必须在帐户设置中启用。",
25462 "zh-cht": "通知設定還必須在帳戶設定中啟用。",
25463 "xloc": [
25469 - "default.handlebars->29->1526"
25464 + "default.handlebars->29->1519"
25465 ]
25466 },
25467 {
@@ -25527,7 +25522,7 @@
25522 "zh-cht": "通知",
25523 "xloc": [
25524 "default.handlebars->29->189",
25530 - "default.handlebars->29->1958"
25525 + "default.handlebars->29->1951"
25526 ]
25527 },
25528 {
@@ -25567,9 +25562,9 @@
25562 "zh-chs": "通知使用者",
25563 "zh-cht": "通知使用者",
25564 "xloc": [
25570 - "default.handlebars->29->1420",
25571 - "default.handlebars->29->1424",
25572 - "default.handlebars->29->1427"
25565 + "default.handlebars->29->1413",
25566 + "default.handlebars->29->1417",
25567 + "default.handlebars->29->1420"
25568 ]
25569 },
25570 {
@@ -25589,7 +25584,7 @@
25584 "zh-chs": "通知{0}",
25585 "zh-cht": "通知{0}",
25586 "xloc": [
25592 - "default.handlebars->29->1760"
25587 + "default.handlebars->29->1753"
25588 ]
25589 },
25590 {
@@ -25681,7 +25676,7 @@
25676 "zh-chs": "发生在{0}",
25677 "zh-cht": "發生在{0}",
25678 "xloc": [
25684 - "default.handlebars->29->2052"
25679 + "default.handlebars->29->2045"
25680 ]
25681 },
25682 {
@@ -25708,7 +25703,7 @@
25703 "zh-chs": "离线用户",
25704 "zh-cht": "離線用戶",
25705 "xloc": [
25711 - "default.handlebars->29->1718"
25706 + "default.handlebars->29->1711"
25707 ]
25708 },
25709 {
@@ -25749,7 +25744,7 @@
25744 "zh-chs": "一天",
25745 "zh-cht": "一天",
25746 "xloc": [
25752 - "default.handlebars->29->1694"
25747 + "default.handlebars->29->1687"
25748 ]
25749 },
25750 {
@@ -25812,7 +25807,7 @@
25807 "zh-chs": "在线用户",
25808 "zh-cht": "在線用戶",
25809 "xloc": [
25815 - "default.handlebars->29->1717"
25810 + "default.handlebars->29->1710"
25811 ]
25812 },
25813 {
@@ -25996,7 +25991,7 @@
25991 "zh-chs": "开头:{0}",
25992 "zh-cht": "開場:{0}",
25993 "xloc": [
25999 - "default.handlebars->29->1608"
25994 + "default.handlebars->29->1601"
25995 ]
25996 },
25997 {
@@ -26042,8 +26037,8 @@
26037 "zh-cht": "操作",
26038 "xloc": [
26039 "default-mobile.handlebars->9->269",
26045 - "default.handlebars->29->1744",
26046 - "default.handlebars->29->1825",
26040 + "default.handlebars->29->1737",
26041 + "default.handlebars->29->1818",
26042 "default.handlebars->29->468",
26043 "default.handlebars->29->483",
26044 "default.handlebars->29->757"
@@ -26251,7 +26246,7 @@
26246 "zh-chs": "部分的",
26247 "zh-cht": "部分的",
26248 "xloc": [
26254 - "default.handlebars->29->1732"
26249 + "default.handlebars->29->1725"
26250 ]
26251 },
26252 {
@@ -26327,7 +26322,7 @@
26322 "zh-chs": "部分权限",
26323 "zh-cht": "部分權限",
26324 "xloc": [
26330 - "default.handlebars->29->1903"
26325 + "default.handlebars->29->1896"
26326 ]
26327 },
26328 {
@@ -26368,12 +26363,12 @@
26363 "zh-cht": "密碼",
26364 "xloc": [
26365 "default-mobile.handlebars->9->275",
26371 - "default.handlebars->29->1788",
26372 - "default.handlebars->29->1789",
26373 - "default.handlebars->29->1924",
26374 - "default.handlebars->29->1926",
26375 - "default.handlebars->29->1977",
26376 - "default.handlebars->29->1978",
26366 + "default.handlebars->29->1781",
26367 + "default.handlebars->29->1782",
26368 + "default.handlebars->29->1917",
26369 + "default.handlebars->29->1919",
26370 + "default.handlebars->29->1970",
26371 + "default.handlebars->29->1971",
26372 "default.handlebars->29->296",
26373 "default.handlebars->29->327",
26374 "default.handlebars->29->772",
@@ -26517,7 +26512,7 @@
26512 "zh-chs": "密码提示",
26513 "zh-cht": "密碼提示",
26514 "xloc": [
26520 - "default.handlebars->29->1979"
26515 + "default.handlebars->29->1972"
26516 ]
26517 },
26518 {
@@ -26558,7 +26553,7 @@
26553 "zh-chs": "密码不符",
26554 "zh-cht": "密碼不符",
26555 "xloc": [
26561 - "default.handlebars->29->1393"
26556 + "default.handlebars->29->1390"
26557 ]
26558 },
26559 {
@@ -26600,8 +26595,8 @@
26595 "zh-chs": "密码*",
26596 "zh-cht": "密碼*",
26597 "xloc": [
26603 - "default.handlebars->29->1391",
26604 - "default.handlebars->29->1392"
26598 + "default.handlebars->29->1388",
26599 + "default.handlebars->29->1389"
26600 ]
26601 },
26602 {
@@ -26663,7 +26658,7 @@
26658 "default-mobile.handlebars->9->318",
26659 "default-mobile.handlebars->container->page_content->column_l->p10->p10files->p13toolbar->1->2->1->3",
26660 "default-mobile.handlebars->container->page_content->column_l->p5->p5myfiles->p5toolbar->1->0->1->3",
26666 - "default.handlebars->29->1581",
26661 + "default.handlebars->29->1574",
26662 "default.handlebars->29->886",
26663 "default.handlebars->29->912",
26664 "default.handlebars->container->column_l->p12->termTable->1->1->6->1->3",
@@ -26927,7 +26922,7 @@
26922 "zh-chs": "执行电源操作= {0},强制执行= {1}",
26923 "zh-cht": "執行電源操作= {0},強制執行= {1}",
26924 "xloc": [
26930 - "default.handlebars->29->1613"
26925 + "default.handlebars->29->1606"
26926 ]
26927 },
26928 {
@@ -26948,8 +26943,8 @@
26943 "zh-cht": "權限",
26944 "xloc": [
26945 "default-mobile.handlebars->9->460",
26951 - "default.handlebars->29->1504",
26952 - "default.handlebars->29->1716"
26946 + "default.handlebars->29->1497",
26947 + "default.handlebars->29->1709"
26948 ]
26949 },
26950 {
@@ -26995,7 +26990,7 @@
26990 "default.handlebars->29->1029",
26991 "default.handlebars->29->1032",
26992 "default.handlebars->29->159",
26998 - "default.handlebars->29->1969"
26993 + "default.handlebars->29->1962"
26994 ]
26995 },
26996 {
@@ -27015,7 +27010,7 @@
27010 "zh-chs": "电话号码",
27011 "zh-cht": "電話號碼",
27012 "xloc": [
27018 - "default.handlebars->29->1918"
27013 + "default.handlebars->29->1911"
27014 ]
27015 },
27016 {
@@ -27037,7 +27032,7 @@
27032 "xloc": [
27033 "default-mobile.handlebars->9->66",
27034 "default.handlebars->29->1031",
27040 - "default.handlebars->29->1968"
27035 + "default.handlebars->29->1961"
27036 ]
27037 },
27038 {
@@ -27197,7 +27192,7 @@
27192 "zh-cht": "外掛指令",
27193 "xloc": [
27194 "default.handlebars->29->199",
27200 - "default.handlebars->29->2131"
27195 + "default.handlebars->29->2124"
27196 ]
27197 },
27198 {
@@ -27482,7 +27477,7 @@
27477 "zh-chs": "电源状态",
27478 "zh-cht": "電源狀態",
27479 "xloc": [
27485 - "default.handlebars->29->1537",
27480 + "default.handlebars->29->1530",
27481 "default.handlebars->container->column_l->p21->3->1->meshPowerChartDiv->1"
27482 ]
27483 },
@@ -27592,7 +27587,7 @@
27587 "zh-chs": "存在于服务器上",
27588 "zh-cht": "存在於伺服器上",
27589 "xloc": [
27595 - "default.handlebars->29->2037"
27590 + "default.handlebars->29->2030"
27591 ]
27592 },
27593 {
@@ -27738,7 +27733,7 @@
27733 "zh-chs": "处理控制台命令:“{0}”",
27734 "zh-cht": "處理控制台命令:“{0}”",
27735 "xloc": [
27741 - "default.handlebars->29->1605"
27736 + "default.handlebars->29->1598"
27737 ]
27738 },
27739 {
@@ -27798,9 +27793,9 @@
27793 "zh-chs": "用户同意提示",
27794 "zh-cht": "用戶同意提示",
27795 "xloc": [
27801 - "default.handlebars->29->1421",
27802 - "default.handlebars->29->1425",
27803 - "default.handlebars->29->1428"
27796 + "default.handlebars->29->1414",
27797 + "default.handlebars->29->1418",
27798 + "default.handlebars->29->1421"
27799 ]
27800 },
27801 {
@@ -27820,7 +27815,7 @@
27815 "zh-chs": "协议",
27816 "zh-cht": "協議",
27817 "xloc": [
27823 - "default.handlebars->29->2035",
27818 + "default.handlebars->29->2028",
27819 "player.handlebars->3->16"
27820 ]
27821 },
@@ -27880,7 +27875,7 @@
27875 "zh-cht": "公開鏈結",
27876 "xloc": [
27877 "default-mobile.handlebars->9->115",
27883 - "default.handlebars->29->1566"
27878 + "default.handlebars->29->1559"
27879 ]
27880 },
27881 {
@@ -28187,7 +28182,7 @@
28182 "zh-chs": "RSS",
28183 "zh-cht": "RSS",
28184 "xloc": [
28190 - "default.handlebars->29->2101"
28185 + "default.handlebars->29->2094"
28186 ]
28187 },
28188 {
@@ -28207,7 +28202,7 @@
28202 "zh-chs": "随机密码。",
28203 "zh-cht": "隨機密碼。",
28204 "xloc": [
28210 - "default.handlebars->29->1790"
28205 + "default.handlebars->29->1783"
28206 ]
28207 },
28208 {
@@ -28247,7 +28242,7 @@
28242 "zh-chs": "重新激活英特尔&reg;AMT",
28243 "zh-cht": "重新啟動Intel&reg; AMT",
28244 "xloc": [
28250 - "default.handlebars->29->1395"
28245 + "default.handlebars->29->1392"
28246 ]
28247 },
28248 {
@@ -28267,9 +28262,9 @@
28262 "zh-chs": "真正的名字",
28263 "zh-cht": "真正的名字",
28264 "xloc": [
28270 - "default.handlebars->29->1915",
28271 - "default.handlebars->29->1917",
28272 - "default.handlebars->29->1970"
28265 + "default.handlebars->29->1908",
28266 + "default.handlebars->29->1910",
28267 + "default.handlebars->29->1963"
28268 ]
28269 },
28270 {
@@ -28289,7 +28284,7 @@
28284 "zh-chs": "境界",
28285 "zh-cht": "境界",
28286 "xloc": [
28292 - "default.handlebars->29->1799"
28287 + "default.handlebars->29->1792"
28288 ]
28289 },
28290 {
@@ -28344,7 +28339,7 @@
28339 "en": "Record sessions",
28340 "nl": "Sessies opnemen",
28341 "xloc": [
28347 - "default.handlebars->29->1431"
28342 + "default.handlebars->29->1424"
28343 ]
28344 },
28345 {
@@ -28364,7 +28359,7 @@
28359 "zh-chs": "记录细节",
28360 "zh-cht": "記錄細節",
28361 "xloc": [
28367 - "default.handlebars->29->2049"
28362 + "default.handlebars->29->2042"
28363 ]
28364 },
28365 {
@@ -28406,7 +28401,7 @@
28401 "xloc": [
28402 "default-mobile.handlebars->9->121",
28403 "default-mobile.handlebars->9->306",
28409 - "default.handlebars->29->1573",
28404 + "default.handlebars->29->1566",
28405 "default.handlebars->29->899"
28406 ]
28407 },
@@ -28525,7 +28520,7 @@
28520 "zh-chs": "中继数量",
28521 "zh-cht": "中繼數量",
28522 "xloc": [
28528 - "default.handlebars->29->2080"
28523 + "default.handlebars->29->2073"
28524 ]
28525 },
28526 {
@@ -28545,7 +28540,7 @@
28540 "zh-chs": "中继错误",
28541 "zh-cht": "中繼錯誤",
28542 "xloc": [
28548 - "default.handlebars->29->2073"
28543 + "default.handlebars->29->2066"
28544 ]
28545 },
28546 {
@@ -28565,8 +28560,8 @@
28560 "zh-chs": "中继连接",
28561 "zh-cht": "中繼連接",
28562 "xloc": [
28568 - "default.handlebars->29->2079",
28569 - "default.handlebars->29->2095"
28563 + "default.handlebars->29->2072",
28564 + "default.handlebars->29->2088"
28565 ]
28566 },
28567 {
@@ -28711,7 +28706,7 @@
28706 "nl": "Externe opdrachten",
28707 "xloc": [
28708 "default-mobile.handlebars->9->435",
28714 - "default.handlebars->29->1463"
28709 + "default.handlebars->29->1456"
28710 ]
28711 },
28712 {
@@ -28733,8 +28728,8 @@
28728 "xloc": [
28729 "default-mobile.handlebars->9->422",
28730 "default-mobile.handlebars->9->442",
28736 - "default.handlebars->29->1449",
28737 - "default.handlebars->29->1485"
28731 + "default.handlebars->29->1442",
28732 + "default.handlebars->29->1478"
28733 ]
28734 },
28735 {
@@ -28777,8 +28772,8 @@
28772 "zh-chs": "远程桌面连接栏已激活/更新",
28773 "zh-cht": "遠程桌面連接欄已激活/更新",
28774 "xloc": [
28780 - "default.handlebars->29->1619",
28781 - "default.handlebars->29->1625"
28775 + "default.handlebars->29->1612",
28776 + "default.handlebars->29->1618"
28777 ]
28778 },
28779 {
@@ -28798,7 +28793,7 @@
28793 "zh-chs": "远程桌面连接栏失败或不受支持",
28794 "zh-cht": "遠程桌面連接欄失敗或不受支持",
28795 "xloc": [
28801 - "default.handlebars->29->1620"
28796 + "default.handlebars->29->1613"
28797 ]
28798 },
28799 {
@@ -28818,7 +28813,7 @@
28813 "zh-chs": "远程桌面连接栏失败或不受支持",
28814 "zh-cht": "遠程桌面連接欄失敗或不受支持",
28815 "xloc": [
28821 - "default.handlebars->29->1626"
28816 + "default.handlebars->29->1619"
28817 ]
28818 },
28819 {
@@ -28838,9 +28833,9 @@
28833 "zh-chs": "本地用户强行关闭了远程桌面连接",
28834 "zh-cht": "本地用戶強行關閉了遠程桌面連接",
28835 "xloc": [
28841 - "default.handlebars->29->1617",
28842 - "default.handlebars->29->1621",
28843 - "default.handlebars->29->1627"
28836 + "default.handlebars->29->1610",
28837 + "default.handlebars->29->1614",
28838 + "default.handlebars->29->1620"
28839 ]
28840 },
28841 {
@@ -28963,8 +28958,8 @@
28958 "xloc": [
28959 "default-mobile.handlebars->9->423",
28960 "default-mobile.handlebars->9->447",
28966 - "default.handlebars->29->1450",
28967 - "default.handlebars->29->1490"
28961 + "default.handlebars->29->1443",
28962 + "default.handlebars->29->1483"
28963 ]
28964 },
28965 {
@@ -29099,8 +29094,8 @@
29094 "zh-chs": "删除设备组权限",
29095 "zh-cht": "刪除裝置群權限",
29096 "xloc": [
29102 - "default.handlebars->29->1884",
29103 - "default.handlebars->29->2018"
29097 + "default.handlebars->29->1877",
29098 + "default.handlebars->29->2011"
29099 ]
29100 },
29101 {
@@ -29120,8 +29115,8 @@
29115 "zh-chs": "删除设备权限",
29116 "zh-cht": "刪除裝置權限",
29117 "xloc": [
29123 - "default.handlebars->29->1882",
29124 - "default.handlebars->29->2005"
29118 + "default.handlebars->29->1875",
29119 + "default.handlebars->29->1998"
29120 ]
29121 },
29122 {
@@ -29158,7 +29153,7 @@
29153 "zh-chs": "删除用户组成员身份",
29154 "zh-cht": "刪除用戶群成員身份",
29155 "xloc": [
29161 - "default.handlebars->29->2014"
29156 + "default.handlebars->29->2007"
29157 ]
29158 },
29159 {
@@ -29178,8 +29173,8 @@
29173 "zh-chs": "删除用户组权限",
29174 "zh-cht": "刪除用戶群權限",
29175 "xloc": [
29181 - "default.handlebars->29->1509",
29182 - "default.handlebars->29->2010"
29176 + "default.handlebars->29->1502",
29177 + "default.handlebars->29->2003"
29178 ]
29179 },
29180 {
@@ -29199,7 +29194,7 @@
29194 "zh-chs": "删除用户成员资格",
29195 "zh-cht": "刪除用戶成員資格",
29196 "xloc": [
29202 - "default.handlebars->29->1892"
29197 + "default.handlebars->29->1885"
29198 ]
29199 },
29200 {
@@ -29219,8 +29214,8 @@
29214 "zh-chs": "删除用户权限",
29215 "zh-cht": "刪除用戶權限",
29216 "xloc": [
29222 - "default.handlebars->29->1507",
29223 - "default.handlebars->29->2007"
29217 + "default.handlebars->29->1500",
29218 + "default.handlebars->29->2000"
29219 ]
29220 },
29221 {
@@ -29240,7 +29235,7 @@
29235 "zh-chs": "删除所有两因素认证。",
29236 "zh-cht": "刪除所有二因子鑑別。",
29237 "xloc": [
29243 - "default.handlebars->29->1982"
29238 + "default.handlebars->29->1975"
29239 ]
29240 },
29241 {
@@ -29260,7 +29255,7 @@
29255 "zh-chs": "删除此用户标识的所有先前事件。",
29256 "zh-cht": "刪除此用戶標識的所有先前事件。",
29257 "xloc": [
29263 - "default.handlebars->29->1791"
29258 + "default.handlebars->29->1784"
29259 ]
29260 },
29261 {
@@ -29280,7 +29275,7 @@
29275 "zh-chs": "断开连接后移除设备",
29276 "zh-cht": "斷開連接後删除裝置",
29277 "xloc": [
29283 - "default.handlebars->29->1429"
29278 + "default.handlebars->29->1422"
29279 ]
29280 },
29281 {
@@ -29381,7 +29376,7 @@
29376 "zh-chs": "删除此用户",
29377 "zh-cht": "刪除此用戶",
29378 "xloc": [
29384 - "default.handlebars->29->1960"
29379 + "default.handlebars->29->1953"
29380 ]
29381 },
29382 {
@@ -29401,7 +29396,7 @@
29396 "zh-chs": "删除用户组成员身份",
29397 "zh-cht": "刪除用戶群成員身份",
29398 "xloc": [
29404 - "default.handlebars->29->1996"
29399 + "default.handlebars->29->1989"
29400 ]
29401 },
29402 {
@@ -29421,7 +29416,7 @@
29416 "zh-chs": "删除此设备的用户组权限",
29417 "zh-cht": "刪除此裝置的用戶群權限",
29418 "xloc": [
29424 - "default.handlebars->29->1878"
29419 + "default.handlebars->29->1871"
29420 ]
29421 },
29422 {
@@ -29441,7 +29436,7 @@
29436 "zh-chs": "删除此设备组的用户组权限",
29437 "zh-cht": "刪除此裝置群的用戶群權限",
29438 "xloc": [
29444 - "default.handlebars->29->1872",
29439 + "default.handlebars->29->1865",
29440 "default.handlebars->29->675"
29441 ]
29442 },
@@ -29463,9 +29458,9 @@
29458 "zh-cht": "刪除此裝置群的用戶權限",
29459 "xloc": [
29460 "default.handlebars->29->1379",
29466 - "default.handlebars->29->1866",
29467 - "default.handlebars->29->1990",
29468 - "default.handlebars->29->2002",
29461 + "default.handlebars->29->1859",
29462 + "default.handlebars->29->1983",
29463 + "default.handlebars->29->1995",
29464 "default.handlebars->29->676"
29465 ]
29466 },
@@ -29486,7 +29481,7 @@
29481 "zh-chs": "删除身份验证应用程序",
29482 "zh-cht": "刪除身份驗證應用程序",
29483 "xloc": [
29489 - "default.handlebars->29->1679"
29484 + "default.handlebars->29->1672"
29485 ]
29486 },
29487 {
@@ -29506,7 +29501,7 @@
29501 "zh-chs": "从设备组{1}中删除了设备{0}",
29502 "zh-cht": "從設備組{1}中刪除了設備{0}",
29503 "xloc": [
29509 - "default.handlebars->29->1675"
29504 + "default.handlebars->29->1668"
29505 ]
29506 },
29507 {
@@ -29526,7 +29521,7 @@
29521 "zh-chs": "已删除用户{0}的电话号码",
29522 "zh-cht": "已刪除用戶{0}的電話號碼",
29523 "xloc": [
29529 - "default.handlebars->29->1685"
29524 + "default.handlebars->29->1678"
29525 ]
29526 },
29527 {
@@ -29546,7 +29541,7 @@
29541 "zh-chs": "移除安全密钥",
29542 "zh-cht": "移除安全密鑰",
29543 "xloc": [
29549 - "default.handlebars->29->1682"
29544 + "default.handlebars->29->1675"
29545 ]
29546 },
29547 {
@@ -29566,9 +29561,9 @@
29561 "zh-chs": "删除了{0}的用户设备权限",
29562 "zh-cht": "刪除了{0}的用戶設備權限",
29563 "xloc": [
29569 - "default.handlebars->29->1648",
29570 - "default.handlebars->29->1669",
29571 - "default.handlebars->29->1674"
29564 + "default.handlebars->29->1641",
29565 + "default.handlebars->29->1662",
29566 + "default.handlebars->29->1667"
29567 ]
29568 },
29569 {
@@ -29588,7 +29583,7 @@
29583 "zh-chs": "从设备组{1}中删除了用户组{0}",
29584 "zh-cht": "從設備組{1}中刪除了用戶組{0}",
29585 "xloc": [
29591 - "default.handlebars->29->1658"
29586 + "default.handlebars->29->1651"
29587 ]
29588 },
29589 {
@@ -29608,7 +29603,7 @@
29603 "zh-chs": "从设备组{1}中删除了用户{0}",
29604 "zh-cht": "已從設備組{1}中刪除用戶{0}",
29605 "xloc": [
29611 - "default.handlebars->29->1671"
29606 + "default.handlebars->29->1664"
29607 ]
29608 },
29609 {
@@ -29628,8 +29623,8 @@
29623 "zh-chs": "从用户组{1}中删除了用户{0}",
29624 "zh-cht": "從用戶組{1}中刪除了用戶{0}",
29625 "xloc": [
29631 - "default.handlebars->29->1650",
29632 - "default.handlebars->29->1660"
29626 + "default.handlebars->29->1643",
29627 + "default.handlebars->29->1653"
29628 ]
29629 },
29630 {
@@ -29653,7 +29648,7 @@
29648 "default-mobile.handlebars->9->310",
29649 "default-mobile.handlebars->container->page_content->column_l->p10->p10files->p13toolbar->1->2->1->1",
29650 "default-mobile.handlebars->container->page_content->column_l->p5->p5myfiles->p5toolbar->1->0->1->1",
29656 - "default.handlebars->29->1577",
29651 + "default.handlebars->29->1570",
29652 "default.handlebars->29->526",
29653 "default.handlebars->29->903",
29654 "default.handlebars->container->column_l->p13->p13toolbar->1->2->1->3",
@@ -29678,7 +29673,7 @@
29673 "zh-chs": "重命名:“{0}”为“{1}”",
29674 "zh-cht": "重命名:“{0}”為“{1}”",
29675 "xloc": [
29681 - "default.handlebars->29->1636"
29676 + "default.handlebars->29->1629"
29677 ]
29678 },
29679 {
@@ -29698,7 +29693,7 @@
29693 "zh-chs": "报告日",
29694 "zh-cht": "報告日",
29695 "xloc": [
29701 - "default.handlebars->29->1695"
29696 + "default.handlebars->29->1688"
29697 ]
29698 },
29699 {
@@ -29718,7 +29713,7 @@
29713 "zh-chs": "报告类型",
29714 "zh-cht": "報告類型",
29715 "xloc": [
29721 - "default.handlebars->29->1690"
29716 + "default.handlebars->29->1683"
29717 ]
29718 },
29719 {
@@ -29759,8 +29754,8 @@
29754 "zh-cht": "要求:{0}。",
29755 "xloc": [
29756 "default-mobile.handlebars->9->89",
29762 - "default.handlebars->29->1796",
29763 - "default.handlebars->29->1980"
29757 + "default.handlebars->29->1789",
29758 + "default.handlebars->29->1973"
29759 ]
29760 },
29761 {
@@ -29853,7 +29848,7 @@
29848 "nl": "Reset / uitschakelen",
29849 "xloc": [
29850 "default-mobile.handlebars->9->436",
29856 - "default.handlebars->29->1464"
29851 + "default.handlebars->29->1457"
29852 ]
29853 },
29854 {
@@ -29967,7 +29962,7 @@
29962 "nl": "Reset/Uit",
29963 "xloc": [
29964 "default-mobile.handlebars->9->456",
29970 - "default.handlebars->29->1500",
29965 + "default.handlebars->29->1493",
29966 "default.handlebars->29->696",
29967 "default.handlebars->29->717"
29968 ]
@@ -30078,7 +30073,7 @@
30073 "zh-chs": "限制条件",
30074 "zh-cht": "限制條件",
30075 "xloc": [
30081 - "default.handlebars->29->1904"
30076 + "default.handlebars->29->1897"
30077 ]
30078 },
30079 {
@@ -30160,7 +30155,7 @@
30155 "xloc": [
30156 "default-mobile.handlebars->9->107",
30157 "default-mobile.handlebars->9->300",
30163 - "default.handlebars->29->1545",
30158 + "default.handlebars->29->1538",
30159 "default.handlebars->29->893"
30160 ]
30161 },
@@ -30422,19 +30417,19 @@
30417 "zh-chs": "运行命令",
30418 "zh-cht": "運行命令",
30419 "xloc": [
30425 - "default.handlebars->29->1612"
30420 + "default.handlebars->29->1605"
30421 ]
30422 },
30423 {
30424 "en": "Running commands as user",
30425 "xloc": [
30431 - "default.handlebars->29->1687"
30426 + "default.handlebars->29->1680"
30427 ]
30428 },
30429 {
30430 "en": "Running commands as user if possible",
30431 "xloc": [
30437 - "default.handlebars->29->1688"
30432 + "default.handlebars->29->1681"
30433 ]
30434 },
30435 {
@@ -30494,8 +30489,8 @@
30489 "zh-chs": "短信",
30490 "zh-cht": "短信",
30491 "xloc": [
30497 - "default.handlebars->29->1949",
30498 - "default.handlebars->29->1954",
30492 + "default.handlebars->29->1942",
30493 + "default.handlebars->29->1947",
30494 "login-mobile.handlebars->container->page_content->column_l->1->1->0->1->tokenpanel->1->7->1->4->1->3",
30495 "login.handlebars->container->column_l->centralTable->1->0->logincell->resettokenpanel->1->5->1->2->1->3",
30496 "login.handlebars->container->column_l->centralTable->1->0->logincell->tokenpanel->1->7->1->4->1->3",
@@ -30520,7 +30515,7 @@
30515 "zh-chs": "此用户的短信功能电话号码。",
30516 "zh-cht": "此用戶的短信功能電話號碼。",
30517 "xloc": [
30523 - "default.handlebars->29->1966"
30518 + "default.handlebars->29->1959"
30519 ]
30520 },
30521 {
@@ -31070,7 +31065,7 @@
31065 "xloc": [
31066 "default-mobile.handlebars->9->276",
31067 "default-mobile.handlebars->9->369",
31073 - "default.handlebars->29->1950",
31068 + "default.handlebars->29->1943",
31069 "default.handlebars->29->297",
31070 "default.handlebars->29->773",
31071 "default.handlebars->29->980"
@@ -31093,7 +31088,7 @@
31088 "zh-chs": "安全密钥",
31089 "zh-cht": "安全密鑰",
31090 "xloc": [
31096 - "default.handlebars->29->1947"
31091 + "default.handlebars->29->1940"
31092 ]
31093 },
31094 {
@@ -31133,9 +31128,9 @@
31128 "zh-chs": "全选",
31129 "zh-cht": "全選",
31130 "xloc": [
31136 - "default.handlebars->29->1569",
31137 - "default.handlebars->29->1740",
31138 - "default.handlebars->29->1823",
31131 + "default.handlebars->29->1562",
31132 + "default.handlebars->29->1733",
31133 + "default.handlebars->29->1816",
31134 "default.handlebars->29->455",
31135 "default.handlebars->29->895",
31136 "default.handlebars->29->897",
@@ -31164,9 +31159,9 @@
31159 "zh-chs": "选择无",
31160 "zh-cht": "選擇無",
31161 "xloc": [
31167 - "default.handlebars->29->1568",
31168 - "default.handlebars->29->1739",
31169 - "default.handlebars->29->1822",
31162 + "default.handlebars->29->1561",
31163 + "default.handlebars->29->1732",
31164 + "default.handlebars->29->1815",
31165 "default.handlebars->29->454",
31166 "default.handlebars->29->896",
31167 "default.handlebars->meshContextMenu->cxselectnone"
@@ -31269,8 +31264,8 @@
31264 "zh-chs": "选择要对所有选定用户执行的操作。",
31265 "zh-cht": "選擇要對所有選定用戶執行的操作。",
31266 "xloc": [
31272 - "default.handlebars->29->1743",
31273 - "default.handlebars->29->1824"
31267 + "default.handlebars->29->1736",
31268 + "default.handlebars->29->1817"
31269 ]
31270 },
31271 {
@@ -31333,7 +31328,7 @@
31328 "zh-cht": "僅自我事件",
31329 "xloc": [
31330 "default-mobile.handlebars->9->452",
31336 - "default.handlebars->29->1496"
31331 + "default.handlebars->29->1489"
31332 ]
31333 },
31334 {
@@ -31376,7 +31371,7 @@
31371 "zh-chs": "发电邮",
31372 "zh-cht": "發電郵",
31373 "xloc": [
31379 - "default.handlebars->29->1754"
31374 + "default.handlebars->29->1747"
31375 ]
31376 },
31377 {
@@ -31437,7 +31432,7 @@
31432 "zh-chs": "发送短信",
31433 "zh-cht": "發送簡訊",
31434 "xloc": [
31440 - "default.handlebars->29->1752"
31435 + "default.handlebars->29->1745"
31436 ]
31437 },
31438 {
@@ -31457,7 +31452,7 @@
31452 "zh-chs": "发送短信给该用户",
31453 "zh-cht": "發送短信給該用戶",
31454 "xloc": [
31460 - "default.handlebars->29->1955"
31455 + "default.handlebars->29->1948"
31456 ]
31457 },
31458 {
@@ -31477,7 +31472,7 @@
31472 "zh-chs": "发送电邮给该用户",
31473 "zh-cht": "發送電郵給該用戶",
31474 "xloc": [
31480 - "default.handlebars->29->1957"
31475 + "default.handlebars->29->1950"
31476 ]
31477 },
31478 {
@@ -31497,7 +31492,7 @@
31492 "zh-chs": "向该组中的所有用户发送通知。",
31493 "zh-cht": "向該群中的所有用戶發送通知。",
31494 "xloc": [
31500 - "default.handlebars->29->1863"
31495 + "default.handlebars->29->1856"
31496 ]
31497 },
31498 {
@@ -31517,7 +31512,7 @@
31512 "zh-chs": "向该用户发送文本通知。",
31513 "zh-cht": "向該用戶發送文本通知。",
31514 "xloc": [
31520 - "default.handlebars->29->1755"
31515 + "default.handlebars->29->1748"
31516 ]
31517 },
31518 {
@@ -31537,7 +31532,7 @@
31532 "zh-chs": "发送电邮给用户",
31533 "zh-cht": "發送電郵給用戶",
31534 "xloc": [
31540 - "default.handlebars->29->1735"
31535 + "default.handlebars->29->1728"
31536 ]
31537 },
31538 {
@@ -31577,7 +31572,7 @@
31572 "zh-chs": "发送邀请电邮。",
31573 "zh-cht": "發送邀請電郵。",
31574 "xloc": [
31580 - "default.handlebars->29->1795"
31575 + "default.handlebars->29->1788"
31576 ]
31577 },
31578 {
@@ -31699,7 +31694,7 @@
31694 "zh-chs": "发送用户通知",
31695 "zh-cht": "發送用戶通知",
31696 "xloc": [
31702 - "default.handlebars->29->1959"
31697 + "default.handlebars->29->1952"
31698 ]
31699 },
31700 {
@@ -31760,7 +31755,7 @@
31755 "zh-chs": "服务器备份",
31756 "zh-cht": "伺服器備份",
31757 "xloc": [
31763 - "default.handlebars->29->1805"
31758 + "default.handlebars->29->1798"
31759 ]
31760 },
31761 {
@@ -31780,7 +31775,7 @@
31775 "zh-chs": "服务器证书",
31776 "zh-cht": "伺服器憑證",
31777 "xloc": [
31783 - "default.handlebars->29->2111"
31778 + "default.handlebars->29->2104"
31779 ]
31780 },
31781 {
@@ -31800,7 +31795,7 @@
31795 "zh-chs": "服务器数据库",
31796 "zh-cht": "伺服器數據庫",
31797 "xloc": [
31803 - "default.handlebars->29->2112"
31798 + "default.handlebars->29->2105"
31799 ]
31800 },
31801 {
@@ -31822,9 +31817,9 @@
31817 "xloc": [
31818 "default-mobile.handlebars->9->429",
31819 "default-mobile.handlebars->9->444",
31825 - "default.handlebars->29->1457",
31826 - "default.handlebars->29->1487",
31827 - "default.handlebars->29->1802",
31820 + "default.handlebars->29->1450",
31821 + "default.handlebars->29->1480",
31822 + "default.handlebars->29->1795",
31823 "default.handlebars->29->689",
31824 "default.handlebars->29->710"
31825 ]
@@ -31846,8 +31841,8 @@
31841 "zh-chs": "服务器权限",
31842 "zh-cht": "伺服器權限",
31843 "xloc": [
31849 - "default.handlebars->29->1727",
31850 - "default.handlebars->29->1816"
31844 + "default.handlebars->29->1720",
31845 + "default.handlebars->29->1809"
31846 ]
31847 },
31848 {
@@ -31867,7 +31862,7 @@
31862 "zh-chs": "服务器配额",
31863 "zh-cht": "伺服器配額",
31864 "xloc": [
31870 - "default.handlebars->29->1921"
31865 + "default.handlebars->29->1914"
31866 ]
31867 },
31868 {
@@ -31887,7 +31882,7 @@
31882 "zh-chs": "服务器还原",
31883 "zh-cht": "伺服器還原",
31884 "xloc": [
31890 - "default.handlebars->29->1806"
31885 + "default.handlebars->29->1799"
31886 ]
31887 },
31888 {
@@ -31907,7 +31902,7 @@
31902 "zh-chs": "服务器权限",
31903 "zh-cht": "伺服器權限",
31904 "xloc": [
31910 - "default.handlebars->29->1920"
31905 + "default.handlebars->29->1913"
31906 ]
31907 },
31908 {
@@ -31927,7 +31922,7 @@
31922 "zh-chs": "服务器状态",
31923 "zh-cht": "伺服器狀態",
31924 "xloc": [
31930 - "default.handlebars->29->2059"
31925 + "default.handlebars->29->2052"
31926 ]
31927 },
31928 {
@@ -31967,7 +31962,7 @@
31962 "zh-chs": "服务器跟踪",
31963 "zh-cht": "伺服器追蹤",
31964 "xloc": [
31970 - "default.handlebars->29->2122"
31965 + "default.handlebars->29->2115"
31966 ]
31967 },
31968 {
@@ -31987,7 +31982,7 @@
31982 "zh-chs": "服务器更新",
31983 "zh-cht": "伺服器更新",
31984 "xloc": [
31990 - "default.handlebars->29->1807"
31985 + "default.handlebars->29->1800"
31986 ]
31987 },
31988 {
@@ -32130,7 +32125,7 @@
32125 "zh-chs": "ServerStats.csv",
32126 "zh-cht": "ServerStats.csv",
32127 "xloc": [
32133 - "default.handlebars->29->2103"
32128 + "default.handlebars->29->2096"
32129 ]
32130 },
32131 {
@@ -32190,7 +32185,7 @@
32185 "zh-chs": "会话",
32186 "zh-cht": "節",
32187 "xloc": [
32193 - "default.handlebars->29->2023"
32188 + "default.handlebars->29->2016"
32189 ]
32190 },
32191 {
@@ -32315,7 +32310,7 @@
32310 "zh-chs": "设置剪贴板内容,{0}个字节",
32311 "zh-cht": "設置剪貼板內容,{0}個字節",
32312 "xloc": [
32318 - "default.handlebars->29->1610"
32313 + "default.handlebars->29->1603"
32314 ]
32315 },
32316 {
@@ -32696,7 +32691,7 @@
32691 "zh-cht": "只顯示自己的事件",
32692 "xloc": [
32693 "default-mobile.handlebars->9->432",
32699 - "default.handlebars->29->1460"
32694 + "default.handlebars->29->1453"
32695 ]
32696 },
32697 {
@@ -32716,7 +32711,7 @@
32711 "zh-chs": "显示连接工具栏",
32712 "zh-cht": "顯示連接工具欄",
32713 "xloc": [
32719 - "default.handlebars->29->1422"
32714 + "default.handlebars->29->1415"
32715 ]
32716 },
32717 {
@@ -32796,8 +32791,8 @@
32791 "zh-chs": "显示1分钟",
32792 "zh-cht": "顯示1分鐘",
32793 "xloc": [
32799 - "default.handlebars->29->1758",
32800 - "default.handlebars->29->1781"
32794 + "default.handlebars->29->1751",
32795 + "default.handlebars->29->1774"
32796 ]
32797 },
32798 {
@@ -32817,8 +32812,8 @@
32812 "zh-chs": "显示10秒",
32813 "zh-cht": "顯示10秒",
32814 "xloc": [
32820 - "default.handlebars->29->1757",
32821 - "default.handlebars->29->1780"
32815 + "default.handlebars->29->1750",
32816 + "default.handlebars->29->1773"
32817 ]
32818 },
32819 {
@@ -32838,8 +32833,8 @@
32833 "zh-chs": "显示5分钟",
32834 "zh-cht": "顯示5分鐘",
32835 "xloc": [
32841 - "default.handlebars->29->1759",
32842 - "default.handlebars->29->1782"
32836 + "default.handlebars->29->1752",
32837 + "default.handlebars->29->1775"
32838 ]
32839 },
32840 {
@@ -32859,8 +32854,8 @@
32854 "zh-chs": "显示消息,直到被用户拒绝",
32855 "zh-cht": "顯示消息,直到被用戶拒絕",
32856 "xloc": [
32862 - "default.handlebars->29->1756",
32863 - "default.handlebars->29->1779"
32857 + "default.handlebars->29->1749",
32858 + "default.handlebars->29->1772"
32859 ]
32860 },
32861 {
@@ -33076,8 +33071,7 @@
33071 "zh-cht": "簡單客戶端控制模式(CCM)",
33072 "xloc": [
33073 "default.handlebars->29->1357",
33079 - "default.handlebars->29->1385",
33080 - "default.handlebars->29->1389"
33074 + "default.handlebars->29->1386"
33075 ]
33076 },
33077 {
@@ -33159,8 +33153,8 @@
33153 "zh-chs": "尺寸",
33154 "zh-cht": "尺寸",
33155 "xloc": [
33162 - "default.handlebars->29->2026",
33163 - "default.handlebars->29->2041",
33156 + "default.handlebars->29->2019",
33157 + "default.handlebars->29->2034",
33158 "default.handlebars->container->column_l->p1->devListToolbarSpan->1->0->9->devListToolbarSize"
33159 ]
33160 },
@@ -34150,8 +34144,8 @@
34144 "zh-chs": "开始时间",
34145 "zh-cht": "開始時間",
34146 "xloc": [
34153 - "default.handlebars->29->2024",
34154 - "default.handlebars->29->2043",
34147 + "default.handlebars->29->2017",
34148 + "default.handlebars->29->2036",
34149 "default.handlebars->29->839",
34150 "desktop.handlebars->3->15"
34151 ]
@@ -34173,7 +34167,7 @@
34167 "zh-chs": "开始桌面多重会话",
34168 "zh-cht": "啟動桌面多路復用會話",
34169 "xloc": [
34176 - "default.handlebars->29->1594"
34170 + "default.handlebars->29->1587"
34171 ]
34172 },
34173 {
@@ -34193,7 +34187,7 @@
34187 "zh-chs": "从{1}到{2}开始了桌面会话“{0}”",
34188 "zh-cht": "從{1}到{2}開始了桌面會話“{0}”",
34189 "xloc": [
34196 - "default.handlebars->29->1603"
34190 + "default.handlebars->29->1596"
34191 ]
34192 },
34193 {
@@ -34213,7 +34207,7 @@
34207 "zh-chs": "从{1}到{2}开始文件管理会话“{0}”",
34208 "zh-cht": "從{1}到{2}開始文件管理會話“{0}”",
34209 "xloc": [
34216 - "default.handlebars->29->1604"
34210 + "default.handlebars->29->1597"
34211 ]
34212 },
34213 {
@@ -34233,7 +34227,7 @@
34227 "zh-chs": "从{1}到{2}开始中继会话“{0}”",
34228 "zh-cht": "從{1}到{2}開始中繼會話“{0}”",
34229 "xloc": [
34236 - "default.handlebars->29->1601"
34230 + "default.handlebars->29->1594"
34231 ]
34232 },
34233 {
@@ -34253,7 +34247,7 @@
34247 "zh-chs": "使用Toast通知启动远程桌面",
34248 "zh-cht": "使用Toast通知啟動遠程桌面",
34249 "xloc": [
34256 - "default.handlebars->29->1623"
34250 + "default.handlebars->29->1616"
34251 ]
34252 },
34253 {
@@ -34273,7 +34267,7 @@
34267 "zh-chs": "启动远程桌面,而无需通知",
34268 "zh-cht": "啟動遠程桌面,而無需通知",
34269 "xloc": [
34276 - "default.handlebars->29->1624"
34270 + "default.handlebars->29->1617"
34271 ]
34272 },
34273 {
@@ -34293,7 +34287,7 @@
34287 "zh-chs": "启动带有Toast通知的远程文件",
34288 "zh-cht": "啟動帶有Toast通知的遠程文件",
34289 "xloc": [
34296 - "default.handlebars->29->1630"
34290 + "default.handlebars->29->1623"
34291 ]
34292 },
34293 {
@@ -34313,7 +34307,7 @@
34307 "zh-chs": "已启动的远程文件,恕不另行通知",
34308 "zh-cht": "已啟動的遠程文件,恕不另行通知",
34309 "xloc": [
34316 - "default.handlebars->29->1631"
34310 + "default.handlebars->29->1624"
34311 ]
34312 },
34313 {
@@ -34333,7 +34327,7 @@
34327 "zh-chs": "从{1}到{2}开始了终端会话“{0}”",
34328 "zh-cht": "從{1}到{2}開始了終端會話“{0}”",
34329 "xloc": [
34336 - "default.handlebars->29->1602"
34330 + "default.handlebars->29->1595"
34331 ]
34332 },
34333 {
@@ -34353,7 +34347,7 @@
34347 "zh-chs": "接受本地用户后启动远程桌面",
34348 "zh-cht": "接受本地用戶後啟動遠程桌面",
34349 "xloc": [
34356 - "default.handlebars->29->1618"
34350 + "default.handlebars->29->1611"
34351 ]
34352 },
34353 {
@@ -34373,7 +34367,7 @@
34367 "zh-chs": "本地用户接受后启动远程文件",
34368 "zh-cht": "本地用戶接受後啟動遠程文件",
34369 "xloc": [
34376 - "default.handlebars->29->1628"
34370 + "default.handlebars->29->1621"
34371 ]
34372 },
34373 {
@@ -34434,8 +34428,8 @@
34428 "zh-chs": "状况",
34429 "zh-cht": "狀態",
34430 "xloc": [
34437 - "default.handlebars->29->1973",
34438 - "default.handlebars->29->2036",
34431 + "default.handlebars->29->1966",
34432 + "default.handlebars->29->2029",
34433 "default.handlebars->container->column_l->p42->p42tbl->1->0->7"
34434 ]
34435 },
@@ -34563,7 +34557,7 @@
34557 "zh-chs": "超出储存空间",
34558 "zh-cht": "超出儲存空間",
34559 "xloc": [
34566 - "default.handlebars->29->1549"
34560 + "default.handlebars->29->1542"
34561 ]
34562 },
34563 {
@@ -34628,7 +34622,7 @@
34622 "zh-chs": "主题",
34623 "zh-cht": "主題",
34624 "xloc": [
34631 - "default.handlebars->29->1753"
34625 + "default.handlebars->29->1746"
34626 ]
34627 },
34628 {
@@ -34835,7 +34829,7 @@
34829 "zh-chs": "将服务器设备名称同步到主机名称",
34830 "zh-cht": "將伺服器裝置名稱同步到主機名稱",
34831 "xloc": [
34838 - "default.handlebars->29->1430"
34832 + "default.handlebars->29->1423"
34833 ]
34834 },
34835 {
@@ -35154,8 +35148,8 @@
35148 "zh-cht": "終端機",
35149 "xloc": [
35150 "default-mobile.handlebars->9->168",
35157 - "default.handlebars->29->1423",
35158 - "default.handlebars->29->2030",
35151 + "default.handlebars->29->1416",
35152 + "default.handlebars->29->2023",
35153 "default.handlebars->29->255",
35154 "default.handlebars->29->536",
35155 "default.handlebars->container->topbar->1->1->MainSubMenuSpan->MainSubMenu->1->0->MainDevTerminal",
@@ -35200,8 +35194,8 @@
35194 "zh-cht": "終端機通知",
35195 "xloc": [
35196 "default.handlebars->29->1342",
35203 - "default.handlebars->29->1852",
35204 - "default.handlebars->29->1939",
35197 + "default.handlebars->29->1845",
35198 + "default.handlebars->29->1932",
35199 "default.handlebars->29->608"
35200 ]
35201 },
@@ -35223,8 +35217,8 @@
35217 "zh-cht": "終端機提示",
35218 "xloc": [
35219 "default.handlebars->29->1341",
35226 - "default.handlebars->29->1851",
35227 - "default.handlebars->29->1938",
35220 + "default.handlebars->29->1844",
35221 + "default.handlebars->29->1931",
35222 "default.handlebars->29->607"
35223 ]
35224 },
@@ -35395,7 +35389,7 @@
35389 "zh-chs": "目前没有任何通知",
35390 "zh-cht": "目前沒有任何通知",
35391 "xloc": [
35398 - "default.handlebars->29->2051"
35392 + "default.handlebars->29->2044"
35393 ]
35394 },
35395 {
@@ -35533,7 +35527,7 @@
35527 "zh-chs": "这是不安全政策,因为将用代理执行激活。",
35528 "zh-cht": "這是不安全政策,因為將用代理執行啟動。",
35529 "xloc": [
35536 - "default.handlebars->29->1407"
35530 + "default.handlebars->29->1400"
35531 ]
35532 },
35533 {
@@ -35574,7 +35568,7 @@
35568 "zh-chs": "此政策不会影响采用ACM模式的英特尔&reg;AMT的设备。",
35569 "zh-cht": "此政策不會影響採用ACM模式的Intel&reg; AMT的裝置。",
35570 "xloc": [
35577 - "default.handlebars->29->1406"
35571 + "default.handlebars->29->1399"
35572 ]
35573 },
35574 {
@@ -35774,7 +35768,7 @@
35768 "zh-chs": "时间跨度",
35769 "zh-cht": "時間跨度",
35770 "xloc": [
35777 - "default.handlebars->29->1692"
35771 + "default.handlebars->29->1685"
35772 ]
35773 },
35774 {
@@ -36636,7 +36630,6 @@
36630 "default.handlebars->29->1286",
36631 "default.handlebars->29->1328",
36632 "default.handlebars->29->1383",
36639 - "default.handlebars->29->1386",
36633 "default.handlebars->29->865",
36634 "default.handlebars->container->column_l->p11->deskarea0->deskarea4->3",
36635 "desktop.handlebars->p11->deskarea0->deskarea4->3"
@@ -36951,7 +36944,7 @@
36944 "zh-cht": "卸載",
36945 "xloc": [
36946 "default-mobile.handlebars->9->454",
36954 - "default.handlebars->29->1498",
36947 + "default.handlebars->29->1491",
36948 "default.handlebars->29->694",
36949 "default.handlebars->29->715"
36950 ]
@@ -36995,7 +36988,7 @@
36988 "zh-chs": "卸载代理/删除设备",
36989 "zh-cht": "卸載代理/刪除設備",
36990 "xloc": [
36998 - "default.handlebars->29->1462"
36991 + "default.handlebars->29->1455"
36992 ]
36993 },
36994 {
@@ -37048,9 +37041,9 @@
37041 "default.handlebars->29->13",
37042 "default.handlebars->29->1315",
37043 "default.handlebars->29->1316",
37051 - "default.handlebars->29->2013",
37052 - "default.handlebars->29->2028",
37053 - "default.handlebars->29->2029",
37044 + "default.handlebars->29->2006",
37045 + "default.handlebars->29->2021",
37046 + "default.handlebars->29->2022",
37047 "default.handlebars->29->42",
37048 "default.handlebars->29->453",
37049 "default.handlebars->29->972",
@@ -37095,7 +37088,7 @@
37088 "zh-chs": "未知动作",
37089 "zh-cht": "未知動作",
37090 "xloc": [
37098 - "default.handlebars->29->2065"
37091 + "default.handlebars->29->2058"
37092 ]
37093 },
37094 {
@@ -37115,8 +37108,8 @@
37108 "zh-chs": "未知设备",
37109 "zh-cht": "未知裝置",
37110 "xloc": [
37118 - "default.handlebars->29->1877",
37119 - "default.handlebars->29->2001"
37111 + "default.handlebars->29->1870",
37112 + "default.handlebars->29->1994"
37113 ]
37114 },
37115 {
@@ -37136,9 +37129,9 @@
37129 "zh-chs": "未知设备组",
37130 "zh-cht": "未知裝置群",
37131 "xloc": [
37139 - "default.handlebars->29->1871",
37140 - "default.handlebars->29->1989",
37141 - "default.handlebars->29->2069"
37132 + "default.handlebars->29->1864",
37133 + "default.handlebars->29->1982",
37134 + "default.handlebars->29->2062"
37135 ]
37136 },
37137 {
@@ -37158,7 +37151,7 @@
37151 "zh-chs": "未知群组",
37152 "zh-cht": "未知群組",
37153 "xloc": [
37161 - "default.handlebars->29->2061"
37154 + "default.handlebars->29->2054"
37155 ]
37156 },
37157 {
@@ -37199,7 +37192,7 @@
37192 "zh-chs": "未知用户组",
37193 "zh-cht": "未知用戶群",
37194 "xloc": [
37202 - "default.handlebars->29->1995"
37195 + "default.handlebars->29->1988"
37196 ]
37197 },
37198 {
@@ -37262,7 +37255,7 @@
37255 "zh-chs": "解锁帐户",
37256 "zh-cht": "解鎖帳戶",
37257 "xloc": [
37265 - "default.handlebars->29->1746"
37258 + "default.handlebars->29->1739"
37259 ]
37260 },
37261 {
@@ -37306,7 +37299,7 @@
37299 "zh-chs": "最新",
37300 "zh-cht": "最新",
37301 "xloc": [
37309 - "default.handlebars->29->2129"
37302 + "default.handlebars->29->2122"
37303 ]
37304 },
37305 {
@@ -37354,8 +37347,8 @@
37347 "default-mobile.handlebars->9->311",
37348 "default-mobile.handlebars->9->329",
37349 "default-mobile.handlebars->9->332",
37357 - "default.handlebars->29->1578",
37358 - "default.handlebars->29->1586",
37350 + "default.handlebars->29->1571",
37351 + "default.handlebars->29->1579",
37352 "default.handlebars->29->904",
37353 "default.handlebars->29->928",
37354 "default.handlebars->29->931",
@@ -37480,7 +37473,7 @@
37473 "zh-cht": "上傳將覆蓋1個檔案。繼續?",
37474 "xloc": [
37475 "default-mobile.handlebars->9->330",
37483 - "default.handlebars->29->1587",
37476 + "default.handlebars->29->1580",
37477 "default.handlebars->29->929"
37478 ]
37479 },
@@ -37502,7 +37495,7 @@
37495 "zh-cht": "上傳將覆蓋{0}個檔案。繼續?",
37496 "xloc": [
37497 "default-mobile.handlebars->9->331",
37505 - "default.handlebars->29->1588",
37498 + "default.handlebars->29->1581",
37499 "default.handlebars->29->930"
37500 ]
37501 },
@@ -37540,7 +37533,7 @@
37533 "zh-chs": "上传:“{0}”",
37534 "zh-cht": "上傳:“{0}”",
37535 "xloc": [
37543 - "default.handlebars->29->1638"
37536 + "default.handlebars->29->1631"
37537 ]
37538 },
37539 {
@@ -37644,8 +37637,8 @@
37637 "zh-chs": "用过的",
37638 "zh-cht": "用過的",
37639 "xloc": [
37647 - "default.handlebars->29->2055",
37648 - "default.handlebars->29->2057"
37640 + "default.handlebars->29->2048",
37641 + "default.handlebars->29->2050"
37642 ]
37643 },
37644 {
@@ -37666,10 +37659,10 @@
37659 "zh-cht": "用戶",
37660 "xloc": [
37661 "default.handlebars->29->1380",
37669 - "default.handlebars->29->1699",
37670 - "default.handlebars->29->1728",
37671 - "default.handlebars->29->1867",
37672 - "default.handlebars->29->2048",
37662 + "default.handlebars->29->1692",
37663 + "default.handlebars->29->1721",
37664 + "default.handlebars->29->1860",
37665 + "default.handlebars->29->2041",
37666 "default.handlebars->29->232",
37667 "default.handlebars->29->678"
37668 ]
@@ -37691,7 +37684,7 @@
37684 "zh-chs": "用户+档案",
37685 "zh-cht": "用戶+檔案",
37686 "xloc": [
37694 - "default.handlebars->29->1729"
37687 + "default.handlebars->29->1722"
37688 ]
37689 },
37690 {
@@ -37711,10 +37704,10 @@
37704 "zh-chs": "用户帐户导入",
37705 "zh-cht": "用戶帳戶導入",
37706 "xloc": [
37714 - "default.handlebars->29->1762",
37715 - "default.handlebars->29->1763",
37716 - "default.handlebars->29->1765",
37717 - "default.handlebars->29->1767"
37707 + "default.handlebars->29->1755",
37708 + "default.handlebars->29->1756",
37709 + "default.handlebars->29->1758",
37710 + "default.handlebars->29->1760"
37711 ]
37712 },
37713 {
@@ -37734,7 +37727,7 @@
37727 "zh-chs": "用户帐户",
37728 "zh-cht": "用戶帳戶",
37729 "xloc": [
37737 - "default.handlebars->29->2074"
37730 + "default.handlebars->29->2067"
37731 ]
37732 },
37733 {
@@ -37777,9 +37770,9 @@
37770 "zh-cht": "用戶同意",
37771 "xloc": [
37772 "default.handlebars->29->1348",
37780 - "default.handlebars->29->1858",
37773 + "default.handlebars->29->1851",
37774 "default.handlebars->29->193",
37782 - "default.handlebars->29->1945",
37775 + "default.handlebars->29->1938",
37776 "default.handlebars->29->614",
37777 "default.handlebars->29->745"
37778 ]
@@ -37801,11 +37794,11 @@
37794 "zh-chs": "用户组",
37795 "zh-cht": "用戶群",
37796 "xloc": [
37804 - "default.handlebars->29->1438",
37805 - "default.handlebars->29->1439",
37806 - "default.handlebars->29->1831",
37807 - "default.handlebars->29->1997",
37808 - "default.handlebars->29->2016",
37797 + "default.handlebars->29->1431",
37798 + "default.handlebars->29->1432",
37799 + "default.handlebars->29->1824",
37800 + "default.handlebars->29->1990",
37801 + "default.handlebars->29->2009",
37802 "default.handlebars->29->677"
37803 ]
37804 },
@@ -37846,7 +37839,7 @@
37839 "zh-chs": "用户组成员",
37840 "zh-cht": "用戶群成員",
37841 "xloc": [
37849 - "default.handlebars->29->1994"
37842 + "default.handlebars->29->1987"
37843 ]
37844 },
37845 {
@@ -37886,9 +37879,9 @@
37879 "zh-chs": "用户识别码",
37880 "zh-cht": "用戶識別碼",
37881 "xloc": [
37889 - "default.handlebars->29->1503",
37890 - "default.handlebars->29->1912",
37891 - "default.handlebars->29->1913"
37882 + "default.handlebars->29->1496",
37883 + "default.handlebars->29->1905",
37884 + "default.handlebars->29->1906"
37885 ]
37886 },
37887 {
@@ -37908,8 +37901,8 @@
37901 "zh-chs": "用户标识符",
37902 "zh-cht": "用戶標識符",
37903 "xloc": [
37911 - "default.handlebars->29->1436",
37912 - "default.handlebars->29->1896"
37904 + "default.handlebars->29->1429",
37905 + "default.handlebars->29->1889"
37906 ]
37907 },
37908 {
@@ -37929,7 +37922,7 @@
37922 "zh-chs": "用户列表输出",
37923 "zh-cht": "用戶列表輸出",
37924 "xloc": [
37932 - "default.handlebars->29->1774"
37925 + "default.handlebars->29->1767"
37926 ]
37927 },
37928 {
@@ -37950,7 +37943,7 @@
37943 "zh-cht": "用戶名",
37944 "xloc": [
37945 "default-mobile.handlebars->9->458",
37953 - "default.handlebars->29->1502"
37946 + "default.handlebars->29->1495"
37947 ]
37948 },
37949 {
@@ -38025,7 +38018,7 @@
38018 "zh-chs": "用户节",
38019 "zh-cht": "用戶節",
38020 "xloc": [
38028 - "default.handlebars->29->2094"
38021 + "default.handlebars->29->2087"
38022 ]
38023 },
38024 {
@@ -38150,7 +38143,7 @@
38143 "zh-chs": "用户组已更改:{0}",
38144 "zh-cht": "用戶組已更改:{0}",
38145 "xloc": [
38153 - "default.handlebars->29->1667"
38146 + "default.handlebars->29->1660"
38147 ]
38148 },
38149 {
@@ -38170,7 +38163,7 @@
38163 "zh-chs": "已创建用户组:{0}",
38164 "zh-cht": "已創建用戶組:{0}",
38165 "xloc": [
38173 - "default.handlebars->29->1657"
38166 + "default.handlebars->29->1650"
38167 ]
38168 },
38169 {
@@ -38190,7 +38183,7 @@
38183 "zh-chs": "用户组成员身份已更改:{0}",
38184 "zh-cht": "用戶組成員身份已更改:{0}",
38185 "xloc": [
38193 - "default.handlebars->29->1655"
38186 + "default.handlebars->29->1648"
38187 ]
38188 },
38189 {
@@ -38256,7 +38249,7 @@
38249 "zh-cht": "用戶名",
38250 "xloc": [
38251 "default-mobile.handlebars->9->274",
38259 - "default.handlebars->29->1786",
38252 + "default.handlebars->29->1779",
38253 "default.handlebars->29->294",
38254 "default.handlebars->29->326",
38255 "default.handlebars->29->771",
@@ -38331,9 +38324,9 @@
38324 "zh-chs": "用户",
38325 "zh-cht": "用戶",
38326 "xloc": [
38334 - "default.handlebars->29->1819",
38335 - "default.handlebars->29->1859",
38336 - "default.handlebars->29->2093",
38327 + "default.handlebars->29->1812",
38328 + "default.handlebars->29->1852",
38329 + "default.handlebars->29->2086",
38330 "default.handlebars->container->topbar->1->1->UsersSubMenuSpan->UsersSubMenu->1->0->UsersGeneral"
38331 ]
38332 },
@@ -38354,7 +38347,7 @@
38347 "zh-chs": "用户会话",
38348 "zh-cht": "用戶節",
38349 "xloc": [
38357 - "default.handlebars->29->2078"
38350 + "default.handlebars->29->2071"
38351 ]
38352 },
38353 {
@@ -38394,7 +38387,7 @@
38387 "zh-chs": "验证电邮",
38388 "zh-cht": "驗證電郵",
38389 "xloc": [
38397 - "default.handlebars->29->1741"
38390 + "default.handlebars->29->1734"
38391 ]
38392 },
38393 {
@@ -38478,7 +38471,7 @@
38471 "zh-chs": "已验证",
38472 "zh-cht": "已驗證",
38473 "xloc": [
38481 - "default.handlebars->29->1975"
38474 + "default.handlebars->29->1968"
38475 ]
38476 },
38477 {
@@ -38521,7 +38514,7 @@
38514 "xloc": [
38515 "default-mobile.handlebars->9->63",
38516 "default.handlebars->29->1027",
38524 - "default.handlebars->29->1737"
38517 + "default.handlebars->29->1730"
38518 ]
38519 },
38520 {
@@ -38541,7 +38534,7 @@
38534 "zh-chs": "用户{0}的已验证电话号码",
38535 "zh-cht": "用戶{0}的已驗證電話號碼",
38536 "xloc": [
38544 - "default.handlebars->29->1684"
38537 + "default.handlebars->29->1677"
38538 ]
38539 },
38540 {
@@ -38630,7 +38623,7 @@
38623 "zh-chs": "版本不兼容,请先升级您的MeshCentral",
38624 "zh-cht": "版本不兼容,請先升級你的MeshCentral",
38625 "xloc": [
38633 - "default.handlebars->29->2125"
38626 + "default.handlebars->29->2118"
38627 ]
38628 },
38629 {
@@ -38712,7 +38705,7 @@
38705 "zh-chs": "查看所有事件",
38706 "zh-cht": "查看所有事件",
38707 "xloc": [
38715 - "default.handlebars->29->1811"
38708 + "default.handlebars->29->1804"
38709 ]
38710 },
38711 {
@@ -38732,8 +38725,8 @@
38725 "zh-chs": "查看变更日志",
38726 "zh-cht": "查看變更日誌",
38727 "xloc": [
38735 - "default.handlebars->29->2128",
38736 - "default.handlebars->29->2130"
38728 + "default.handlebars->29->2121",
38729 + "default.handlebars->29->2123"
38730 ]
38731 },
38732 {
@@ -38793,7 +38786,7 @@
38786 "zh-chs": "查看有关此用户的注释",
38787 "zh-cht": "查看有關此用戶的註釋",
38788 "xloc": [
38796 - "default.handlebars->29->1953"
38789 + "default.handlebars->29->1946"
38790 ]
38791 },
38792 {
@@ -38917,8 +38910,8 @@
38910 "xloc": [
38911 "default-mobile.handlebars->9->430",
38912 "default-mobile.handlebars->9->445",
38920 - "default.handlebars->29->1458",
38921 - "default.handlebars->29->1488"
38913 + "default.handlebars->29->1451",
38914 + "default.handlebars->29->1481"
38915 ]
38916 },
38917 {
@@ -39044,8 +39037,8 @@
39037 "zh-chs": "网络服务器",
39038 "zh-cht": "網絡伺服器",
39039 "xloc": [
39047 - "default.handlebars->29->2114",
39048 - "default.handlebars->29->2115"
39040 + "default.handlebars->29->2107",
39041 + "default.handlebars->29->2108"
39042 ]
39043 },
39044 {
@@ -39065,7 +39058,7 @@
39058 "zh-chs": "Web服务器请求",
39059 "zh-cht": "Web伺服器請求",
39060 "xloc": [

This file is too large to show in full.

views/default.handlebars
+1 -1
@@ -5674,8 +5674,8 @@
5674 str += (', v' + EscapeHtml(node.intelamt.ver));
5675 }
5676
5677 - if (node.intelamt.tls == 1) { str += ', <span title="' + "Intel&reg; AMT is setup with TLS network security" + '">' + "TLS" + '</span>'; }
5677 if (node.intelamt.state == 2) {
5678 + if (node.intelamt.tls == 1) { str += ', <span title="' + "Intel&reg; AMT is setup with TLS network security" + '">' + "TLS" + '</span>'; }
5679 if (node.intelamt.user == null || node.intelamt.user == '') {
5680 if ((meshrights & 4) != 0) {
5681 str += ', <i style=color:#FF0000;cursor:pointer title="' + "Edit Intel&reg; AMT credentials" + '" onclick=editDeviceAmtSettings("' + node._id + '")>' + "No Credentials" + '</i>';