Added multi-pipe meshcore.js

Ylian Saint-Hilaire committed Oct 9, 2020 at 18:35 UTC 07e32b93990a83f3253205c8dc82513cf7232778
2 files changed +3898 -205
agents/meshcore-multipipe.js new
+3789
@@ -0,0 +1,3789 @@
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 });
186 + this._send({ cmd: 'sessions', sessions: tunnelUserCount });
187 + }
188 + break;
189 + case 'query':
190 + switch (data.value) {
191 + case 'connection':
192 + data.result = require('MeshAgent').ConnectedServer;
193 + this._send(data);
194 + break;
195 + case 'descriptors':
196 + require('ChainViewer').getSnapshot().then(function (f)
197 + {
198 + this.tag.payload.result = f;
199 + this.tag.ipc._send(this.tag.payload);
200 + }).parentPromise.tag = { ipc: this, payload: data };
201 + break;
202 + }
203 + break;
204 + case 'sessions':
205 + this._send({ cmd: 'sessions', sessions: tunnelUserCount });
206 + break;
207 + }
208 + }
209 + catch (e) { removeRegisteredApp(this); this.end(); return; }
210 + });
211 + });
212 +
213 + // Send this object to all registered local applications
214 + function broadcastToRegisteredApps(x) {
215 + if ((obj.DAIPC == null) || (obj.DAIPC._daipc == null)) return;
216 + for (var i in obj.DAIPC._daipc) { if (obj.DAIPC._daipc[i]._registered != null) { obj.DAIPC._daipc[i]._send(x); } }
217 + }
218 +
219 + // Send list of registered apps to the server
220 + function updateRegisteredAppsToServer() {
221 + if ((obj.DAIPC == null) || (obj.DAIPC._daipc == null)) return;
222 + var apps = {};
223 + 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]++; } }
224 + try { mesh.SendCommand({ action: 'sessions', type: 'app', value: apps }); } catch (e) { }
225 + }
226 +
227 + // Remove a registered app
228 + function removeRegisteredApp(pipe) {
229 + for (var i = obj.DAIPC._daipc.length - 1; i >= 0; i--) { if (obj.DAIPC._daipc[i] === pipe) { obj.DAIPC._daipc.splice(i, 1); } }
230 + if (pipe._registered != null) updateRegisteredAppsToServer();
231 + }
232 +
233 + function diagnosticAgent_uninstall() {
234 + require('service-manager').manager.uninstallService('meshagentDiagnostic');
235 + require('task-scheduler').delete('meshagentDiagnostic/periodicStart');
236 + };
237 + function diagnosticAgent_installCheck(install) {
238 + try {
239 + var diag = require('service-manager').manager.getService('meshagentDiagnostic');
240 + return (diag);
241 + }
242 + catch (e) {
243 + }
244 + if (!install) { return (null); }
245 +
246 + var svc = null;
247 + try {
248 + require('service-manager').manager.installService(
249 + {
250 + name: 'meshagentDiagnostic',
251 + displayName: "Mesh Agent Diagnostic Service",
252 + description: "Mesh Agent Diagnostic Service",
253 + servicePath: process.execPath,
254 + parameters: ['-recovery']
255 + //files: [{ newName: 'diagnostic.js', _buffer: Buffer.from('LyoNCkNvcHlyaWdodCAyMDE5IEludGVsIENvcnBvcmF0aW9uDQoNCkxpY2Vuc2VkIHVuZGVyIHRoZSBBcGFjaGUgTGljZW5zZSwgVmVyc2lvbiAyLjAgKHRoZSAiTGljZW5zZSIpOw0KeW91IG1heSBub3QgdXNlIHRoaXMgZmlsZSBleGNlcHQgaW4gY29tcGxpYW5jZSB3aXRoIHRoZSBMaWNlbnNlLg0KWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0DQoNCiAgICBodHRwOi8vd3d3LmFwYWNoZS5vcmcvbGljZW5zZXMvTElDRU5TRS0yLjANCg0KVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQ0KZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywNCldJVEhPVVQgV0FSUkFOVElFUyBPUiBDT05ESVRJT05TIE9GIEFOWSBLSU5ELCBlaXRoZXIgZXhwcmVzcyBvciBpbXBsaWVkLg0KU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZA0KbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuDQoqLw0KDQp2YXIgaG9zdCA9IHJlcXVpcmUoJ3NlcnZpY2UtaG9zdCcpLmNyZWF0ZSgnbWVzaGFnZW50RGlhZ25vc3RpYycpOw0KdmFyIFJlY292ZXJ5QWdlbnQgPSByZXF1aXJlKCdNZXNoQWdlbnQnKTsNCg0KaG9zdC5vbignc2VydmljZVN0YXJ0JywgZnVuY3Rpb24gKCkNCnsNCiAgICBjb25zb2xlLnNldERlc3RpbmF0aW9uKGNvbnNvbGUuRGVzdGluYXRpb25zLkxPR0ZJTEUpOw0KICAgIGhvc3Quc3RvcCA9IGZ1bmN0aW9uKCkNCiAgICB7DQogICAgICAgIHJlcXVpcmUoJ3NlcnZpY2UtbWFuYWdlcicpLm1hbmFnZXIuZ2V0U2VydmljZSgnbWVzaGFnZW50RGlhZ25vc3RpYycpLnN0b3AoKTsNCiAgICB9DQogICAgUmVjb3ZlcnlBZ2VudC5vbignQ29ubmVjdGVkJywgZnVuY3Rpb24gKHN0YXR1cykNCiAgICB7DQogICAgICAgIGlmIChzdGF0dXMgPT0gMCkNCiAgICAgICAgew0KICAgICAgICAgICAgY29uc29sZS5sb2coJ0RpYWdub3N0aWMgQWdlbnQ6IFNlcnZlciBjb25uZWN0aW9uIGxvc3QuLi4nKTsNCiAgICAgICAgICAgIHJldHVybjsNCiAgICAgICAgfQ0KICAgICAgICBjb25zb2xlLmxvZygnRGlhZ25vc3RpYyBBZ2VudDogQ29ubmVjdGlvbiBFc3RhYmxpc2hlZCB3aXRoIFNlcnZlcicpOw0KICAgICAgICBzdGFydCgpOw0KICAgIH0pOw0KfSk7DQpob3N0Lm9uKCdub3JtYWxTdGFydCcsIGZ1bmN0aW9uICgpDQp7DQogICAgaG9zdC5zdG9wID0gZnVuY3Rpb24gKCkNCiAgICB7DQogICAgICAgIHByb2Nlc3MuZXhpdCgpOw0KICAgIH0NCiAgICBjb25zb2xlLmxvZygnTm9uIFNlcnZpY2UgTW9kZScpOw0KICAgIFJlY292ZXJ5QWdlbnQub24oJ0Nvbm5lY3RlZCcsIGZ1bmN0aW9uIChzdGF0dXMpDQogICAgew0KICAgICAgICBpZiAoc3RhdHVzID09IDApDQogICAgICAgIHsNCiAgICAgICAgICAgIGNvbnNvbGUubG9nKCdEaWFnbm9zdGljIEFnZW50OiBTZXJ2ZXIgY29ubmVjdGlvbiBsb3N0Li4uJyk7DQogICAgICAgICAgICByZXR1cm47DQogICAgICAgIH0NCiAgICAgICAgY29uc29sZS5sb2coJ0RpYWdub3N0aWMgQWdlbnQ6IENvbm5lY3Rpb24gRXN0YWJsaXNoZWQgd2l0aCBTZXJ2ZXInKTsNCiAgICAgICAgc3RhcnQoKTsNCiAgICB9KTsNCn0pOw0KaG9zdC5vbignc2VydmljZVN0b3AnLCBmdW5jdGlvbiAoKSB7IHByb2Nlc3MuZXhpdCgpOyB9KTsNCmhvc3QucnVuKCk7DQoNCg0KZnVuY3Rpb24gc3RhcnQoKQ0Kew0KDQp9Ow0K', 'base64') }]
256 + });
257 + svc = require('service-manager').manager.getService('meshagentDiagnostic');
258 + }
259 + catch (e) {
260 + return (null);
261 + }
262 + var proxyConfig = require('global-tunnel').proxyConfig;
263 + var cert = require('MeshAgent').GenerateAgentCertificate('CN=MeshNodeDiagnosticCertificate');
264 + var nodeid = require('tls').loadCertificate(cert.root).getKeyHash().toString('base64');
265 + ddb = require('SimpleDataStore').Create(svc.appWorkingDirectory().replace('\\', '/') + '/meshagentDiagnostic.db');
266 + ddb.Put('disableUpdate', '1');
267 + ddb.Put('MeshID', Buffer.from(require('MeshAgent').ServerInfo.MeshID, 'hex'));
268 + ddb.Put('ServerID', require('MeshAgent').ServerInfo.ServerID);
269 + ddb.Put('MeshServer', require('MeshAgent').ServerInfo.ServerUri);
270 + if (cert.root.pfx) { ddb.Put('SelfNodeCert', cert.root.pfx); }
271 + if (cert.tls) { ddb.Put('SelfNodeTlsCert', cert.tls.pfx); }
272 + if (proxyConfig) {
273 + ddb.Put('WebProxy', proxyConfig.host + ':' + proxyConfig.port);
274 + } else {
275 + ddb.Put('ignoreProxyFile', '1');
276 + }
277 +
278 + require('MeshAgent').SendCommand({ action: 'diagnostic', value: { command: 'register', value: nodeid } });
279 + require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: "Diagnostic Agent Registered [" + nodeid.length + "/" + nodeid + "]" });
280 +
281 + delete ddb;
282 +
283 + // Set a recurrent task, to run the Diagnostic Agent every 2 days
284 + 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' });
285 + //require('task-scheduler').create({ name: 'meshagentDiagnostic/periodicStart', daily: '1', time: '17:16', service: 'meshagentDiagnostic' });
286 +
287 + return (svc);
288 + }
289 +
290 + // Monitor the file 'batterystate.txt' in the agent's folder and sends battery update when this file is changed.
291 + if ((require('fs').existsSync(process.cwd() + 'batterystate.txt')) && (require('fs').watch != null)) {
292 + // Setup manual battery monitoring
293 + require('MeshAgent')._batteryFileWatcher = require('fs').watch(process.cwd(), function () {
294 + if (require('MeshAgent')._batteryFileTimer != null) return;
295 + require('MeshAgent')._batteryFileTimer = setTimeout(function () {
296 + try {
297 + require('MeshAgent')._batteryFileTimer = null;
298 + var data = null;
299 + try { data = require('fs').readFileSync(process.cwd() + 'batterystate.txt').toString(); } catch (e) { }
300 + if ((data != null) && (data.length < 10)) {
301 + data = data.split(',');
302 + if ((data.length == 2) && ((data[0] == 'ac') || (data[0] == 'dc'))) {
303 + var level = parseInt(data[1]);
304 + if ((level >= 0) && (level <= 100)) { require('MeshAgent').SendCommand({ action: 'battery', state: data[0], level: level }); }
305 + }
306 + }
307 + } catch (e) { }
308 + }, 1000);
309 + });
310 + } else {
311 + // Setup normal battery monitoring
312 + if (require('identifiers').isBatteryPowered && require('identifiers').isBatteryPowered()) {
313 + require('MeshAgent')._battLevelChanged = function _battLevelChanged(val) {
314 + _battLevelChanged.self._currentBatteryLevel = val;
315 + _battLevelChanged.self.SendCommand({ action: 'battery', state: _battLevelChanged.self._currentPowerState, level: val });
316 + };
317 + require('MeshAgent')._battLevelChanged.self = require('MeshAgent');
318 + require('MeshAgent')._powerChanged = function _powerChanged(val) {
319 + _powerChanged.self._currentPowerState = (val == 'AC' ? 'ac' : 'dc');
320 + _powerChanged.self.SendCommand({ action: 'battery', state: (val == 'AC' ? 'ac' : 'dc'), level: _powerChanged.self._currentBatteryLevel });
321 + };
322 + require('MeshAgent')._powerChanged.self = require('MeshAgent');
323 + require('MeshAgent').on('Connected', function (status) {
324 + if (status == 0) {
325 + require('power-monitor').removeListener('acdc', this._powerChanged);
326 + require('power-monitor').removeListener('batteryLevel', this._battLevelChanged);
327 + } else {
328 + require('power-monitor').on('acdc', this._powerChanged);
329 + require('power-monitor').on('batteryLevel', this._battLevelChanged);
330 + }
331 + });
332 + }
333 + }
334 +
335 +
336 + /*
337 + function borderController() {
338 + this.container = null;
339 + this.Start = function Start(user) {
340 + if (this.container == null) {
341 + if (process.platform == 'win32') {
342 + try {
343 + this.container = require('ScriptContainer').Create({ processIsolation: 1, sessionId: user.SessionId });
344 + } catch (e) {
345 + this.container = require('ScriptContainer').Create({ processIsolation: 1 });
346 + }
347 + } else {
348 + this.container = require('ScriptContainer').Create({ processIsolation: 1, sessionId: user.uid });
349 + }
350 + this.container.parent = this;
351 + this.container.addModule('monitor-info', getJSModule('monitor-info'));
352 + this.container.addModule('monitor-border', getJSModule('monitor-border'));
353 + this.container.addModule('promise', getJSModule('promise'));
354 + this.container.once('exit', function (code) { sendConsoleText('Border Process Exited with code: ' + code); this.parent.container = this.parent._container = null; });
355 + this.container.ExecuteString("var border = require('monitor-border'); border.Start();");
356 + }
357 + }
358 + this.Stop = function Stop() {
359 + if (this.container != null) {
360 + this._container = this.container;
361 + this._container.parent = this;
362 + this.container = null;
363 + this._container.exit();
364 + }
365 + }
366 + }
367 + obj.borderManager = new borderController();
368 + */
369 +
370 + // MeshAgent JavaScript Core Module. This code is sent to and running on the mesh agent.
371 + var meshCoreObj = { action: 'coreinfo', value: (require('MeshAgent').coreHash ? ('MeshCore CRC-' + crc32c(require('MeshAgent').coreHash)) : ('MeshCore v6')), caps: 14 }; // Capability bitmask: 1 = Desktop, 2 = Terminal, 4 = Files, 8 = Console, 16 = JavaScript, 32 = Temporary Agent, 64 = Recovery Agent
372 +
373 +
374 + // Get the operating system description string
375 + try { require('os').name().then(function (v) { meshCoreObj.osdesc = v; }); } catch (e) { }
376 +
377 + var meshServerConnectionState = 0;
378 + var tunnels = {};
379 + var lastMeInfo = null;
380 + var lastNetworkInfo = null;
381 + var lastPublicLocationInfo = null;
382 + var selfInfoUpdateTimer = null;
383 + var http = require('http');
384 + var net = require('net');
385 + var fs = require('fs');
386 + var rtc = require('ILibWebRTC');
387 + var amt = null;
388 + var processManager = require('process-manager');
389 + var wifiScannerLib = null;
390 + var wifiScanner = null;
391 + var networkMonitor = null;
392 + var amtscanner = null;
393 + var nextTunnelIndex = 1;
394 + var amtPolicy = null;
395 + var apftunnel = null;
396 + var tunnelUserCount = { terminal: {}, files: {}, tcp: {}, udp: {}, msg: {} }; // List of userid->count sessions for terminal, files and TCP/UDP routing
397 +
398 + // Add to the server event log
399 + function MeshServerLog(msg, state) {
400 + if (typeof msg == 'string') { msg = { action: 'log', msg: msg }; } else { msg.action = 'log'; }
401 + if (state) {
402 + if (state.userid) { msg.userid = state.userid; }
403 + if (state.username) { msg.username = state.username; }
404 + if (state.sessionid) { msg.sessionid = state.sessionid; }
405 + if (state.remoteaddr) { msg.remoteaddr = state.remoteaddr; }
406 + }
407 + mesh.SendCommand(msg);
408 + }
409 +
410 + // Add to the server event log, use internationalized events
411 + function MeshServerLogEx(id, args, msg, state) {
412 + var msg = { action: 'log', msgid: id, msgArgs: args, msg: msg };
413 + if (state) {
414 + if (state.userid) { msg.userid = state.userid; }
415 + if (state.username) { msg.username = state.username; }
416 + if (state.sessionid) { msg.sessionid = state.sessionid; }
417 + if (state.remoteaddr) { msg.remoteaddr = state.remoteaddr; }
418 + }
419 + mesh.SendCommand(msg);
420 + }
421 +
422 + // If we are running in Duktape, agent will be null
423 + if (agent == null) {
424 + // Running in native agent, Import libraries
425 + db = require('SimpleDataStore').Shared();
426 + sha = require('SHA256Stream');
427 + mesh = require('MeshAgent');
428 + childProcess = require('child_process');
429 + if (mesh.hasKVM == 1) { // if the agent is compiled with KVM support
430 + // Check if this computer supports a desktop
431 + try
432 + {
433 + if ((process.platform == 'win32') || (process.platform == 'darwin') || (require('monitor-info').kvm_x11_support))
434 + {
435 + meshCoreObj.caps |= 1;
436 + }
437 + else if(process.platform == 'linux' || process.platform == 'freebsd')
438 + {
439 + require('monitor-info').on('kvmSupportDetected', function (value)
440 + {
441 + meshCoreObj.caps |= 1;
442 + mesh.SendCommand(meshCoreObj);
443 + });
444 + }
445 + } catch (e) { }
446 + }
447 + } else {
448 + // Running in nodejs
449 + meshCoreObj.value += '-NodeJS';
450 + meshCoreObj.caps = 8;
451 + mesh = agent.getMeshApi();
452 + }
453 +
454 + mesh.DAIPC = obj.DAIPC;
455 +
456 + /*
457 + var AMTScanner = require("AMTScanner");
458 + var scan = new AMTScanner();
459 +
460 + scan.on("found", function (data) {
461 + if (typeof data === 'string') {
462 + console.log(data);
463 + } else {
464 + console.log(JSON.stringify(data, null, " "));
465 + }
466 + });
467 + scan.scan("10.2.55.140", 1000);
468 + scan.scan("10.2.55.139-10.2.55.145", 1000);
469 + scan.scan("10.2.55.128/25", 2000);
470 + */
471 +
472 + /*
473 + // Try to load up the network monitor
474 + try {
475 + networkMonitor = require('NetworkMonitor');
476 + networkMonitor.on('change', function () { sendNetworkUpdateNagle(); });
477 + networkMonitor.on('add', function (addr) { sendNetworkUpdateNagle(); });
478 + networkMonitor.on('remove', function (addr) { sendNetworkUpdateNagle(); });
479 + } catch (e) { networkMonitor = null; }
480 + */
481 +
482 + // Try to load up the Intel AMT scanner
483 + try {
484 + var AMTScannerModule = require('amt-scanner');
485 + amtscanner = new AMTScannerModule();
486 + //amtscanner.on('found', function (data) { if (typeof data != 'string') { data = JSON.stringify(data, null, " "); } sendConsoleText(data); });
487 + } catch (e) { amtscanner = null; }
488 +
489 + // Fetch the SMBios Tables
490 + var SMBiosTables = null;
491 + var SMBiosTablesRaw = null;
492 + try {
493 + var SMBiosModule = null;
494 + try { SMBiosModule = require('smbios'); } catch (e) { }
495 + if (SMBiosModule != null) {
496 + SMBiosModule.get(function (data) {
497 + if (data != null) {
498 + SMBiosTablesRaw = data;
499 + SMBiosTables = require('smbios').parse(data)
500 + if (mesh.isControlChannelConnected) { mesh.SendCommand({ action: 'smbios', value: SMBiosTablesRaw }); }
501 +
502 + // If SMBios tables say that Intel AMT is present, try to connect MEI
503 + if (SMBiosTables.amtInfo && (SMBiosTables.amtInfo.AMT == true))
504 + {
505 + var amtmodule = require('amt-manage');
506 + amt = new amtmodule(mesh, db, false);
507 + amt.on('portBinding_LMS', function (map)
508 + {
509 + var j = { action: 'lmsinfo', value: { ports: map.keys() } };
510 + mesh.SendCommand(j);
511 + });
512 + amt.on('stateChange_LMS', function (v)
513 + {
514 + if (!meshCoreObj.intelamt) { meshCoreObj.intelamt = {}; }
515 + switch(v)
516 + {
517 + case 0:
518 + meshCoreObj.intelamt.microlms = 'DISABLED';
519 + break;
520 + case 1:
521 + meshCoreObj.intelamt.microlms = 'CONNECTING';
522 + break;
523 + case 2:
524 + meshCoreObj.intelamt.microlms = 'CONNECTED';
525 + break;
526 + default:
527 + break;
528 + }
529 + mesh.SendCommand(meshCoreObj);
530 + });
531 + amt.onStateChange = function (state) { if (state == 2) { sendPeriodicServerUpdate(1); } }
532 + if (amtPolicy != null) { amt.setPolicy(amtPolicy); }
533 + amt.start();
534 + }
535 + }
536 + });
537 + }
538 + } catch (e) { sendConsoleText("ex1: " + e); }
539 +
540 + // Try to load up the WIFI scanner
541 + try {
542 + var wifiScannerLib = require('wifi-scanner');
543 + wifiScanner = new wifiScannerLib();
544 + wifiScanner.on('accessPoint', function (data) { sendConsoleText("wifiScanner: " + data); });
545 + } catch (e) { wifiScannerLib = null; wifiScanner = null; }
546 +
547 + // Get our location (lat/long) using our public IP address
548 + var getIpLocationDataExInProgress = false;
549 + var getIpLocationDataExCounts = [0, 0];
550 + function getIpLocationDataEx(func) {
551 + if (getIpLocationDataExInProgress == true) { return false; }
552 + try {
553 + getIpLocationDataExInProgress = true;
554 + getIpLocationDataExCounts[0]++;
555 + var options = http.parseUri("http://ipinfo.io/json");
556 + options.method = 'GET';
557 + http.request(options, function (resp) {
558 + if (resp.statusCode == 200) {
559 + var geoData = '';
560 + resp.data = function (geoipdata) { geoData += geoipdata; };
561 + resp.end = function () {
562 + var location = null;
563 + try {
564 + if (typeof geoData == 'string') {
565 + var result = JSON.parse(geoData);
566 + if (result.ip && result.loc) { location = result; }
567 + }
568 + } catch (e) { }
569 + if (func) { getIpLocationDataExCounts[1]++; func(location); }
570 + }
571 + } else { func(null); }
572 + getIpLocationDataExInProgress = false;
573 + }).end();
574 + return true;
575 + }
576 + catch (e) { return false; }
577 + }
578 +
579 + // Remove all Gateway MAC addresses for interface list. This is useful because the gateway MAC is not always populated reliably.
580 + function clearGatewayMac(str) {
581 + if (str == null) return null;
582 + var x = JSON.parse(str);
583 + for (var i in x.netif) { if (x.netif[i].gatewaymac) { delete x.netif[i].gatewaymac } }
584 + return JSON.stringify(x);
585 + }
586 +
587 + function getIpLocationData(func) {
588 + // Get the location information for the cache if possible
589 + var publicLocationInfo = db.Get('publicLocationInfo');
590 + if (publicLocationInfo != null) { publicLocationInfo = JSON.parse(publicLocationInfo); }
591 + if (publicLocationInfo == null) {
592 + // Nothing in the cache, fetch the data
593 + getIpLocationDataEx(function (locationData) {
594 + if (locationData != null) {
595 + publicLocationInfo = {};
596 + publicLocationInfo.netInfoStr = lastNetworkInfo;
597 + publicLocationInfo.locationData = locationData;
598 + var x = db.Put('publicLocationInfo', JSON.stringify(publicLocationInfo)); // Save to database
599 + if (func) func(locationData); // Report the new location
600 + } else {
601 + if (func) func(null); // Report no location
602 + }
603 + });
604 + } else {
605 + // Check the cache
606 + if (clearGatewayMac(publicLocationInfo.netInfoStr) == clearGatewayMac(lastNetworkInfo)) {
607 + // Cache match
608 + if (func) func(publicLocationInfo.locationData);
609 + } else {
610 + // Cache mismatch
611 + getIpLocationDataEx(function (locationData) {
612 + if (locationData != null) {
613 + publicLocationInfo = {};
614 + publicLocationInfo.netInfoStr = lastNetworkInfo;
615 + publicLocationInfo.locationData = locationData;
616 + var x = db.Put('publicLocationInfo', JSON.stringify(publicLocationInfo)); // Save to database
617 + if (func) func(locationData); // Report the new location
618 + } else {
619 + if (func) func(publicLocationInfo.locationData); // Can't get new location, report the old location
620 + }
621 + });
622 + }
623 + }
624 + }
625 +
626 + // Polyfill String.endsWith
627 + if (!String.prototype.endsWith) {
628 + String.prototype.endsWith = function (searchString, position) {
629 + var subjectString = this.toString();
630 + if (typeof position !== 'number' || !isFinite(position) || Math.floor(position) !== position || position > subjectString.length) { position = subjectString.length; }
631 + position -= searchString.length;
632 + var lastIndex = subjectString.lastIndexOf(searchString, position);
633 + return lastIndex !== -1 && lastIndex === position;
634 + };
635 + }
636 +
637 + // Polyfill path.join
638 + obj.path = {
639 + join: function () {
640 + var x = [];
641 + for (var i in arguments) {
642 + var w = arguments[i];
643 + if (w != null) {
644 + while (w.endsWith('/') || w.endsWith('\\')) { w = w.substring(0, w.length - 1); }
645 + if (i != 0) {
646 + while (w.startsWith('/') || w.startsWith('\\')) { w = w.substring(1); }
647 + }
648 + x.push(w);
649 + }
650 + }
651 + if (x.length == 0) return '/';
652 + return x.join('/');
653 + }
654 + };
655 +
656 + // Replace a string with a number if the string is an exact number
657 + function toNumberIfNumber(x) { if ((typeof x == 'string') && (+parseInt(x) === x)) { x = parseInt(x); } return x; }
658 +
659 + // Convert decimal to hex
660 + function char2hex(i) { return (i + 0x100).toString(16).substr(-2).toUpperCase(); }
661 +
662 + // Convert a raw string to a hex string
663 + function rstr2hex(input) { var r = '', i; for (i = 0; i < input.length; i++) { r += char2hex(input.charCodeAt(i)); } return r; }
664 +
665 + // Convert a buffer into a string
666 + function buf2rstr(buf) { var r = ''; for (var i = 0; i < buf.length; i++) { r += String.fromCharCode(buf[i]); } return r; }
667 +
668 + // Convert a hex string to a raw string // TODO: Do this using Buffer(), will be MUCH faster
669 + function hex2rstr(d) {
670 + if (typeof d != "string" || d.length == 0) return '';
671 + var r = '', m = ('' + d).match(/../g), t;
672 + while (t = m.shift()) r += String.fromCharCode('0x' + t);
673 + return r
674 + }
675 +
676 + // Convert an object to string with all functions
677 + function objToString(x, p, pad, ret) {
678 + if (ret == undefined) ret = '';
679 + if (p == undefined) p = 0;
680 + if (x == null) { return '[null]'; }
681 + if (p > 8) { return '[...]'; }
682 + if (x == undefined) { return '[undefined]'; }
683 + if (typeof x == 'string') { if (p == 0) return x; return '"' + x + '"'; }
684 + if (typeof x == 'buffer') { return '[buffer]'; }
685 + if (typeof x != 'object') { return x; }
686 + var r = '{' + (ret ? '\r\n' : ' ');
687 + for (var i in x) { if (i != '_ObjectID') { r += (addPad(p + 2, pad) + i + ': ' + objToString(x[i], p + 2, pad, ret) + (ret ? '\r\n' : ' ')); } }
688 + return r + addPad(p, pad) + '}';
689 + }
690 +
691 + // Return p number of spaces
692 + function addPad(p, ret) { var r = ''; for (var i = 0; i < p; i++) { r += ret; } return r; }
693 +
694 + // Split a string taking into account the quoats. Used for command line parsing
695 + function splitArgs(str) {
696 + var myArray = [], myRegexp = /[^\s"]+|"([^"]*)"/gi;
697 + do { var match = myRegexp.exec(str); if (match != null) { myArray.push(match[1] ? match[1] : match[0]); } } while (match != null);
698 + return myArray;
699 + }
700 +
701 + // Parse arguments string array into an object
702 + function parseArgs(argv) {
703 + var results = { '_': [] }, current = null;
704 + for (var i = 1, len = argv.length; i < len; i++) {
705 + var x = argv[i];
706 + if (x.length > 2 && x[0] == '-' && x[1] == '-') {
707 + if (current != null) { results[current] = true; }
708 + current = x.substring(2);
709 + } else {
710 + if (current != null) { results[current] = toNumberIfNumber(x); current = null; } else { results['_'].push(toNumberIfNumber(x)); }
711 + }
712 + }
713 + if (current != null) { results[current] = true; }
714 + return results;
715 + }
716 +
717 + // Get server target url with a custom path
718 + function getServerTargetUrl(path) {
719 + var x = mesh.ServerUrl;
720 + //sendConsoleText("mesh.ServerUrl: " + mesh.ServerUrl);
721 + if (x == null) { return null; }
722 + if (path == null) { path = ''; }
723 + x = http.parseUri(x);
724 + if (x == null) return null;
725 + return x.protocol + '//' + x.host + ':' + x.port + '/' + path;
726 + }
727 +
728 + // Get server url. If the url starts with "*/..." change it, it not use the url as is.
729 + function getServerTargetUrlEx(url) {
730 + if (url.substring(0, 2) == '*/') { return getServerTargetUrl(url.substring(2)); }
731 + return url;
732 + }
733 +
734 + // Send a wake-on-lan packet
735 + function sendWakeOnLan(hexMac) {
736 + hexMac = hexMac.split(':').join('');
737 + var count = 0;
738 + try {
739 + var interfaces = require('os').networkInterfaces();
740 + var magic = 'FFFFFFFFFFFF';
741 + for (var x = 1; x <= 16; ++x) { magic += hexMac; }
742 + var magicbin = Buffer.from(magic, 'hex');
743 +
744 + for (var adapter in interfaces) {
745 + if (interfaces.hasOwnProperty(adapter)) {
746 + for (var i = 0; i < interfaces[adapter].length; ++i) {
747 + var addr = interfaces[adapter][i];
748 + if ((addr.family == 'IPv4') && (addr.mac != '00:00:00:00:00:00')) {
749 + try {
750 + var socket = require('dgram').createSocket({ type: 'udp4' });
751 + socket.bind({ address: addr.address });
752 + socket.setBroadcast(true);
753 + socket.setMulticastInterface(addr.address);
754 + socket.setMulticastTTL(1);
755 + socket.send(magicbin, 7, '255.255.255.255');
756 + socket.descriptorMetadata = 'WoL (' + addr.address + ' => ' + hexMac + ')';
757 + count++;
758 + }
759 + catch (e) { }
760 + }
761 + }
762 + }
763 + }
764 + } catch (e) { }
765 + return count;
766 + }
767 +
768 + // Handle a mesh agent command
769 + function handleServerCommand(data) {
770 + if (typeof data == 'object') {
771 + // If this is a console command, parse it and call the console handler
772 + switch (data.action) {
773 + case 'msg': {
774 + switch (data.type) {
775 + case 'console': { // Process a console command
776 + if (data.value && data.sessionid) {
777 + MeshServerLogEx(17, [data.value], "Processing console command: " + data.value, data);
778 + var args = splitArgs(data.value);
779 + processConsoleCommand(args[0].toLowerCase(), parseArgs(args), data.rights, data.sessionid);
780 + }
781 + break;
782 + }
783 + case 'tunnel': {
784 + if (data.value != null) { // Process a new tunnel connection request
785 + // Create a new tunnel object
786 + var xurl = getServerTargetUrlEx(data.value);
787 + if (xurl != null) {
788 + xurl = xurl.split('$').join('%24').split('@').join('%40'); // Escape the $ and @ characters
789 + var woptions = http.parseUri(xurl);
790 + woptions.perMessageDeflate = false;
791 + if (typeof data.perMessageDeflate == 'boolean') { woptions.perMessageDeflate = data.perMessageDeflate; }
792 + woptions.rejectUnauthorized = 0;
793 + //sendConsoleText(JSON.stringify(woptions));
794 + //sendConsoleText('TUNNEL: ' + JSON.stringify(data));
795 + var tunnel = http.request(woptions);
796 + tunnel.upgrade = onTunnelUpgrade;
797 + tunnel.on('error', function (e) { sendConsoleText("ERROR: Unable to connect relay tunnel to: " + this.url + ", " + JSON.stringify(e)); });
798 + tunnel.sessionid = data.sessionid;
799 + tunnel.rights = data.rights;
800 + tunnel.consent = data.consent;
801 + tunnel.privacybartext = data.privacybartext ? data.privacybartext : "Sharing desktop with: {0}";
802 + tunnel.username = data.username + (data.guestname ? (' - ' + data.guestname) : '');
803 + tunnel.realname = (data.realname ? data.realname : data.username) + (data.guestname ? (' - ' + data.guestname) : '');
804 + tunnel.userid = data.userid;
805 + tunnel.remoteaddr = data.remoteaddr;
806 + tunnel.state = 0;
807 + tunnel.url = xurl;
808 + tunnel.protocol = 0;
809 + tunnel.soptions = data.soptions;
810 + tunnel.tcpaddr = data.tcpaddr;
811 + tunnel.tcpport = data.tcpport;
812 + tunnel.udpaddr = data.udpaddr;
813 + tunnel.udpport = data.udpport;
814 + tunnel.end();
815 + // Put the tunnel in the tunnels list
816 + var index = nextTunnelIndex++;
817 + tunnel.index = index;
818 + tunnels[index] = tunnel;
819 +
820 + //sendConsoleText('New tunnel connection #' + index + ': ' + tunnel.url + ', rights: ' + tunnel.rights, data.sessionid);
821 + }
822 + }
823 + break;
824 + }
825 + case 'messagebox': {
826 + // Display a message box
827 + if (data.title && data.msg) {
828 + MeshServerLogEx(18, [data.title, data.msg], "Displaying message box, title=" + data.title + ", message=" + data.msg, data);
829 + data.msg = data.msg.split('\r').join('\\r').split('\n').join('\\n');
830 + try { require('message-box').create(data.title, data.msg, 120); } catch (e) { }
831 + }
832 + break;
833 + }
834 + case 'ps': {
835 + // Return the list of running processes
836 + if (data.sessionid) {
837 + processManager.getProcesses(function (plist) {
838 + mesh.SendCommand({ action: 'msg', type: 'ps', value: JSON.stringify(plist), sessionid: data.sessionid });
839 + });
840 + }
841 + break;
842 + }
843 + case 'pskill': {
844 + // Kill a process
845 + if (data.value) {
846 + MeshServerLogEx(19, [data.value], "Killing process " + data.value, data);
847 + try { process.kill(data.value); } catch (e) { sendConsoleText("pskill: " + JSON.stringify(e)); }
848 + }
849 + break;
850 + }
851 + case 'services': {
852 + // Return the list of installed services
853 + var services = null;
854 + try { services = require('service-manager').manager.enumerateService(); } catch (e) { }
855 + if (services != null) { mesh.SendCommand({ action: 'msg', type: 'services', value: JSON.stringify(services), sessionid: data.sessionid }); }
856 + break;
857 + }
858 + case 'serviceStop': {
859 + // Stop a service
860 + try {
861 + var service = require('service-manager').manager.getService(data.serviceName);
862 + if (service != null) { service.stop(); }
863 + } catch (e) { }
864 + break;
865 + }
866 + case 'serviceStart': {
867 + // Start a service
868 + try {
869 + var service = require('service-manager').manager.getService(data.serviceName);
870 + if (service != null) { service.start(); }
871 + } catch (e) { }
872 + break;
873 + }
874 + case 'serviceRestart': {
875 + // Restart a service
876 + try {
877 + var service = require('service-manager').manager.getService(data.serviceName);
878 + if (service != null) { service.restart(); }
879 + } catch (e) { }
880 + break;
881 + }
882 + case 'deskBackground':
883 + {
884 + // Toggle desktop background
885 + try {
886 + if (process.platform == 'win32') {
887 + var stype = require('user-sessions').getProcessOwnerName(process.pid).tsid == 0 ? 1 : 0;
888 + var sid = undefined;
889 + if (stype == 1) {
890 + if (require('MeshAgent')._tsid != null) {
891 + stype = 5;
892 + sid = require('MeshAgent')._tsid;
893 + }
894 + }
895 + var id = require('user-sessions').getProcessOwnerName(process.pid).tsid == 0 ? 1 : 0;
896 + var child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0='], { type: stype, uid: sid });
897 + child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
898 + child.stderr.on('data', function () { });
899 + child.waitExit();
900 + var current = child.stdout.str.trim();
901 + if (current != '') { require('MeshAgent')._wallpaper = current; }
902 + child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0=', current != '' ? '""' : require('MeshAgent')._wallpaper], { type: stype, uid: sid });
903 + child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
904 + child.stderr.on('data', function () { });
905 + child.waitExit();
906 + } else {
907 + var id = require('user-sessions').consoleUid();
908 + var current = require('linux-gnome-helpers').getDesktopWallpaper(id);
909 + if (current != '/dev/null') { require('MeshAgent')._wallpaper = current; }
910 + require('linux-gnome-helpers').setDesktopWallpaper(id, current != '/dev/null' ? undefined : require('MeshAgent')._wallpaper);
911 + }
912 + } catch (e) {
913 + sendConsoleText(e);
914 + }
915 + break;
916 + }
917 + case 'openUrl': {
918 + // Open a local web browser and return success/fail
919 + MeshServerLogEx(20, [data.url], "Opening: " + data.url, data);
920 + sendConsoleText("OpenURL: " + data.url);
921 + if (data.url) { mesh.SendCommand({ action: 'msg', type: 'openUrl', url: data.url, sessionid: data.sessionid, success: (openUserDesktopUrl(data.url) != null) }); }
922 + break;
923 + }
924 + case 'getclip': {
925 + // Send the load clipboard back to the user
926 + //sendConsoleText('getClip: ' + JSON.stringify(data));
927 + if (require('MeshAgent').isService) {
928 + require('clipboard').dispatchRead().then(function (str) {
929 + if (str) {
930 + MeshServerLogEx(21, [str.length], "Getting clipboard content, " + str.length + " byte(s)", data);
931 + mesh.SendCommand({ action: 'msg', type: 'getclip', sessionid: data.sessionid, data: str, tag: data.tag });
932 + }
933 + });
934 + } else {
935 + require("clipboard").read().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 + }
942 + break;
943 + }
944 + case 'setclip': {
945 + // Set the load clipboard to a user value
946 + //sendConsoleText('setClip: ' + JSON.stringify(data));
947 + if (typeof data.data == 'string') {
948 + MeshServerLogEx(22, [data.data.length], "Setting clipboard content, " + data.data.length + " byte(s)", data);
949 + if (require('MeshAgent').isService) { require('clipboard').dispatchWrite(data.data); } else { require("clipboard")(data.data); } // Set the clipboard
950 + mesh.SendCommand({ action: 'msg', type: 'setclip', sessionid: data.sessionid, success: true });
951 + }
952 + break;
953 + }
954 + case 'userSessions': {
955 + // Send back current user sessions list, this is Windows only.
956 + //sendConsoleText('userSessions: ' + JSON.stringify(data));
957 + if (process.platform != 'win32') break;
958 + var p = require('user-sessions').enumerateUsers();
959 + p.sessionid = data.sessionid;
960 + p.then(function (u) { mesh.SendCommand({ action: 'msg', type: 'userSessions', sessionid: data.sessionid, data: u, tag: data.tag }); });
961 + break;
962 + }
963 + default:
964 + // Unknown action, ignore it.
965 + break;
966 + }
967 + break;
968 + }
969 + case 'acmactivate': {
970 + if (amt != null) {
971 + MeshServerLogEx(23, null, "Attempting Intel AMT ACM mode activation", data);
972 + amt.setAcmResponse(data);
973 + }
974 + break;
975 + }
976 + case 'wakeonlan': {
977 + // Send wake-on-lan on all interfaces for all MAC addresses in data.macs array. The array is a list of HEX MAC addresses.
978 + sendConsoleText("Server requesting wake-on-lan for: " + data.macs.join(', '));
979 + for (var i in data.macs) { sendWakeOnLan(data.macs[i]); }
980 + break;
981 + }
982 + case 'runcommands': {
983 + if (mesh.cmdchild != null) { sendConsoleText("Run commands can't execute, already busy."); break; }
984 + sendConsoleText("Run commands (" + data.runAsUser + "): " + data.cmds);
985 +
986 + // data.runAsUser: 0=Agent,1=UserOrAgent,2=UserOnly
987 + var options = {};
988 + if (data.runAsUser > 0) {
989 + try { options.uid = require('user-sessions').consoleUid(); } catch (e) { }
990 + options.type = require('child_process').SpawnTypes.TERM;
991 + }
992 + if (data.runAsUser == 2) {
993 + if (options.uid == null) break;
994 + if (((require('user-sessions').minUid != null) && (options.uid < require('user-sessions').minUid()))) break; // This command can only run as user.
995 + }
996 +
997 + if (process.platform == 'win32') {
998 + if (data.type == 1) {
999 + // Windows command shell
1000 + mesh.cmdchild = require('child_process').execFile(process.env['windir'] + '\\system32\\cmd.exe', ['cmd'], options);
1001 + mesh.cmdchild.descriptorMetadata = 'UserCommandsShell';
1002 + mesh.cmdchild.stdout.on('data', function (c) { sendConsoleText(c.toString()); });
1003 + mesh.cmdchild.stderr.on('data', function (c) { sendConsoleText(c.toString()); });
1004 + mesh.cmdchild.stdin.write(data.cmds + '\r\nexit\r\n');
1005 + mesh.cmdchild.on('exit', function () { sendConsoleText("Run commands completed."); delete mesh.cmdchild; });
1006 + } else if (data.type == 2) {
1007 + // Windows Powershell
1008 + mesh.cmdchild = require('child_process').execFile(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', ['powershell', '-noprofile', '-nologo', '-command', '-'], options);
1009 + mesh.cmdchild.descriptorMetadata = 'UserCommandsPowerShell';
1010 + mesh.cmdchild.stdout.on('data', function (c) { sendConsoleText(c.toString()); });
1011 + mesh.cmdchild.stderr.on('data', function (c) { sendConsoleText(c.toString()); });
1012 + mesh.cmdchild.stdin.write(data.cmds + '\r\nexit\r\n');
1013 + mesh.cmdchild.on('exit', function () { sendConsoleText("Run commands completed."); delete mesh.cmdchild; });
1014 + }
1015 + } else if (data.type == 3) {
1016 + // Linux shell
1017 + mesh.cmdchild = require('child_process').execFile('/bin/sh', ['sh'], options);
1018 + mesh.cmdchild.descriptorMetadata = 'UserCommandsShell';
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.split('\r').join('') + '\nexit\n');
1022 + mesh.cmdchild.on('exit', function () { sendConsoleText("Run commands completed."); delete mesh.cmdchild; });
1023 + }
1024 + break;
1025 + }
1026 + case 'uninstallagent':
1027 + // Uninstall this agent
1028 + var agentName = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent';
1029 + if (require('service-manager').manager.getService(agentName).isMe()) {
1030 + try { diagnosticAgent_uninstall(); } catch (e) { }
1031 + var js = "require('service-manager').manager.getService('" + agentName + "').stop(); require('service-manager').manager.uninstallService('" + agentName + "'); process.exit();";
1032 + 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 });
1033 + }
1034 + break;
1035 + case 'poweraction': {
1036 + // Server telling us to execute a power action
1037 + if ((mesh.ExecPowerState != undefined) && (data.actiontype)) {
1038 + var forced = 0;
1039 + if (data.forced == 1) { forced = 1; }
1040 + data.actiontype = parseInt(data.actiontype);
1041 + MeshServerLogEx(25, [data.actiontype, forced], "Performing power action=" + data.actiontype + ", forced=" + forced, data);
1042 + sendConsoleText("Performing power action=" + data.actiontype + ", forced=" + forced + '.');
1043 + var r = mesh.ExecPowerState(data.actiontype, forced);
1044 + sendConsoleText("ExecPowerState returned code: " + r);
1045 + }
1046 + break;
1047 + }
1048 + case 'iplocation': {
1049 + // 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
1050 + getIpLocationData(function (location) { mesh.SendCommand({ action: 'iplocation', type: 'publicip', value: location }); });
1051 + break;
1052 + }
1053 + case 'toast': {
1054 + // Display a toast message
1055 + if (data.title && data.msg) {
1056 + MeshServerLogEx(26, [data.title, data.msg], "Displaying toast message, title=" + data.title + ", message=" + data.msg, data);
1057 + data.msg = data.msg.split('\r').join('\\r').split('\n').join('\\n');
1058 + try { require('toaster').Toast(data.title, data.msg); } catch (e) { }
1059 + }
1060 + break;
1061 + }
1062 + case 'openUrl': {
1063 + // Open a local web browser and return success/fail
1064 + //sendConsoleText('OpenURL: ' + data.url);
1065 + MeshServerLogEx(20, [data.url], "Opening: " + data.url, data);
1066 + if (data.url) { mesh.SendCommand({ action: 'openUrl', url: data.url, sessionid: data.sessionid, success: (openUserDesktopUrl(data.url) != null) }); }
1067 + break;
1068 + }
1069 + case 'amtPolicy': {
1070 + // Store the latest Intel AMT policy
1071 + amtPolicy = data.amtPolicy;
1072 + if (data.amtPolicy != null) { db.Put('amtPolicy', JSON.stringify(data.amtPolicy)); } else { db.Put('amtPolicy', null); }
1073 + if (amt != null) { amt.setPolicy(amtPolicy, true); }
1074 + break;
1075 + }
1076 + case 'getScript': {
1077 + // Received a configuration script from the server
1078 + sendConsoleText('getScript: ' + JSON.stringify(data));
1079 + break;
1080 + }
1081 + case 'sysinfo': {
1082 + // Fetch system information
1083 + getSystemInformation(function (results) {
1084 + if ((results != null) && (data.hash != results.hash)) { mesh.SendCommand({ action: 'sysinfo', sessionid: this.sessionid, data: results }); }
1085 + });
1086 + break;
1087 + }
1088 + case 'ping': { mesh.SendCommand('{"action":"pong"}'); break; }
1089 + case 'pong': { break; }
1090 + case 'plugin': {
1091 + try { require(data.plugin).consoleaction(data, data.rights, data.sessionid, this); } catch (e) { throw e; }
1092 + break;
1093 + }
1094 + case 'coredump':
1095 + // Set the current agent coredump situation.
1096 + if (data.value === true) {
1097 + // TODO: This replace() below is not ideal, would be better to remove the .exe at the end instead of replace.
1098 + process.coreDumpLocation = (process.platform == 'win32') ? (process.execPath.replace('.exe', '.dmp')) : (process.execPath + '.dmp');
1099 + } else if (data.value === false) {
1100 + process.coreDumpLocation = null;
1101 + }
1102 + break;
1103 + case 'getcoredump':
1104 + // Ask the agent if a core dump is currently available, if yes, also return the hash of the agent.
1105 + var r = { action: 'getcoredump', value: (process.coreDumpLocation != null) };
1106 + var coreDumpPath = null;
1107 + if (process.platform == 'win32') { coreDumpPath = process.coreDumpLocation; } else { coreDumpPath = (process.cwd() != '//') ? fs.existsSync(process.cwd() + 'core') : null; }
1108 + if ((coreDumpPath != null) && (fs.existsSync(coreDumpPath))) { r.exists = (db.Get('CoreDumpTime') != require('fs').statSync(coreDumpPath).mtime); }
1109 + if (r.exists == true) { r.agenthashhex = getSHA384FileHash(process.execPath).toString('hex'); }
1110 + mesh.SendCommand(JSON.stringify(r));
1111 + default:
1112 + // Unknown action, ignore it.
1113 + break;
1114 + }
1115 + }
1116 + }
1117 +
1118 + // Called when a file changed in the file system
1119 + /*
1120 + function onFileWatcher(a, b) {
1121 + console.log('onFileWatcher', a, b, this.path);
1122 + var response = getDirectoryInfo(this.path);
1123 + if ((response != undefined) && (response != null)) { this.tunnel.s.write(JSON.stringify(response)); }
1124 + }
1125 + */
1126 +
1127 + function getSystemInformation(func) {
1128 + try {
1129 + var results = { hardware: require('identifiers').get() }; // Hardware info
1130 + if (results.hardware && results.hardware.windows) {
1131 + // Remove extra entries and things that change quickly
1132 + var x = results.hardware.windows.osinfo;
1133 + try { delete x.FreePhysicalMemory; } catch (e) { }
1134 + try { delete x.FreeSpaceInPagingFiles; } catch (e) { }
1135 + try { delete x.FreeVirtualMemory; } catch (e) { }
1136 + try { delete x.LocalDateTime; } catch (e) { }
1137 + try { delete x.MaxProcessMemorySize; } catch (e) { }
1138 + try { delete x.TotalVirtualMemorySize; } catch (e) { }
1139 + try { delete x.TotalVisibleMemorySize; } catch (e) { }
1140 + try {
1141 + if (results.hardware.windows.memory) { for (var i in results.hardware.windows.memory) { delete results.hardware.windows.memory[i].Node; } }
1142 + if (results.hardware.windows.osinfo) { delete results.hardware.windows.osinfo.Node; }
1143 + if (results.hardware.windows.partitions) { for (var i in results.hardware.windows.partitions) { delete results.hardware.windows.partitions[i].Node; } }
1144 + } catch (e) { }
1145 + }
1146 + if (process.platform == 'win32') { results.pendingReboot = require('win-info').pendingReboot(); } // Pending reboot
1147 + /*
1148 + if (process.platform == 'win32') {
1149 + var defragResult = function (r) {
1150 + if (typeof r == 'object') { results[this.callname] = r; }
1151 + if (this.callname == 'defrag') {
1152 + var pr = require('win-info').installedApps(); // Installed apps
1153 + pr.callname = 'installedApps';
1154 + pr.sessionid = data.sessionid;
1155 + pr.then(defragResult, defragResult);
1156 + }
1157 + else {
1158 + results.winpatches = require('win-info').qfe(); // Windows patches
1159 + results.hash = require('SHA384Stream').create().syncHash(JSON.stringify(results)).toString('hex');
1160 + func(results);
1161 + }
1162 + }
1163 + var pr = require('win-info').defrag({ volume: 'C:' }); // Defrag TODO
1164 + pr.callname = 'defrag';
1165 + pr.sessionid = data.sessionid;
1166 + pr.then(defragResult, defragResult);
1167 + } else {
1168 + */
1169 + results.hash = require('SHA384Stream').create().syncHash(JSON.stringify(results)).toString('hex');
1170 + func(results);
1171 + //}
1172 + } catch (e) { func(null, e); }
1173 + }
1174 +
1175 + // Get a formated response for a given directory path
1176 + function getDirectoryInfo(reqpath) {
1177 + var response = { path: reqpath, dir: [] };
1178 + if (((reqpath == undefined) || (reqpath == '')) && (process.platform == 'win32')) {
1179 + // List all the drives in the root, or the root itself
1180 + var results = null;
1181 + try { results = fs.readDrivesSync(); } catch (e) { } // TODO: Anyway to get drive total size and free space? Could draw a progress bar.
1182 + if (results != null) {
1183 + for (var i = 0; i < results.length; ++i) {
1184 + var drive = { n: results[i].name, t: 1 };
1185 + if (results[i].type == 'REMOVABLE') { drive.dt = 'removable'; } // TODO: See if this is USB/CDROM or something else, we can draw icons.
1186 + response.dir.push(drive);
1187 + }
1188 + }
1189 + } else {
1190 + // List all the files and folders in this path
1191 + if (reqpath == '') { reqpath = '/'; }
1192 + var results = null, xpath = obj.path.join(reqpath, '*');
1193 + //if (process.platform == "win32") { xpath = xpath.split('/').join('\\'); }
1194 + try { results = fs.readdirSync(xpath); } catch (e) { }
1195 + if (results != null) {
1196 + for (var i = 0; i < results.length; ++i) {
1197 + if ((results[i] != '.') && (results[i] != '..')) {
1198 + var stat = null, p = obj.path.join(reqpath, results[i]);
1199 + //if (process.platform == "win32") { p = p.split('/').join('\\'); }
1200 + try { stat = fs.statSync(p); } catch (e) { } // TODO: Get file size/date
1201 + if ((stat != null) && (stat != undefined)) {
1202 + if (stat.isDirectory() == true) {
1203 + response.dir.push({ n: results[i], t: 2, d: stat.mtime });
1204 + } else {
1205 + response.dir.push({ n: results[i], t: 3, s: stat.size, d: stat.mtime });
1206 + }
1207 + }
1208 + }
1209 + }
1210 + }
1211 + }
1212 + return response;
1213 + }
1214 +
1215 + // Tunnel callback operations
1216 + function onTunnelUpgrade(response, s, head) {
1217 + this.s = s;
1218 + s.httprequest = this;
1219 + s.end = onTunnelClosed;
1220 + s.tunnel = this;
1221 + s.descriptorMetadata = "MeshAgent_relayTunnel";
1222 +
1223 + if (require('MeshAgent').idleTimeout != null)
1224 + {
1225 + s.setTimeout(require('MeshAgent').idleTimeout * 1000);
1226 + s.on('timeout', function ()
1227 + {
1228 + this.ping();
1229 + this.setTimeout(require('MeshAgent').idleTimeout * 1000);
1230 + });
1231 + }
1232 +
1233 + //sendConsoleText('onTunnelUpgrade - ' + this.tcpport + ' - ' + this.udpport);
1234 +
1235 + if (this.tcpport != null) {
1236 + // This is a TCP relay connection, pause now and try to connect to the target.
1237 + s.pause();
1238 + s.data = onTcpRelayServerTunnelData;
1239 + var connectionOptions = { port: parseInt(this.tcpport) };
1240 + if (this.tcpaddr != null) { connectionOptions.host = this.tcpaddr; } else { connectionOptions.host = '127.0.0.1'; }
1241 + s.tcprelay = net.createConnection(connectionOptions, onTcpRelayTargetTunnelConnect);
1242 + s.tcprelay.peerindex = this.index;
1243 +
1244 + // Add the TCP session to the count and update the server
1245 + if (s.httprequest.userid != null) {
1246 + if (tunnelUserCount.tcp[s.httprequest.userid] == null) { tunnelUserCount.tcp[s.httprequest.userid] = 1; } else { tunnelUserCount.tcp[s.httprequest.userid]++; }
1247 + try { mesh.SendCommand({ action: 'sessions', type: 'tcp', value: tunnelUserCount.tcp }); } catch (e) { }
1248 + }
1249 + } if (this.udpport != null) {
1250 + // This is a UDP relay connection, get the UDP socket setup. // TODO: ***************
1251 + s.data = onUdpRelayServerTunnelData;
1252 + s.udprelay = require('dgram').createSocket({ type: 'udp4' });
1253 + s.udprelay.bind({ port: 0 });
1254 + s.udprelay.peerindex = this.index;
1255 + s.udprelay.on('message', onUdpRelayTargetTunnelConnect);
1256 + s.udprelay.udpport = this.udpport;
1257 + s.udprelay.udpaddr = this.udpaddr;
1258 + s.udprelay.first = true;
1259 +
1260 + // Add the UDP session to the count and update the server
1261 + if (s.httprequest.userid != null) {
1262 + if (tunnelUserCount.udp[s.httprequest.userid] == null) { tunnelUserCount.udp[s.httprequest.userid] = 1; } else { tunnelUserCount.udp[s.httprequest.userid]++; }
1263 + try { mesh.SendCommand({ action: 'sessions', type: 'udp', value: tunnelUserCount.tcp }); } catch (e) { }
1264 + }
1265 + } else {
1266 + // This is a normal connect for KVM/Terminal/Files
1267 + s.data = onTunnelData;
1268 + }
1269 + }
1270 +
1271 + // Called when UDP relay data is received // TODO****
1272 + function onUdpRelayTargetTunnelConnect(data) {
1273 + var peerTunnel = tunnels[this.peerindex];
1274 + peerTunnel.s.write(data);
1275 + }
1276 +
1277 + // Called when we get data from the server for a TCP relay (We have to skip the first received 'c' and pipe the rest)
1278 + function onUdpRelayServerTunnelData(data) {
1279 + if (this.udprelay.first === true) {
1280 + delete this.udprelay.first; // Skip the first 'c' that is received.
1281 + } else {
1282 + this.udprelay.send(data, parseInt(this.udprelay.udpport), this.udprelay.udpaddr ? this.udprelay.udpaddr : '127.0.0.1');
1283 + }
1284 + }
1285 +
1286 + // Called when the TCP relay target is connected
1287 + function onTcpRelayTargetTunnelConnect() {
1288 + var peerTunnel = tunnels[this.peerindex];
1289 + this.pipe(peerTunnel.s); // Pipe Target --> Server
1290 + peerTunnel.s.first = true;
1291 + peerTunnel.s.resume();
1292 + }
1293 +
1294 + // Called when we get data from the server for a TCP relay (We have to skip the first received 'c' and pipe the rest)
1295 + function onTcpRelayServerTunnelData(data) {
1296 + if (this.first == true) {
1297 + this.first = false;
1298 + this.pipe(this.tcprelay, { dataTypeSkip: 1 }); // Pipe Server --> Target (don't pipe text type websocket frames)
1299 + }
1300 + }
1301 +
1302 + function onTunnelClosed() {
1303 + var tunnel = tunnels[this.httprequest.index];
1304 + if (tunnel == null) return; // Stop duplicate calls.
1305 +
1306 + // If this is a routing session, clean up and send the new session counts.
1307 + if (this.httprequest.userid != null) {
1308 + if (this.httprequest.tcpport != null) {
1309 + 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]; } }
1310 + try { mesh.SendCommand({ action: 'sessions', type: 'tcp', value: tunnelUserCount.tcp }); } catch (e) { }
1311 + } else if (this.httprequest.udpport != null) {
1312 + 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]; } }
1313 + try { mesh.SendCommand({ action: 'sessions', type: 'udp', value: tunnelUserCount.udp }); } catch (e) { }
1314 + }
1315 + }
1316 +
1317 + // Sent tunnel statistics to the server, only send this if compression was used.
1318 + if ((this.bytesSent_uncompressed) && (this.bytesSent_uncompressed.toString() != this.bytesSent_actual.toString())) {
1319 + mesh.SendCommand({
1320 + action: 'tunnelCloseStats',
1321 + url: tunnel.url,
1322 + userid: tunnel.userid,
1323 + protocol: tunnel.protocol,
1324 + sessionid: tunnel.sessionid,
1325 + sent: this.bytesSent_uncompressed.toString(),
1326 + sentActual: this.bytesSent_actual.toString(),
1327 + sentRatio: this.bytesSent_ratio,
1328 + received: this.bytesReceived_uncompressed.toString(),
1329 + receivedActual: this.bytesReceived_actual.toString(),
1330 + receivedRatio: this.bytesReceived_ratio
1331 + });
1332 + }
1333 +
1334 + //sendConsoleText("Tunnel #" + this.httprequest.index + " closed. Sent -> " + this.bytesSent_uncompressed + ' bytes (uncompressed), ' + this.bytesSent_actual + ' bytes (actual), ' + this.bytesSent_ratio + '% compression', this.httprequest.sessionid);
1335 + if (this.httprequest.index) { delete tunnels[this.httprequest.index]; }
1336 +
1337 + /*
1338 + // Close the watcher if required
1339 + if (this.httprequest.watcher != undefined) {
1340 + //console.log('Closing watcher: ' + this.httprequest.watcher.path);
1341 + //this.httprequest.watcher.close(); // TODO: This line causes the agent to crash!!!!
1342 + delete this.httprequest.watcher;
1343 + }
1344 + */
1345 +
1346 + // If there is a upload or download active on this connection, close the file
1347 + if (this.httprequest.uploadFile) { fs.closeSync(this.httprequest.uploadFile); delete this.httprequest.uploadFile; delete this.httprequest.uploadFileid; delete this.httprequest.uploadFilePath; }
1348 + if (this.httprequest.downloadFile) { delete this.httprequest.downloadFile; }
1349 +
1350 + // Clean up WebRTC
1351 + if (this.webrtc != null) {
1352 + 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; }
1353 + if (this.webrtc.websocket) { delete this.webrtc.websocket; }
1354 + try { this.webrtc.close(); } catch (e) { }
1355 + this.webrtc.removeAllListeners('connected');
1356 + this.webrtc.removeAllListeners('disconnected');
1357 + this.webrtc.removeAllListeners('dataChannel');
1358 + delete this.webrtc;
1359 + }
1360 +
1361 + // Clean up WebSocket
1362 + this.removeAllListeners('data');
1363 + }
1364 + function onTunnelSendOk() { /*sendConsoleText("Tunnel #" + this.index + " SendOK.", this.sessionid);*/ }
1365 + function onTunnelData(data) {
1366 + //console.log("OnTunnelData");
1367 + //sendConsoleText('OnTunnelData, ' + data.length + ', ' + typeof data + ', ' + data);
1368 +
1369 + // If this is upload data, save it to file
1370 + if ((this.httprequest.uploadFile) && (typeof data == 'object') && (data[0] != 123)) {
1371 + // Save the data to file being uploaded.
1372 + if (data[0] == 0) {
1373 + // If data starts with zero, skip the first byte. This is used to escape binary file data from JSON.
1374 + 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.
1375 + } else {
1376 + // If data does not start with zero, save as-is.
1377 + 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.
1378 + }
1379 + this.write(Buffer.from(JSON.stringify({ action: 'uploadack', reqid: this.httprequest.uploadFileid }))); // Ask for more data.
1380 + return;
1381 + }
1382 +
1383 + if (this.httprequest.state == 0) {
1384 + // Check if this is a relay connection
1385 + if ((data == 'c') || (data == 'cr')) { this.httprequest.state = 1; /*sendConsoleText("Tunnel #" + this.httprequest.index + " now active", this.httprequest.sessionid);*/ }
1386 + }
1387 + else
1388 + {
1389 + // Handle tunnel data
1390 + 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
1391 + // Take a look at the protocol
1392 + if ((data.length > 3) && (data[0] == '{')) { onTunnelControlData(data, this); return; }
1393 + this.httprequest.protocol = parseInt(data);
1394 + if (typeof this.httprequest.protocol != 'number') { this.httprequest.protocol = 0; }
1395 + if (this.httprequest.protocol == 10) {
1396 + //
1397 + // Basic file transfer
1398 + //
1399 + var stats = null;
1400 + if ((process.platform != 'win32') && (this.httprequest.xoptions.file.startsWith('/') == false)) { this.httprequest.xoptions.file = '/' + this.httprequest.xoptions.file; }
1401 + try { stats = require('fs').statSync(this.httprequest.xoptions.file) } catch (e) { }
1402 + try { if (stats) { this.httprequest.downloadFile = fs.createReadStream(this.httprequest.xoptions.file, { flags: 'rbN' }); } } catch (e) { }
1403 + if (this.httprequest.downloadFile) {
1404 + //sendConsoleText('BasicFileTransfer, ok, ' + this.httprequest.xoptions.file + ', ' + JSON.stringify(stats));
1405 + this.write(JSON.stringify({ op: 'ok', size: stats.size }));
1406 + this.httprequest.downloadFile.pipe(this);
1407 + this.httprequest.downloadFile.end = function () { }
1408 + } else {
1409 + //sendConsoleText('BasicFileTransfer, cancel, ' + this.httprequest.xoptions.file);
1410 + this.write(JSON.stringify({ op: 'cancel' }));
1411 + }
1412 + }
1413 + else if ((this.httprequest.protocol == 1) || (this.httprequest.protocol == 6) || (this.httprequest.protocol == 8) || (this.httprequest.protocol == 9))
1414 + {
1415 + //
1416 + // Remote Terminal
1417 + //
1418 +
1419 + // Check user access rights for terminal
1420 + if (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) || ((this.httprequest.rights != 0xFFFFFFFF) && ((this.httprequest.rights & MESHRIGHT_NOTERMINAL) != 0))) {
1421 + // Disengage this tunnel, user does not have the rights to do this!!
1422 + this.httprequest.protocol = 999999;
1423 + this.httprequest.s.end();
1424 + sendConsoleText("Error: No Terminal Control Rights.");
1425 + return;
1426 + }
1427 +
1428 + this.descriptorMetadata = "Remote Terminal";
1429 +
1430 + if (process.platform == 'win32')
1431 + {
1432 + if (!require('win-terminal').PowerShellCapable() && (this.httprequest.protocol == 6 || this.httprequest.protocol == 9))
1433 + {
1434 + this.httprequest.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: 'PowerShell is not supported on this version of windows', msgid: 1 }));
1435 + this.httprequest.s.end();
1436 + return;
1437 + }
1438 + }
1439 +
1440 + var prom = require('promise');
1441 + this.httprequest.tpromise = new prom(function (res, rej) { this._res = res; this._rej = rej; });
1442 + this.httprequest.tpromise.that = this;
1443 + this.httprequest.tpromise.httprequest = this.httprequest;
1444 +
1445 + this.end = function ()
1446 + {
1447 + if (this.httprequest.tpromise._consent) { this.httprequest.tpromise._consent.close(); }
1448 + if (this.httprequest.connectionPromise) { this.httprequest.connectionPromise._rej('Closed'); }
1449 +
1450 + // Remove the terminal session to the count to update the server
1451 + if (this.httprequest.userid != null)
1452 + {
1453 + 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]; } }
1454 + try { mesh.SendCommand({ action: 'sessions', type: 'terminal', value: tunnelUserCount.terminal }); } catch (e) { }
1455 + }
1456 +
1457 + if (process.platform == 'win32')
1458 + {
1459 + // Unpipe the web socket
1460 + this.unpipe(this.httprequest._term);
1461 + if (this.httprequest._term) { this.httprequest._term.unpipe(this); }
1462 +
1463 + // Unpipe the WebRTC channel if needed (This will also be done when the WebRTC channel ends).
1464 + if (this.rtcchannel)
1465 + {
1466 + this.rtcchannel.unpipe(this.httprequest._term);
1467 + if (this.httprequest._term) { this.httprequest._term.unpipe(this.rtcchannel); }
1468 + }
1469 +
1470 + // Clean up
1471 + if (this.httprequest._term) { this.httprequest._term.end(); }
1472 + this.httprequest._term = null;
1473 + }
1474 + };
1475 +
1476 + // Perform User-Consent if needed.
1477 + if (this.httprequest.consent && (this.httprequest.consent & 16))
1478 + {
1479 + this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 }));
1480 + var consentMessage = this.httprequest.username + " requesting remote terminal access. Grant access?", consentTitle = 'MeshCentral';
1481 + if (this.httprequest.soptions != null) {
1482 + if (this.httprequest.soptions.consentTitle != null) { consentTitle = this.httprequest.soptions.consentTitle; }
1483 + if (this.httprequest.soptions.consentMsgTerminal != null) { consentMessage = this.httprequest.soptions.consentMsgTerminal.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); }
1484 + }
1485 + this.httprequest.tpromise._consent = require('message-box').create(consentTitle, consentMessage, 30);
1486 + this.httprequest.tpromise._consent.retPromise = this.httprequest.tpromise;
1487 + this.httprequest.tpromise._consent.then(
1488 + function ()
1489 + {
1490 + // Success
1491 + MeshServerLogEx(27, null, "Local user accepted remote terminal request (" + this.retPromise.httprequest.remoteaddr + ")", this.retPromise.that.httprequest);
1492 + this.retPromise.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null, msgid: 0 }));
1493 + this.retPromise._consent = null;
1494 + this.retPromise._res();
1495 + },
1496 + function (e)
1497 + {
1498 + // Denied
1499 + MeshServerLogEx(28, null, "Local user rejected remote terminal request (" + this.retPromise.that.httprequest.remoteaddr + ")", this.retPromise.that.httprequest);
1500 + this.retPromise.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
1501 + this.retPromise._rej(e.toString());
1502 + });
1503 + }
1504 + else
1505 + {
1506 + // User-Consent is not required, so just resolve this promise
1507 + this.httprequest.tpromise._res();
1508 + }
1509 +
1510 +
1511 + this.httprequest.tpromise.then(
1512 + function ()
1513 + {
1514 + this.httprequest.connectionPromise = new prom(function (res, rej) { this._res = res; this._rej = rej; });
1515 + this.httprequest.connectionPromise.ws = this.that;
1516 +
1517 + // Start Terminal
1518 + if(process.platform == 'win32')
1519 + {
1520 + try
1521 + {
1522 + var cols = 80, rows = 25;
1523 + if (this.httprequest.xoptions)
1524 + {
1525 + if (this.httprequest.xoptions.rows) { rows = this.httprequest.xoptions.rows; }
1526 + if (this.httprequest.xoptions.cols) { cols = this.httprequest.xoptions.cols; }
1527 + }
1528 +
1529 + if ((this.httprequest.protocol == 1) || (this.httprequest.protocol == 6))
1530 + {
1531 + // Admin Terminal
1532 + if (require('win-virtual-terminal').supported)
1533 + {
1534 + // ConPTY PseudoTerminal
1535 + // this.httprequest._term = require('win-virtual-terminal')[this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'](80, 25);
1536 +
1537 + // The above line is commented out, because there is a bug with ClosePseudoConsole() API, so this is the workaround
1538 + 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] } });
1539 + this.httprequest._dispatcher.httprequest = this.httprequest;
1540 + this.httprequest._dispatcher.on('connection', function (c)
1541 + {
1542 + if (this.httprequest.connectionPromise.completed)
1543 + {
1544 + c.end();
1545 + }
1546 + else
1547 + {
1548 + this.httprequest.connectionPromise._res(c);
1549 + }
1550 + });
1551 + }
1552 + else
1553 + {
1554 + // Legacy Terminal
1555 + this.httprequest.connectionPromise._res(require('win-terminal')[this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'](cols, rows));
1556 + }
1557 + }
1558 + else
1559 + {
1560 + // Logged in user
1561 + var userPromise = require('user-sessions').enumerateUsers();
1562 + userPromise.that = this;
1563 + userPromise.then(function (u)
1564 + {
1565 + var that = this.that;
1566 + if (u.Active.length > 0)
1567 + {
1568 + var username = u.Active[0].Username;
1569 + if (require('win-virtual-terminal').supported)
1570 + {
1571 + // ConPTY PseudoTerminal
1572 + 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] } });
1573 + }
1574 + else
1575 + {
1576 + // Legacy Terminal
1577 + 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] } });
1578 + }
1579 + that.httprequest._dispatcher.ws = that;
1580 + that.httprequest._dispatcher.on('connection', function (c)
1581 + {
1582 + if (this.ws.httprequest.connectionPromise.completed)
1583 + {
1584 + c.end();
1585 + }
1586 + else
1587 + {
1588 + this.ws.httprequest.connectionPromise._res(c);
1589 + }
1590 + });
1591 + }
1592 + });
1593 + }
1594 + }
1595 + catch (e)
1596 + {
1597 + this.httprequest.connectionPromise._rej('Failed to start remote terminal session, ' + e.toString());
1598 + }
1599 + }
1600 + else
1601 + {
1602 + try
1603 + {
1604 + var bash = fs.existsSync('/bin/bash') ? '/bin/bash' : false;
1605 + var sh = fs.existsSync('/bin/sh') ? '/bin/sh' : false;
1606 + var login = process.platform == 'linux' ? '/bin/login' : '/usr/bin/login';
1607 +
1608 + var env = { HISTCONTROL: 'ignoreboth' };
1609 + if (this.httprequest.xoptions)
1610 + {
1611 + if (this.httprequest.xoptions.rows) { env.LINES = ('' + this.httprequest.xoptions.rows); }
1612 + if (this.httprequest.xoptions.cols) { env.COLUMNS = ('' + this.httprequest.xoptions.cols); }
1613 + }
1614 + var options = { type: childProcess.SpawnTypes.TERM, uid: (this.httprequest.protocol == 8) ? require('user-sessions').consoleUid() : null, env: env };
1615 + if (this.httprequest.xoptions && this.httprequest.xoptions.requireLogin)
1616 + {
1617 + if (!require('fs').existsSync(login)) { throw ('Unable to spawn login process'); }
1618 + this.httprequest.connectionPromise._res(childProcess.execFile(login, ['login'], options)); // Start login shell
1619 + }
1620 + else if (bash)
1621 + {
1622 + var p = childProcess.execFile(bash, ['bash'], options); // Start bash
1623 + // Spaces at the beginning of lines are needed to hide commands from the command history
1624 + if (process.platform == 'linux') { p.stdin.write(' alias ls=\'ls --color=auto\';clear\n'); }
1625 + this.httprequest.connectionPromise._res(p);
1626 + }
1627 + else if (sh)
1628 + {
1629 + var p = childProcess.execFile(sh, ['sh'], options); // Start sh
1630 + // Spaces at the beginning of lines are needed to hide commands from the command history
1631 + if (process.platform == 'linux') { p.stdin.write(' alias ls=\'ls --color=auto\';clear\n'); }
1632 + this.httprequest.connectionPromise._res(p);
1633 + }
1634 + else
1635 + {
1636 + this.httprequest.connectionPromise._rej('Failed to start remote terminal session, no shell found');
1637 + }
1638 + }
1639 + catch (e)
1640 + {
1641 + this.httprequest.connectionPromise._rej('Failed to start remote terminal session, ' + e.toString());
1642 + }
1643 + }
1644 +
1645 + this.httprequest.connectionPromise.then(
1646 + function (term)
1647 + {
1648 + // SUCCESS
1649 + var stdoutstream;
1650 + var stdinstream;
1651 + if (process.platform == 'win32')
1652 + {
1653 + this.ws.httprequest._term = term;
1654 + this.ws.httprequest._term.tunnel = this.ws;
1655 + stdoutstream = stdinstream = term;
1656 + }
1657 + else
1658 + {
1659 + term.descriptorMetadata = 'Remote Terminal';
1660 + this.ws.httprequest.process = term;
1661 + this.ws.httprequest.process.tunnel = this.ws;
1662 + term.stderr.stdout = term.stdout;
1663 + term.stderr.on('data', function (c) { this.stdout.write(c); });
1664 + stdoutstream = term.stdout;
1665 + stdinstream = term.stdin;
1666 + this.ws.prependListener('end', function () { this.httprequest.process.kill(); });
1667 + term.prependListener('exit', function () { this.tunnel.end(); });
1668 + }
1669 +
1670 + this.ws.removeAllListeners('data');
1671 + this.ws.on('data', onTunnelControlData);
1672 +
1673 + stdoutstream.pipe(this.ws, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
1674 + this.ws.pipe(stdinstream, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
1675 +
1676 + // Add the terminal session to the count to update the server
1677 + if (this.ws.httprequest.userid != null)
1678 + {
1679 + if (tunnelUserCount.terminal[this.ws.httprequest.userid] == null) { tunnelUserCount.terminal[this.ws.httprequest.userid] = 1; } else { tunnelUserCount.terminal[this.ws.httprequest.userid]++; }
1680 + try { mesh.SendCommand({ action: 'sessions', type: 'terminal', value: tunnelUserCount.terminal }); } catch (e) { }
1681 + }
1682 +
1683 + // Toast Notification, if required
1684 + if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 2))
1685 + {
1686 + // User Notifications is required
1687 + var notifyMessage = this.ws.httprequest.username + " started a remote terminal session.", notifyTitle = "MeshCentral";
1688 + if (this.ws.httprequest.soptions != null) {
1689 + if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; }
1690 + 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); }
1691 + }
1692 + try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (e) { }
1693 + }
1694 + },
1695 + function (e)
1696 + {
1697 + // FAILED to connect terminal
1698 + this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
1699 + this.ws.end();
1700 + });
1701 + },
1702 + function (e)
1703 + {
1704 + // DO NOT start terminal
1705 + this.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
1706 + this.that.end();
1707 + });
1708 + }
1709 + else if (this.httprequest.protocol == 2)
1710 + {
1711 + //
1712 + // Remote KVM
1713 + //
1714 +
1715 + // Check user access rights for desktop
1716 + if ((((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) && ((this.httprequest.rights & MESHRIGHT_REMOTEVIEW) == 0)) || ((this.httprequest.rights != 0xFFFFFFFF) && ((this.httprequest.rights & MESHRIGHT_NODESKTOP) != 0))) {
1717 + // Disengage this tunnel, user does not have the rights to do this!!
1718 + this.httprequest.protocol = 999999;
1719 + this.httprequest.s.end();
1720 + sendConsoleText("Error: No Desktop Control Rights.");
1721 + return;
1722 + }
1723 +
1724 + this.descriptorMetadata = "Remote KVM";
1725 +
1726 + // Look for a TSID
1727 + var tsid = null;
1728 + if ((this.httprequest.xoptions != null) && (typeof this.httprequest.xoptions.tsid == 'number')) { tsid = this.httprequest.xoptions.tsid; }
1729 + require('MeshAgent')._tsid = tsid;
1730 +
1731 + // Remote desktop using native pipes
1732 + this.httprequest.desktop = { state: 0, kvm: mesh.getRemoteDesktopStream(tsid), tunnel: this };
1733 + this.httprequest.desktop.kvm.parent = this.httprequest.desktop;
1734 + this.desktop = this.httprequest.desktop;
1735 +
1736 + // Add ourself to the list of remote desktop sessions
1737 + if (this.httprequest.desktop.kvm.tunnels == null) { this.httprequest.desktop.kvm.tunnels = []; }
1738 + this.httprequest.desktop.kvm.tunnels.push(this);
1739 +
1740 + // Send a metadata update to all desktop sessions
1741 + var users = {};
1742 + if (this.httprequest.desktop.kvm.tunnels != null) {
1743 + 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) { } }
1744 + 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) { } }
1745 + try { mesh.SendCommand({ action: 'sessions', type: 'kvm', value: users }); } catch (e) { }
1746 + }
1747 +
1748 + this.end = function () {
1749 + --this.desktop.kvm.connectionCount;
1750 +
1751 + // Remove ourself from the list of remote desktop session
1752 + var i = this.desktop.kvm.tunnels.indexOf(this);
1753 + if (i >= 0) { this.desktop.kvm.tunnels.splice(i, 1); }
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 + try { mesh.SendCommand({ action: 'sessions', type: 'kvm', value: users }); } catch (e) { }
1761 + }
1762 +
1763 + // Unpipe the web socket
1764 + try
1765 + {
1766 + this.unpipe(this.httprequest.desktop.kvm);
1767 + this.httprequest.desktop.kvm.unpipe(this);
1768 + }
1769 + catch(e) { }
1770 +
1771 + // Unpipe the WebRTC channel if needed (This will also be done when the WebRTC channel ends).
1772 + if (this.rtcchannel)
1773 + {
1774 + try
1775 + {
1776 + this.rtcchannel.unpipe(this.httprequest.desktop.kvm);
1777 + this.httprequest.desktop.kvm.unpipe(this.rtcchannel);
1778 + }
1779 + catch(e) { }
1780 + }
1781 +
1782 + // Place wallpaper back if needed
1783 + // TODO
1784 +
1785 + if (this.desktop.kvm.connectionCount == 0) {
1786 + // Display a toast message. This may not be supported on all platforms.
1787 + // try { require('toaster').Toast('MeshCentral', 'Remote Desktop Control Ended.'); } catch (e) { }
1788 +
1789 + this.httprequest.desktop.kvm.end();
1790 + if (this.httprequest.desktop.kvm.connectionBar) {
1791 + this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close');
1792 + this.httprequest.desktop.kvm.connectionBar.close();
1793 + this.httprequest.desktop.kvm.connectionBar = null;
1794 + }
1795 + } else {
1796 + for (var i in this.httprequest.desktop.kvm.users) {
1797 + if ((this.httprequest.desktop.kvm.users[i] == this.httprequest.username) && this.httprequest.desktop.kvm.connectionBar) {
1798 + 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; } }
1799 + this.httprequest.desktop.kvm.users.splice(i, 1);
1800 + this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close');
1801 + this.httprequest.desktop.kvm.connectionBar.close();
1802 + 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);
1803 + this.httprequest.desktop.kvm.connectionBar.httprequest = this.httprequest;
1804 + this.httprequest.desktop.kvm.connectionBar.on('close', function () {
1805 + MeshServerLogEx(29, null, "Remote Desktop Connection forcefully closed by local user (" + this.httprequest.remoteaddr + ")", this.httprequest);
1806 + for (var i in this.httprequest.desktop.kvm._pipedStreams) {
1807 + this.httprequest.desktop.kvm._pipedStreams[i].end();
1808 + }
1809 + this.httprequest.desktop.kvm.end();
1810 + });
1811 + break;
1812 + }
1813 + }
1814 + }
1815 + };
1816 + if (this.httprequest.desktop.kvm.hasOwnProperty('connectionCount')) {
1817 + this.httprequest.desktop.kvm.connectionCount++;
1818 + this.httprequest.desktop.kvm.rusers.push(this.httprequest.realname);
1819 + this.httprequest.desktop.kvm.users.push(this.httprequest.username);
1820 + this.httprequest.desktop.kvm.rusers.sort();
1821 + this.httprequest.desktop.kvm.users.sort();
1822 + } else {
1823 + this.httprequest.desktop.kvm.connectionCount = 1;
1824 + this.httprequest.desktop.kvm.rusers = [this.httprequest.realname];
1825 + this.httprequest.desktop.kvm.users = [this.httprequest.username];
1826 + }
1827 +
1828 + if ((this.httprequest.rights == 0xFFFFFFFF) || (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) != 0) && ((this.httprequest.rights & MESHRIGHT_REMOTEVIEW) == 0))) {
1829 + // If we have remote control rights, pipe the KVM input
1830 + this.pipe(this.httprequest.desktop.kvm, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. Pipe the Browser --> KVM input.
1831 + } else {
1832 + // We need to only pipe non-mouse & non-keyboard inputs.
1833 + //sendConsoleText('Warning: No Remote Desktop Input Rights.');
1834 + // TODO!!!
1835 + }
1836 +
1837 + // Perform notification if needed. Toast messages may not be supported on all platforms.
1838 + if (this.httprequest.consent && (this.httprequest.consent & 8))
1839 + {
1840 + // User Consent Prompt is required
1841 + // Send a console message back using the console channel, "\n" is supported.
1842 + this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 }));
1843 + var consentMessage = this.httprequest.realname + " requesting remote desktop access. Grant access?", consentTitle = 'MeshCentral';
1844 + if (this.httprequest.soptions != null) {
1845 + if (this.httprequest.soptions.consentTitle != null) { consentTitle = this.httprequest.soptions.consentTitle; }
1846 + if (this.httprequest.soptions.consentMsgDesktop != null) { consentMessage = this.httprequest.soptions.consentMsgDesktop.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); }
1847 + }
1848 + var pr = require('message-box').create(consentTitle, consentMessage, 30, null, tsid);
1849 + pr.ws = this;
1850 + this.pause();
1851 + this._consentpromise = pr;
1852 + this.prependOnceListener('end', function () { if (this._consentpromise && this._consentpromise.close) { this._consentpromise.close(); }});
1853 + pr.then(
1854 + function ()
1855 + {
1856 + // Success
1857 + this.ws._consentpromise = null;
1858 + MeshServerLogEx(30, null, "Starting remote desktop after local user accepted (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
1859 + this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null, msgid: 0 }));
1860 + if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 1)) {
1861 + // User Notifications is required
1862 + var notifyMessage = this.ws.httprequest.realname + " started a remote desktop session.", notifyTitle = "MeshCentral";
1863 + if (this.ws.httprequest.soptions != null) {
1864 + if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; }
1865 + 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); }
1866 + }
1867 + try { require('toaster').Toast(notifyTitle, notifyMessage, tsid); } catch (e) { }
1868 + }
1869 + if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 0x40)) {
1870 + // Connection Bar is required
1871 + if (this.ws.httprequest.desktop.kvm.connectionBar) {
1872 + this.ws.httprequest.desktop.kvm.connectionBar.removeAllListeners('close');
1873 + this.ws.httprequest.desktop.kvm.connectionBar.close();
1874 + }
1875 + try {
1876 + 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);
1877 + MeshServerLogEx(31, null, "Remote Desktop Connection Bar Activated/Updated (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
1878 + }
1879 + catch (e) {
1880 + if (process.platform != 'darwin') {
1881 + MeshServerLogEx(32, null, "Remote Desktop Connection Bar Failed or Not Supported (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
1882 + }
1883 + }
1884 + if (this.ws.httprequest.desktop.kvm.connectionBar) {
1885 + this.ws.httprequest.desktop.kvm.connectionBar.httprequest = this.ws.httprequest;
1886 + this.ws.httprequest.desktop.kvm.connectionBar.on('close', function () {
1887 + MeshServerLogEx(33, null, "Remote Desktop Connection forcefully closed by local user (" + this.httprequest.remoteaddr + ")", this.httprequest);
1888 + for (var i in this.httprequest.desktop.kvm._pipedStreams) {
1889 + this.httprequest.desktop.kvm._pipedStreams[i].end();
1890 + }
1891 + this.httprequest.desktop.kvm.end();
1892 + });
1893 + }
1894 + }
1895 + this.ws.httprequest.desktop.kvm.pipe(this.ws, { dataTypeSkip: 1 });
1896 + this.ws.resume();
1897 + },
1898 + function (e)
1899 + {
1900 + // User Consent Denied/Failed
1901 + this.ws._consentpromise = null;
1902 + MeshServerLogEx(34, null, "Failed to start remote desktop after local user rejected (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
1903 + this.ws.end(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
1904 + });
1905 + } else {
1906 + // User Consent Prompt is not required
1907 + if (this.httprequest.consent && (this.httprequest.consent & 1)) {
1908 + // User Notifications is required
1909 + MeshServerLogEx(35, null, "Started remote desktop with toast notification (" + this.httprequest.remoteaddr + ")", this.httprequest);
1910 + var notifyMessage = this.httprequest.realname + " started a remote desktop session.", notifyTitle = "MeshCentral";
1911 + if (this.httprequest.soptions != null) {
1912 + if (this.httprequest.soptions.notifyTitle != null) { notifyTitle = this.httprequest.soptions.notifyTitle; }
1913 + if (this.httprequest.soptions.notifyMsgDesktop != null) { notifyMessage = this.httprequest.soptions.notifyMsgDesktop.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); }
1914 + }
1915 + try { require('toaster').Toast(notifyTitle, notifyMessage, tsid); } catch (e) { }
1916 + } else {
1917 + MeshServerLogEx(36, null, "Started remote desktop without notification (" + this.httprequest.remoteaddr + ")", this.httprequest);
1918 + }
1919 + if (this.httprequest.consent && (this.httprequest.consent & 0x40)) {
1920 + // Connection Bar is required
1921 + if (this.httprequest.desktop.kvm.connectionBar) {
1922 + this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close');
1923 + this.httprequest.desktop.kvm.connectionBar.close();
1924 + }
1925 + try {
1926 + 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);
1927 + MeshServerLogEx(37, null, "Remote Desktop Connection Bar Activated/Updated (" + this.httprequest.remoteaddr + ")", this.httprequest);
1928 + }
1929 + catch (e) {
1930 + MeshServerLogEx(38, null, "Remote Desktop Connection Bar Failed or not Supported (" + this.httprequest.remoteaddr + ")", this.httprequest);
1931 + }
1932 + if (this.httprequest.desktop.kvm.connectionBar) {
1933 + this.httprequest.desktop.kvm.connectionBar.httprequest = this.httprequest;
1934 + this.httprequest.desktop.kvm.connectionBar.on('close', function () {
1935 + MeshServerLogEx(39, null, "Remote Desktop Connection forcefully closed by local user (" + this.httprequest.remoteaddr + ")", this.httprequest);
1936 + for (var i in this.httprequest.desktop.kvm._pipedStreams) {
1937 + this.httprequest.desktop.kvm._pipedStreams[i].end();
1938 + }
1939 + this.httprequest.desktop.kvm.end();
1940 + });
1941 + }
1942 + }
1943 + this.httprequest.desktop.kvm.pipe(this, { dataTypeSkip: 1 });
1944 + }
1945 +
1946 + this.removeAllListeners('data');
1947 + this.on('data', onTunnelControlData);
1948 + //this.write('MeshCore KVM Hello!1');
1949 +
1950 + } else if (this.httprequest.protocol == 5) {
1951 + //
1952 + // Remote Files
1953 + //
1954 +
1955 + // Check user access rights for files
1956 + if (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) || ((this.httprequest.rights != 0xFFFFFFFF) && ((this.httprequest.rights & MESHRIGHT_NOFILES) != 0))) {
1957 + // Disengage this tunnel, user does not have the rights to do this!!
1958 + this.httprequest.protocol = 999999;
1959 + this.httprequest.s.end();
1960 + sendConsoleText("Error: No files control rights.");
1961 + return;
1962 + }
1963 +
1964 + this.descriptorMetadata = "Remote Files";
1965 +
1966 + // Add the files session to the count to update the server
1967 + if (this.httprequest.userid != null) {
1968 + if (tunnelUserCount.files[this.httprequest.userid] == null) { tunnelUserCount.files[this.httprequest.userid] = 1; } else { tunnelUserCount.files[this.httprequest.userid]++; }
1969 + try { mesh.SendCommand({ action: 'sessions', type: 'files', value: tunnelUserCount.files }); } catch (e) { }
1970 + }
1971 +
1972 + this.end = function () {
1973 + // Remove the files session from the count to update the server
1974 + if (this.httprequest.userid != null) {
1975 + 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]; } }
1976 + try { mesh.SendCommand({ action: 'sessions', type: 'files', value: tunnelUserCount.files }); } catch (e) { }
1977 + }
1978 + };
1979 +
1980 + // Perform notification if needed. Toast messages may not be supported on all platforms.
1981 + if (this.httprequest.consent && (this.httprequest.consent & 32)) {
1982 + // User Consent Prompt is required
1983 + // Send a console message back using the console channel, "\n" is supported.
1984 + this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 }));
1985 + var consentMessage = this.httprequest.realname + " requesting remote file Access. Grant access?", consentTitle = 'MeshCentral';
1986 + if (this.httprequest.soptions != null) {
1987 + if (this.httprequest.soptions.consentTitle != null) { consentTitle = this.httprequest.soptions.consentTitle; }
1988 + if (this.httprequest.soptions.consentMsgFiles != null) { consentMessage = this.httprequest.soptions.consentMsgFiles.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); }
1989 + }
1990 + var pr = require('message-box').create(consentTitle, consentMessage, 30);
1991 + pr.ws = this;
1992 + this.pause();
1993 + this._consentpromise = pr;
1994 + this.prependOnceListener('end', function () { if (this._consentpromise && this._consentpromise.close) { this._consentpromise.close(); } });
1995 + pr.then(
1996 + function ()
1997 + {
1998 + // Success
1999 + this.ws._consentpromise = null;
2000 + MeshServerLogEx(40, null, "Starting remote files after local user accepted (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
2001 + this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null }));
2002 + if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 4)) {
2003 + // User Notifications is required
2004 + var notifyMessage = this.ws.httprequest.realname + " started a remote file session.", notifyTitle = "MeshCentral";
2005 + if (this.ws.httprequest.soptions != null) {
2006 + if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; }
2007 + 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); }
2008 + }
2009 + try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (e) { }
2010 + }
2011 + this.ws.resume();
2012 + },
2013 + function (e)
2014 + {
2015 + // User Consent Denied/Failed
2016 + this.ws._consentpromise = null;
2017 + MeshServerLogEx(41, null, "Failed to start remote files after local user rejected (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
2018 + this.ws.end(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
2019 + });
2020 + } else {
2021 + // User Consent Prompt is not required
2022 + if (this.httprequest.consent && (this.httprequest.consent & 4)) {
2023 + // User Notifications is required
2024 + MeshServerLogEx(42, null, "Started remote files with toast notification (" + this.httprequest.remoteaddr + ")", this.httprequest);
2025 + var notifyMessage = this.httprequest.realname + " started a remote file session.", notifyTitle = "MeshCentral";
2026 + if (this.httprequest.soptions != null) {
2027 + if (this.httprequest.soptions.notifyTitle != null) { notifyTitle = this.httprequest.soptions.notifyTitle; }
2028 + if (this.httprequest.soptions.notifyMsgFiles != null) { notifyMessage = this.httprequest.soptions.notifyMsgFiles.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); }
2029 + }
2030 + try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (e) { }
2031 + } else {
2032 + MeshServerLogEx(43, null, "Started remote files without notification (" + this.httprequest.remoteaddr + ")", this.httprequest);
2033 + }
2034 + this.resume();
2035 + }
2036 +
2037 + // Setup files
2038 + // NOP
2039 + }
2040 + } else if (this.httprequest.protocol == 1) {
2041 + // Send data into terminal stdin
2042 + //this.write(data); // Echo back the keys (Does not seem to be a good idea)
2043 + } else if (this.httprequest.protocol == 2) {
2044 + // Send data into remote desktop
2045 + if (this.httprequest.desktop.state == 0) {
2046 + this.write(Buffer.from(String.fromCharCode(0x11, 0xFE, 0x00, 0x00, 0x4D, 0x45, 0x53, 0x48, 0x00, 0x00, 0x00, 0x00, 0x02)));
2047 + this.httprequest.desktop.state = 1;
2048 + } else {
2049 + this.httprequest.desktop.write(data);
2050 + }
2051 + } else if (this.httprequest.protocol == 5) {
2052 + // Process files commands
2053 + var cmd = null;
2054 + try { cmd = JSON.parse(data); } catch (e) { };
2055 + if (cmd == null) { return; }
2056 + if ((cmd.ctrlChannel == '102938') || ((cmd.type == 'offer') && (cmd.sdp != null))) { onTunnelControlData(cmd, this); return; } // If this is control data, handle it now.
2057 + if (cmd.action == undefined) { return; }
2058 + //sendConsoleText('CMD: ' + JSON.stringify(cmd));
2059 +
2060 + if ((cmd.path != null) && (process.platform != 'win32') && (cmd.path[0] != '/')) { cmd.path = '/' + cmd.path; } // Add '/' to paths on non-windows
2061 + //console.log(objToString(cmd, 0, ' '));
2062 + switch (cmd.action) {
2063 + case 'ls': {
2064 + /*
2065 + // Close the watcher if required
2066 + var samepath = ((this.httprequest.watcher != undefined) && (cmd.path == this.httprequest.watcher.path));
2067 + if ((this.httprequest.watcher != undefined) && (samepath == false)) {
2068 + //console.log('Closing watcher: ' + this.httprequest.watcher.path);
2069 + //this.httprequest.watcher.close(); // TODO: This line causes the agent to crash!!!!
2070 + delete this.httprequest.watcher;
2071 + }
2072 + */
2073 +
2074 + // Send the folder content to the browser
2075 + var response = getDirectoryInfo(cmd.path);
2076 + if (cmd.reqid != undefined) { response.reqid = cmd.reqid; }
2077 + this.write(Buffer.from(JSON.stringify(response)));
2078 +
2079 + /*
2080 + // Start the directory watcher
2081 + if ((cmd.path != '') && (samepath == false)) {
2082 + var watcher = fs.watch(cmd.path, onFileWatcher);
2083 + watcher.tunnel = this.httprequest;
2084 + watcher.path = cmd.path;
2085 + this.httprequest.watcher = watcher;
2086 + //console.log('Starting watcher: ' + this.httprequest.watcher.path);
2087 + }
2088 + */
2089 + break;
2090 + }
2091 + case 'mkdir': {
2092 + // Create a new empty folder
2093 + fs.mkdirSync(cmd.path);
2094 + MeshServerLogEx(44, [cmd.path], "Create folder: \"" + cmd.path + "\"", this.httprequest);
2095 + break;
2096 + }
2097 + case 'rm': {
2098 + // Delete, possibly recursive delete
2099 + for (var i in cmd.delfiles) {
2100 + var p = obj.path.join(cmd.path, cmd.delfiles[i]), delcount = 0;
2101 + try { delcount = deleteFolderRecursive(p, cmd.rec); } catch (e) { }
2102 + if ((delcount == 1) && !cmd.rec) {
2103 + MeshServerLogEx(45, [p], "Delete: \"" + p + "\"", this.httprequest);
2104 + } else {
2105 + if (cmd.rec) {
2106 + MeshServerLogEx(46, [p, delcount], "Delete recursive: \"" + p + "\", " + delcount + " element(s) removed", this.httprequest);
2107 + } else {
2108 + MeshServerLogEx(47, [p, delcount], "Delete: \"" + p + "\", " + delcount + " element(s) removed", this.httprequest);
2109 + }
2110 + }
2111 + }
2112 + break;
2113 + }
2114 + case 'markcoredump': {
2115 + // If we are asking for the coredump file, set the right path.
2116 + var coreDumpPath = null;
2117 + if (process.platform == 'win32') {
2118 + if (fs.existsSync(process.coreDumpLocation)) { coreDumpPath = process.coreDumpLocation; }
2119 + } else {
2120 + if ((process.cwd() != '//') && fs.existsSync(process.cwd() + 'core')) { coreDumpPath = process.cwd() + 'core'; }
2121 + }
2122 + if (coreDumpPath != null) { db.Put('CoreDumpTime', require('fs').statSync(coreDumpPath).mtime); }
2123 + break;
2124 + }
2125 + case 'rename': {
2126 + // Rename a file or folder
2127 + var oldfullpath = obj.path.join(cmd.path, cmd.oldname);
2128 + var newfullpath = obj.path.join(cmd.path, cmd.newname);
2129 + MeshServerLogEx(48, [oldfullpath, cmd.newname], 'Rename: \"' + oldfullpath + '\" to \"' + cmd.newname + '\"', this.httprequest);
2130 + try { fs.renameSync(oldfullpath, newfullpath); } catch (e) { console.log(e); }
2131 + break;
2132 + }
2133 + case 'download': {
2134 + // Download a file
2135 + var sendNextBlock = 0;
2136 + if (cmd.sub == 'start') { // Setup the download
2137 + if ((cmd.path == null) && (cmd.ask == 'coredump')) { // If we are asking for the coredump file, set the right path.
2138 + if (process.platform == 'win32') {
2139 + if (fs.existsSync(process.coreDumpLocation)) { cmd.path = process.coreDumpLocation; }
2140 + } else {
2141 + if ((process.cwd() != '//') && fs.existsSync(process.cwd() + 'core')) { cmd.path = process.cwd() + 'core'; }
2142 + }
2143 + }
2144 + MeshServerLogEx(49, [cmd.path], 'Download: \"' + cmd.path + '\"', this.httprequest);
2145 + if ((cmd.path == null) || (this.filedownload != null)) { this.write({ action: 'download', sub: 'cancel', id: this.filedownload.id }); delete this.filedownload; }
2146 + this.filedownload = { id: cmd.id, path: cmd.path, ptr: 0 }
2147 + 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; }
2148 + if (this.filedownload) { this.write({ action: 'download', sub: 'start', id: cmd.id }); }
2149 + } else if ((this.filedownload != null) && (cmd.id == this.filedownload.id)) { // Download commands
2150 + 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; }
2151 + }
2152 + // Send the next download block(s)
2153 + while (sendNextBlock > 0) {
2154 + sendNextBlock--;
2155 + var buf = Buffer.alloc(16384);
2156 + var len = fs.readSync(this.filedownload.f, buf, 4, 16380, null);
2157 + this.filedownload.ptr += len;
2158 + if (len < 16380) { buf.writeInt32BE(0x01000001, 0); fs.closeSync(this.filedownload.f); delete this.filedownload; sendNextBlock = 0; } else { buf.writeInt32BE(0x01000000, 0); }
2159 + this.write(buf.slice(0, len + 4)); // Write as binary
2160 + }
2161 + break;
2162 + }
2163 + /*
2164 + case 'download': {
2165 + // Packet download of a file, agent to browser
2166 + if (cmd.path == undefined) break;
2167 + var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path;
2168 + //console.log('Download: ' + filepath);
2169 + try { this.httprequest.downloadFile = fs.openSync(filepath, 'rbN'); } catch (e) { this.write(Buffer.from(JSON.stringify({ action: 'downloaderror', reqid: cmd.reqid }))); break; }
2170 + this.httprequest.downloadFileId = cmd.reqid;
2171 + this.httprequest.downloadFilePtr = 0;
2172 + if (this.httprequest.downloadFile) { this.write(Buffer.from(JSON.stringify({ action: 'downloadstart', reqid: this.httprequest.downloadFileId }))); }
2173 + break;
2174 + }
2175 + case 'download2': {
2176 + // Stream download of a file, agent to browser
2177 + if (cmd.path == undefined) break;
2178 + var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path;
2179 + try { this.httprequest.downloadFile = fs.createReadStream(filepath, { flags: 'rbN' }); } catch (e) { console.log(e); }
2180 + this.httprequest.downloadFile.pipe(this);
2181 + this.httprequest.downloadFile.end = function () { }
2182 + break;
2183 + }
2184 + */
2185 + case 'upload': {
2186 + // Upload a file, browser to agent
2187 + if (this.httprequest.uploadFile != null) { fs.closeSync(this.httprequest.uploadFile); delete this.httprequest.uploadFile; }
2188 + if (cmd.path == undefined) break;
2189 + var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path;
2190 + this.httprequest.uploadFilePath = filepath;
2191 + MeshServerLogEx(50, [filepath], 'Upload: \"' + filepath + '\"', this.httprequest);
2192 + try { this.httprequest.uploadFile = fs.openSync(filepath, 'wbN'); } catch (e) { this.write(Buffer.from(JSON.stringify({ action: 'uploaderror', reqid: cmd.reqid }))); break; }
2193 + this.httprequest.uploadFileid = cmd.reqid;
2194 + if (this.httprequest.uploadFile) { this.write(Buffer.from(JSON.stringify({ action: 'uploadstart', reqid: this.httprequest.uploadFileid }))); }
2195 + break;
2196 + }
2197 + case 'uploaddone': {
2198 + // Indicates that an upload is done
2199 + if (this.httprequest.uploadFile) {
2200 + fs.closeSync(this.httprequest.uploadFile);
2201 + this.write(Buffer.from(JSON.stringify({ action: 'uploaddone', reqid: this.httprequest.uploadFileid }))); // Indicate that we closed the file.
2202 + delete this.httprequest.uploadFile;
2203 + delete this.httprequest.uploadFileid;
2204 + delete this.httprequest.uploadFilePath;
2205 + }
2206 + break;
2207 + }
2208 + case 'uploadcancel': {
2209 + // Indicates that an upload is canceled
2210 + if (this.httprequest.uploadFile) {
2211 + fs.closeSync(this.httprequest.uploadFile);
2212 + fs.unlinkSync(this.httprequest.uploadFilePath);
2213 + this.write(Buffer.from(JSON.stringify({ action: 'uploadcancel', reqid: this.httprequest.uploadFileid }))); // Indicate that we closed the file.
2214 + delete this.httprequest.uploadFile;
2215 + delete this.httprequest.uploadFileid;
2216 + delete this.httprequest.uploadFilePath;
2217 + }
2218 + break;
2219 + }
2220 + case 'copy': {
2221 + // Copy a bunch of files from scpath to dspath
2222 + for (var i in cmd.names) {
2223 + var sc = obj.path.join(cmd.scpath, cmd.names[i]), ds = obj.path.join(cmd.dspath, cmd.names[i]);
2224 + MeshServerLogEx(51, [sc, ds], 'Copy: \"' + sc + '\" to \"' + ds + '\"', this.httprequest);
2225 + if (sc != ds) { try { fs.copyFileSync(sc, ds); } catch (e) { } }
2226 + }
2227 + break;
2228 + }
2229 + case 'move': {
2230 + // Move a bunch of files from scpath to dspath
2231 + for (var i in cmd.names) {
2232 + var sc = obj.path.join(cmd.scpath, cmd.names[i]), ds = obj.path.join(cmd.dspath, cmd.names[i]);
2233 + MeshServerLogEx(52, [sc, ds], 'Move: \"' + sc + '\" to \"' + ds + '\"', this.httprequest);
2234 + if (sc != ds) { try { fs.copyFileSync(sc, ds); fs.unlinkSync(sc); } catch (e) { } }
2235 + }
2236 + break;
2237 + }
2238 + case 'zip':
2239 + // Zip a bunch of files
2240 + if (this.zip != null) return; // Zip operating is currently running, exit now.
2241 +
2242 + // Check that the specified files exist & build full paths
2243 + var fp, stat, p = [];
2244 + 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); } }
2245 + if (p.length == 0) return; // No files, quit now.
2246 +
2247 + // Setup file compression
2248 + var ofile = cmd.path + '/' + cmd.output;
2249 + this.write(Buffer.from(JSON.stringify({ action: 'dialogmessage', msg: 'zipping' })));
2250 + this.zipfile = ofile;
2251 + delete this.zipcancel;
2252 + var out = require('fs').createWriteStream(ofile, { flags: 'wb' });
2253 + out.xws = this;
2254 + out.on('close', function () {
2255 + this.xws.write(Buffer.from(JSON.stringify({ action: 'dialogmessage', msg: null })));
2256 + this.xws.write(Buffer.from(JSON.stringify({ action: 'refresh' })));
2257 + if (this.xws.zipcancel === true) { fs.unlinkSync(this.xws.zipfile); } // Delete the complete file.
2258 + delete this.xws.zipcancel;
2259 + delete this.xws.zipfile;
2260 + delete this.xws.zip;
2261 + });
2262 + this.zip = require('zip-writer').write({ files: p, basePath: cmd.path });
2263 + this.zip.xws = this;
2264 + 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));
2265 + this.zip.pipe(out);
2266 + break;
2267 + case 'cancel':
2268 + // Cancel zip operation if present
2269 + try { this.zipcancel = true; this.zip.cancel(function () { }); } catch (e) { }
2270 + this.zip = null;
2271 + break;
2272 + default:
2273 + // Unknown action, ignore it.
2274 + break;
2275 + }
2276 + } else if (this.httprequest.protocol == 7) { // Plugin data exchange
2277 + var cmd = null;
2278 + try { cmd = JSON.parse(data); } catch (e) { };
2279 + if (cmd == null) { return; }
2280 + if ((cmd.ctrlChannel == '102938') || ((cmd.type == 'offer') && (cmd.sdp != null))) { onTunnelControlData(cmd, this); return; } // If this is control data, handle it now.
2281 + if (cmd.action == undefined) return;
2282 +
2283 + switch (cmd.action) {
2284 + case 'plugin': {
2285 + try { require(cmd.plugin).consoleaction(cmd, null, null, this); } catch (e) { throw e; }
2286 + break;
2287 + }
2288 + default: {
2289 + // probably shouldn't happen, but just in case this feature is expanded
2290 + }
2291 + }
2292 +
2293 + }
2294 + //sendConsoleText("Got tunnel #" + this.httprequest.index + " data: " + data, this.httprequest.sessionid);
2295 + }
2296 + }
2297 +
2298 + // Delete a directory with a files and directories within it
2299 + function deleteFolderRecursive(path, rec) {
2300 + var count = 0;
2301 + if (fs.existsSync(path)) {
2302 + if (rec == true) {
2303 + fs.readdirSync(obj.path.join(path, '*')).forEach(function (file, index) {
2304 + var curPath = obj.path.join(path, file);
2305 + if (fs.statSync(curPath).isDirectory()) { // recurse
2306 + count += deleteFolderRecursive(curPath, true);
2307 + } else { // delete file
2308 + fs.unlinkSync(curPath);
2309 + count++;
2310 + }
2311 + });
2312 + }
2313 + fs.unlinkSync(path);
2314 + count++;
2315 + }
2316 + return count;
2317 + };
2318 +
2319 + // Called when receiving control data on WebRTC
2320 + function onTunnelWebRTCControlData(data) {
2321 + if (typeof data != 'string') return;
2322 + var obj;
2323 + try { obj = JSON.parse(data); } catch (e) { sendConsoleText('Invalid control JSON on WebRTC: ' + data); return; }
2324 + if (obj.type == 'close') {
2325 + //sendConsoleText('Tunnel #' + this.xrtc.websocket.tunnel.index + ' WebRTC control close');
2326 + try { this.close(); } catch (e) { }
2327 + try { this.xrtc.close(); } catch (e) { }
2328 + }
2329 + }
2330 +
2331 + // Called when receiving control data on websocket
2332 + function onTunnelControlData(data, ws) {
2333 + var obj;
2334 + if (ws == null) { ws = this; }
2335 + if (typeof data == 'string') { try { obj = JSON.parse(data); } catch (e) { sendConsoleText('Invalid control JSON: ' + data); return; } }
2336 + else if (typeof data == 'object') { obj = data; } else { return; }
2337 + //sendConsoleText('onTunnelControlData(' + ws.httprequest.protocol + '): ' + JSON.stringify(data));
2338 + //console.log('onTunnelControlData: ' + JSON.stringify(data));
2339 +
2340 + if (obj.action) {
2341 + switch (obj.action) {
2342 + case 'lock': {
2343 + // Lock the current user out of the desktop
2344 + try {
2345 + if (process.platform == 'win32') {
2346 + MeshServerLogEx(53, null, "Locking remote user out of desktop", ws.httprequest);
2347 + var child = require('child_process');
2348 + child.execFile(process.env['windir'] + '\\system32\\cmd.exe', ['/c', 'RunDll32.exe user32.dll,LockWorkStation'], { type: 1 });
2349 + }
2350 + } catch (e) { }
2351 + break;
2352 + }
2353 + default:
2354 + // Unknown action, ignore it.
2355 + break;
2356 + }
2357 + return;
2358 + }
2359 +
2360 + switch (obj.type) {
2361 + case 'options': {
2362 + // These are additional connection options passed in the control channel.
2363 + //sendConsoleText('options: ' + JSON.stringify(obj));
2364 + delete obj.type;
2365 + ws.httprequest.xoptions = obj;
2366 +
2367 + // Set additional user consent options if present
2368 + if ((obj != null) && (typeof obj.consent == 'number')) { ws.httprequest.consent |= obj.consent; }
2369 +
2370 + break;
2371 + }
2372 + case 'close': {
2373 + // We received the close on the websocket
2374 + //sendConsoleText('Tunnel #' + ws.tunnel.index + ' WebSocket control close');
2375 + try { ws.close(); } catch (e) { }
2376 + break;
2377 + }
2378 + case 'termsize': {
2379 + // Indicates a change in terminal size
2380 + if (process.platform == 'win32')
2381 + {
2382 + if (ws.httprequest._dispatcher == null) return;
2383 + //sendConsoleText('Win32-TermSize: ' + obj.cols + 'x' + obj.rows);
2384 + if (ws.httprequest._dispatcher.invoke) { ws.httprequest._dispatcher.invoke('resizeTerminal', [obj.cols, obj.rows]); }
2385 + } else
2386 + {
2387 + if (ws.httprequest.process == null || ws.httprequest.process.pty == 0) return;
2388 + //sendConsoleText('Linux Resize: ' + obj.cols + 'x' + obj.rows);
2389 +
2390 + if (ws.httprequest.process.tcsetsize) { ws.httprequest.process.tcsetsize(obj.rows, obj.cols); }
2391 + }
2392 + break;
2393 + }
2394 + case 'webrtc0': { // Browser indicates we can start WebRTC switch-over.
2395 + if (ws.httprequest.protocol == 1) { // Terminal
2396 + // 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
2397 + if (process.platform == 'win32') {
2398 + ws.httprequest._term.unpipe(ws);
2399 + } else {
2400 + ws.httprequest.process.stdout.unpipe(ws);
2401 + ws.httprequest.process.stderr.unpipe(ws);
2402 + }
2403 + } else if (ws.httprequest.protocol == 2) { // Desktop
2404 + // 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
2405 + ws.httprequest.desktop.kvm.unpipe(ws);
2406 + } else {
2407 + // Switch things around so all WebRTC data goes to onTunnelData().
2408 + ws.rtcchannel.httprequest = ws.httprequest;
2409 + ws.rtcchannel.removeAllListeners('data');
2410 + ws.rtcchannel.on('data', onTunnelData);
2411 + }
2412 + ws.write("{\"ctrlChannel\":\"102938\",\"type\":\"webrtc1\"}"); // End of data marker
2413 + break;
2414 + }
2415 + case 'webrtc1': {
2416 + if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) { // Terminal
2417 + // Switch the user input from websocket to webrtc at this point.
2418 + if (process.platform == 'win32') {
2419 + ws.unpipe(ws.httprequest._term);
2420 + ws.rtcchannel.pipe(ws.httprequest._term, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
2421 + } else {
2422 + ws.unpipe(ws.httprequest.process.stdin);
2423 + ws.rtcchannel.pipe(ws.httprequest.process.stdin, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
2424 + }
2425 + ws.resume(); // Resume the websocket to keep receiving control data
2426 + } else if (ws.httprequest.protocol == 2) { // Desktop
2427 + // Switch the user input from websocket to webrtc at this point.
2428 + ws.unpipe(ws.httprequest.desktop.kvm);
2429 + try { ws.webrtc.rtcchannel.pipe(ws.httprequest.desktop.kvm, { dataTypeSkip: 1, end: false }); } catch (e) { sendConsoleText('EX2'); } // 0 = Binary, 1 = Text.
2430 + ws.resume(); // Resume the websocket to keep receiving control data
2431 + }
2432 + ws.write('{\"ctrlChannel\":\"102938\",\"type\":\"webrtc2\"}'); // Indicates we will no longer get any data on websocket, switching to WebRTC at this point.
2433 + break;
2434 + }
2435 + case 'webrtc2': {
2436 + // Other side received websocket end of data marker, start sending data on WebRTC channel
2437 + if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) { // Terminal
2438 + if (process.platform == 'win32') {
2439 + ws.httprequest._term.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
2440 + } else {
2441 + ws.httprequest.process.stdout.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
2442 + ws.httprequest.process.stderr.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
2443 + }
2444 + } else if (ws.httprequest.protocol == 2) { // Desktop
2445 + ws.httprequest.desktop.kvm.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
2446 + }
2447 + break;
2448 + }
2449 + case 'offer': {
2450 + // This is a WebRTC offer.
2451 + if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) return; // TODO: Terminal is currently broken with WebRTC. Reject WebRTC upgrade for now.
2452 + ws.webrtc = rtc.createConnection();
2453 + ws.webrtc.websocket = ws;
2454 + ws.webrtc.on('connected', function () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC connected');*/ });
2455 + ws.webrtc.on('disconnected', function () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC disconnected');*/ });
2456 + ws.webrtc.on('dataChannel', function (rtcchannel) {
2457 + //sendConsoleText('WebRTC Datachannel open, protocol: ' + this.websocket.httprequest.protocol);
2458 + rtcchannel.maxFragmentSize = 32768;
2459 + rtcchannel.xrtc = this;
2460 + rtcchannel.websocket = this.websocket;
2461 + this.rtcchannel = rtcchannel;
2462 + this.websocket.rtcchannel = rtcchannel;
2463 + this.websocket.rtcchannel.on('data', onTunnelWebRTCControlData);
2464 + this.websocket.rtcchannel.on('end', function () {
2465 + // The WebRTC channel closed, unpipe the KVM now. This is also done when the web socket closes.
2466 + //sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC data channel closed');
2467 + if (this.websocket.desktop && this.websocket.desktop.kvm)
2468 + {
2469 + try
2470 + {
2471 + this.unpipe(this.websocket.desktop.kvm);
2472 + this.websocket.httprequest.desktop.kvm.unpipe(this);
2473 + }
2474 + catch (e) { }
2475 + }
2476 + });
2477 + this.websocket.write('{\"ctrlChannel\":\"102938\",\"type\":\"webrtc0\"}'); // Indicate we are ready for WebRTC switch-over.
2478 + });
2479 + var sdp = null;
2480 + try { sdp = ws.webrtc.setOffer(obj.sdp); } catch (e) { }
2481 + if (sdp != null) { ws.write({ type: 'answer', ctrlChannel: '102938', sdp: sdp }); }
2482 + break;
2483 + }
2484 + case 'ping': {
2485 + ws.write("{\"ctrlChannel\":\"102938\",\"type\":\"pong\"}"); // Send pong response
2486 + break;
2487 + }
2488 + case 'pong': { // NOP
2489 + break;
2490 + }
2491 + case 'rtt': {
2492 + ws.write({ type: 'rtt', ctrlChannel: '102938', time: obj.time });
2493 + break;
2494 + }
2495 + }
2496 + }
2497 +
2498 + // Console state
2499 + var consoleWebSockets = {};
2500 + var consoleHttpRequest = null;
2501 +
2502 + // Console HTTP response
2503 + function consoleHttpResponse(response) {
2504 + response.data = function (data) { sendConsoleText(rstr2hex(buf2rstr(data)), this.sessionid); consoleHttpRequest = null; }
2505 + response.close = function () { sendConsoleText('httprequest.response.close', this.sessionid); consoleHttpRequest = null; }
2506 + };
2507 +
2508 + // Open a web browser to a specified URL on current user's desktop
2509 + function openUserDesktopUrl(url) {
2510 + if ((url.toLowerCase().startsWith('http://') == false) && (url.toLowerCase().startsWith('https://') == false)) { return null; }
2511 + var child = null;
2512 + try {
2513 + switch (process.platform) {
2514 + case 'win32':
2515 + var user = require('user-sessions').getUsername(require('user-sessions').consoleUid());
2516 + child = require('child_process').execFile(process.env['windir'] + '\\system32\\cmd.exe', ['cmd']);
2517 + child.stderr.on('data', function () { });
2518 + child.stdout.on('data', function () { });
2519 + 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');
2520 + child.stdin.write('SCHTASKS /RUN /TN MeshChatTask\r\n');
2521 + child.stdin.write('SCHTASKS /DELETE /F /TN MeshChatTask\r\n');
2522 + child.stdin.write('exit\r\n');
2523 + child.waitExit();
2524 + break;
2525 + case 'linux':
2526 + child = require('child_process').execFile('/usr/bin/xdg-open', ['xdg-open', url], { uid: require('user-sessions').consoleUid() });
2527 + break;
2528 + case 'darwin':
2529 + child = require('child_process').execFile('/usr/bin/open', ['open', url], { uid: require('user-sessions').consoleUid() });
2530 + break;
2531 + default:
2532 + // Unknown platform, ignore this command.
2533 + break;
2534 + }
2535 + } catch (e) { }
2536 + return child;
2537 + }
2538 +
2539 + // Process a mesh agent console command
2540 + function processConsoleCommand(cmd, args, rights, sessionid) {
2541 + try {
2542 + var response = null;
2543 + switch (cmd) {
2544 + case 'help': { // Displays available commands
2545 + var fin = '', f = '', availcommands = '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';
2546 + if (process.platform == 'win32') { availcommands += ',safemode,wpfhwacceleration,uac'; }
2547 + if (process.platform != 'freebsd') { availcommands += ',vm';}
2548 + if (require('MeshAgent').maxKvmTileSize != null) { availcommands += ',kvmmode'; }
2549 + try { require('zip-reader'); availcommands += ',zip,unzip'; } catch (e) { }
2550 +
2551 + availcommands = availcommands.split(',').sort();
2552 + while (availcommands.length > 0) {
2553 + if (f.length > 90) { fin += (f + ',\r\n'); f = ''; }
2554 + f += (((f != '') ? ', ' : ' ') + availcommands.shift());
2555 + }
2556 + if (f != '') { fin += f; }
2557 + response = "Available commands: \r\n" + fin + ".";
2558 + break;
2559 + }
2560 + case 'agentmsg': {
2561 + if (args['_'].length == 0) {
2562 + response = "Proper usage:\r\n agentmsg add \"[message]\" [iconIndex]\r\n agentmsg remove [index]\r\n agentmsg list"; // Display usage
2563 + } else {
2564 + if ((args['_'][0] == 'add') && (args['_'].length > 1)) {
2565 + var msgIndex = 1, iconIndex = 0;
2566 + while (tunnelUserCount.msg[msgIndex] != null) { msgIndex++; }
2567 + if (args['_'].length >= 3) { try { iconIndex = parseInt(args['_'][2]); } catch (e) { } }
2568 + if (typeof iconIndex != 'number') { iconIndex = 0; }
2569 + tunnelUserCount.msg[msgIndex] = { msg: args['_'][1], icon: iconIndex };
2570 + response = 'Agent message ' + msgIndex + ' added.';
2571 + } else if ((args['_'][0] == 'remove') && (args['_'].length > 1)) {
2572 + var msgIndex = 0;
2573 + try { msgIndex = parseInt(args['_'][1]); } catch (x) { }
2574 + if (tunnelUserCount.msg[msgIndex] == null) { response = "Message not found."; } else { delete tunnelUserCount.msg[msgIndex]; response = "Message removed."; }
2575 + } else if (args['_'][0] == 'list') {
2576 + response = JSON.stringify(tunnelUserCount.msg, null, 2);
2577 + }
2578 + try { mesh.SendCommand({ action: 'sessions', type: 'msg', value: tunnelUserCount.msg }); } catch (x) { }
2579 + }
2580 + break;
2581 + }
2582 + case 'clearagentmsg': {
2583 + tunnelUserCount.msg = {};
2584 + try { mesh.SendCommand({ action: 'sessions', type: 'msg', value: tunnelUserCount.msg }); } catch (x) { }
2585 + break;
2586 + }
2587 + case 'coredump':
2588 + if (args['_'].length != 1) {
2589 + response = "Proper usage: coredump on|off|status|clear"; // Display usage
2590 + } else {
2591 + switch (args['_'][0].toLowerCase())
2592 + {
2593 + case 'on':
2594 + process.coreDumpLocation = (process.platform == 'win32') ? (process.execPath.replace('.exe', '.dmp')) : (process.execPath + '.dmp');
2595 + response = 'coredump is now on';
2596 + break;
2597 + case 'off':
2598 + process.coreDumpLocation = null;
2599 + response = 'coredump is now off';
2600 + break;
2601 + case 'status':
2602 + response = 'coredump is: ' + ((process.coreDumpLocation == null) ? 'off' : 'on');
2603 + if (process.coreDumpLocation != null) {
2604 + if (process.platform == 'win32') {
2605 + if (fs.existsSync(process.coreDumpLocation)) { response += '\r\n CoreDump present at: ' + process.coreDumpLocation; }
2606 + } else {
2607 + if ((process.cwd() != '//') && fs.existsSync(process.cwd() + 'core')) { response += '\r\n CoreDump present at: ' + process.cwd() + 'core'; }
2608 + }
2609 + }
2610 + break;
2611 + case 'clear':
2612 + db.Put('CoreDumpTime', null);
2613 + response = 'coredump db cleared';
2614 + break;
2615 + default:
2616 + response = "Proper usage: coredump on|off|status"; // Display usage
2617 + break;
2618 + }
2619 + }
2620 + break;
2621 + case 'service':
2622 + if (args['_'].length != 1)
2623 + {
2624 + response = "Proper usage: service status|restart"; // Display usage
2625 + }
2626 + else
2627 + {
2628 + var s = require('service-manager').manager.getService(process.platform == 'win32' ? 'Mesh Agent' : 'meshagent');
2629 + switch(args['_'][0].toLowerCase())
2630 + {
2631 + case 'status':
2632 + response = 'Service ' + (s.isRunning() ? (s.isMe() ? '[SELF]' : '[RUNNING]') : ('[NOT RUNNING]'));
2633 + break;
2634 + case 'restart':
2635 + if (s.isMe())
2636 + {
2637 + s.restart();
2638 + }
2639 + else
2640 + {
2641 + response = 'Restarting another agent instance is not allowed';
2642 + }
2643 + break;
2644 + default:
2645 + response = "Proper usage: service status|restart"; // Display usage
2646 + break;
2647 + }
2648 + if (process.platform == 'win32') { s.close(); }
2649 + }
2650 + break;
2651 + case 'zip':
2652 + if (args['_'].length == 0) {
2653 + response = "Proper usage: zip (output file name), input1 [, input n]"; // Display usage
2654 + } else {
2655 + var p = args['_'].join(' ').split(',');
2656 + var ofile = p.shift();
2657 + sendConsoleText('Writing ' + ofile + '...');
2658 + var out = require('fs').createWriteStream(ofile, { flags: 'wb' });
2659 + out.fname = ofile;
2660 + out.sessionid = sessionid;
2661 + out.on('close', function () { sendConsoleText('DONE writing ' + this.fname, this.sessionid); });
2662 + var zip = require('zip-writer').write({ files: p });
2663 + zip.pipe(out);
2664 + }
2665 + break;
2666 + case 'unzip':
2667 + if (args['_'].length == 0) {
2668 + response = "Proper usage: unzip input, destination"; // Display usage
2669 + } else {
2670 + var p = args['_'].join(' ').split(',');
2671 + if (p.length != 2) { response = "Proper usage: unzip input, destination"; break; } // Display usage
2672 + var prom = require('zip-reader').read(p[0]);
2673 + prom._dest = p[1];
2674 + prom.self = this;
2675 + prom.sessionid = sessionid;
2676 + prom.then(function (zipped) {
2677 + sendConsoleText('Extracting to ' + this._dest + '...', this.sessionid);
2678 + zipped.extractAll(this._dest).then(function () { sendConsoleText('finished unzipping', this.sessionid); }, function (e) { sendConsoleText('Error unzipping: ' + e, this.sessionid); }).parentPromise.sessionid = this.sessionid;
2679 + }, function (e) { sendConsoleText('Error unzipping: ' + e, this.sessionid); });
2680 + }
2681 + break;
2682 + case 'setbattery':
2683 + // require('MeshAgent').SendCommand({ action: 'battery', state: 'dc', level: 55 });
2684 + if ((args['_'].length > 0) && ((args['_'][0] == 'ac') || (args['_'][0] == 'dc'))) {
2685 + var b = { action: 'battery', state: args['_'][0] };
2686 + if (args['_'].length == 2) { b.level = parseInt(args['_'][1]); }
2687 + require('MeshAgent').SendCommand(b);
2688 + } else {
2689 + require('MeshAgent').SendCommand({ action: 'battery' });
2690 + }
2691 + break;
2692 + case 'fdsnapshot':
2693 + require('ChainViewer').getSnapshot().then(function (c) { sendConsoleText(c, this.sessionid); }).parentPromise.sessionid = sessionid;
2694 + break;
2695 + case 'fdcount':
2696 + require('DescriptorEvents').getDescriptorCount().then(
2697 + function (c)
2698 + {
2699 + sendConsoleText('Descriptor Count: ' + c, this.sessionid);
2700 + }, function (e)
2701 + {
2702 + sendConsoleText('Error fetching descriptor count: ' + e, this.sessionid);
2703 + }).parentPromise.sessionid = sessionid;
2704 + break;
2705 + case 'uac':
2706 + if (process.platform != 'win32')
2707 + {
2708 + response = 'Unknown command "uac", type "help" for list of avaialble commands.';
2709 + break;
2710 + }
2711 + if (args['_'].length != 1)
2712 + {
2713 + response = 'Proper usage: uac [get|interactive|secure]';
2714 + }
2715 + else
2716 + {
2717 + switch(args['_'][0].toUpperCase())
2718 + {
2719 + case 'GET':
2720 + var secd = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System', 'PromptOnSecureDesktop');
2721 + response = "UAC mode: " + (secd == 0 ? "Interactive Desktop" : "Secure Desktop");
2722 + break;
2723 + case 'INTERACTIVE':
2724 + try
2725 + {
2726 + require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System', 'PromptOnSecureDesktop', 0);
2727 + response = 'UAC mode changed to: Interactive Desktop';
2728 + }
2729 + catch (e)
2730 + {
2731 + response = "Unable to change UAC Mode";
2732 + }
2733 + break;
2734 + case 'SECURE':
2735 + try
2736 + {
2737 + require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System', 'PromptOnSecureDesktop', 1);
2738 + response = 'UAC mode changed to: Secure Desktop';
2739 + }
2740 + catch(e)
2741 + {
2742 + response = "Unable to change UAC Mode";
2743 + }
2744 + break;
2745 + default:
2746 + response = 'Proper usage: uac [get|interactive|secure]';
2747 + break;
2748 + }
2749 + }
2750 + break;
2751 + case 'vm':
2752 + response = 'Virtual Machine = ' + require('identifiers').isVM();
2753 + break;
2754 + case 'startupoptions':
2755 + response = JSON.stringify(require('MeshAgent').getStartupOptions());
2756 + break;
2757 + case 'kvmmode':
2758 + if (require('MeshAgent').maxKvmTileSize == null)
2759 + {
2760 + response = "Unknown command \"kvmmode\", type \"help\" for list of avaialble commands.";
2761 + }
2762 + else
2763 + {
2764 + if(require('MeshAgent').maxKvmTileSize == 0)
2765 + {
2766 + response = 'KVM Mode: Full JUMBO';
2767 + }
2768 + else
2769 + {
2770 + response = 'KVM Mode: ' + (require('MeshAgent').maxKvmTileSize <= 65500 ? 'NO JUMBO' : 'Partial JUMBO');
2771 + response += (', TileLimit: ' + (require('MeshAgent').maxKvmTileSize < 1024 ? (require('MeshAgent').maxKvmTileSize + ' bytes') : (Math.round(require('MeshAgent').maxKvmTileSize/1024) + ' Kbytes')));
2772 + }
2773 + }
2774 + break;
2775 + case 'alert':
2776 + if (args['_'].length == 0)
2777 + {
2778 + response = "Proper usage: alert TITLE, CAPTION [, TIMEOUT]"; // Display usage
2779 + }
2780 + else
2781 + {
2782 + var p = args['_'].join(' ').split(',');
2783 + if(p.length<2)
2784 + {
2785 + response = "Proper usage: alert TITLE, CAPTION [, TIMEOUT]"; // Display usage
2786 + }
2787 + else
2788 + {
2789 + this._alert = require('message-box').create(p[0], p[1], p.length==3?parseInt(p[2]):9999,1);
2790 + }
2791 + }
2792 + break;
2793 + case 'agentsize':
2794 + var actualSize = Math.floor(require('fs').statSync(process.execPath).size / 1024);
2795 + if (process.platform == 'win32') {
2796 + // Check the Agent Uninstall MetaData for correctness, as the installer may have written an incorrect value
2797 + var writtenSize = 0;
2798 + try { writtenSize = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MeshCentralAgent', 'EstimatedSize'); } catch (e) { response = e; }
2799 + if (writtenSize != actualSize) {
2800 + response = "Size updated from: " + writtenSize + " to: " + actualSize;
2801 + try { require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MeshCentralAgent', 'EstimatedSize', actualSize); } catch (e) { response = e; }
2802 + } else { response = "Agent Size: " + actualSize + " kb"; }
2803 + } else { response = "Agent Size: " + actualSize + " kb"; }
2804 + break;
2805 + case 'versions':
2806 + response = JSON.stringify(process.versions, null, ' ');
2807 + break;
2808 + case 'wpfhwacceleration':
2809 + if (process.platform != 'win32') { throw ("wpfhwacceleration setting is only supported on Windows"); }
2810 + if (args['_'].length != 1) {
2811 + response = "Proper usage: wpfhwacceleration (ON|OFF|STATUS)"; // Display usage
2812 + }
2813 + else {
2814 + var reg = require('win-registry');
2815 + var uname = require('user-sessions').getUsername(require('user-sessions').consoleUid());
2816 + var key = reg.usernameToUserKey(uname);
2817 +
2818 + switch (args['_'][0].toUpperCase()) {
2819 + default:
2820 + response = "Proper usage: wpfhwacceleration (ON|OFF|STATUS|DEFAULT)"; // Display usage
2821 + break;
2822 + case 'ON':
2823 + try {
2824 + reg.WriteKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration', 0);
2825 + response = "OK";
2826 + } catch (e) { response = "FAILED"; }
2827 + break;
2828 + case 'OFF':
2829 + try {
2830 + reg.WriteKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration', 1);
2831 + response = 'OK';
2832 + } catch (e) { response = 'FAILED'; }
2833 + break;
2834 + case 'STATUS':
2835 + var s;
2836 + try { s = reg.QueryKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration') == 1 ? 'DISABLED' : 'ENABLED'; } catch (e) { s = 'DEFAULT'; }
2837 + response = "WPF Hardware Acceleration: " + s;
2838 + break;
2839 + case 'DEFAULT':
2840 + try { reg.DeleteKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration'); } catch (e) { }
2841 + response = 'OK';
2842 + break;
2843 + }
2844 + }
2845 + break;
2846 + case 'tsid':
2847 + if (process.platform == 'win32') {
2848 + if (args['_'].length != 1) {
2849 + response = "TSID: " + (require('MeshAgent')._tsid == null ? "console" : require('MeshAgent')._tsid);
2850 + } else {
2851 + var i = parseInt(args['_'][0]);
2852 + require('MeshAgent')._tsid = (isNaN(i) ? null : i);
2853 + response = "TSID set to: " + (require('MeshAgent')._tsid == null ? "console" : require('MeshAgent')._tsid);
2854 + }
2855 + } else { response = "TSID command only supported on Windows"; }
2856 + break;
2857 + case 'activeusers':
2858 + if (process.platform == 'win32') {
2859 + var p = require('user-sessions').enumerateUsers();
2860 + p.sessionid = sessionid;
2861 + p.then(function (u) {
2862 + var v = [];
2863 + for (var i in u) {
2864 + if (u[i].State == 'Active') { v.push({ tsid: i, type: u[i].StationName, user: u[i].Username, domain: u[i].Domain }); }
2865 + }
2866 + sendConsoleText(JSON.stringify(v, null, 1), this.sessionid);
2867 + });
2868 + } else { response = "activeusers command only supported on Windows"; }
2869 + break;
2870 + case 'wallpaper':
2871 + if (process.platform != 'win32' && !(process.platform == 'linux' && require('linux-gnome-helpers').available)) {
2872 + response = "wallpaper command not supported on this platform";
2873 + }
2874 + else {
2875 + if (args['_'].length != 1) {
2876 + response = 'Proper usage: wallpaper (GET|TOGGLE)'; // Display usage
2877 + }
2878 + else {
2879 + switch (args['_'][0].toUpperCase()) {
2880 + default:
2881 + response = 'Proper usage: wallpaper (GET|TOGGLE)'; // Display usage
2882 + break;
2883 + case 'GET':
2884 + case 'TOGGLE':
2885 + if (process.platform == 'win32') {
2886 + var id = require('user-sessions').getProcessOwnerName(process.pid).tsid == 0 ? 1 : 0;
2887 + var child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0='], { type: id });
2888 + child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
2889 + child.stderr.on('data', function () { });
2890 + child.waitExit();
2891 + var current = child.stdout.str.trim();
2892 + if (args['_'][0].toUpperCase() == 'GET') {
2893 + response = current;
2894 + break;
2895 + }
2896 + if (current != '') {
2897 + require('MeshAgent')._wallpaper = current;
2898 + response = 'Wallpaper cleared';
2899 + } else {
2900 + response = 'Wallpaper restored';
2901 + }
2902 + child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0=', current != '' ? '""' : require('MeshAgent')._wallpaper], { type: id });
2903 + child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
2904 + child.stderr.on('data', function () { });
2905 + child.waitExit();
2906 + }
2907 + else {
2908 + var id = require('user-sessions').consoleUid();
2909 + var current = require('linux-gnome-helpers').getDesktopWallpaper(id);
2910 + if (args['_'][0].toUpperCase() == 'GET') {
2911 + response = current;
2912 + break;
2913 + }
2914 + if (current != '/dev/null') {
2915 + require('MeshAgent')._wallpaper = current;
2916 + response = 'Wallpaper cleared';
2917 + } else {
2918 + response = 'Wallpaper restored';
2919 + }
2920 + require('linux-gnome-helpers').setDesktopWallpaper(id, current != '/dev/null' ? undefined : require('MeshAgent')._wallpaper);
2921 + }
2922 + break;
2923 + }
2924 + }
2925 + }
2926 + break;
2927 + case 'safemode':
2928 + if (process.platform != 'win32') {
2929 + response = 'safemode only supported on Windows Platforms'
2930 + }
2931 + else {
2932 + if (args['_'].length != 1) {
2933 + response = 'Proper usage: safemode (ON|OFF|STATUS)'; // Display usage
2934 + }
2935 + else {
2936 + switch (args['_'][0].toUpperCase()) {
2937 + default:
2938 + response = 'Proper usage: safemode (ON|OFF|STATUS)'; // Display usage
2939 + break;
2940 + case 'ON':
2941 + require('win-bcd').setKey('safeboot', 'Network');
2942 + require('win-bcd').enableSafeModeService('Mesh Agent');
2943 + break;
2944 + case 'OFF':
2945 + require('win-bcd').deleteKey('safeboot');
2946 + break;
2947 + case 'STATUS':
2948 + var nextboot = require('win-bcd').getKey('safeboot');
2949 + if (nextboot) {
2950 + switch (nextboot) {
2951 + case 'Network':
2952 + case 'network':
2953 + nextboot = 'SAFE_MODE_NETWORK';
2954 + break;
2955 + default:
2956 + nextboot = 'SAFE_MODE';
2957 + break;
2958 + }
2959 + }
2960 + response = 'Current: ' + require('win-bcd').bootMode + ', NextBoot: ' + (nextboot ? nextboot : 'NORMAL');
2961 + break;
2962 + }
2963 + }
2964 + }
2965 + break;
2966 + /*
2967 + case 'border':
2968 + {
2969 + if ((args['_'].length == 1) && (args['_'][0] == 'on')) {
2970 + if (meshCoreObj.users.length > 0) {
2971 + obj.borderManager.Start(meshCoreObj.users[0]);
2972 + response = 'Border blinking is on.';
2973 + } else {
2974 + response = 'Cannot turn on border blinking, no logged in users.';
2975 + }
2976 + } else if ((args['_'].length == 1) && (args['_'][0] == 'off')) {
2977 + obj.borderManager.Stop();
2978 + response = 'Border blinking is off.';
2979 + } else {
2980 + response = 'Proper usage: border "on|off"'; // Display correct command usage
2981 + }
2982 + }
2983 + break;
2984 + */
2985 + case 'av':
2986 + if (process.platform == 'win32') {
2987 + // Windows Command: "wmic /Namespace:\\root\SecurityCenter2 Path AntiVirusProduct get /FORMAT:CSV"
2988 + response = JSON.stringify(require('win-info').av(), null, 1);
2989 + } else {
2990 + response = 'Not supported on the platform';
2991 + }
2992 + break;
2993 + case 'log':
2994 + if (args['_'].length != 1) { response = 'Proper usage: log "sample text"'; } else { MeshServerLog(args['_'][0]); response = 'ok'; }
2995 + break;
2996 + case 'getclip':
2997 + if (require('MeshAgent').isService) {
2998 + require('clipboard').dispatchRead().then(function (str) { sendConsoleText(str, sessionid); });
2999 + } else {
3000 + require("clipboard").read().then(function (str) { sendConsoleText(str, sessionid); });
3001 + }
3002 + break;
3003 + case 'setclip': {
3004 + if (args['_'].length != 1) {
3005 + response = 'Proper usage: setclip "sample text"';
3006 + } else {
3007 + if (require('MeshAgent').isService) {
3008 + require('clipboard').dispatchWrite(args['_'][0]);
3009 + response = 'Setting clipboard to: "' + args['_'][0] + '"';
3010 + }
3011 + else {
3012 + require("clipboard")(args['_'][0]); response = 'Setting clipboard to: "' + args['_'][0] + '"';
3013 + }
3014 + }
3015 + break;
3016 + }
3017 + case 'amtreset': {
3018 + if (amt != null) { amt.reset(); response = 'Done.'; }
3019 + break;
3020 + }
3021 + case 'amtlmsreset': {
3022 + if (amt != null) { amt.lmsreset(); response = 'Done.'; }
3023 + break;
3024 + }
3025 + case 'amtccm': {
3026 + if (amt == null) { response = 'Intel AMT not supported.'; } else {
3027 + if (args['_'].length != 1) { response = 'Proper usage: amtccm (adminPassword)'; } // Display usage
3028 + else { amt.setPolicy({ type: 0 }); amt.activeToCCM(args['_'][0]); }
3029 + }
3030 + break;
3031 + }
3032 + case 'amtacm': {
3033 + if (amt == null) { response = 'Intel AMT not supported.'; } else {
3034 + amt.setPolicy({ type: 0 });
3035 + amt.getAmtInfo(function (meinfo) { amt.activeToACM(meinfo); });
3036 + }
3037 + break;
3038 + }
3039 + case 'amtdeactivate': {
3040 + if (amt == null) { response = 'Intel AMT not supported.'; } else { amt.setPolicy({ type: 0 }); amt.deactivateCCM(); }
3041 + break;
3042 + }
3043 + case 'amtpolicy': {
3044 + if (amtPolicy == null) {
3045 + response = 'No Intel(R) AMT policy.';
3046 + } else {
3047 + response = JSON.stringify(amtPolicy);
3048 + }
3049 + break;
3050 + }
3051 + case 'openurl': {
3052 + if (args['_'].length != 1) { response = 'Proper usage: openurl (url)'; } // Display usage
3053 + else { if (openUserDesktopUrl(args['_'][0]) == null) { response = 'Failed.'; } else { response = 'Success.'; } }
3054 + break;
3055 + }
3056 + case 'users': {
3057 + if (meshCoreObj.users == null) { response = 'Active users are unknown.'; } else { response = 'Active Users: ' + meshCoreObj.users.join(', ') + '.'; }
3058 + require('user-sessions').enumerateUsers().then(function (u) { for (var i in u) { sendConsoleText(u[i]); } });
3059 + break;
3060 + }
3061 + case 'toast': {
3062 + if (args['_'].length < 1) { response = 'Proper usage: toast "message"'; } else {
3063 + if (require('MeshAgent')._tsid == null) {
3064 + require('toaster').Toast('MeshCentral', args['_'][0]).then(sendConsoleText, sendConsoleText);
3065 + }
3066 + else {
3067 + require('toaster').Toast('MeshCentral', args['_'][0], require('MeshAgent')._tsid).then(sendConsoleText, sendConsoleText);
3068 + }
3069 + }
3070 + break;
3071 + }
3072 + case 'setdebug': {
3073 + if (args['_'].length < 1) { response = 'Proper usage: setdebug (target), 0 = Disabled, 1 = StdOut, 2 = This Console, * = All Consoles, 4 = WebLog, 8 = Logfile'; } // Display usage
3074 + else { if (args['_'][0] == '*') { console.setDestination(2); } else { console.setDestination(parseInt(args['_'][0]), sessionid); } }
3075 + break;
3076 + }
3077 + case 'ps': {
3078 + processManager.getProcesses(function (plist) {
3079 + var x = '';
3080 + for (var i in plist) { x += i + ((plist[i].user) ? (', ' + plist[i].user) : '') + ', ' + plist[i].cmd + '\r\n'; }
3081 + sendConsoleText(x, sessionid);
3082 + });
3083 + break;
3084 + }
3085 + case 'kill': {
3086 + if ((args['_'].length < 1)) {
3087 + response = 'Proper usage: kill [pid]'; // Display correct command usage
3088 + } else {
3089 + process.kill(parseInt(args['_'][0]));
3090 + response = 'Killed process ' + args['_'][0] + '.';
3091 + }
3092 + break;
3093 + }
3094 + case 'smbios': {
3095 + if (SMBiosTables == null) { response = 'SMBios tables not available.'; } else { response = objToString(SMBiosTables, 0, ' ', true); }
3096 + break;
3097 + }
3098 + case 'rawsmbios': {
3099 + if (SMBiosTablesRaw == null) { response = 'SMBios tables not available.'; } else {
3100 + response = '';
3101 + for (var i in SMBiosTablesRaw) {
3102 + var header = false;
3103 + for (var j in SMBiosTablesRaw[i]) {
3104 + if (SMBiosTablesRaw[i][j].length > 0) {
3105 + if (header == false) { response += ('Table type #' + i + ((require('smbios').smTableTypes[i] == null) ? '' : (', ' + require('smbios').smTableTypes[i]))) + '\r\n'; header = true; }
3106 + response += (' ' + SMBiosTablesRaw[i][j].toString('hex')) + '\r\n';
3107 + }
3108 + }
3109 + }
3110 + }
3111 + break;
3112 + }
3113 + case 'eval': { // Eval JavaScript
3114 + if (args['_'].length < 1) {
3115 + response = 'Proper usage: eval "JavaScript code"'; // Display correct command usage
3116 + } else {
3117 + response = JSON.stringify(mesh.eval(args['_'][0])); // This can only be run by trusted administrator.
3118 + }
3119 + break;
3120 + }
3121 + case 'uninstallagent': // Uninstall this agent
3122 + var agentName = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent';
3123 + if (!require('service-manager').manager.getService(agentName).isMe()) {
3124 + response = 'Uininstall failed, this instance is not the service instance';
3125 + } else {
3126 + try { diagnosticAgent_uninstall(); } catch (e) { }
3127 + var js = "require('service-manager').manager.getService('" + agentName + "').stop(); require('service-manager').manager.uninstallService('" + agentName + "'); process.exit();";
3128 + 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 });
3129 + }
3130 + break;
3131 + case 'notify': { // Send a notification message to the mesh
3132 + if (args['_'].length != 1) {
3133 + response = 'Proper usage: notify "message" [--session]'; // Display correct command usage
3134 + } else {
3135 + var notification = { action: 'msg', type: 'notify', value: args['_'][0], tag: 'console' };
3136 + if (args.session) { notification.sessionid = sessionid; } // If "--session" is specified, notify only this session, if not, the server will notify the mesh
3137 + mesh.SendCommand(notification); // no sessionid or userid specified, notification will go to the entire mesh
3138 + response = "ok";
3139 + }
3140 + break;
3141 + }
3142 + case 'cpuinfo': { // Return system information
3143 + // CPU & memory utilization
3144 + pr = require('sysinfo').cpuUtilization();
3145 + pr.sessionid = sessionid;
3146 + pr.then(function (data) {
3147 + sendConsoleText(JSON.stringify({ cpu: data, memory: require('sysinfo').memUtilization() }, null, 1), this.sessionid);
3148 + }, function (e) {
3149 + sendConsoleText(e);
3150 + });
3151 + break;
3152 + }
3153 + case 'sysinfo': { // Return system information
3154 + getSystemInformation(function (results, err) {
3155 + if (results == null) { sendConsoleText(err, this.sessionid); } else {
3156 + sendConsoleText(JSON.stringify(results, null, 1), this.sessionid);
3157 + }
3158 + });
3159 + break;
3160 + }
3161 + case 'info': { // Return information about the agent and agent core module
3162 + 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 + '.';
3163 + if (amt != null) { response += '\r\nBuilt-in LMS: ' + ['Disabled', 'Connecting..', 'Connected'][amt.lmsstate] + '.'; }
3164 + if (meshCoreObj.osdesc) { response += '\r\nOS: ' + meshCoreObj.osdesc + '.'; }
3165 + response += '\r\nModules: ' + addedModules.join(', ') + '.';
3166 + response += '\r\nServer Connection: ' + mesh.isControlChannelConnected + ', State: ' + meshServerConnectionState + '.';
3167 + response += '\r\lastMeInfo: ' + lastMeInfo + '.';
3168 + var oldNodeId = db.Get('OldNodeId');
3169 + if (oldNodeId != null) { response += '\r\nOldNodeID: ' + oldNodeId + '.'; }
3170 + if (process.platform == 'linux' || process.platform == 'freebsd') { response += '\r\nX11 support: ' + require('monitor-info').kvm_x11_support + '.'; }
3171 + break;
3172 + }
3173 + case 'osinfo': { // Return the operating system information
3174 + var i = 1;
3175 + if (args['_'].length > 0) { i = parseInt(args['_'][0]); if (i > 8) { i = 8; } response = 'Calling ' + i + ' times.'; }
3176 + for (var j = 0; j < i; j++) {
3177 + var pr = require('os').name();
3178 + pr.sessionid = sessionid;
3179 + pr.then(function (v) {
3180 + sendConsoleText("OS: " + v + (process.platform == 'win32' ? (require('win-virtual-terminal').supported ? ' [ConPTY: YES]' : ' [ConPTY: NO]') : ''), this.sessionid);
3181 + });
3182 + }
3183 + break;
3184 + }
3185 + case 'sendcaps': { // Send capability flags to the server
3186 + if (args['_'].length == 0) {
3187 + response = 'Proper usage: sendcaps (number)'; // Display correct command usage
3188 + } else {
3189 + meshCoreObj.caps = parseInt(args['_'][0]);
3190 + mesh.SendCommand(meshCoreObj);
3191 + response = JSON.stringify(meshCoreObj);
3192 + }
3193 + break;
3194 + }
3195 + case 'sendosdesc': { // Send OS description
3196 + if (args['_'].length > 0) {
3197 + meshCoreObj.osdesc = args['_'][0];
3198 + mesh.SendCommand(meshCoreObj);
3199 + response = JSON.stringify(meshCoreObj);
3200 + } else {
3201 + response = 'Proper usage: sendosdesc [os description]'; // Display correct command usage
3202 + }
3203 + break;
3204 + }
3205 + case 'args': { // Displays parsed command arguments
3206 + response = 'args ' + objToString(args, 0, ' ', true);
3207 + break;
3208 + }
3209 + case 'print': { // Print a message on the mesh agent console, does nothing when running in the background
3210 + var r = [];
3211 + for (var i in args['_']) { r.push(args['_'][i]); }
3212 + console.log(r.join(' '));
3213 + response = 'Message printed on agent console.';
3214 + break;
3215 + }
3216 + case 'type': { // Returns the content of a file
3217 + if (args['_'].length == 0) {
3218 + response = 'Proper usage: type (filepath) [maxlength]'; // Display correct command usage
3219 + } else {
3220 + var max = 4096;
3221 + if ((args['_'].length > 1) && (typeof args['_'][1] == 'number')) { max = args['_'][1]; }
3222 + if (max > 4096) max = 4096;
3223 + var buf = Buffer.alloc(max), fd = fs.openSync(args['_'][0], "r"), r = fs.readSync(fd, buf, 0, max); // Read the file content
3224 + response = buf.toString();
3225 + var i = response.indexOf('\n');
3226 + if ((i > 0) && (response[i - 1] != '\r')) { response = response.split('\n').join('\r\n'); }
3227 + if (r == max) response += '...';
3228 + fs.closeSync(fd);
3229 + }
3230 + break;
3231 + }
3232 + case 'dbkeys': { // Return all data store keys
3233 + response = JSON.stringify(db.Keys);
3234 + break;
3235 + }
3236 + case 'dbget': { // Return the data store value for a given key
3237 + if (db == null) { response = 'Database not accessible.'; break; }
3238 + if (args['_'].length != 1) {
3239 + response = 'Proper usage: dbget (key)'; // Display the value for a given database key
3240 + } else {
3241 + response = db.Get(args['_'][0]);
3242 + }
3243 + break;
3244 + }
3245 + case 'dbset': { // Set a data store key and value pair
3246 + if (db == null) { response = 'Database not accessible.'; break; }
3247 + if (args['_'].length != 2) {
3248 + response = 'Proper usage: dbset (key) (value)'; // Set a database key
3249 + } else {
3250 + var r = db.Put(args['_'][0], args['_'][1]);
3251 + response = 'Key set: ' + r;
3252 + }
3253 + break;
3254 + }
3255 + case 'dbcompact': { // Compact the data store
3256 + if (db == null) { response = 'Database not accessible.'; break; }
3257 + var r = db.Compact();
3258 + response = 'Database compacted: ' + r;
3259 + break;
3260 + }
3261 + case 'httpget': {
3262 + if (consoleHttpRequest != null) {
3263 + response = 'HTTP operation already in progress.';
3264 + } else {
3265 + if (args['_'].length != 1) {
3266 + response = 'Proper usage: httpget (url)';
3267 + } else {
3268 + var options = http.parseUri(args['_'][0]);
3269 + options.method = 'GET';
3270 + if (options == null) {
3271 + response = 'Invalid url.';
3272 + } else {
3273 + try { consoleHttpRequest = http.request(options, consoleHttpResponse); } catch (e) { response = 'Invalid HTTP GET request'; }
3274 + consoleHttpRequest.sessionid = sessionid;
3275 + if (consoleHttpRequest != null) {
3276 + consoleHttpRequest.end();
3277 + response = 'HTTPGET ' + options.protocol + '//' + options.host + ':' + options.port + options.path;
3278 + }
3279 + }
3280 + }
3281 + }
3282 + break;
3283 + }
3284 + case 'wslist': { // List all web sockets
3285 + response = '';
3286 + for (var i in consoleWebSockets) {
3287 + var httprequest = consoleWebSockets[i];
3288 + response += 'Websocket #' + i + ', ' + httprequest.url + '\r\n';
3289 + }
3290 + if (response == '') { response = 'no websocket sessions.'; }
3291 + break;
3292 + }
3293 + case 'wsconnect': { // Setup a web socket
3294 + if (args['_'].length == 0) {
3295 + response = 'Proper usage: wsconnect (url)\r\nFor example: wsconnect wss://localhost:443/meshrelay.ashx?id=abc'; // Display correct command usage
3296 + } else {
3297 + var httprequest = null;
3298 + try {
3299 + var options = http.parseUri(args['_'][0].split('$').join('%24').split('@').join('%40')); // Escape the $ and @ characters in the URL
3300 + options.rejectUnauthorized = 0;
3301 + httprequest = http.request(options);
3302 + } catch (e) { response = 'Invalid HTTP websocket request'; }
3303 + if (httprequest != null) {
3304 + httprequest.upgrade = onWebSocketUpgrade;
3305 + httprequest.on('error', function (e) { sendConsoleText("ERROR: Unable to connect to: " + this.url + ", " + JSON.stringify(e)); });
3306 +
3307 + var index = 1;
3308 + while (consoleWebSockets[index]) { index++; }
3309 + httprequest.sessionid = sessionid;
3310 + httprequest.index = index;
3311 + httprequest.url = args['_'][0];
3312 + consoleWebSockets[index] = httprequest;
3313 + response = 'New websocket session #' + index;
3314 + }
3315 + }
3316 + break;
3317 + }
3318 + case 'wssend': { // Send data on a web socket
3319 + if (args['_'].length == 0) {
3320 + response = 'Proper usage: wssend (socketnumber)\r\n'; // Display correct command usage
3321 + for (var i in consoleWebSockets) {
3322 + var httprequest = consoleWebSockets[i];
3323 + response += 'Websocket #' + i + ', ' + httprequest.url + '\r\n';
3324 + }
3325 + } else {
3326 + var i = parseInt(args['_'][0]);
3327 + var httprequest = consoleWebSockets[i];
3328 + if (httprequest != undefined) {
3329 + httprequest.s.write(args['_'][1]);
3330 + response = 'ok';
3331 + } else {
3332 + response = 'Invalid web socket number';
3333 + }
3334 + }
3335 + break;
3336 + }
3337 + case 'wsclose': { // Close a websocket
3338 + if (args['_'].length == 0) {
3339 + response = 'Proper usage: wsclose (socketnumber)'; // Display correct command usage
3340 + } else {
3341 + var i = parseInt(args['_'][0]);
3342 + var httprequest = consoleWebSockets[i];
3343 + if (httprequest != undefined) {
3344 + if (httprequest.s != null) { httprequest.s.end(); } else { httprequest.end(); }
3345 + response = 'ok';
3346 + } else {
3347 + response = 'Invalid web socket number';
3348 + }
3349 + }
3350 + break;
3351 + }
3352 + case 'tunnels': { // Show the list of current tunnels
3353 + response = '';
3354 + for (var i in tunnels) { response += 'Tunnel #' + i + ', ' + tunnels[i].url + '\r\n'; }
3355 + if (response == '') { response = 'No websocket sessions.'; }
3356 + break;
3357 + }
3358 + case 'ls': { // Show list of files and folders
3359 + response = '';
3360 + var xpath = '*';
3361 + if (args['_'].length > 0) { xpath = obj.path.join(args['_'][0], '*'); }
3362 + response = 'List of ' + xpath + '\r\n';
3363 + var results = fs.readdirSync(xpath);
3364 + for (var i = 0; i < results.length; ++i) {
3365 + var stat = null, p = obj.path.join(args['_'][0], results[i]);
3366 + try { stat = fs.statSync(p); } catch (e) { }
3367 + if ((stat == null) || (stat == undefined)) {
3368 + response += (results[i] + "\r\n");
3369 + } else {
3370 + response += (results[i] + " " + ((stat.isDirectory()) ? "(Folder)" : "(File)") + "\r\n");
3371 + }
3372 + }
3373 + break;
3374 + }
3375 + case 'lsx': { // Show list of files and folders
3376 + response = objToString(getDirectoryInfo(args['_'][0]), 0, ' ', true);
3377 + break;
3378 + }
3379 + case 'lock': { // Lock the current user out of the desktop
3380 + 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'; }
3381 + else { response = 'Not supported on the platform'; }
3382 + break;
3383 + }
3384 + case 'amt': { // Show Intel AMT status
3385 + if (amt != null) {
3386 + amt.getAmtInfo(function (state) {
3387 + var resp = 'Intel AMT not detected.';
3388 + if (state != null) { resp = objToString(state, 0, ' ', true); }
3389 + sendConsoleText(resp, sessionid);
3390 + });
3391 + } else {
3392 + response = 'Intel AMT not detected.';
3393 + }
3394 + break;
3395 + }
3396 + case 'netinfo': { // Show network interface information
3397 + var interfaces = require('os').networkInterfaces();
3398 + response = objToString(interfaces, 0, ' ', true);
3399 + break;
3400 + }
3401 + case 'wakeonlan': { // Send wake-on-lan
3402 + if ((args['_'].length != 1) || (args['_'][0].length != 12)) {
3403 + response = 'Proper usage: wakeonlan [mac], for example "wakeonlan 010203040506".';
3404 + } else {
3405 + var count = sendWakeOnLan(args['_'][0]);
3406 + response = 'Sent wake-on-lan on ' + count + ' interface(s).';
3407 + }
3408 + break;
3409 + }
3410 + case 'sendall': { // Send a message to all consoles on this mesh
3411 + sendConsoleText(args['_'].join(' '));
3412 + break;
3413 + }
3414 + case 'power': { // Execute a power action on this computer
3415 + if (mesh.ExecPowerState == undefined) {
3416 + response = 'Power command not supported on this agent.';
3417 + } else {
3418 + if ((args['_'].length == 0) || isNaN(Number(args['_'][0]))) {
3419 + 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
3420 + } else {
3421 + var r = mesh.ExecPowerState(Number(args['_'][0]), Number(args['_'][1]));
3422 + response = 'Power action executed with return code: ' + r + '.';
3423 + }
3424 + }
3425 + break;
3426 + }
3427 + case 'location': {
3428 + getIpLocationData(function (location) {
3429 + sendConsoleText(objToString({ action: 'iplocation', type: 'publicip', value: location }, 0, ' '));
3430 + });
3431 + break;
3432 + }
3433 + case 'parseuri': {
3434 + response = JSON.stringify(http.parseUri(args['_'][0]));
3435 + break;
3436 + }
3437 + case 'scanwifi': {
3438 + if (wifiScanner != null) {
3439 + var wifiPresent = wifiScanner.hasWireless;
3440 + if (wifiPresent) { response = "Perfoming Wifi scan..."; wifiScanner.Scan(); } else { response = "Wifi absent."; }
3441 + } else { response = "Wifi module not present."; }
3442 + break;
3443 + }
3444 + case 'scanamt': {
3445 + if (amtscanner != null) {
3446 + if (args['_'].length != 1) {
3447 + 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
3448 + } else {
3449 + response = 'Scanning: ' + args['_'][0] + '...';
3450 + amtscanner.scan(args['_'][0], 2000, function (data) {
3451 + if (data.length > 0) {
3452 + var r = '', pstates = ['NotActivated', 'InActivation', 'Activated'];
3453 + for (var i in data) {
3454 + var x = data[i];
3455 + if (r != '') { r += '\r\n'; }
3456 + r += x.address + ' - Intel AMT v' + x.majorVersion + '.' + x.minorVersion;
3457 + if (x.provisioningState < 3) { r += (', ' + pstates[x.provisioningState]); }
3458 + if (x.provisioningState == 2) { r += (', ' + x.openPorts.join(', ')); }
3459 + r += '.';
3460 + }
3461 + } else {
3462 + r = 'No Intel AMT found.';
3463 + }
3464 + sendConsoleText(r);
3465 + });
3466 + }
3467 + } else { response = "Intel AMT scanner module not present."; }
3468 + break;
3469 + }
3470 + case 'modules': {
3471 + response = JSON.stringify(addedModules);
3472 + break;
3473 + }
3474 + case 'listservices': {
3475 + var services = require('service-manager').manager.enumerateService();
3476 + response = JSON.stringify(services, null, 1);
3477 + break;
3478 + }
3479 + case 'getscript': {
3480 + if (args['_'].length != 1) {
3481 + response = "Proper usage: getscript [scriptNumber].";
3482 + } else {
3483 + mesh.SendCommand({ action: 'getScript', type: args['_'][0] });
3484 + }
3485 + break;
3486 + }
3487 + case 'diagnostic':
3488 + {
3489 + if (!mesh.DAIPC.listening) {
3490 + response = 'Unable to bind to Diagnostic IPC, most likely because the path (' + process.cwd() + ') is not on a local file system';
3491 + break;
3492 + }
3493 + var diag = diagnosticAgent_installCheck();
3494 + if (diag) {
3495 + if (args['_'].length == 1 && args['_'][0] == 'uninstall') {
3496 + diagnosticAgent_uninstall();
3497 + response = 'Diagnostic Agent uninstalled';
3498 + }
3499 + else {
3500 + response = 'Diagnostic Agent installed at: ' + diag.appLocation();
3501 + }
3502 + }
3503 + else {
3504 + if (args['_'].length == 1 && args['_'][0] == 'install') {
3505 + diag = diagnosticAgent_installCheck(true);
3506 + if (diag) {
3507 + response = 'Diagnostic agent was installed at: ' + diag.appLocation();
3508 + }
3509 + else {
3510 + response = 'Diagnostic agent installation failed';
3511 + }
3512 + }
3513 + else {
3514 + response = 'Diagnostic Agent Not installed. To install: diagnostic install';
3515 + }
3516 + }
3517 + if (diag) { diag.close(); diag = null; }
3518 + break;
3519 + }
3520 + case 'apf': {
3521 + if (meshCoreObj.intelamt !== null) {
3522 + if (args['_'].length == 1) {
3523 + if (args['_'][0] == 'on') {
3524 + response = "Starting APF tunnel";
3525 + var apfarg = {
3526 + mpsurl: mesh.ServerUrl.replace('agent.ashx', 'apf.ashx'),
3527 + mpsuser: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16),
3528 + mpspass: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16),
3529 + mpskeepalive: 60000,
3530 + clientname: require('os').hostname(),
3531 + clientaddress: '127.0.0.1',
3532 + clientuuid: meshCoreObj.intelamt.uuid,
3533 + conntype: 2 // 0 = CIRA, 1 = Relay, 2 = LMS. The correct value is 2 since we are performing an LMS relay, other values for testing.
3534 + };
3535 + if ((apfarg.clientuuid == null) || (apfarg.clientuuid.length != 36)) {
3536 + response = "Unable to get Intel AMT UUID: " + apfarg.clientuuid;
3537 + } else {
3538 + var tobj = { debug: false };
3539 + apftunnel = require('apfclient')(tobj, apfarg);
3540 + try {
3541 + apftunnel.connect();
3542 + response += "...success";
3543 + } catch (e) {
3544 + response += JSON.stringify(e);
3545 + }
3546 + }
3547 + } else if (args['_'][0] == 'off') {
3548 + response = "Stopping APF tunnel";
3549 + try {
3550 + apftunnel.disconnect();
3551 + response += "..success";
3552 + } catch (e) {
3553 + response += JSON.stringify(e);
3554 + }
3555 + apftunnel = null;
3556 + } else {
3557 + response = "Invalid command.\r\nCmd syntax: apf on|off";
3558 + }
3559 + } else {
3560 + response = "APF tunnel is " + (apftunnel == null ? "off" : "on");
3561 + }
3562 + } else {
3563 + response = "APF tunnel requires Intel AMT";
3564 + }
3565 + break;
3566 + }
3567 + case 'plugin': {
3568 + if (typeof args['_'][0] == 'string') {
3569 + try {
3570 + // Pass off the action to the plugin
3571 + // for plugin creators, you'll want to have a plugindir/modules_meshcore/plugin.js
3572 + // to control the output / actions here.
3573 + response = require(args['_'][0]).consoleaction(args, rights, sessionid, mesh);
3574 + } catch (e) {
3575 + response = "There was an error in the plugin (" + e + ")";
3576 + }
3577 + } else {
3578 + response = "Proper usage: plugin [pluginName] [args].";
3579 + }
3580 + break;
3581 + }
3582 + default: { // This is an unknown command, return an error message
3583 + response = "Unknown command \"" + cmd + "\", type \"help\" for list of avaialble commands.";
3584 + break;
3585 + }
3586 + }
3587 + } catch (e) { response = "Command returned an exception error: " + e; console.log(e); }
3588 + if (response != null) { sendConsoleText(response, sessionid); }
3589 + }
3590 +
3591 + // Send a mesh agent console command
3592 + function sendConsoleText(text, sessionid) {
3593 + if (typeof text == 'object') { text = JSON.stringify(text); }
3594 + require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: text, sessionid: sessionid });
3595 + }
3596 +
3597 + // Called before the process exits
3598 + //process.exit = function (code) { console.log("Exit with code: " + code.toString()); }
3599 +
3600 + // Called when the server connection state changes
3601 + function handleServerConnection(state) {
3602 + meshServerConnectionState = state;
3603 + if (meshServerConnectionState == 0) {
3604 + // Server disconnected
3605 + if (selfInfoUpdateTimer != null) { clearInterval(selfInfoUpdateTimer); selfInfoUpdateTimer = null; }
3606 + lastSelfInfo = null;
3607 + } else {
3608 + // Server connected, send mesh core information
3609 + var oldNodeId = db.Get('OldNodeId');
3610 + if (oldNodeId != null) { mesh.SendCommand({ action: 'mc1migration', oldnodeid: oldNodeId }); }
3611 +
3612 + // Update the server with basic info, logged in users and more.
3613 + mesh.SendCommand(meshCoreObj);
3614 +
3615 + // Send SMBios tables if present
3616 + if (SMBiosTablesRaw != null) { mesh.SendCommand({ action: 'smbios', value: SMBiosTablesRaw }); }
3617 +
3618 + // Update the server on more advanced stuff, like Intel ME and Network Settings
3619 + meInfoStr = null;
3620 + sendPeriodicServerUpdate();
3621 + if (selfInfoUpdateTimer == null) { selfInfoUpdateTimer = setInterval(sendPeriodicServerUpdate, 1200000); } // 20 minutes
3622 +
3623 + // Send any state messages
3624 + if (Object.keys(tunnelUserCount.msg).length > 0) {
3625 + try { mesh.SendCommand({ action: 'sessions', type: 'msg', value: tunnelUserCount.msg }); } catch (e) { }
3626 + }
3627 +
3628 + // Send update of registered applications to the server
3629 + updateRegisteredAppsToServer();
3630 + }
3631 +
3632 + // Send server state update to registered applications
3633 + broadcastToRegisteredApps({ cmd: 'serverstate', value: meshServerConnectionState, url: require('MeshAgent').ConnectedServer });
3634 + }
3635 +
3636 + // Update the server with the latest network interface information
3637 + var sendNetworkUpdateNagleTimer = null;
3638 + function sendNetworkUpdateNagle() { if (sendNetworkUpdateNagleTimer != null) { clearTimeout(sendNetworkUpdateNagleTimer); sendNetworkUpdateNagleTimer = null; } sendNetworkUpdateNagleTimer = setTimeout(sendNetworkUpdate, 5000); }
3639 + function sendNetworkUpdate(force) {
3640 + sendNetworkUpdateNagleTimer = null;
3641 +
3642 + // Update the network interfaces information data
3643 + var netInfo = { netif2: require('os').networkInterfaces() };
3644 + if (netInfo.netif2) {
3645 + netInfo.action = 'netinfo';
3646 + var netInfoStr = JSON.stringify(netInfo);
3647 + if ((force == true) || (clearGatewayMac(netInfoStr) != clearGatewayMac(lastNetworkInfo))) { mesh.SendCommand(netInfo); lastNetworkInfo = netInfoStr; }
3648 + }
3649 + }
3650 +
3651 + // Called periodically to check if we need to send updates to the server
3652 + function sendPeriodicServerUpdate(flags) {
3653 + if (meshServerConnectionState == 0) return; // Not connected to server, do nothing.
3654 + if (!flags) { flags = 0xFFFFFFFF; }
3655 +
3656 + if ((flags & 1) && (amt != null)) {
3657 + // If we have a connected MEI, get Intel ME information
3658 + amt.getAmtInfo(function (meinfo) {
3659 + try {
3660 + if (meinfo == null) return;
3661 + var intelamt = {};
3662 + if (amt != null)
3663 + {
3664 + switch(amt.lmsstate)
3665 + {
3666 + case 0: intelamt.microlms = 'DISABLED'; break;
3667 + case 1: intelamt.microlms = 'CONNECTING'; break;
3668 + case 2: intelamt.microlms = 'CONNECTED'; break;
3669 + default: intelamt.microlms = 'unknown'; break;
3670 + }
3671 + }
3672 + var p = false;
3673 + 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); } }
3674 + if (meinfo.ProvisioningState != null) { intelamt.state = meinfo.ProvisioningState; p = true; }
3675 + if (meinfo.Flags != null) { intelamt.flags = meinfo.Flags; p = true; }
3676 + if (meinfo.OsHostname != null) { intelamt.host = meinfo.OsHostname; p = true; }
3677 + if (meinfo.UUID != null) { intelamt.uuid = meinfo.UUID; p = true; }
3678 + if ((meinfo.ProvisioningState == 0) && (meinfo.net0 != null) && (meinfo.net0.enabled == 1)) { // If not activated, look to see if we have wired net working.
3679 + // Not activated and we have wired ethernet, look for the trusted DNS
3680 + var dns = meinfo.DNS;
3681 + if (dns == null) {
3682 + // Trusted DNS not set, let's look for the OS network DNS suffix
3683 + var interfaces = require('os').networkInterfaces();
3684 + for (var i in interfaces) {
3685 + for (var j in interfaces[i]) {
3686 + if ((interfaces[i][j].mac == mestate.net0.mac) && (interfaces[i][j].fqdn != null) && (interfaces[i][j].fqdn != '')) { dns = interfaces[i][j].fqdn; }
3687 + }
3688 + }
3689 + }
3690 + if (intelamt.dns != dns) { intelamt.dns = dns; p = true; }
3691 + } else { if (intelamt.dns != null) { delete intelamt.dns; p = true; } }
3692 + if (p == true) {
3693 + var meInfoStr = JSON.stringify(intelamt);
3694 + if (meInfoStr != lastMeInfo) {
3695 + meshCoreObj.intelamt = intelamt;
3696 + mesh.SendCommand(meshCoreObj);
3697 + lastMeInfo = meInfoStr;
3698 + }
3699 + }
3700 + } catch (e) { }
3701 + });
3702 + }
3703 +
3704 + if (flags & 2) {
3705 + // Update network information
3706 + sendNetworkUpdateNagle(false);
3707 + }
3708 +
3709 + if ((flags & 4) && (process.platform == 'win32')) {
3710 + // Update anti-virus information
3711 + // Windows Command: "wmic /Namespace:\\root\SecurityCenter2 Path AntiVirusProduct get /FORMAT:CSV"
3712 + var av, pr;
3713 + try { av = require('win-info').av(); } catch (e) { av = null; } // Antivirus
3714 + //if (process.platform == 'win32') { try { pr = require('win-info').pendingReboot(); } catch (e) { pr = null; } } // Pending reboot
3715 + if ((meshCoreObj.av == null) || (JSON.stringify(meshCoreObj.av) != JSON.stringify(av))) { meshCoreObj.av = av; mesh.SendCommand(meshCoreObj); }
3716 + }
3717 + }
3718 +
3719 + // Starting function
3720 + obj.start = function () {
3721 + // Setup the mesh agent event handlers
3722 + mesh.AddCommandHandler(handleServerCommand);
3723 + mesh.AddConnectHandler(handleServerConnection);
3724 +
3725 + // Parse input arguments
3726 + //var args = parseArgs(process.argv);
3727 + //console.log(args);
3728 +
3729 + //resetMicroLms();
3730 +
3731 + // Setup logged in user monitoring (THIS IS BROKEN IN WIN7)
3732 + try {
3733 + var userSession = require('user-sessions');
3734 + userSession.on('changed', function onUserSessionChanged() {
3735 + userSession.enumerateUsers().then(function (users) {
3736 + var u = [], a = users.Active;
3737 + for (var i = 0; i < a.length; i++) {
3738 + var un = a[i].Domain ? (a[i].Domain + '\\' + a[i].Username) : (a[i].Username);
3739 + if (u.indexOf(un) == -1) { u.push(un); } // Only push users in the list once.
3740 + }
3741 + meshCoreObj.users = u;
3742 + mesh.SendCommand(meshCoreObj);
3743 + });
3744 + });
3745 + userSession.emit('changed');
3746 + //userSession.on('locked', function (user) { sendConsoleText('[' + (user.Domain ? user.Domain + '\\' : '') + user.Username + '] has LOCKED the desktop'); });
3747 + //userSession.on('unlocked', function (user) { sendConsoleText('[' + (user.Domain ? user.Domain + '\\' : '') + user.Username + '] has UNLOCKED the desktop'); });
3748 + } catch (e) { }
3749 + }
3750 +
3751 + obj.stop = function () {
3752 + mesh.AddCommandHandler(null);
3753 + mesh.AddConnectHandler(null);
3754 + }
3755 +
3756 + function onWebSocketClosed() { sendConsoleText("WebSocket #" + this.httprequest.index + " closed.", this.httprequest.sessionid); delete consoleWebSockets[this.httprequest.index]; }
3757 + function onWebSocketData(data) { sendConsoleText("Got WebSocket #" + this.httprequest.index + " data: " + data, this.httprequest.sessionid); }
3758 + function onWebSocketSendOk() { sendConsoleText("WebSocket #" + this.index + " SendOK.", this.sessionid); }
3759 +
3760 + function onWebSocketUpgrade(response, s, head) {
3761 + sendConsoleText("WebSocket #" + this.index + " connected.", this.sessionid);
3762 + this.s = s;
3763 + s.httprequest = this;
3764 + s.end = onWebSocketClosed;
3765 + s.data = onWebSocketData;
3766 + }
3767 +
3768 + return obj;
3769 +}
3770 +
3771 +//
3772 +// Module startup
3773 +//
3774 +
3775 +try {
3776 + var xexports = null, mainMeshCore = null;
3777 + try { xexports = module.exports; } catch (e) { }
3778 +
3779 + if (xexports != null) {
3780 + // If we are running within NodeJS, export the core
3781 + module.exports.createMeshCore = createMeshCore;
3782 + } else {
3783 + // If we are not running in NodeJS, launch the core
3784 + mainMeshCore = createMeshCore();
3785 + mainMeshCore.start(null);
3786 + }
3787 +} catch (e) {
3788 + require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: "uncaughtException2: " + ex });
3789 +}
agents/meshcore.js
+109 -205
@@ -153,7 +153,7 @@ function createMeshCore(agent) {
153 this.end(); // TODO: Must call end() on self to close the named pipe correctly.
154 this.parent._daipc = null;
155 if (this._registered != null) { try { mesh.SendCommand({ action: 'sessions', type: 'app', value: {} }); } catch (e) { } }
156 - });
156 + });
157 c.on('data', function (chunk) {
158 if (chunk.length < 4) { this.unshift(chunk); return; }
159 var len = chunk.readUInt32LE(0);
@@ -195,8 +195,7 @@ function createMeshCore(agent) {
195 this._send(data);
196 break;
197 case 'descriptors':
198 - require('ChainViewer').getSnapshot().then(function (f)
199 - {
198 + require('ChainViewer').getSnapshot().then(function (f) {
199 this.tag.payload.result = f;
200 this.tag.ipc._send(this.tag.payload);
201 }).parentPromise.tag = { ipc: this, payload: data };
@@ -406,16 +405,12 @@ function createMeshCore(agent) {
405 childProcess = require('child_process');
406 if (mesh.hasKVM == 1) { // if the agent is compiled with KVM support
407 // Check if this computer supports a desktop
409 - try
410 - {
411 - if ((process.platform == 'win32') || (process.platform == 'darwin') || (require('monitor-info').kvm_x11_support))
412 - {
408 + try {
409 + if ((process.platform == 'win32') || (process.platform == 'darwin') || (require('monitor-info').kvm_x11_support)) {
410 meshCoreObj.caps |= 1;
411 }
415 - else if(process.platform == 'linux' || process.platform == 'freebsd')
416 - {
417 - require('monitor-info').on('kvmSupportDetected', function (value)
418 - {
412 + else if (process.platform == 'linux' || process.platform == 'freebsd') {
413 + require('monitor-info').on('kvmSupportDetected', function (value) {
414 meshCoreObj.caps |= 1;
415 mesh.SendCommand(meshCoreObj);
416 });
@@ -478,20 +473,16 @@ function createMeshCore(agent) {
473 if (mesh.isControlChannelConnected) { mesh.SendCommand({ action: 'smbios', value: SMBiosTablesRaw }); }
474
475 // If SMBios tables say that Intel AMT is present, try to connect MEI
481 - if (SMBiosTables.amtInfo && (SMBiosTables.amtInfo.AMT == true))
482 - {
476 + if (SMBiosTables.amtInfo && (SMBiosTables.amtInfo.AMT == true)) {
477 var amtmodule = require('amt-manage');
478 amt = new amtmodule(mesh, db, false);
485 - amt.on('portBinding_LMS', function (map)
486 - {
479 + amt.on('portBinding_LMS', function (map) {
480 var j = { action: 'lmsinfo', value: { ports: map.keys() } };
481 mesh.SendCommand(j);
482 });
490 - amt.on('stateChange_LMS', function (v)
491 - {
483 + amt.on('stateChange_LMS', function (v) {
484 if (!meshCoreObj.intelamt) { meshCoreObj.intelamt = {}; }
493 - switch(v)
494 - {
485 + switch (v) {
486 case 0:
487 meshCoreObj.intelamt.microlms = 'DISABLED';
488 break;
@@ -1198,11 +1189,9 @@ function createMeshCore(agent) {
1189 s.tunnel = this;
1190 s.descriptorMetadata = "MeshAgent_relayTunnel";
1191
1201 - if (require('MeshAgent').idleTimeout != null)
1202 - {
1192 + if (require('MeshAgent').idleTimeout != null) {
1193 s.setTimeout(require('MeshAgent').idleTimeout * 1000);
1204 - s.on('timeout', function ()
1205 - {
1194 + s.on('timeout', function () {
1195 this.ping();
1196 this.setTimeout(require('MeshAgent').idleTimeout * 1000);
1197 });
@@ -1272,7 +1261,7 @@ function createMeshCore(agent) {
1261 // Called when we get data from the server for a TCP relay (We have to skip the first received 'c' and pipe the rest)
1262 function onTcpRelayServerTunnelData(data) {
1263 if (this.first == true) {
1275 - this.first = false;
1264 + this.first = false;
1265 this.pipe(this.tcprelay, { dataTypeSkip: 1 }); // Pipe Server --> Target (don't pipe text type websocket frames)
1266 }
1267 }
@@ -1362,8 +1351,7 @@ function createMeshCore(agent) {
1351 // Check if this is a relay connection
1352 if ((data == 'c') || (data == 'cr')) { this.httprequest.state = 1; /*sendConsoleText("Tunnel #" + this.httprequest.index + " now active", this.httprequest.sessionid);*/ }
1353 }
1365 - else
1366 - {
1354 + else {
1355 // Handle tunnel data
1356 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
1357 // Take a look at the protocol
@@ -1388,8 +1376,7 @@ function createMeshCore(agent) {
1376 this.write(JSON.stringify({ op: 'cancel' }));
1377 }
1378 }
1391 - else if ((this.httprequest.protocol == 1) || (this.httprequest.protocol == 6) || (this.httprequest.protocol == 8) || (this.httprequest.protocol == 9))
1392 - {
1379 + else if ((this.httprequest.protocol == 1) || (this.httprequest.protocol == 6) || (this.httprequest.protocol == 8) || (this.httprequest.protocol == 9)) {
1380 //
1381 // Remote Terminal
1382 //
@@ -1405,10 +1392,8 @@ function createMeshCore(agent) {
1392
1393 this.descriptorMetadata = "Remote Terminal";
1394
1408 - if (process.platform == 'win32')
1409 - {
1410 - if (!require('win-terminal').PowerShellCapable() && (this.httprequest.protocol == 6 || this.httprequest.protocol == 9))
1411 - {
1395 + if (process.platform == 'win32') {
1396 + if (!require('win-terminal').PowerShellCapable() && (this.httprequest.protocol == 6 || this.httprequest.protocol == 9)) {
1397 this.httprequest.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: 'PowerShell is not supported on this version of windows', msgid: 1 }));
1398 this.httprequest.s.end();
1399 return;
@@ -1420,27 +1405,23 @@ function createMeshCore(agent) {
1405 this.httprequest.tpromise.that = this;
1406 this.httprequest.tpromise.httprequest = this.httprequest;
1407
1423 - this.end = function ()
1424 - {
1408 + this.end = function () {
1409 if (this.httprequest.tpromise._consent) { this.httprequest.tpromise._consent.close(); }
1410 if (this.httprequest.connectionPromise) { this.httprequest.connectionPromise._rej('Closed'); }
1411
1412 // Remove the terminal session to the count to update the server
1429 - if (this.httprequest.userid != null)
1430 - {
1413 + if (this.httprequest.userid != null) {
1414 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]; } }
1415 try { mesh.SendCommand({ action: 'sessions', type: 'terminal', value: tunnelUserCount.terminal }); } catch (e) { }
1416 }
1417
1435 - if (process.platform == 'win32')
1436 - {
1418 + if (process.platform == 'win32') {
1419 // Unpipe the web socket
1420 this.unpipe(this.httprequest._term);
1421 if (this.httprequest._term) { this.httprequest._term.unpipe(this); }
1422
1423 // Unpipe the WebRTC channel if needed (This will also be done when the WebRTC channel ends).
1442 - if (this.rtcchannel)
1443 - {
1424 + if (this.rtcchannel) {
1425 this.rtcchannel.unpipe(this.httprequest._term);
1426 if (this.httprequest._term) { this.httprequest._term.unpipe(this.rtcchannel); }
1427 }
@@ -1452,8 +1433,7 @@ function createMeshCore(agent) {
1433 };
1434
1435 // Perform User-Consent if needed.
1455 - if (this.httprequest.consent && (this.httprequest.consent & 16))
1456 - {
1436 + if (this.httprequest.consent && (this.httprequest.consent & 16)) {
1437 this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 }));
1438 var consentMessage = this.httprequest.username + " requesting remote terminal access. Grant access?", consentTitle = 'MeshCentral';
1439 if (this.httprequest.soptions != null) {
@@ -1463,106 +1443,85 @@ function createMeshCore(agent) {
1443 this.httprequest.tpromise._consent = require('message-box').create(consentTitle, consentMessage, 30);
1444 this.httprequest.tpromise._consent.retPromise = this.httprequest.tpromise;
1445 this.httprequest.tpromise._consent.then(
1466 - function ()
1467 - {
1446 + function () {
1447 // Success
1448 MeshServerLogEx(27, null, "Local user accepted remote terminal request (" + this.retPromise.httprequest.remoteaddr + ")", this.retPromise.that.httprequest);
1449 this.retPromise.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null, msgid: 0 }));
1450 this.retPromise._consent = null;
1451 this.retPromise._res();
1452 },
1474 - function (e)
1475 - {
1453 + function (e) {
1454 // Denied
1455 MeshServerLogEx(28, null, "Local user rejected remote terminal request (" + this.retPromise.that.httprequest.remoteaddr + ")", this.retPromise.that.httprequest);
1456 this.retPromise.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
1457 this.retPromise._rej(e.toString());
1458 });
1459 }
1482 - else
1483 - {
1460 + else {
1461 // User-Consent is not required, so just resolve this promise
1462 this.httprequest.tpromise._res();
1463 }
1464
1465
1466 this.httprequest.tpromise.then(
1490 - function ()
1491 - {
1467 + function () {
1468 this.httprequest.connectionPromise = new prom(function (res, rej) { this._res = res; this._rej = rej; });
1469 this.httprequest.connectionPromise.ws = this.that;
1470
1471 // Start Terminal
1496 - if(process.platform == 'win32')
1497 - {
1498 - try
1499 - {
1472 + if (process.platform == 'win32') {
1473 + try {
1474 var cols = 80, rows = 25;
1501 - if (this.httprequest.xoptions)
1502 - {
1475 + if (this.httprequest.xoptions) {
1476 if (this.httprequest.xoptions.rows) { rows = this.httprequest.xoptions.rows; }
1477 if (this.httprequest.xoptions.cols) { cols = this.httprequest.xoptions.cols; }
1478 }
1479
1507 - if ((this.httprequest.protocol == 1) || (this.httprequest.protocol == 6))
1508 - {
1480 + if ((this.httprequest.protocol == 1) || (this.httprequest.protocol == 6)) {
1481 // Admin Terminal
1510 - if (require('win-virtual-terminal').supported)
1511 - {
1482 + if (require('win-virtual-terminal').supported) {
1483 // ConPTY PseudoTerminal
1484 // this.httprequest._term = require('win-virtual-terminal')[this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'](80, 25);
1485
1486 // The above line is commented out, because there is a bug with ClosePseudoConsole() API, so this is the workaround
1487 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] } });
1488 this.httprequest._dispatcher.httprequest = this.httprequest;
1518 - this.httprequest._dispatcher.on('connection', function (c)
1519 - {
1520 - if (this.httprequest.connectionPromise.completed)
1521 - {
1489 + this.httprequest._dispatcher.on('connection', function (c) {
1490 + if (this.httprequest.connectionPromise.completed) {
1491 c.end();
1492 }
1524 - else
1525 - {
1493 + else {
1494 this.httprequest.connectionPromise._res(c);
1495 }
1496 });
1497 }
1530 - else
1531 - {
1498 + else {
1499 // Legacy Terminal
1500 this.httprequest.connectionPromise._res(require('win-terminal')[this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'](cols, rows));
1501 }
1502 }
1536 - else
1537 - {
1503 + else {
1504 // Logged in user
1505 var userPromise = require('user-sessions').enumerateUsers();
1506 userPromise.that = this;
1541 - userPromise.then(function (u)
1542 - {
1507 + userPromise.then(function (u) {
1508 var that = this.that;
1544 - if (u.Active.length > 0)
1545 - {
1509 + if (u.Active.length > 0) {
1510 var username = u.Active[0].Username;
1547 - if (require('win-virtual-terminal').supported)
1548 - {
1511 + if (require('win-virtual-terminal').supported) {
1512 // ConPTY PseudoTerminal
1513 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] } });
1514 }
1552 - else
1553 - {
1515 + else {
1516 // Legacy Terminal
1517 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] } });
1518 }
1519 that.httprequest._dispatcher.ws = that;
1558 - that.httprequest._dispatcher.on('connection', function (c)
1559 - {
1560 - if (this.ws.httprequest.connectionPromise.completed)
1561 - {
1520 + that.httprequest._dispatcher.on('connection', function (c) {
1521 + if (this.ws.httprequest.connectionPromise.completed) {
1522 c.end();
1523 }
1564 - else
1565 - {
1524 + else {
1525 this.ws.httprequest.connectionPromise._res(c);
1526 }
1527 });
@@ -1570,70 +1529,58 @@ function createMeshCore(agent) {
1529 });
1530 }
1531 }
1573 - catch (e)
1574 - {
1532 + catch (e) {
1533 this.httprequest.connectionPromise._rej('Failed to start remote terminal session, ' + e.toString());
1534 }
1535 }
1578 - else
1579 - {
1580 - try
1581 - {
1536 + else {
1537 + try {
1538 var bash = fs.existsSync('/bin/bash') ? '/bin/bash' : false;
1539 var sh = fs.existsSync('/bin/sh') ? '/bin/sh' : false;
1540 var login = process.platform == 'linux' ? '/bin/login' : '/usr/bin/login';
1541
1542 var env = { HISTCONTROL: 'ignoreboth' };
1587 - if (this.httprequest.xoptions)
1588 - {
1543 + if (this.httprequest.xoptions) {
1544 if (this.httprequest.xoptions.rows) { env.LINES = ('' + this.httprequest.xoptions.rows); }
1545 if (this.httprequest.xoptions.cols) { env.COLUMNS = ('' + this.httprequest.xoptions.cols); }
1546 }
1547 var options = { type: childProcess.SpawnTypes.TERM, uid: (this.httprequest.protocol == 8) ? require('user-sessions').consoleUid() : null, env: env };
1593 - if (this.httprequest.xoptions && this.httprequest.xoptions.requireLogin)
1594 - {
1548 + if (this.httprequest.xoptions && this.httprequest.xoptions.requireLogin) {
1549 if (!require('fs').existsSync(login)) { throw ('Unable to spawn login process'); }
1550 this.httprequest.connectionPromise._res(childProcess.execFile(login, ['login'], options)); // Start login shell
1551 }
1598 - else if (bash)
1599 - {
1552 + else if (bash) {
1553 var p = childProcess.execFile(bash, ['bash'], options); // Start bash
1554 // Spaces at the beginning of lines are needed to hide commands from the command history
1555 if (process.platform == 'linux') { p.stdin.write(' alias ls=\'ls --color=auto\';clear\n'); }
1556 this.httprequest.connectionPromise._res(p);
1557 }
1605 - else if (sh)
1606 - {
1558 + else if (sh) {
1559 var p = childProcess.execFile(sh, ['sh'], options); // Start sh
1560 // Spaces at the beginning of lines are needed to hide commands from the command history
1561 if (process.platform == 'linux') { p.stdin.write(' alias ls=\'ls --color=auto\';clear\n'); }
1562 this.httprequest.connectionPromise._res(p);
1563 }
1612 - else
1613 - {
1564 + else {
1565 this.httprequest.connectionPromise._rej('Failed to start remote terminal session, no shell found');
1566 }
1567 }
1617 - catch (e)
1618 - {
1568 + catch (e) {
1569 this.httprequest.connectionPromise._rej('Failed to start remote terminal session, ' + e.toString());
1570 }
1571 }
1572
1573 this.httprequest.connectionPromise.then(
1624 - function (term)
1625 - {
1574 + function (term) {
1575 // SUCCESS
1576 var stdoutstream;
1577 var stdinstream;
1629 - if (process.platform == 'win32')
1630 - {
1578 + if (process.platform == 'win32') {
1579 this.ws.httprequest._term = term;
1580 this.ws.httprequest._term.tunnel = this.ws;
1581 stdoutstream = stdinstream = term;
1582 }
1635 - else
1636 - {
1583 + else {
1584 term.descriptorMetadata = 'Remote Terminal';
1585 this.ws.httprequest.process = term;
1586 this.ws.httprequest.process.tunnel = this.ws;
@@ -1652,15 +1599,13 @@ function createMeshCore(agent) {
1599 this.ws.pipe(stdinstream, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
1600
1601 // Add the terminal session to the count to update the server
1655 - if (this.ws.httprequest.userid != null)
1656 - {
1602 + if (this.ws.httprequest.userid != null) {
1603 if (tunnelUserCount.terminal[this.ws.httprequest.userid] == null) { tunnelUserCount.terminal[this.ws.httprequest.userid] = 1; } else { tunnelUserCount.terminal[this.ws.httprequest.userid]++; }
1604 try { mesh.SendCommand({ action: 'sessions', type: 'terminal', value: tunnelUserCount.terminal }); } catch (e) { }
1605 }
1606
1607 // Toast Notification, if required
1662 - if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 2))
1663 - {
1608 + if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 2)) {
1609 // User Notifications is required
1610 var notifyMessage = this.ws.httprequest.username + " started a remote terminal session.", notifyTitle = "MeshCentral";
1611 if (this.ws.httprequest.soptions != null) {
@@ -1670,22 +1615,19 @@ function createMeshCore(agent) {
1615 try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (e) { }
1616 }
1617 },
1673 - function (e)
1674 - {
1618 + function (e) {
1619 // FAILED to connect terminal
1620 this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
1621 this.ws.end();
1622 });
1623 },
1680 - function (e)
1681 - {
1624 + function (e) {
1625 // DO NOT start terminal
1626 this.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
1627 this.that.end();
1685 - });
1628 + });
1629 }
1687 - else if (this.httprequest.protocol == 2)
1688 - {
1630 + else if (this.httprequest.protocol == 2) {
1631 //
1632 // Remote KVM
1633 //
@@ -1739,22 +1681,19 @@ function createMeshCore(agent) {
1681 }
1682
1683 // Unpipe the web socket
1742 - try
1743 - {
1684 + try {
1685 this.unpipe(this.httprequest.desktop.kvm);
1686 this.httprequest.desktop.kvm.unpipe(this);
1687 }
1747 - catch(e) { }
1688 + catch (e) { }
1689
1690 // Unpipe the WebRTC channel if needed (This will also be done when the WebRTC channel ends).
1750 - if (this.rtcchannel)
1751 - {
1752 - try
1753 - {
1691 + if (this.rtcchannel) {
1692 + try {
1693 this.rtcchannel.unpipe(this.httprequest.desktop.kvm);
1694 this.httprequest.desktop.kvm.unpipe(this.rtcchannel);
1695 }
1757 - catch(e) { }
1696 + catch (e) { }
1697 }
1698
1699 // Place wallpaper back if needed
@@ -1813,8 +1752,7 @@ function createMeshCore(agent) {
1752 }
1753
1754 // Perform notification if needed. Toast messages may not be supported on all platforms.
1816 - if (this.httprequest.consent && (this.httprequest.consent & 8))
1817 - {
1755 + if (this.httprequest.consent && (this.httprequest.consent & 8)) {
1756 // User Consent Prompt is required
1757 // Send a console message back using the console channel, "\n" is supported.
1758 this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 }));
@@ -1827,10 +1765,9 @@ function createMeshCore(agent) {
1765 pr.ws = this;
1766 this.pause();
1767 this._consentpromise = pr;
1830 - this.prependOnceListener('end', function () { if (this._consentpromise && this._consentpromise.close) { this._consentpromise.close(); }});
1768 + this.prependOnceListener('end', function () { if (this._consentpromise && this._consentpromise.close) { this._consentpromise.close(); } });
1769 pr.then(
1832 - function ()
1833 - {
1770 + function () {
1771 // Success
1772 this.ws._consentpromise = null;
1773 MeshServerLogEx(30, null, "Starting remote desktop after local user accepted (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
@@ -1873,8 +1810,7 @@ function createMeshCore(agent) {
1810 this.ws.httprequest.desktop.kvm.pipe(this.ws, { dataTypeSkip: 1 });
1811 this.ws.resume();
1812 },
1876 - function (e)
1877 - {
1813 + function (e) {
1814 // User Consent Denied/Failed
1815 this.ws._consentpromise = null;
1816 MeshServerLogEx(34, null, "Failed to start remote desktop after local user rejected (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
@@ -1971,8 +1907,7 @@ function createMeshCore(agent) {
1907 this._consentpromise = pr;
1908 this.prependOnceListener('end', function () { if (this._consentpromise && this._consentpromise.close) { this._consentpromise.close(); } });
1909 pr.then(
1974 - function ()
1975 - {
1910 + function () {
1911 // Success
1912 this.ws._consentpromise = null;
1913 MeshServerLogEx(40, null, "Starting remote files after local user accepted (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
@@ -1988,8 +1923,7 @@ function createMeshCore(agent) {
1923 }
1924 this.ws.resume();
1925 },
1991 - function (e)
1992 - {
1926 + function (e) {
1927 // User Consent Denied/Failed
1928 this.ws._consentpromise = null;
1929 MeshServerLogEx(41, null, "Failed to start remote files after local user rejected (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
@@ -2355,13 +2289,11 @@ function createMeshCore(agent) {
2289 }
2290 case 'termsize': {
2291 // Indicates a change in terminal size
2358 - if (process.platform == 'win32')
2359 - {
2292 + if (process.platform == 'win32') {
2293 if (ws.httprequest._dispatcher == null) return;
2294 //sendConsoleText('Win32-TermSize: ' + obj.cols + 'x' + obj.rows);
2295 if (ws.httprequest._dispatcher.invoke) { ws.httprequest._dispatcher.invoke('resizeTerminal', [obj.cols, obj.rows]); }
2363 - } else
2364 - {
2296 + } else {
2297 if (ws.httprequest.process == null || ws.httprequest.process.pty == 0) return;
2298 //sendConsoleText('Linux Resize: ' + obj.cols + 'x' + obj.rows);
2299
@@ -2442,10 +2374,8 @@ function createMeshCore(agent) {
2374 this.websocket.rtcchannel.on('end', function () {
2375 // The WebRTC channel closed, unpipe the KVM now. This is also done when the web socket closes.
2376 //sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC data channel closed');
2445 - if (this.websocket.desktop && this.websocket.desktop.kvm)
2446 - {
2447 - try
2448 - {
2377 + if (this.websocket.desktop && this.websocket.desktop.kvm) {
2378 + try {
2379 this.unpipe(this.websocket.desktop.kvm);
2380 this.websocket.httprequest.desktop.kvm.unpipe(this);
2381 }
@@ -2522,7 +2452,7 @@ function createMeshCore(agent) {
2452 case 'help': { // Displays available commands
2453 var fin = '', f = '', availcommands = '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';
2454 if (process.platform == 'win32') { availcommands += ',safemode,wpfhwacceleration,uac'; }
2525 - if (process.platform != 'freebsd') { availcommands += ',vm';}
2455 + if (process.platform != 'freebsd') { availcommands += ',vm'; }
2456 if (require('MeshAgent').maxKvmTileSize != null) { availcommands += ',kvmmode'; }
2457 try { require('zip-reader'); availcommands += ',zip,unzip'; } catch (e) { }
2458
@@ -2566,8 +2496,7 @@ function createMeshCore(agent) {
2496 if (args['_'].length != 1) {
2497 response = "Proper usage: coredump on|off|status|clear"; // Display usage
2498 } else {
2569 - switch (args['_'][0].toLowerCase())
2570 - {
2499 + switch (args['_'][0].toLowerCase()) {
2500 case 'on':
2501 process.coreDumpLocation = (process.platform == 'win32') ? (process.execPath.replace('.exe', '.dmp')) : (process.execPath + '.dmp');
2502 response = 'coredump is now on';
@@ -2597,25 +2526,20 @@ function createMeshCore(agent) {
2526 }
2527 break;
2528 case 'service':
2600 - if (args['_'].length != 1)
2601 - {
2529 + if (args['_'].length != 1) {
2530 response = "Proper usage: service status|restart"; // Display usage
2531 }
2604 - else
2605 - {
2532 + else {
2533 var s = require('service-manager').manager.getService(process.platform == 'win32' ? 'Mesh Agent' : 'meshagent');
2607 - switch(args['_'][0].toLowerCase())
2608 - {
2534 + switch (args['_'][0].toLowerCase()) {
2535 case 'status':
2536 response = 'Service ' + (s.isRunning() ? (s.isMe() ? '[SELF]' : '[RUNNING]') : ('[NOT RUNNING]'));
2537 break;
2538 case 'restart':
2613 - if (s.isMe())
2614 - {
2539 + if (s.isMe()) {
2540 s.restart();
2541 }
2617 - else
2618 - {
2542 + else {
2543 response = 'Restarting another agent instance is not allowed';
2544 }
2545 break;
@@ -2672,51 +2596,41 @@ function createMeshCore(agent) {
2596 break;
2597 case 'fdcount':
2598 require('DescriptorEvents').getDescriptorCount().then(
2675 - function (c)
2676 - {
2599 + function (c) {
2600 sendConsoleText('Descriptor Count: ' + c, this.sessionid);
2678 - }, function (e)
2679 - {
2601 + }, function (e) {
2602 sendConsoleText('Error fetching descriptor count: ' + e, this.sessionid);
2603 }).parentPromise.sessionid = sessionid;
2604 break;
2605 case 'uac':
2684 - if (process.platform != 'win32')
2685 - {
2606 + if (process.platform != 'win32') {
2607 response = 'Unknown command "uac", type "help" for list of avaialble commands.';
2608 break;
2609 }
2689 - if (args['_'].length != 1)
2690 - {
2610 + if (args['_'].length != 1) {
2611 response = 'Proper usage: uac [get|interactive|secure]';
2612 }
2693 - else
2694 - {
2695 - switch(args['_'][0].toUpperCase())
2696 - {
2613 + else {
2614 + switch (args['_'][0].toUpperCase()) {
2615 case 'GET':
2616 var secd = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System', 'PromptOnSecureDesktop');
2617 response = "UAC mode: " + (secd == 0 ? "Interactive Desktop" : "Secure Desktop");
2618 break;
2619 case 'INTERACTIVE':
2702 - try
2703 - {
2620 + try {
2621 require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System', 'PromptOnSecureDesktop', 0);
2622 response = 'UAC mode changed to: Interactive Desktop';
2623 }
2707 - catch (e)
2708 - {
2624 + catch (e) {
2625 response = "Unable to change UAC Mode";
2626 }
2627 break;
2628 case 'SECURE':
2713 - try
2714 - {
2629 + try {
2630 require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System', 'PromptOnSecureDesktop', 1);
2631 response = 'UAC mode changed to: Secure Desktop';
2632 }
2718 - catch(e)
2719 - {
2633 + catch (e) {
2634 response = "Unable to change UAC Mode";
2635 }
2636 break;
@@ -2726,45 +2640,37 @@ function createMeshCore(agent) {
2640 }
2641 }
2642 break;
2729 - case 'vm':
2730 - response = 'Virtual Machine = ' + require('identifiers').isVM();
2731 - break;
2643 + case 'vm':
2644 + response = 'Virtual Machine = ' + require('identifiers').isVM();
2645 + break;
2646 case 'startupoptions':
2647 response = JSON.stringify(require('MeshAgent').getStartupOptions());
2648 break;
2649 case 'kvmmode':
2736 - if (require('MeshAgent').maxKvmTileSize == null)
2737 - {
2650 + if (require('MeshAgent').maxKvmTileSize == null) {
2651 response = "Unknown command \"kvmmode\", type \"help\" for list of avaialble commands.";
2652 }
2740 - else
2741 - {
2742 - if(require('MeshAgent').maxKvmTileSize == 0)
2743 - {
2653 + else {
2654 + if (require('MeshAgent').maxKvmTileSize == 0) {
2655 response = 'KVM Mode: Full JUMBO';
2656 }
2746 - else
2747 - {
2657 + else {
2658 response = 'KVM Mode: ' + (require('MeshAgent').maxKvmTileSize <= 65500 ? 'NO JUMBO' : 'Partial JUMBO');
2749 - response += (', TileLimit: ' + (require('MeshAgent').maxKvmTileSize < 1024 ? (require('MeshAgent').maxKvmTileSize + ' bytes') : (Math.round(require('MeshAgent').maxKvmTileSize/1024) + ' Kbytes')));
2659 + response += (', TileLimit: ' + (require('MeshAgent').maxKvmTileSize < 1024 ? (require('MeshAgent').maxKvmTileSize + ' bytes') : (Math.round(require('MeshAgent').maxKvmTileSize / 1024) + ' Kbytes')));
2660 }
2661 }
2662 break;
2663 case 'alert':
2754 - if (args['_'].length == 0)
2755 - {
2664 + if (args['_'].length == 0) {
2665 response = "Proper usage: alert TITLE, CAPTION [, TIMEOUT]"; // Display usage
2666 }
2758 - else
2759 - {
2667 + else {
2668 var p = args['_'].join(' ').split(',');
2761 - if(p.length<2)
2762 - {
2669 + if (p.length < 2) {
2670 response = "Proper usage: alert TITLE, CAPTION [, TIMEOUT]"; // Display usage
2671 }
2765 - else
2766 - {
2767 - this._alert = require('message-box').create(p[0], p[1], p.length==3?parseInt(p[2]):9999,1);
2672 + else {
2673 + this._alert = require('message-box').create(p[0], p[1], p.length == 3 ? parseInt(p[2]) : 9999, 1);
2674 }
2675 }
2676 break;
@@ -3137,7 +3043,7 @@ function createMeshCore(agent) {
3043 break;
3044 }
3045 case 'info': { // Return information about the agent and agent core module
3140 - 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 + '.';
3046 + 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 + '.';
3047 if (amt != null) { response += '\r\nBuilt-in LMS: ' + ['Disabled', 'Connecting..', 'Connected'][amt.lmsstate] + '.'; }
3048 if (meshCoreObj.osdesc) { response += '\r\nOS: ' + meshCoreObj.osdesc + '.'; }
3049 response += '\r\nModules: ' + addedModules.join(', ') + '.';
@@ -3641,10 +3547,8 @@ function createMeshCore(agent) {
3547 try {
3548 if (meinfo == null) return;
3549 var intelamt = {};
3644 - if (amt != null)
3645 - {
3646 - switch(amt.lmsstate)
3647 - {
3550 + if (amt != null) {
3551 + switch (amt.lmsstate) {
3552 case 0: intelamt.microlms = 'DISABLED'; break;
3553 case 1: intelamt.microlms = 'CONNECTING'; break;
3554 case 2: intelamt.microlms = 'CONNECTED'; break;