EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
meshcore.js
Go to the documentation of this file.
1/*
2Copyright 2018-2022 Intel Corporation
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17process.on('uncaughtException', function (ex) {
18 require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: "uncaughtException1: " + ex });
19});
20if (process.platform == 'win32' && require('user-sessions').getDomain == null) {
21 require('user-sessions').getDomain = function getDomain(uid) {
22 return (this.getSessionAttribute(uid, this.InfoClass.WTSDomainName));
23 };
24}
25
26var promise = require('promise');
27
28// Mesh Rights
29var MNG_ERROR = 65;
30var MESHRIGHT_EDITMESH = 1;
31var MESHRIGHT_MANAGEUSERS = 2;
32var MESHRIGHT_MANAGECOMPUTERS = 4;
33var MESHRIGHT_REMOTECONTROL = 8;
34var MESHRIGHT_AGENTCONSOLE = 16;
35var MESHRIGHT_SERVERFILES = 32;
36var MESHRIGHT_WAKEDEVICE = 64;
37var MESHRIGHT_SETNOTES = 128;
38var MESHRIGHT_REMOTEVIEW = 256; // Remote View Only
39var MESHRIGHT_NOTERMINAL = 512;
40var MESHRIGHT_NOFILES = 1024;
41var MESHRIGHT_NOAMT = 2048;
42var MESHRIGHT_LIMITEDINPUT = 4096;
43var MESHRIGHT_LIMITEVENTS = 8192;
44var MESHRIGHT_CHATNOTIFY = 16384;
45var MESHRIGHT_UNINSTALL = 32768;
46var MESHRIGHT_NODESKTOP = 65536;
47
48var pendingSetClip = false; // This is a temporary hack to prevent multiple setclips at the same time to stop the agent from crashing.
49
50//
51// This is a helper function used by the 32 bit Windows Agent, when running on 64 bit windows. It will check if the agent is already patched for this
52// and will use this helper if it is not. This helper will inject 'sysnative' into the results when calling readdirSync() on %windir%.
53//
54function __readdirSync_fix(path)
55{
56 var sysnative = false;
57 pathstr = require('fs')._fixwinpath(path);
58 if (pathstr.split('\\*').join('').toLowerCase() == process.env['windir'].toLowerCase()) { sysnative = true; }
59
60 var ret = require('fs').__readdirSync_old(path);
61 if (sysnative) { ret.push('sysnative'); }
62 return (ret);
63}
64
65if (process.platform == 'win32' && require('_GenericMarshal').PointerSize == 4 && require('os').arch() == 'x64')
66{
67 if (require('fs').readdirSync.version == null)
68 {
69 //
70 // 32 Bit Windows Agent on 64 bit Windows has not been patched for sysnative issue, so lets use our own solution
71 //
72 require('fs').__readdirSync_old = require('fs').readdirSync;
73 require('fs').readdirSync = __readdirSync_fix;
74 }
75}
76
77function bcdOK() {
78 if (process.platform != 'win32') { return (false); }
79 if (require('os').arch() == 'x64') {
80 return (require('_GenericMarshal').PointerSize == 8);
81 }
82 return (true);
83}
84function getDomainInfo() {
85 var hostname = require('os').hostname();
86 var ret = { Name: hostname, Domain: "", PartOfDomain: false };
87
88 switch (process.platform) {
89 case 'win32':
90 try {
91 ret = require('win-wmi').query('ROOT\\CIMV2', 'SELECT * FROM Win32_ComputerSystem', ['Name', 'Domain', 'PartOfDomain'])[0];
92 }
93 catch (x) {
94 }
95 break;
96 case 'linux':
97 var hasrealm = false;
98
99 try {
100 hasrealm = require('lib-finder').hasBinary('realm');
101 }
102 catch (x) {
103 }
104 if (hasrealm) {
105 var child = require('child_process').execFile('/bin/sh', ['sh']);
106 child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
107 child.stdin.write("realm list | grep domain-name: | tr '\\n' '`' | ");
108 child.stdin.write("awk -F'`' '{ ");
109 child.stdin.write(' printf("[");');
110 child.stdin.write(' ST="";');
111 child.stdin.write(' for(i=1;i<NF;++i)');
112 child.stdin.write(' {');
113 child.stdin.write(' match($i,/domain-name: /);');
114 child.stdin.write(' printf("%s\\"%s\\"", ST, substr($i, RSTART+RLENGTH));');
115 child.stdin.write(' ST=",";');
116 child.stdin.write(' }');
117 child.stdin.write(' printf("]");');
118 child.stdin.write(" }'");
119 child.stdin.write('\nexit\n');
120 child.waitExit();
121 var names = [];
122 try {
123 names = JSON.parse(child.stdout.str);
124 }
125 catch (e) {
126 }
127 while (names.length > 0) {
128 if (hostname.endsWith('.' + names.peek())) {
129 ret = { Name: hostname.substring(0, hostname.length - names.peek().length - 1), Domain: names.peek(), PartOfDomain: true };
130 break;
131 }
132 names.pop();
133 }
134 }
135 break;
136 }
137 return (ret);
138}
139
140function getLogonCacheKeys() {
141 var registry = require('win-registry');
142 var HKLM = registry.HKEY.LocalMachine;
143
144 var userObj = [];
145
146 function readSubKeys(path) {
147 var vals = registry.QueryKey(HKLM, path);
148 if (!vals) return;
149
150 // Extract IdentityName, SAMName, SID if they exist
151 var identityName = null, samName = null, sid = null;
152
153 for (var i = 0; i in vals.values; i++) {
154 if (vals.values[i].toLowerCase() === 'identityname' && identityName === null) {
155 identityName = registry.QueryKey(HKLM, path, vals.values[i]);
156 }
157 if (vals.values[i].toLowerCase() === 'samname' && samName === null) {
158 samName = registry.QueryKey(HKLM, path, vals.values[i]);
159 }
160 if (vals.values[i].toLowerCase() === 'sid' && sid === null) {
161 sid = registry.QueryKey(HKLM, path, vals.values[i]);
162 }
163 }
164
165 // If IdentityName exists, add to userObj
166 if (identityName) {
167 userObj.push({
168 UPN: identityName,
169 SAM: samName,
170 SID: sid
171 });
172 }
173
174 // Recurse into subkeys if any
175 if (vals.subkeys && vals.subkeys.length > 0) {
176 for (var j = 0; j < vals.subkeys.length; j++) {
177 readSubKeys(path + '\\' + vals.subkeys[j]);
178 }
179 }
180 }
181
182 // Start recursion from the LogonCache root
183 readSubKeys('SOFTWARE\\Microsoft\\IdentityStore\\LogonCache');
184
185 var grouped = {};
186
187 function pushUnique(arr, val) {
188 if (val && arr.indexOf(val) === -1) arr.push(val);
189 }
190
191 // Group by UPN and merge values
192 for (var i = 0; i < userObj.length; i++) {
193 var u = userObj[i];
194
195 if (!grouped[u.UPN]) grouped[u.UPN] = {UPN: u.UPN, SID: [], SAM: []};
196
197 pushUnique(grouped[u.UPN].SID, u.SID);
198 pushUnique(grouped[u.UPN].SAM, u.SAM);
199 }
200
201 userObj = [];
202 // Convert grouped object to array
203 for (var k in grouped) if (grouped.hasOwnProperty(k)) userObj.push(grouped[k]);
204
205 return userObj;
206
207}
208
209function getJoinState() {
210 if (process.platform != 'win32') { return -1; }
211 var isAzureAD = false;
212 var isOnPrem = false;
213 var isHybrid = false;
214 var isMicrosoft = false;
215 // 1 Azure AD / Entra ID
216 try {
217 const joinInfo = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'SYSTEM\\CurrentControlSet\\Control\\CloudDomainJoin\\JoinInfo');
218 isAzureAD = Array.isArray(joinInfo.subkeys) && joinInfo.subkeys.length > 0;
219 } catch (e) {}
220 // 2 On-prem AD
221 try {
222 const tcpip = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters','Domain');
223 isOnPrem = !!(tcpip !== "" || null);
224 } catch (e) {}
225 // 3 Hybrid AD
226 isHybrid = isAzureAD && isOnPrem;
227 // 4 Microsoft Account
228 try {
229 const userAccounts = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'SOFTWARE\\Microsoft\\IdentityStore\\LogonCache\\D7F9888F-E3FC-49b0-9EA6-A85B5F392A4F');
230 isMicrosoft = Array.isArray(userAccounts.subkeys) && userAccounts.subkeys.length > 0;
231 } catch (e) {}
232 if (isMicrosoft) return 4;
233 if (isHybrid) return 3;
234 if (isOnPrem) return 2;
235 if (isAzureAD) return 1;
236 return 0;
237
238}
239
240
241try {
242 Object.defineProperty(Array.prototype, 'findIndex', {
243 value: function (func) {
244 var i = 0;
245 for (i = 0; i < this.length; ++i) {
246 if (func(this[i], i, this)) {
247 return (i);
248 }
249 }
250 return (-1);
251 }
252 });
253} catch (ex) { }
254
255if (require('MeshAgent').ARCHID == null) {
256 var id = null;
257 switch (process.platform) {
258 case 'win32':
259 id = require('_GenericMarshal').PointerSize == 4 ? 3 : 4;
260 break;
261 case 'freebsd':
262 id = require('_GenericMarshal').PointerSize == 4 ? 31 : 30;
263 break;
264 case 'darwin':
265 try {
266 id = require('os').arch() == 'x64' ? 16 : 29;
267 } catch (ex) { id = 16; }
268 break;
269 }
270 if (id != null) { Object.defineProperty(require('MeshAgent'), 'ARCHID', { value: id }); }
271}
272
273function setDefaultCoreTranslation(obj, field, value) {
274 if (obj[field] == null || obj[field] == '') { obj[field] = value; }
275}
276
277function getCoreTranslation() {
278 var ret = {};
279 if (global.coretranslations != null) {
280 try {
281 var lang = require('util-language').current;
282 if (coretranslations[lang] == null) { lang = lang.split('-')[0]; }
283 if (coretranslations[lang] == null) { lang = 'en'; }
284 if (coretranslations[lang] != null) { ret = coretranslations[lang]; }
285 }
286 catch (ex) { }
287 }
288
289 setDefaultCoreTranslation(ret, 'allow', 'Allow');
290 setDefaultCoreTranslation(ret, 'deny', 'Deny');
291 setDefaultCoreTranslation(ret, 'autoAllowForFive', 'Auto accept all connections for next 5 minutes');
292 setDefaultCoreTranslation(ret, 'terminalConsent', '{0} requesting remote terminal access. Grant access?');
293 setDefaultCoreTranslation(ret, 'desktopConsent', '{0} requesting remote desktop access. Grant access?');
294 setDefaultCoreTranslation(ret, 'fileConsent', '{0} requesting remote file Access. Grant access?');
295 setDefaultCoreTranslation(ret, 'terminalNotify', '{0} started a remote terminal session.');
296 setDefaultCoreTranslation(ret, 'desktopNotify', '{0} started a remote desktop session.');
297 setDefaultCoreTranslation(ret, 'fileNotify', '{0} started a remote file session.');
298 setDefaultCoreTranslation(ret, 'privacyBar', 'Sharing desktop with: {0}');
299
300 return (ret);
301}
302var currentTranslation = getCoreTranslation();
303
304try {
305 require('kvm-helper');
306}
307catch (e) {
308 var j =
309 {
310 users: function () {
311 var r = {};
312 require('user-sessions').Current(function (c) { r = c; });
313 if (process.platform != 'win32') {
314 for (var i in r) {
315 r[i].SessionId = r[i].uid;
316 }
317 }
318 return (r);
319 }
320 };
321 addModuleObject('kvm-helper', j);
322}
323
324
325function lockDesktop(uid) {
326 switch (process.platform) {
327 case 'linux':
328 if (uid != null) {
329 var name = require('user-sessions').getUsername(uid);
330 var child = require('child_process').execFile('/bin/sh', ['sh']);
331 child.stdout.str = ''; child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });
332 child.stderr.str = ''; child.stderr.on('data', function (chunk) { this.str += chunk.toString(); });
333 child.stdin.write('loginctl show-user -p Sessions ' + name + " | awk '{");
334 child.stdin.write('gsub(/^Sessions=/,"",$0);');
335 child.stdin.write('cmd = sprintf("loginctl lock-session %s",$0);');
336 child.stdin.write('system(cmd);');
337 child.stdin.write("}'\nexit\n");
338 child.waitExit();
339 }
340 else {
341 var child = require('child_process').execFile('/bin/sh', ['sh']);
342 child.stdout.str = ''; child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });
343 child.stderr.str = ''; child.stderr.on('data', function (chunk) { this.str += chunk.toString(); });
344 child.stdin.write('loginctl lock-sessions\nexit\n');
345 child.waitExit();
346 }
347 break;
348 case 'win32':
349 {
350 var options = { type: 1, uid: uid };
351 var child = require('child_process').execFile(process.env['windir'] + '\\system32\\cmd.exe', ['/c', 'RunDll32.exe user32.dll,LockWorkStation'], options);
352 child.waitExit();
353 }
354 break;
355 default:
356 break;
357 }
358}
359var writable = require('stream').Writable;
360function destopLockHelper_pipe(httprequest) {
361 if (process.platform != 'linux' && process.platform != 'freebsd') { return; }
362
363 if (httprequest.unlockerHelper == null && httprequest.desktop != null && httprequest.desktop.kvm != null) {
364 httprequest.unlockerHelper = new writable(
365 {
366 'write': function (chunk, flush) {
367 if (chunk.readUInt16BE(0) == 65) {
368 delete this.request.autolock;
369 }
370 flush();
371 return (true);
372 },
373 'final': function (flush) {
374 flush();
375 }
376 });
377 httprequest.unlockerHelper.request = httprequest;
378 httprequest.desktop.kvm.pipe(httprequest.unlockerHelper);
379 }
380}
381
382var obj = { serverInfo: {} };
383var agentFileHttpRequests = {}; // Currently active agent HTTPS GET requests from the server.
384var agentFileHttpPendingRequests = []; // Pending HTTPS GET requests from the server.
385var debugConsole = (global._MSH && (_MSH().debugConsole == 1));
386
387var color_options =
388 {
389 background: (global._MSH != null) ? global._MSH().background : '0,54,105',
390 foreground: (global._MSH != null) ? global._MSH().foreground : '255,255,255'
391 };
392
393if (process.platform == 'win32' && require('user-sessions').isRoot()) {
394 // Check the Agent Uninstall MetaData for correctness, as the installer may have written an incorrect value
395 try {
396 var writtenSize = 0, actualSize = Math.floor(require('fs').statSync(process.execPath).size / 1024);
397 var serviceName = (_MSH().serviceName ? _MSH().serviceName : (require('_agentNodeId').serviceName() ? require('_agentNodeId').serviceName() : 'Mesh Agent'));
398 try { writtenSize = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + serviceName, 'EstimatedSize'); } catch (ex) { }
399 if (writtenSize != actualSize) { try { require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + serviceName, 'EstimatedSize', actualSize); } catch (ex) { } }
400 } catch (ex) { }
401
402 // Check to see if we are the Installed Mesh Agent Service, if we are, make sure we can run in Safe Mode
403 var svcname = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent';
404 try {
405 svcname = require('MeshAgent').serviceName;
406 } catch (ex) { }
407
408 try {
409 var meshCheck = false;
410 try { meshCheck = require('service-manager').manager.getService(svcname).isMe(); } catch (ex) { }
411 if (meshCheck && require('win-bcd').isSafeModeService && !require('win-bcd').isSafeModeService(svcname)) { require('win-bcd').enableSafeModeService(svcname); }
412 } catch (ex) { }
413
414 // Check the Agent Uninstall MetaData for DisplayVersion and update if not the same and only on windows
415 if (process.platform == 'win32') {
416 try {
417 var writtenDisplayVersion = 0, actualDisplayVersion = process.versions.commitDate.toString();
418 var serviceName = (_MSH().serviceName ? _MSH().serviceName : (require('_agentNodeId').serviceName() ? require('_agentNodeId').serviceName() : 'Mesh Agent'));
419 try { writtenDisplayVersion = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + serviceName, 'DisplayVersion'); } catch (ex) { }
420 if (writtenDisplayVersion != actualDisplayVersion) { try { require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + serviceName, 'DisplayVersion', actualDisplayVersion); } catch (ex) { } }
421 } catch (ex) { }
422 }
423}
424
425if (process.platform != 'win32') {
426 var ch = require('child_process');
427 ch._execFile = ch.execFile;
428 ch.execFile = function execFile(path, args, options) {
429 if (options && options.type && options.type == ch.SpawnTypes.TERM && options.env) {
430 options.env['TERM'] = 'xterm-256color';
431 }
432 return (this._execFile(path, args, options));
433 };
434}
435
436
437if (process.platform == 'darwin' && !process.versions) {
438 // This is an older MacOS Agent, so we'll need to check the service definition so that Auto-Update will function correctly
439 var child = require('child_process').execFile('/bin/sh', ['sh']);
440 child.stdout.str = '';
441 child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });
442 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, \">\"); ");
443 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]; } }");
444 child.stdin.write(" else { split(c[1], ka, \"/\"); if(ka[1]==\"true\") {print \"ALWAYS\";} } }'\nexit\n");
445 child.waitExit();
446 if (child.stdout.str.trim() == 'Crashed') {
447 child = require('child_process').execFile('/bin/sh', ['sh']);
448 child.stdout.str = '';
449 child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });
450 child.stdin.write("launchctl list | grep 'meshagent' | awk '{ if($3==\"meshagent\"){print $1;}}'\nexit\n");
451 child.waitExit();
452
453 if (parseInt(child.stdout.str.trim()) == process.pid) {
454 // The currently running MeshAgent is us, so we can continue with the update
455 var plist = require('fs').readFileSync('/Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist').toString();
456 var tokens = plist.split('<key>KeepAlive</key>');
457 if (tokens[1].split('>')[0].split('<')[1] == 'dict') {
458 var tmp = tokens[1].split('</dict>');
459 tmp.shift();
460 tokens[1] = '\n <true/>' + tmp.join('</dict>');
461 tokens = tokens.join('<key>KeepAlive</key>');
462
463 require('fs').writeFileSync('/Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist', tokens);
464
465 var fix = '';
466 fix += ("function macosRepair()\n");
467 fix += ("{\n");
468 fix += (" var child = require('child_process').execFile('/bin/sh', ['sh']);\n");
469 fix += (" child.stdout.str = '';\n");
470 fix += (" child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });\n");
471 fix += (" child.stderr.on('data', function (chunk) { });\n");
472 fix += (" child.stdin.write('launchctl unload /Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist\\n');\n");
473 fix += (" child.stdin.write('launchctl load /Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist\\n');\n");
474 fix += (" child.stdin.write('rm /Library/LaunchDaemons/meshagentRepair.plist\\n');\n");
475 fix += (" child.stdin.write('rm " + process.cwd() + "/macosRepair.js\\n');\n");
476 fix += (" child.stdin.write('launchctl stop meshagentRepair\\nexit\\n');\n");
477 fix += (" child.waitExit();\n");
478 fix += ("}\n");
479 fix += ("macosRepair();\n");
480 fix += ("process.exit();\n");
481 require('fs').writeFileSync(process.cwd() + '/macosRepair.js', fix);
482
483 var plist = '<?xml version="1.0" encoding="UTF-8"?>\n';
484 plist += '<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n';
485 plist += '<plist version="1.0">\n';
486 plist += ' <dict>\n';
487 plist += ' <key>Label</key>\n';
488 plist += (' <string>meshagentRepair</string>\n');
489 plist += ' <key>ProgramArguments</key>\n';
490 plist += ' <array>\n';
491 plist += (' <string>' + process.execPath + '</string>\n');
492 plist += ' <string>macosRepair.js</string>\n';
493 plist += ' </array>\n';
494 plist += ' <key>WorkingDirectory</key>\n';
495 plist += (' <string>' + process.cwd() + '</string>\n');
496 plist += ' <key>RunAtLoad</key>\n';
497 plist += ' <true/>\n';
498 plist += ' </dict>\n';
499 plist += '</plist>';
500 require('fs').writeFileSync('/Library/LaunchDaemons/meshagentRepair.plist', plist);
501
502 child = require('child_process').execFile('/bin/sh', ['sh']);
503 child.stdout.str = '';
504 child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });
505 child.stdin.write("launchctl load /Library/LaunchDaemons/meshagentRepair.plist\nexit\n");
506 child.waitExit();
507 }
508 }
509 }
510}
511
512// Add an Intel AMT event to the log
513function addAmtEvent(msg) {
514 if (obj.amtevents == null) { obj.amtevents = []; }
515 var d = new Date(), e = zeroPad(d.getHours(), 2) + ':' + zeroPad(d.getMinutes(), 2) + ':' + zeroPad(d.getSeconds(), 2) + ', ' + msg;
516 obj.amtevents.push(e);
517 if (obj.amtevents.length > 100) { obj.amtevents.splice(0, obj.amtevents.length - 100); }
518 if (obj.showamtevent) { require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: e }); }
519}
520function zeroPad(num, size) { var s = '000000000' + num; return s.substr(s.length - size); }
521
522
523// Create Secure IPC for Diagnostic Agent Communications
524obj.DAIPC = require('net').createServer();
525if (process.platform != 'win32') { try { require('fs').unlinkSync(process.cwd() + '/DAIPC'); } catch (ex) { } }
526obj.DAIPC.IPCPATH = process.platform == 'win32' ? ('\\\\.\\pipe\\' + require('_agentNodeId')() + '-DAIPC') : (process.cwd() + '/DAIPC');
527try { obj.DAIPC.listen({ path: obj.DAIPC.IPCPATH, writableAll: true, maxConnections: 5 }); } catch (ex) { }
528obj.DAIPC._daipc = [];
529obj.DAIPC.on('connection', function (c) {
530 c._send = function (j) {
531 var data = JSON.stringify(j);
532 var packet = Buffer.alloc(data.length + 4);
533 packet.writeUInt32LE(data.length + 4, 0);
534 Buffer.from(data).copy(packet, 4);
535 this.write(packet);
536 };
537 this._daipc.push(c);
538 c.parent = this;
539 c.on('end', function () { removeRegisteredApp(this); });
540 c.on('data', function (chunk) {
541 if (chunk.length < 4) { this.unshift(chunk); return; }
542 var len = chunk.readUInt32LE(0);
543 if (len > 8192) { removeRegisteredApp(this); this.end(); return; }
544 if (chunk.length < len) { this.unshift(chunk); return; }
545
546 var data = chunk.slice(4, len);
547 try { data = JSON.parse(data.toString()); } catch (ex) { }
548 if ((data == null) || (typeof data.cmd != 'string')) return;
549
550 try {
551 switch (data.cmd) {
552 case 'requesthelp':
553 if (this._registered == null) return;
554 sendConsoleText('Request Help (' + this._registered + '): ' + data.value);
555 var help = {};
556 help[this._registered] = data.value;
557 try { mesh.SendCommand({ action: 'sessions', type: 'help', value: help }); } catch (ex) { }
558 MeshServerLogEx(98, [this._registered, data.value], "Help Requested, user: " + this._registered + ", details: " + data.value, null);
559 break;
560 case 'cancelhelp':
561 if (this._registered == null) return;
562 sendConsoleText('Cancel Help (' + this._registered + ')');
563 try { mesh.SendCommand({ action: 'sessions', type: 'help', value: {} }); } catch (ex) { }
564 break;
565 case 'register':
566 if (typeof data.value == 'string') {
567 this._registered = data.value;
568 var apps = {};
569 apps[data.value] = 1;
570 try { mesh.SendCommand({ action: 'sessions', type: 'app', value: apps }); } catch (ex) { }
571 this._send({ cmd: 'serverstate', value: meshServerConnectionState, url: require('MeshAgent').ConnectedServer, amt: (amt != null) });
572 }
573 break;
574 case 'query':
575 switch (data.value) {
576 case 'connection':
577 data.result = require('MeshAgent').ConnectedServer;
578 this._send(data);
579 break;
580 case 'descriptors':
581 require('ChainViewer').getSnapshot().then(function (f) {
582 this.tag.payload.result = f;
583 this.tag.ipc._send(this.tag.payload);
584 }).parentPromise.tag = { ipc: this, payload: data };
585 break;
586 case 'timerinfo':
587 data.result = require('ChainViewer').getTimerInfo();
588 this._send(data);
589 break;
590 }
591 break;
592 case 'amtstate':
593 if (amt == null) return;
594 var func = function amtStateFunc(state) { if (state != null) { amtStateFunc.pipe._send({ cmd: 'amtstate', value: state }); } }
595 func.pipe = this;
596 amt.getMeiState(11, func);
597 break;
598 case 'sessions':
599 this._send({ cmd: 'sessions', sessions: tunnelUserCount });
600 break;
601 case 'meshToolInfo':
602 try { mesh.SendCommand({ action: 'meshToolInfo', name: data.name, hash: data.hash, cookie: data.cookie ? true : false, pipe: true }); } catch (ex) { }
603 break;
604 case 'getUserImage':
605 try { mesh.SendCommand({ action: 'getUserImage', userid: data.userid, pipe: true }); } catch (ex) { }
606 break;
607 case 'console':
608 if (debugConsole) {
609 var args = splitArgs(data.value);
610 processConsoleCommand(args[0].toLowerCase(), parseArgs(args), 0, 'pipe');
611 }
612 break;
613 }
614 }
615 catch (ex) { removeRegisteredApp(this); this.end(); return; }
616 });
617});
618
619// Send current sessions to registered apps
620function broadcastSessionsToRegisteredApps(x) {
621 var p = {}, i;
622 for (i = 0; sendAgentMessage.messages != null && i < sendAgentMessage.messages.length; ++i) {
623 p[i] = sendAgentMessage.messages[i];
624 }
625 tunnelUserCount.msg = p;
626 broadcastToRegisteredApps({ cmd: 'sessions', sessions: tunnelUserCount });
627 tunnelUserCount.msg = {};
628}
629
630// Send this object to all registered local applications
631function broadcastToRegisteredApps(x) {
632 if ((obj.DAIPC == null) || (obj.DAIPC._daipc == null)) return;
633 for (var i in obj.DAIPC._daipc) {
634 if (obj.DAIPC._daipc[i]._registered != null) { obj.DAIPC._daipc[i]._send(x); }
635 }
636}
637
638// Send this object to a specific registered local applications
639function sendToRegisteredApp(appid, x) {
640 if ((obj.DAIPC == null) || (obj.DAIPC._daipc == null)) return;
641 for (var i in obj.DAIPC._daipc) { if (obj.DAIPC._daipc[i]._registered == appid) { obj.DAIPC._daipc[i]._send(x); } }
642}
643
644// Send list of registered apps to the server
645function updateRegisteredAppsToServer() {
646 if ((obj.DAIPC == null) || (obj.DAIPC._daipc == null)) return;
647 var apps = {};
648 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]++; } }
649 try { mesh.SendCommand({ action: 'sessions', type: 'app', value: apps }); } catch (ex) { }
650}
651
652// Remove a registered app
653function removeRegisteredApp(pipe) {
654 for (var i = obj.DAIPC._daipc.length - 1; i >= 0; i--) { if (obj.DAIPC._daipc[i] === pipe) { obj.DAIPC._daipc.splice(i, 1); } }
655 if (pipe._registered != null) updateRegisteredAppsToServer();
656}
657
658function diagnosticAgent_uninstall() {
659 require('service-manager').manager.uninstallService('meshagentDiagnostic');
660 require('task-scheduler').delete('meshagentDiagnostic/periodicStart'); // TODO: Using "delete" here breaks the minifier since this is a reserved keyword
661}
662function diagnosticAgent_installCheck(install) {
663 try {
664 var diag = require('service-manager').manager.getService('meshagentDiagnostic');
665 return (diag);
666 } catch (ex) { }
667 if (!install) { return null; }
668
669 var svc = null;
670 try {
671 require('service-manager').manager.installService(
672 {
673 name: 'meshagentDiagnostic',
674 displayName: "Mesh Agent Diagnostic Service",
675 description: "Mesh Agent Diagnostic Service",
676 servicePath: process.execPath,
677 parameters: ['-recovery']
678 //files: [{ newName: 'diagnostic.js', _buffer: Buffer.from('LyoNCkNvcHlyaWdodCAyMDE5IEludGVsIENvcnBvcmF0aW9uDQoNCkxpY2Vuc2VkIHVuZGVyIHRoZSBBcGFjaGUgTGljZW5zZSwgVmVyc2lvbiAyLjAgKHRoZSAiTGljZW5zZSIpOw0KeW91IG1heSBub3QgdXNlIHRoaXMgZmlsZSBleGNlcHQgaW4gY29tcGxpYW5jZSB3aXRoIHRoZSBMaWNlbnNlLg0KWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0DQoNCiAgICBodHRwOi8vd3d3LmFwYWNoZS5vcmcvbGljZW5zZXMvTElDRU5TRS0yLjANCg0KVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQ0KZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywNCldJVEhPVVQgV0FSUkFOVElFUyBPUiBDT05ESVRJT05TIE9GIEFOWSBLSU5ELCBlaXRoZXIgZXhwcmVzcyBvciBpbXBsaWVkLg0KU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZA0KbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuDQoqLw0KDQp2YXIgaG9zdCA9IHJlcXVpcmUoJ3NlcnZpY2UtaG9zdCcpLmNyZWF0ZSgnbWVzaGFnZW50RGlhZ25vc3RpYycpOw0KdmFyIFJlY292ZXJ5QWdlbnQgPSByZXF1aXJlKCdNZXNoQWdlbnQnKTsNCg0KaG9zdC5vbignc2VydmljZVN0YXJ0JywgZnVuY3Rpb24gKCkNCnsNCiAgICBjb25zb2xlLnNldERlc3RpbmF0aW9uKGNvbnNvbGUuRGVzdGluYXRpb25zLkxPR0ZJTEUpOw0KICAgIGhvc3Quc3RvcCA9IGZ1bmN0aW9uKCkNCiAgICB7DQogICAgICAgIHJlcXVpcmUoJ3NlcnZpY2UtbWFuYWdlcicpLm1hbmFnZXIuZ2V0U2VydmljZSgnbWVzaGFnZW50RGlhZ25vc3RpYycpLnN0b3AoKTsNCiAgICB9DQogICAgUmVjb3ZlcnlBZ2VudC5vbignQ29ubmVjdGVkJywgZnVuY3Rpb24gKHN0YXR1cykNCiAgICB7DQogICAgICAgIGlmIChzdGF0dXMgPT0gMCkNCiAgICAgICAgew0KICAgICAgICAgICAgY29uc29sZS5sb2coJ0RpYWdub3N0aWMgQWdlbnQ6IFNlcnZlciBjb25uZWN0aW9uIGxvc3QuLi4nKTsNCiAgICAgICAgICAgIHJldHVybjsNCiAgICAgICAgfQ0KICAgICAgICBjb25zb2xlLmxvZygnRGlhZ25vc3RpYyBBZ2VudDogQ29ubmVjdGlvbiBFc3RhYmxpc2hlZCB3aXRoIFNlcnZlcicpOw0KICAgICAgICBzdGFydCgpOw0KICAgIH0pOw0KfSk7DQpob3N0Lm9uKCdub3JtYWxTdGFydCcsIGZ1bmN0aW9uICgpDQp7DQogICAgaG9zdC5zdG9wID0gZnVuY3Rpb24gKCkNCiAgICB7DQogICAgICAgIHByb2Nlc3MuZXhpdCgpOw0KICAgIH0NCiAgICBjb25zb2xlLmxvZygnTm9uIFNlcnZpY2UgTW9kZScpOw0KICAgIFJlY292ZXJ5QWdlbnQub24oJ0Nvbm5lY3RlZCcsIGZ1bmN0aW9uIChzdGF0dXMpDQogICAgew0KICAgICAgICBpZiAoc3RhdHVzID09IDApDQogICAgICAgIHsNCiAgICAgICAgICAgIGNvbnNvbGUubG9nKCdEaWFnbm9zdGljIEFnZW50OiBTZXJ2ZXIgY29ubmVjdGlvbiBsb3N0Li4uJyk7DQogICAgICAgICAgICByZXR1cm47DQogICAgICAgIH0NCiAgICAgICAgY29uc29sZS5sb2coJ0RpYWdub3N0aWMgQWdlbnQ6IENvbm5lY3Rpb24gRXN0YWJsaXNoZWQgd2l0aCBTZXJ2ZXInKTsNCiAgICAgICAgc3RhcnQoKTsNCiAgICB9KTsNCn0pOw0KaG9zdC5vbignc2VydmljZVN0b3AnLCBmdW5jdGlvbiAoKSB7IHByb2Nlc3MuZXhpdCgpOyB9KTsNCmhvc3QucnVuKCk7DQoNCg0KZnVuY3Rpb24gc3RhcnQoKQ0Kew0KDQp9Ow0K', 'base64') }]
679 });
680 svc = require('service-manager').manager.getService('meshagentDiagnostic');
681 }
682 catch (ex) { return null; }
683 var proxyConfig = require('global-tunnel').proxyConfig;
684 var cert = require('MeshAgent').GenerateAgentCertificate('CN=MeshNodeDiagnosticCertificate');
685 var nodeid = require('tls').loadCertificate(cert.root).getKeyHash().toString('base64');
686 ddb = require('SimpleDataStore').Create(svc.appWorkingDirectory().replace('\\', '/') + '/meshagentDiagnostic.db');
687 ddb.Put('disableUpdate', '1');
688 ddb.Put('MeshID', Buffer.from(require('MeshAgent').ServerInfo.MeshID, 'hex'));
689 ddb.Put('ServerID', require('MeshAgent').ServerInfo.ServerID);
690 ddb.Put('MeshServer', require('MeshAgent').ServerInfo.ServerUri);
691 if (cert.root.pfx) { ddb.Put('SelfNodeCert', cert.root.pfx); }
692 if (cert.tls) { ddb.Put('SelfNodeTlsCert', cert.tls.pfx); }
693 if (proxyConfig) {
694 ddb.Put('WebProxy', proxyConfig.host + ':' + proxyConfig.port);
695 } else {
696 ddb.Put('ignoreProxyFile', '1');
697 }
698
699 require('MeshAgent').SendCommand({ action: 'diagnostic', value: { command: 'register', value: nodeid } });
700 require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: "Diagnostic Agent Registered [" + nodeid.length + "/" + nodeid + "]" });
701
702 delete ddb;
703
704 // Set a recurrent task, to run the Diagnostic Agent every 2 days
705 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' });
706 //require('task-scheduler').create({ name: 'meshagentDiagnostic/periodicStart', daily: '1', time: '17:16', service: 'meshagentDiagnostic' });
707
708 return (svc);
709}
710
711// Monitor the file 'batterystate.txt' in the agent's folder and sends battery update when this file is changed.
712if ((require('fs').existsSync(process.cwd() + 'batterystate.txt')) && (require('fs').watch != null)) {
713 // Setup manual battery monitoring
714 require('MeshAgent')._batteryFileWatcher = require('fs').watch(process.cwd(), function () {
715 if (require('MeshAgent')._batteryFileTimer != null) return;
716 require('MeshAgent')._batteryFileTimer = setTimeout(function () {
717 try {
718 require('MeshAgent')._batteryFileTimer = null;
719 var data = null;
720 try { data = require('fs').readFileSync(process.cwd() + 'batterystate.txt').toString(); } catch (ex) { }
721 if ((data != null) && (data.length < 10)) {
722 data = data.split(',');
723 if ((data.length == 2) && ((data[0] == 'ac') || (data[0] == 'dc'))) {
724 var level = parseInt(data[1]);
725 if ((level >= 0) && (level <= 100)) { require('MeshAgent').SendCommand({ action: 'battery', state: data[0], level: level }); }
726 }
727 }
728 } catch (ex) { }
729 }, 1000);
730 });
731}
732else {
733 try {
734 // Setup normal battery monitoring
735 if (require('computer-identifiers').isBatteryPowered && require('computer-identifiers').isBatteryPowered()) {
736 require('MeshAgent')._battLevelChanged = function _battLevelChanged(val) {
737 _battLevelChanged.self._currentBatteryLevel = val;
738 _battLevelChanged.self.SendCommand({ action: 'battery', state: _battLevelChanged.self._currentPowerState, level: val });
739 };
740 require('MeshAgent')._battLevelChanged.self = require('MeshAgent');
741 require('MeshAgent')._powerChanged = function _powerChanged(val) {
742 _powerChanged.self._currentPowerState = (val == 'AC' ? 'ac' : 'dc');
743 _powerChanged.self.SendCommand({ action: 'battery', state: (val == 'AC' ? 'ac' : 'dc'), level: _powerChanged.self._currentBatteryLevel });
744 };
745 require('MeshAgent')._powerChanged.self = require('MeshAgent');
746 require('MeshAgent').on('Connected', function (status) {
747 if (status == 0) {
748 require('power-monitor').removeListener('acdc', this._powerChanged);
749 require('power-monitor').removeListener('batteryLevel', this._battLevelChanged);
750 } else {
751 require('power-monitor').on('acdc', this._powerChanged);
752 require('power-monitor').on('batteryLevel', this._battLevelChanged);
753 }
754 });
755 }
756 }
757 catch (ex) { }
758}
759
760
761// MeshAgent JavaScript Core Module. This code is sent to and running on the mesh agent.
762var meshCoreObj = { action: 'coreinfo', value: (require('MeshAgent').coreHash ? ((process.versions.compileTime ? process.versions.compileTime : '').split(', ')[1].replace(' ', ' ') + ', ' + crc32c(require('MeshAgent').coreHash)) : ('MeshCore v6')), caps: 14, root: require('user-sessions').isRoot() }; // Capability bitmask: 1 = Desktop, 2 = Terminal, 4 = Files, 8 = Console, 16 = JavaScript, 32 = Temporary Agent, 64 = Recovery Agent
763
764// Get the operating system description string
765try { require('os').name().then(function (v) { meshCoreObj.osdesc = v; meshCoreObjChanged(); }); } catch (ex) { }
766
767// Setup logged in user monitoring (THIS IS BROKEN IN WIN7)
768function onUserSessionChanged(user, locked) {
769 userSession.enumerateUsers().then(function (users) {
770 if (process.platform == 'linux') {
771 if (userSession._startTime == null) {
772 userSession._startTime = Date.now();
773 userSession._count = users.length;
774 }
775 else if (Date.now() - userSession._startTime < 10000 && users.length == userSession._count) {
776 userSession.removeAllListeners('changed');
777 return;
778 }
779 }
780
781 var u = [], a = users.Active;
782 if(meshCoreObj.lusers == null) { meshCoreObj.lusers = []; }
783 if(meshCoreObj.upnusers == null) { meshCoreObj.upnusers = []; }
784 var ret = getDomainInfo();
785 for (var i = 0; i < a.length; i++) {
786 var un = a[i].Domain ? (a[i].Domain + '\\' + a[i].Username) : (a[i].Username);
787 if (user && locked && (JSON.stringify(a[i]) === JSON.stringify(user))) { if (meshCoreObj.lusers.indexOf(un) == -1) { meshCoreObj.lusers.push(un); } }
788 else if (user && !locked && (JSON.stringify(a[i]) === JSON.stringify(user))) { meshCoreObj.lusers.splice(meshCoreObj.lusers.indexOf(un), 1); }
789 if (u.indexOf(un) == -1) { u.push(un); } // Only push users in the list once.
790 if ((a[i].Domain != null && a[i].Domain == 'AzureAD') || getJoinState() == 1 ){
791 var userobj = getLogonCacheKeys();
792 if(userobj && userobj.length > 0){
793 for (var j = 0; j < userobj.length; j++) {
794 if (userobj[j] && userobj[j].SAM && userobj[j].SAM[0].trim() === a[i].Username) {
795 meshCoreObj.upnusers.push(userobj[j].UPN);
796 break;
797 }
798 }
799 }
800 } else if (a[i].Domain != null) {
801 if (ret != null && ret.PartOfDomain === true) {
802 meshCoreObj.upnusers.push(a[i].Username + '@' + ret.Domain);
803 } else if (getJoinState() == 4) { // One account with Microsoft Account
804 var userobj = getLogonCacheKeys();
805 if(userobj && userobj.length > 0){
806 for (var j = 0; j < userobj.length; j++) {
807 if (userobj[j] && userobj[j].SAM && userobj[j].SAM.length == 0 && userobj[j].UPN && userobj[j].UPN != '') {
808 meshCoreObj.upnusers.push(userobj[j].UPN);
809 break;
810 }
811 }
812 }
813 }
814 }
815 }
816 meshCoreObj.lusers = meshCoreObj.lusers;
817 meshCoreObj.users = u;
818 meshCoreObjChanged();
819 });
820}
821
822try {
823 var userSession = require('user-sessions');
824 userSession.on('changed', function () { onUserSessionChanged(null, false); });
825 userSession.emit('changed');
826 userSession.on('locked', function (user) { if(user != undefined && user != null) { onUserSessionChanged(user, true); } });
827 userSession.on('unlocked', function (user) { if(user != undefined && user != null) { onUserSessionChanged(user, false); } });
828} catch (ex) { }
829
830var meshServerConnectionState = 0;
831var tunnels = {};
832var lastNetworkInfo = null;
833var lastPublicLocationInfo = null;
834var selfInfoUpdateTimer = null;
835var http = require('http');
836var net = require('net');
837var fs = require('fs');
838var rtc = require('ILibWebRTC');
839var amt = null;
840var processManager = require('process-manager');
841var wifiScannerLib = null;
842var wifiScanner = null;
843var networkMonitor = null;
844var nextTunnelIndex = 1;
845var apftunnel = null;
846var tunnelUserCount = { terminal: {}, files: {}, tcp: {}, udp: {}, msg: {} }; // List of userid->count sessions for terminal, files and TCP/UDP routing
847
848// Add to the server event log
849function MeshServerLog(msg, state) {
850 if (typeof msg == 'string') { msg = { action: 'log', msg: msg }; } else { msg.action = 'log'; }
851 if (state) {
852 if (state.userid) { msg.userid = state.userid; }
853 if (state.username) { msg.username = state.username; }
854 if (state.sessionid) { msg.sessionid = state.sessionid; }
855 if (state.remoteaddr) { msg.remoteaddr = state.remoteaddr; }
856 if (state.guestname) { msg.guestname = state.guestname; }
857 }
858 mesh.SendCommand(msg);
859}
860
861// Add to the server event log, use internationalized events
862function MeshServerLogEx(id, args, msg, state) {
863 var msg = { action: 'log', msgid: id, msgArgs: args, msg: msg };
864 if (state) {
865 if (state.userid) { msg.userid = state.userid; }
866 if (state.xuserid) { msg.xuserid = state.xuserid; }
867 if (state.username) { msg.username = state.username; }
868 if (state.sessionid) { msg.sessionid = state.sessionid; }
869 if (state.remoteaddr) { msg.remoteaddr = state.remoteaddr; }
870 if (state.guestname) { msg.guestname = state.guestname; }
871 }
872 mesh.SendCommand(msg);
873}
874
875// Import libraries
876db = require('SimpleDataStore').Shared();
877sha = require('SHA256Stream');
878mesh = require('MeshAgent');
879childProcess = require('child_process');
880
881if (mesh.hasKVM == 1) { // if the agent is compiled with KVM support
882 // Check if this computer supports a desktop
883 try {
884 if ((process.platform == 'win32') || (process.platform == 'darwin') || (require('monitor-info').kvm_x11_support)) {
885 meshCoreObj.caps |= 1; meshCoreObjChanged();
886 } else if (process.platform == 'linux' || process.platform == 'freebsd') {
887 require('monitor-info').on('kvmSupportDetected', function (value) { meshCoreObj.caps |= 1; meshCoreObjChanged(); });
888 }
889 } catch (ex) { }
890}
891mesh.DAIPC = obj.DAIPC;
892
893/*
894// Try to load up the network monitor
895try {
896 networkMonitor = require('NetworkMonitor');
897 networkMonitor.on('change', function () { sendNetworkUpdateNagle(); });
898 networkMonitor.on('add', function (addr) { sendNetworkUpdateNagle(); });
899 networkMonitor.on('remove', function (addr) { sendNetworkUpdateNagle(); });
900} catch (ex) { networkMonitor = null; }
901*/
902
903// Fetch the SMBios Tables
904var SMBiosTables = null;
905var SMBiosTablesRaw = null;
906try {
907 var SMBiosModule = null;
908 try { SMBiosModule = require('smbios'); } catch (ex) { }
909 if (SMBiosModule != null) {
910 SMBiosModule.get(function (data) {
911 if (data != null) {
912 SMBiosTablesRaw = data;
913 SMBiosTables = require('smbios').parse(data)
914 if (mesh.isControlChannelConnected) { mesh.SendCommand({ action: 'smbios', value: SMBiosTablesRaw }); }
915
916 // If SMBios tables say that Intel AMT is present, try to connect MEI
917 if (SMBiosTables.amtInfo && (SMBiosTables.amtInfo.AMT == true)) {
918 var amtmodule = require('amt-manage');
919 amt = new amtmodule(mesh, db, false);
920 amt.on('portBinding_LMS', function (map) { mesh.SendCommand({ action: 'lmsinfo', value: { ports: map.keys() } }); });
921 amt.on('stateChange_LMS', function (v) { if (!meshCoreObj.intelamt) { meshCoreObj.intelamt = {}; } meshCoreObj.intelamt.microlms = v; meshCoreObjChanged(); }); // 0 = Disabled, 1 = Connecting, 2 = Connected
922 amt.onStateChange = function (state) { if (state == 2) { sendPeriodicServerUpdate(1); } } // MEI State
923 amt.reset();
924 }
925 }
926 });
927 }
928} catch (ex) { sendConsoleText("ex1: " + ex); }
929
930// Try to load up the WIFI scanner
931try {
932 var wifiScannerLib = require('wifi-scanner');
933 wifiScanner = new wifiScannerLib();
934 wifiScanner.on('accessPoint', function (data) { sendConsoleText("wifiScanner: " + data); });
935} catch (ex) { wifiScannerLib = null; wifiScanner = null; }
936
937// Get our location (lat/long) using our public IP address
938var getIpLocationDataExInProgress = false;
939var getIpLocationDataExCounts = [0, 0];
940function getIpLocationDataEx(func) {
941 if (getIpLocationDataExInProgress == true) { return false; }
942 try {
943 getIpLocationDataExInProgress = true;
944 getIpLocationDataExCounts[0]++;
945 var options = http.parseUri("http://ipinfo.io/json");
946 options.method = 'GET';
947 http.request(options, function (resp) {
948 if (resp.statusCode == 200) {
949 var geoData = '';
950 resp.data = function (geoipdata) { geoData += geoipdata; };
951 resp.end = function () {
952 var location = null;
953 try {
954 if (typeof geoData == 'string') {
955 var result = JSON.parse(geoData);
956 if (result.ip && result.loc) { location = result; }
957 }
958 } catch (ex) { }
959 if (func) { getIpLocationDataExCounts[1]++; func(location); }
960 }
961 } else
962 { func(null); }
963 getIpLocationDataExInProgress = false;
964 }).end();
965 return true;
966 }
967 catch (ex) { return false; }
968}
969
970// Remove all Gateway MAC addresses for interface list. This is useful because the gateway MAC is not always populated reliably.
971function clearGatewayMac(str) {
972 if (typeof str != 'string') return null;
973 var x = JSON.parse(str);
974 for (var i in x.netif) { try { if (x.netif[i].gatewaymac) { delete x.netif[i].gatewaymac } } catch (ex) { } }
975 return JSON.stringify(x);
976}
977
978function getIpLocationData(func) {
979 // Get the location information for the cache if possible
980 var publicLocationInfo = db.Get('publicLocationInfo');
981 if (publicLocationInfo != null) { publicLocationInfo = JSON.parse(publicLocationInfo); }
982 if (publicLocationInfo == null) {
983 // Nothing in the cache, fetch the data
984 getIpLocationDataEx(function (locationData) {
985 if (locationData != null) {
986 publicLocationInfo = {};
987 publicLocationInfo.netInfoStr = lastNetworkInfo;
988 publicLocationInfo.locationData = locationData;
989 var x = db.Put('publicLocationInfo', JSON.stringify(publicLocationInfo)); // Save to database
990 if (func) func(locationData); // Report the new location
991 }
992 else {
993 if (func) func(null); // Report no location
994 }
995 });
996 }
997 else {
998 // Check the cache
999 if (clearGatewayMac(publicLocationInfo.netInfoStr) == clearGatewayMac(lastNetworkInfo)) {
1000 // Cache match
1001 if (func) func(publicLocationInfo.locationData);
1002 }
1003 else {
1004 // Cache mismatch
1005 getIpLocationDataEx(function (locationData) {
1006 if (locationData != null) {
1007 publicLocationInfo = {};
1008 publicLocationInfo.netInfoStr = lastNetworkInfo;
1009 publicLocationInfo.locationData = locationData;
1010 var x = db.Put('publicLocationInfo', JSON.stringify(publicLocationInfo)); // Save to database
1011 if (func) func(locationData); // Report the new location
1012 }
1013 else {
1014 if (func) func(publicLocationInfo.locationData); // Can't get new location, report the old location
1015 }
1016 });
1017 }
1018 }
1019}
1020
1021// Polyfill String.endsWith
1022if (!String.prototype.endsWith) {
1023 String.prototype.endsWith = function (searchString, position) {
1024 var subjectString = this.toString();
1025 if (typeof position !== 'number' || !isFinite(position) || Math.floor(position) !== position || position > subjectString.length) { position = subjectString.length; }
1026 position -= searchString.length;
1027 var lastIndex = subjectString.lastIndexOf(searchString, position);
1028 return lastIndex !== -1 && lastIndex === position;
1029 };
1030}
1031
1032// Polyfill path.join
1033obj.path =
1034 {
1035 join: function () {
1036 var x = [];
1037 for (var i in arguments) {
1038 var w = arguments[i];
1039 if (w != null) {
1040 while (w.endsWith('/') || w.endsWith('\\')) { w = w.substring(0, w.length - 1); }
1041 if (i != 0) {
1042 while (w.startsWith('/') || w.startsWith('\\')) { w = w.substring(1); }
1043 }
1044 x.push(w);
1045 }
1046 }
1047 if (x.length == 0) return '/';
1048 return x.join('/');
1049 }
1050 };
1051
1052// Replace a string with a number if the string is an exact number
1053function toNumberIfNumber(x) { if ((typeof x == 'string') && (+parseInt(x) === x)) { x = parseInt(x); } return x; }
1054
1055// Convert decimal to hex
1056function char2hex(i) { return (i + 0x100).toString(16).substr(-2).toUpperCase(); }
1057
1058// Convert a raw string to a hex string
1059function rstr2hex(input) { var r = '', i; for (i = 0; i < input.length; i++) { r += char2hex(input.charCodeAt(i)); } return r; }
1060
1061// Convert a buffer into a string
1062function buf2rstr(buf) { var r = ''; for (var i = 0; i < buf.length; i++) { r += String.fromCharCode(buf[i]); } return r; }
1063
1064// Convert a hex string to a raw string // TODO: Do this using Buffer(), will be MUCH faster
1065function hex2rstr(d) {
1066 if (typeof d != "string" || d.length == 0) return '';
1067 var r = '', m = ('' + d).match(/../g), t;
1068 while (t = m.shift()) r += String.fromCharCode('0x' + t);
1069 return r
1070}
1071
1072// Convert an object to string with all functions
1073function objToString(x, p, pad, ret) {
1074 if (ret == undefined) ret = '';
1075 if (p == undefined) p = 0;
1076 if (x == null) { return '[null]'; }
1077 if (p > 8) { return '[...]'; }
1078 if (x == undefined) { return '[undefined]'; }
1079 if (typeof x == 'string') { if (p == 0) return x; return '"' + x + '"'; }
1080 if (typeof x == 'buffer') { return '[buffer]'; }
1081 if (typeof x != 'object') { return x; }
1082 var r = '{' + (ret ? '\r\n' : ' ');
1083 for (var i in x) { if (i != '_ObjectID') { r += (addPad(p + 2, pad) + i + ': ' + objToString(x[i], p + 2, pad, ret) + (ret ? '\r\n' : ' ')); } }
1084 return r + addPad(p, pad) + '}';
1085}
1086
1087// Return p number of spaces
1088function addPad(p, ret) { var r = ''; for (var i = 0; i < p; i++) { r += ret; } return r; }
1089
1090// Split a string taking into account the quoats. Used for command line parsing
1091function splitArgs(str) {
1092 var myArray = [], myRegexp = /[^\s"]+|"([^"]*)"/gi;
1093 do { var match = myRegexp.exec(str); if (match != null) { myArray.push(match[1] ? match[1] : match[0]); } } while (match != null);
1094 return myArray;
1095}
1096
1097// Parse arguments string array into an object
1098function parseArgs(argv) {
1099 var results = { '_': [] }, current = null;
1100 for (var i = 1, len = argv.length; i < len; i++) {
1101 var x = argv[i];
1102 if (x.length > 2 && x[0] == '-' && x[1] == '-') {
1103 if (current != null) { results[current] = true; }
1104 current = x.substring(2);
1105 } else {
1106 if (current != null) { results[current] = toNumberIfNumber(x); current = null; } else { results['_'].push(toNumberIfNumber(x)); }
1107 }
1108 }
1109 if (current != null) { results[current] = true; }
1110 return results;
1111}
1112
1113// Get server target url with a custom path
1114function getServerTargetUrl(path) {
1115 var x = mesh.ServerUrl;
1116 //sendConsoleText("mesh.ServerUrl: " + mesh.ServerUrl);
1117 if (x == null) { return null; }
1118 if (path == null) { path = ''; }
1119 x = http.parseUri(x);
1120 if (x == null) return null;
1121 return x.protocol + '//' + x.host + ':' + x.port + '/' + path;
1122}
1123
1124// Get server url. If the url starts with "*/..." change it, it not use the url as is.
1125function getServerTargetUrlEx(url) {
1126 if (url.substring(0, 2) == '*/') { return getServerTargetUrl(url.substring(2)); }
1127 return url;
1128}
1129
1130function sendWakeOnLanEx_interval() {
1131 var t = require('MeshAgent').wakesockets;
1132 if (t.list.length == 0) {
1133 clearInterval(t);
1134 delete require('MeshAgent').wakesockets;
1135 return;
1136 }
1137
1138 var mac = t.list.shift().split(':').join('')
1139 var magic = 'FFFFFFFFFFFF';
1140 for (var x = 1; x <= 16; ++x) { magic += mac; }
1141 var magicbin = Buffer.from(magic, 'hex');
1142
1143 for (var i in t.sockets) {
1144 t.sockets[i].send(magicbin, 7, '255.255.255.255');
1145 //sendConsoleText('Sending wake packet on ' + JSON.stringify(t.sockets[i].address()));
1146 }
1147}
1148function sendWakeOnLanEx(hexMacList) {
1149 var ret = 0;
1150
1151 if (require('MeshAgent').wakesockets == null) {
1152 // Create a new interval timer
1153 require('MeshAgent').wakesockets = setInterval(sendWakeOnLanEx_interval, 10);
1154 require('MeshAgent').wakesockets.sockets = [];
1155 require('MeshAgent').wakesockets.list = hexMacList;
1156
1157 var interfaces = require('os').networkInterfaces();
1158 for (var adapter in interfaces) {
1159 if (interfaces.hasOwnProperty(adapter)) {
1160 for (var i = 0; i < interfaces[adapter].length; ++i) {
1161 var addr = interfaces[adapter][i];
1162 if ((addr.family == 'IPv4') && (addr.mac != '00:00:00:00:00:00')) {
1163 try {
1164 var socket = require('dgram').createSocket({ type: 'udp4' });
1165 socket.bind({ address: addr.address });
1166 socket.setBroadcast(true);
1167 socket.setMulticastInterface(addr.address);
1168 socket.setMulticastTTL(1);
1169 socket.descriptorMetadata = 'WoL (' + addr.address + ')';
1170 require('MeshAgent').wakesockets.sockets.push(socket);
1171 ++ret;
1172 }
1173 catch (ex) { }
1174 }
1175 }
1176 }
1177 }
1178 }
1179 else {
1180 // Append to an existing interval timer
1181 for (var i in hexMacList) {
1182 require('MeshAgent').wakesockets.list.push(hexMacList[i]);
1183 }
1184 ret = require('MeshAgent').wakesockets.sockets.length;
1185 }
1186
1187 return ret;
1188}
1189
1190function server_promise_default(res, rej) {
1191 this.resolve = res;
1192 this.reject = rej;
1193}
1194function server_getUserImage(userid) {
1195 var xpromise = require('promise');
1196 var ret = new xpromise(server_promise_default);
1197
1198 if (require('MeshAgent')._promises == null) { require('MeshAgent')._promises = {}; }
1199 require('MeshAgent')._promises[ret._hashCode()] = ret;
1200 require('MeshAgent').SendCommand({ action: 'getUserImage', userid: userid, promise: ret._hashCode(), sentDefault: true });
1201 return ret;
1202}
1203require('MeshAgent')._consentTimers = {};
1204function server_set_consentTimer(id) {
1205 require('MeshAgent')._consentTimers[id] = new Date();
1206}
1207function server_check_consentTimer(id) {
1208 if (require('MeshAgent')._consentTimers[id] != null) {
1209 if ((new Date()) - require('MeshAgent')._consentTimers[id] < (60000 * 5)) return true;
1210 require('MeshAgent')._consentTimers[id] = null;
1211 }
1212 return false;
1213}
1214
1215function tunnel_finalized()
1216{
1217 console.info1('Tunnel Request Finalized');
1218}
1219function tunnel_checkServerIdentity(certs)
1220{
1221 /*
1222 try { sendConsoleText("certs[0].digest: " + certs[0].digest); } catch (ex) { sendConsoleText(ex); }
1223 try { sendConsoleText("certs[0].fingerprint: " + certs[0].fingerprint); } catch (ex) { sendConsoleText(ex); }
1224 try { sendConsoleText("control-digest: " + require('MeshAgent').ServerInfo.ControlChannelCertificate.digest); } catch (ex) { sendConsoleText(ex); }
1225 try { sendConsoleText("control-fingerprint: " + require('MeshAgent').ServerInfo.ControlChannelCertificate.fingerprint); } catch (ex) { sendConsoleText(ex); }
1226 */
1227
1228 // Check if this is an old agent, no certificate checks are possible in this situation. Display a warning.
1229 if ((require('MeshAgent').ServerInfo == null) || (require('MeshAgent').ServerInfo.ControlChannelCertificate == null) || (certs[0].digest == null)) { sendAgentMessage("This agent is using insecure tunnels, consider updating.", 3, 119, true); return; }
1230
1231 // If the tunnel certificate matches the control channel certificate, accept the connection
1232 if (require('MeshAgent').ServerInfo.ControlChannelCertificate.digest == certs[0].digest) return; // Control channel certificate matches using full cert hash
1233 if ((certs[0].fingerprint != null) && (require('MeshAgent').ServerInfo.ControlChannelCertificate.fingerprint == certs[0].fingerprint)) return; // Control channel certificate matches using public key hash
1234
1235 // Check that the certificate is the one expected by the server, fail if not.
1236 if ((tunnel_checkServerIdentity.servertlshash != null) && (tunnel_checkServerIdentity.servertlshash.toLowerCase() != certs[0].digest.split(':').join('').toLowerCase())) { throw new Error('BadCert') }
1237}
1238
1239function tunnel_onError()
1240{
1241 sendConsoleText("ERROR: Unable to connect relay tunnel to: " + this.url + ", " + JSON.stringify(e));
1242}
1243
1244// Handle a mesh agent command
1245function handleServerCommand(data) {
1246 if (typeof data == 'object') {
1247 // If this is a console command, parse it and call the console handler
1248 switch (data.action) {
1249 case 'agentupdate':
1250 agentUpdate_Start(data.url, { hash: data.hash, tlshash: data.servertlshash, sessionid: data.sessionid });
1251 break;
1252 case 'msg': {
1253 switch (data.type) {
1254 case 'console': { // Process a console command
1255 if ((typeof data.rights != 'number') || ((data.rights & 8) == 0) || ((data.rights & 16) == 0)) break; // Check console rights (Remote Control and Console)
1256 if (data.value && data.sessionid) {
1257 MeshServerLogEx(17, [data.value], "Processing console command: " + data.value, data);
1258 var args = splitArgs(data.value);
1259 processConsoleCommand(args[0].toLowerCase(), parseArgs(args), data.rights, data.sessionid);
1260 }
1261 break;
1262 }
1263 case 'tunnel':
1264 {
1265 if (data.value != null) { // Process a new tunnel connection request
1266 // Create a new tunnel object
1267 var xurl = getServerTargetUrlEx(data.value);
1268 if (xurl != null) {
1269 xurl = xurl.split('$').join('%24').split('@').join('%40'); // Escape the $ and @ characters
1270 var woptions = http.parseUri(xurl);
1271 woptions.perMessageDeflate = false;
1272 if (typeof data.perMessageDeflate == 'boolean') { woptions.perMessageDeflate = data.perMessageDeflate; }
1273
1274 // Perform manual server TLS certificate checking based on the certificate hash given by the server.
1275 woptions.rejectUnauthorized = 0;
1276 woptions.checkServerIdentity = tunnel_checkServerIdentity;
1277 woptions.checkServerIdentity.servertlshash = data.servertlshash;
1278
1279 //sendConsoleText(JSON.stringify(woptions));
1280 //sendConsoleText('TUNNEL: ' + JSON.stringify(data, null, 2));
1281
1282 var tunnel = http.request(woptions);
1283 tunnel.upgrade = onTunnelUpgrade;
1284 tunnel.on('error', tunnel_onError);
1285 tunnel.sessionid = data.sessionid;
1286 tunnel.rights = data.rights;
1287 tunnel.consent = data.consent;
1288 if (global._MSH && _MSH().LocalConsent != null) { tunnel.consent |= parseInt(_MSH().LocalConsent); }
1289 tunnel.privacybartext = data.privacybartext ? data.privacybartext : currentTranslation['privacyBar'];
1290 tunnel.username = data.username + (data.guestname ? (' - ' + data.guestname) : '');
1291 tunnel.realname = (data.realname ? data.realname : data.username) + (data.guestname ? (' - ' + data.guestname) : '');
1292 tunnel.guestuserid = data.guestuserid;
1293 tunnel.guestname = data.guestname;
1294 tunnel.userid = data.userid;
1295 if (server_check_consentTimer(tunnel.userid)) { tunnel.consent = (tunnel.consent & -57); } // Deleting Consent Requirement
1296 tunnel.desktopviewonly = data.desktopviewonly;
1297 tunnel.remoteaddr = data.remoteaddr;
1298 tunnel.state = 0;
1299 tunnel.url = xurl;
1300 tunnel.protocol = 0;
1301 tunnel.soptions = data.soptions;
1302 tunnel.consentTimeout = (tunnel.soptions && tunnel.soptions.consentTimeout) ? tunnel.soptions.consentTimeout : 30;
1303 tunnel.consentAutoAccept = (tunnel.soptions && (tunnel.soptions.consentAutoAccept === true));
1304 tunnel.consentAutoAcceptIfNoUser = (tunnel.soptions && (tunnel.soptions.consentAutoAcceptIfNoUser === true));
1305 tunnel.consentAutoAcceptIfDesktopNoUser = (tunnel.soptions && (tunnel.soptions.consentAutoAcceptIfDesktopNoUser === true));
1306 tunnel.consentAutoAcceptIfTerminalNoUser = (tunnel.soptions && (tunnel.soptions.consentAutoAcceptIfTerminalNoUser === true));
1307 tunnel.consentAutoAcceptIfFileNoUser = (tunnel.soptions && (tunnel.soptions.consentAutoAcceptIfFileNoUser === true));
1308 tunnel.consentAutoAcceptIfLocked = (tunnel.soptions && (tunnel.soptions.consentAutoAcceptIfLocked === true));
1309 tunnel.consentAutoAcceptIfDesktopLocked = (tunnel.soptions && (tunnel.soptions.consentAutoAcceptIfDesktopLocked === true));
1310 tunnel.consentAutoAcceptIfTerminalLocked = (tunnel.soptions && (tunnel.soptions.consentAutoAcceptIfTerminalLocked === true));
1311 tunnel.consentAutoAcceptIfFileLocked = (tunnel.soptions && (tunnel.soptions.consentAutoAcceptIfFileLocked === true));
1312 tunnel.oldStyle = (tunnel.soptions && tunnel.soptions.oldStyle) ? tunnel.soptions.oldStyle : false;
1313 tunnel.tcpaddr = data.tcpaddr;
1314 tunnel.tcpport = data.tcpport;
1315 tunnel.udpaddr = data.udpaddr;
1316 tunnel.udpport = data.udpport;
1317
1318 // Put the tunnel in the tunnels list
1319 var index = nextTunnelIndex++;
1320 tunnel.index = index;
1321 tunnels[index] = tunnel;
1322 tunnel.once('~', tunnel_finalized);
1323 tunnel.end();
1324
1325 //sendConsoleText('New tunnel connection #' + index + ': ' + tunnel.url + ', rights: ' + tunnel.rights, data.sessionid);
1326 }
1327 }
1328 break;
1329 }
1330 case 'endtunnel': {
1331 // Terminate one or more tunnels
1332 if ((data.rights != 4294967295) && (data.xuserid != data.userid)) return; // This command requires full admin rights on the device or user self-closes it's own sessions
1333 for (var i in tunnels) {
1334 if ((tunnels[i].userid == data.xuserid) && (tunnels[i].guestname == data.guestname)) {
1335 var disconnect = false, msgid = 0;
1336 if ((data.protocol == 'kvm') && (tunnels[i].protocol == 2)) { msgid = 134; disconnect = true; }
1337 else if ((data.protocol == 'terminal') && (tunnels[i].protocol == 1)) { msgid = 135; disconnect = true; }
1338 else if ((data.protocol == 'files') && (tunnels[i].protocol == 5)) { msgid = 136; disconnect = true; }
1339 else if ((data.protocol == 'tcp') && (tunnels[i].tcpport != null)) { msgid = 137; disconnect = true; }
1340 else if ((data.protocol == 'udp') && (tunnels[i].udpport != null)) { msgid = 137; disconnect = true; }
1341 if (disconnect) {
1342 if (tunnels[i].s != null) { tunnels[i].s.end(); } else { tunnels[i].end(); }
1343
1344 // Log tunnel disconnection
1345 var xusername = data.xuserid.split('/')[2];
1346 if (data.guestname != null) { xusername += '/' + guestname; }
1347 MeshServerLogEx(msgid, [xusername], "Forcibly disconnected session of user: " + xusername, data);
1348 }
1349 }
1350 }
1351 break;
1352 }
1353 case 'messagebox': {
1354 // Display a message box
1355 if (data.title && data.msg) {
1356 MeshServerLogEx(18, [data.title, data.msg], "Displaying message box, title=" + data.title + ", message=" + data.msg, data);
1357 if (process.platform == 'win32') {
1358 if (global._clientmessage) {
1359 global._clientmessage.addMessage(data.msg);
1360 }
1361 else {
1362 try {
1363 require('win-dialog');
1364 var ipr = server_getUserImage(data.userid);
1365 ipr.title = data.title;
1366 ipr.message = data.msg;
1367 ipr.username = data.username;
1368 if (data.realname && (data.realname != '')) { ipr.username = data.realname; }
1369 ipr.timeout = (typeof data.timeout === 'number' ? data.timeout : 120000);
1370 global._clientmessage = ipr.then(function (img) {
1371 var options = { b64Image: img.split(',').pop(), background: color_options.background, foreground: color_options.foreground }
1372 if (this.timeout != 0) { options.timeout = this.timeout; }
1373 this.messagebox = require('win-dialog').create(this.title, this.message, this.username, options);
1374 this.__childPromise.addMessage = this.messagebox.addMessage.bind(this.messagebox);
1375 return (this.messagebox);
1376 });
1377
1378 global._clientmessage.then(function () { global._clientmessage = null; });
1379 }
1380 catch (z) {
1381 try { require('message-box').create(data.title, data.msg, 120).then(function () { }).catch(function () { }); } catch (ex) { }
1382 }
1383 }
1384 }
1385 else {
1386 try { require('message-box').create(data.title, data.msg, 120).then(function () { }).catch(function () { }); } catch (ex) { }
1387 }
1388 }
1389 break;
1390 }
1391 case 'ps': {
1392 // Return the list of running processes
1393 if (data.sessionid) {
1394 processManager.getProcesses(function (plist) {
1395 mesh.SendCommand({ action: 'msg', type: 'ps', value: JSON.stringify(plist), sessionid: data.sessionid });
1396 });
1397 }
1398 break;
1399 }
1400 case 'psinfo': {
1401 // Requestion details information about a process
1402 if (data.pid) {
1403 var info = {}; // TODO: Replace with real data. Feel free not to give all values if not available.
1404 try {
1405 info = processManager.getProcessInfo(data.pid);
1406 }catch(e){ }
1407 /*
1408 info.processUser = "User"; // String
1409 info.processDomain = "Domain"; // String
1410 info.cmd = "abc"; // String
1411 info.processName = "dummydata";
1412 info.privateMemorySize = 123; // Bytes
1413 info.virtualMemorySize = 123; // Bytes
1414 info.workingSet = 123; // Bytes
1415 info.totalProcessorTime = 123; // Seconds
1416 info.userProcessorTime = 123; // Seconds
1417 info.startTime = "2012-12-30T23:59:59.000Z"; // Time in UTC ISO format
1418 info.sessionId = 123; // Number
1419 info.privilegedProcessorTime = 123; // Seconds
1420 info.PriorityBoostEnabled = true; // Boolean
1421 info.peakWorkingSet = 123; // Bytes
1422 info.peakVirtualMemorySize = 123; // Bytes
1423 info.peakPagedMemorySize = 123; // Bytes
1424 info.pagedSystemMemorySize = 123; // Bytes
1425 info.pagedMemorySize = 123; // Bytes
1426 info.nonpagedSystemMemorySize = 123; // Bytes
1427 info.mainWindowTitle = "dummydata"; // String
1428 info.machineName = "dummydata"; // Only set this if machine name is not "."
1429 info.handleCount = 123; // Number
1430 */
1431 mesh.SendCommand({ action: 'msg', type: 'psinfo', pid: data.pid, sessionid: data.sessionid, value: info });
1432 }
1433 break;
1434 }
1435 case 'pskill': {
1436 // Kill a process
1437 if (data.value) {
1438 var info = data.value.split('|'), pid = data.value, msg = " (Unknown)";
1439 if (info.length > 1) { pid = info[0]; msg = " (" + info[1] + ")"; } else { info[1] = "Unknown"; }
1440 MeshServerLogEx(19, info, "Killing process " + pid + msg, data);
1441 try { process.kill(parseInt(pid)); } catch (ex) { sendConsoleText("pskill: " + JSON.stringify(ex)); }
1442 }
1443 break;
1444 }
1445 case 'service': {
1446 // return information about the service
1447 try {
1448 var service = require('service-manager').manager.getService(data.serviceName);
1449 if (service != null) {
1450 var reply = {
1451 name: (service.name ? service.name : ''),
1452 status: (service.status ? service.status : ''),
1453 startType: (service.startType ? service.startType : ''),
1454 failureActions: (service.failureActions ? service.failureActions : ''),
1455 installedDate: (service.installedDate ? service.installedDate : ''),
1456 installedBy: (service.installedBy ? service.installedBy : '') ,
1457 user: (service.user ? service.user : '')
1458 };
1459 if(reply.installedBy.indexOf('S-1-5') != -1) {
1460 var cmd = "(Get-WmiObject -Class win32_userAccount -Filter \"SID='"+service.installedBy+"'\").Caption";
1461 var replydata = "";
1462 var pws = require('child_process').execFile(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', ['powershell', '-noprofile', '-nologo', '-command', '-'], {});
1463 pws.descriptorMetadata = 'UserSIDPowerShell';
1464 pws.stdout.on('data', function (c) { replydata += c.toString(); });
1465 pws.stderr.on('data', function (c) { replydata += c.toString(); });
1466 pws.stdin.write(cmd + '\r\nexit\r\n');
1467 pws.on('exit', function () {
1468 if (replydata != "") reply.installedBy = replydata;
1469 mesh.SendCommand({ action: 'msg', type: 'service', value: JSON.stringify(reply), sessionid: data.sessionid });
1470 delete pws;
1471 });
1472 } else {
1473 mesh.SendCommand({ action: 'msg', type: 'service', value: JSON.stringify(reply), sessionid: data.sessionid });
1474 }
1475 }
1476 } catch (ex) {
1477 mesh.SendCommand({ action: 'msg', type: 'service', error: ex, sessionid: data.sessionid })
1478 }
1479 }
1480 case 'services': {
1481 // Return the list of installed services
1482 var services = null;
1483 try { services = require('service-manager').manager.enumerateService(); } catch (ex) { }
1484 if (services != null) { mesh.SendCommand({ action: 'msg', type: 'services', value: JSON.stringify(services), sessionid: data.sessionid }); }
1485 break;
1486 }
1487 case 'serviceStop': {
1488 // Stop a service
1489 try {
1490 var service = require('service-manager').manager.getService(data.serviceName);
1491 if (service != null) { service.stop(); }
1492 } catch (ex) { }
1493 break;
1494 }
1495 case 'serviceStart': {
1496 // Start a service
1497 try {
1498 var service = require('service-manager').manager.getService(data.serviceName);
1499 if (service != null) { service.start(); }
1500 } catch (ex) { }
1501 break;
1502 }
1503 case 'serviceRestart': {
1504 // Restart a service
1505 try {
1506 var service = require('service-manager').manager.getService(data.serviceName);
1507 if (service != null) { service.restart(); }
1508 } catch (ex) { }
1509 break;
1510 }
1511 case 'deskBackground':
1512 {
1513 // Toggle desktop background
1514 try {
1515 if (process.platform == 'win32') {
1516 var stype = require('user-sessions').getProcessOwnerName(process.pid).tsid == 0 ? 1 : 0;
1517 var sid = undefined;
1518 if (stype == 1) {
1519 if (require('MeshAgent')._tsid != null) {
1520 stype = 5;
1521 sid = require('MeshAgent')._tsid;
1522 }
1523 }
1524 var id = require('user-sessions').getProcessOwnerName(process.pid).tsid == 0 ? 1 : 0;
1525 var child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0='], { type: stype, uid: sid });
1526 child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
1527 child.stderr.on('data', function () { });
1528 child.waitExit();
1529 var current = child.stdout.str.trim();
1530 if (current != '') { require('MeshAgent')._wallpaper = current; }
1531 child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0=', current != '' ? '""' : require('MeshAgent')._wallpaper], { type: stype, uid: sid });
1532 child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
1533 child.stderr.on('data', function () { });
1534 child.waitExit();
1535 mesh.SendCommand({ action: 'msg', type: 'deskBackground', sessionid: data.sessionid, data: (current != '' ? "" : require('MeshAgent')._wallpaper), });
1536 } else {
1537 var id = require('user-sessions').consoleUid();
1538 var current = require('linux-gnome-helpers').getDesktopWallpaper(id);
1539 if (current != '/dev/null') { require('MeshAgent')._wallpaper = current; }
1540 require('linux-gnome-helpers').setDesktopWallpaper(id, current != '/dev/null' ? undefined : require('MeshAgent')._wallpaper);
1541 mesh.SendCommand({ action: 'msg', type: 'deskBackground', sessionid: data.sessionid, data: (current != '/dev/null' ? "" : require('MeshAgent')._wallpaper), });
1542 }
1543 } catch (ex) {
1544 sendConsoleText(ex);
1545 }
1546 break;
1547 }
1548 case 'openUrl': {
1549 // Open a local web browser and return success/fail
1550 MeshServerLogEx(20, [data.url], "Opening: " + data.url, data);
1551 sendConsoleText("OpenURL: " + data.url);
1552 if (data.url) { mesh.SendCommand({ action: 'msg', type: 'openUrl', url: data.url, sessionid: data.sessionid, success: (openUserDesktopUrl(data.url) != null) }); }
1553 break;
1554 }
1555 case 'getclip': {
1556 // Send the load clipboard back to the user
1557 //sendConsoleText('getClip: ' + JSON.stringify(data));
1558 if (require('MeshAgent').isService) {
1559 require('clipboard').dispatchRead().then(function (str) {
1560 if (str) {
1561 MeshServerLogEx(21, [str.length], "Getting clipboard content, " + str.length + " byte(s)", data);
1562 mesh.SendCommand({ action: 'msg', type: 'getclip', sessionid: data.sessionid, data: str, tag: data.tag });
1563 }
1564 });
1565 } else {
1566 require('clipboard').read().then(function (str) {
1567 if (str) {
1568 MeshServerLogEx(21, [str.length], "Getting clipboard content, " + str.length + " byte(s)", data);
1569 mesh.SendCommand({ action: 'msg', type: 'getclip', sessionid: data.sessionid, data: str, tag: data.tag });
1570 }
1571 });
1572 }
1573 break;
1574 }
1575 case 'setclip': {
1576 if (pendingSetClip) return;
1577 // Set the load clipboard to a user value
1578 if (typeof data.data == 'string') {
1579 MeshServerLogEx(22, [data.data.length], "Setting clipboard content, " + data.data.length + " byte(s)", data);
1580 if (require('MeshAgent').isService) {
1581 if (process.platform != 'win32') {
1582 require('clipboard').dispatchWrite(data.data);
1583 mesh.SendCommand({ action: 'msg', type: 'setclip', sessionid: data.sessionid, success: true });
1584 }
1585 else {
1586 var clipargs = data.data;
1587 var uid = require('user-sessions').consoleUid();
1588 var user = require('user-sessions').getUsername(uid);
1589 var domain = require('user-sessions').getDomain(uid);
1590 user = (domain + '\\' + user);
1591
1592 if (this._dispatcher) { this._dispatcher.close(); }
1593 this._dispatcher = require('win-dispatcher').dispatch({ user: user, modules: [{ name: 'clip-dispatch', script: "module.exports = { dispatch: function dispatch(val) { require('clipboard')(val); process.exit(); } };" }], launch: { module: 'clip-dispatch', method: 'dispatch', args: [clipargs] } });
1594 this._dispatcher.parent = this;
1595 //require('events').setFinalizerMetadata.call(this._dispatcher, 'clip-dispatch');
1596 pendingSetClip = true;
1597 this._dispatcher.on('connection', function (c) {
1598 this._c = c;
1599 this._c.root = this.parent;
1600 this._c.on('end', function ()
1601 {
1602 pendingSetClip = false;
1603 try { this.root._dispatcher.close(); } catch (ex) { }
1604 this.root._dispatcher = null;
1605 this.root = null;
1606 mesh.SendCommand({ action: 'msg', type: 'setclip', sessionid: data.sessionid, success: true });
1607 });
1608 });
1609 }
1610 }
1611 else {
1612 require('clipboard')(data.data);
1613 mesh.SendCommand({ action: 'msg', type: 'setclip', sessionid: data.sessionid, success: true });
1614 } // Set the clipboard
1615 }
1616 break;
1617 }
1618 case 'userSessions': {
1619 mesh.SendCommand({ action: 'msg', type: 'userSessions', sessionid: data.sessionid, data: require('kvm-helper').users(), tag: data.tag });
1620 break;
1621 }
1622 case 'cpuinfo':
1623 // CPU & memory utilization
1624 var cpuuse = require('sysinfo').cpuUtilization();
1625 cpuuse.sessionid = data.sessionid;
1626 cpuuse.tag = data.tag;
1627 cpuuse.then(function (data) {
1628 mesh.SendCommand(JSON.stringify(
1629 {
1630 action: 'msg',
1631 type: 'cpuinfo',
1632 cpu: data,
1633 memory: require('sysinfo').memUtilization(),
1634 thermals: require('sysinfo').thermals == null ? [] : require('sysinfo').thermals(),
1635 sessionid: this.sessionid,
1636 tag: this.tag
1637 }));
1638 }, function (ex) { });
1639 break;
1640 case 'localapp':
1641 // Send a message to a local application
1642 sendConsoleText('localappMsg: ' + data.appid + ', ' + JSON.stringify(data.value));
1643 if (data.appid != null) { sendToRegisteredApp(data.appid, data.value); } else { broadcastToRegisteredApps(data.value); }
1644 break;
1645 case 'alertbox': {
1646 // Display an old style alert box
1647 if (data.title && data.msg) {
1648 MeshServerLogEx(158, [data.title, data.msg], "Displaying alert box, title=" + data.title + ", message=" + data.msg, data);
1649 try { require('message-box').create(data.title, data.msg, 9999, 1).then(function () { }).catch(function () { }); } catch (ex) { }
1650 }
1651 break;
1652 }
1653 case 'sysinfo': {
1654 // Send system information
1655 getSystemInformation(function (results) {
1656 if ((results != null) && (data.hash != results.hash)) { mesh.SendCommand({ action: 'sysinfo', sessionid: this.sessionid, data: results }); }
1657 });
1658 break;
1659 }
1660 default:
1661 // Unknown action, ignore it.
1662 break;
1663 }
1664 break;
1665 }
1666 case 'acmactivate': {
1667 if (amt != null) {
1668 MeshServerLogEx(23, null, "Attempting Intel AMT ACM mode activation", data);
1669 amt.setAcmResponse(data);
1670 }
1671 break;
1672 }
1673 case 'wakeonlan': {
1674 // Send wake-on-lan on all interfaces for all MAC addresses in data.macs array. The array is a list of HEX MAC addresses.
1675 //sendConsoleText("Server requesting wake-on-lan for: " + data.macs.join(', '));
1676 sendWakeOnLanEx(data.macs);
1677 sendWakeOnLanEx(data.macs);
1678 sendWakeOnLanEx(data.macs);
1679 break;
1680 }
1681 case 'runcommands': {
1682 if (mesh.cmdchild != null) { sendConsoleText("Run commands can't execute, already busy."); break; }
1683 if (!data.reply) sendConsoleText("Run commands (" + data.runAsUser + "): " + data.cmds);
1684
1685 // data.runAsUser: 0=Agent,1=UserOrAgent,2=UserOnly
1686 var options = {};
1687 if (data.runAsUser > 0) {
1688 try { options.uid = require('user-sessions').consoleUid(); } catch (ex) { }
1689 options.type = require('child_process').SpawnTypes.TERM;
1690 }
1691 if (data.runAsUser == 2) {
1692 if (options.uid == null) break;
1693 if (((require('user-sessions').minUid != null) && (options.uid < require('user-sessions').minUid()))) break; // This command can only run as user.
1694 }
1695 var replydata = "";
1696 if (process.platform == 'win32') {
1697 if (data.type == 1) {
1698 // Windows command shell
1699 mesh.cmdchild = require('child_process').execFile(process.env['windir'] + '\\system32\\cmd.exe', ['cmd'], options);
1700 mesh.cmdchild.descriptorMetadata = 'UserCommandsShell';
1701 mesh.cmdchild.stdout.on('data', function (c) { replydata += c.toString(); sendConsoleText(c.toString()); });
1702 mesh.cmdchild.stderr.on('data', function (c) { replydata += c.toString(); sendConsoleText(c.toString()); });
1703 mesh.cmdchild.stdin.write(data.cmds + '\r\nexit\r\n');
1704 mesh.cmdchild.on('exit', function () {
1705 if (data.reply) {
1706 mesh.SendCommand({ action: 'msg', type: 'runcommands', result: replydata, sessionid: data.sessionid, responseid: data.responseid });
1707 } else {
1708 sendConsoleText("Run commands completed.");
1709 }
1710 delete mesh.cmdchild;
1711 });
1712 } else if (data.type == 2) {
1713 // Windows Powershell
1714 mesh.cmdchild = require('child_process').execFile(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', ['powershell', '-noprofile', '-nologo', '-command', '-'], options);
1715 mesh.cmdchild.descriptorMetadata = 'UserCommandsPowerShell';
1716 mesh.cmdchild.stdout.on('data', function (c) { replydata += c.toString(); sendConsoleText(c.toString()); });
1717 mesh.cmdchild.stderr.on('data', function (c) { replydata += c.toString(); sendConsoleText(c.toString()); });
1718 mesh.cmdchild.stdin.write(data.cmds + '\r\nexit\r\n');
1719 mesh.cmdchild.on('exit', function () {
1720 if (data.reply) {
1721 mesh.SendCommand({ action: 'msg', type: 'runcommands', result: replydata, sessionid: data.sessionid, responseid: data.responseid });
1722 } else {
1723 sendConsoleText("Run commands completed.");
1724 }
1725 delete mesh.cmdchild;
1726 });
1727 }
1728 } else if (data.type == 3) {
1729 // Linux shell
1730 mesh.cmdchild = require('child_process').execFile('/bin/sh', ['sh'], options);
1731 mesh.cmdchild.descriptorMetadata = 'UserCommandsShell';
1732 mesh.cmdchild.stdout.on('data', function (c) { replydata += c.toString(); sendConsoleText(c.toString()); });
1733 mesh.cmdchild.stderr.on('data', function (c) { replydata += c.toString(); sendConsoleText(c.toString()); });
1734 mesh.cmdchild.stdin.write(data.cmds.split('\r').join('') + '\nexit\n');
1735 mesh.cmdchild.on('exit', function () {
1736 if (data.reply) {
1737 mesh.SendCommand({ action: 'msg', type: 'runcommands', result: replydata, sessionid: data.sessionid, responseid: data.responseid });
1738 } else {
1739 sendConsoleText("Run commands completed.");
1740 }
1741 delete mesh.cmdchild;
1742 });
1743 }
1744 break;
1745 }
1746 case 'uninstallagent':
1747 // Uninstall this agent
1748 var agentName = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent';
1749 try {
1750 agentName = require('MeshAgent').serviceName;
1751 } catch (ex) { }
1752
1753 if (require('service-manager').manager.getService(agentName).isMe()) {
1754 try { diagnosticAgent_uninstall(); } catch (ex) { }
1755 var js = "require('service-manager').manager.getService('" + agentName + "').stop(); require('service-manager').manager.uninstallService('" + agentName + "'); process.exit();";
1756 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 });
1757 }
1758 break;
1759 case 'poweraction': {
1760 // Server telling us to execute a power action
1761 if ((mesh.ExecPowerState != undefined) && (data.actiontype)) {
1762 var forced = 0;
1763 if (data.forced == 1) { forced = 1; }
1764 data.actiontype = parseInt(data.actiontype);
1765 MeshServerLogEx(25, [data.actiontype, forced], "Performing power action=" + data.actiontype + ", forced=" + forced, data);
1766 sendConsoleText("Performing power action=" + data.actiontype + ", forced=" + forced + '.');
1767 var r = mesh.ExecPowerState(data.actiontype, forced);
1768 sendConsoleText("ExecPowerState returned code: " + r);
1769 }
1770 break;
1771 }
1772 case 'iplocation': {
1773 // 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
1774 getIpLocationData(function (location) { mesh.SendCommand({ action: 'iplocation', type: 'publicip', value: location }); });
1775 break;
1776 }
1777 case 'toast': {
1778 // Display a toast message
1779 if (data.title && data.msg) {
1780 MeshServerLogEx(26, [data.title, data.msg], "Displaying toast message, title=" + data.title + ", message=" + data.msg, data);
1781 data.msg = data.msg.split('\r').join('\\r').split('\n').join('\\n');
1782 try { require('toaster').Toast(data.title, data.msg); } catch (ex) { }
1783 }
1784 break;
1785 }
1786 case 'openUrl': {
1787 // Open a local web browser and return success/fail
1788 //sendConsoleText('OpenURL: ' + data.url);
1789 MeshServerLogEx(20, [data.url], "Opening: " + data.url, data);
1790 if (data.url) { mesh.SendCommand({ action: 'openUrl', url: data.url, sessionid: data.sessionid, success: (openUserDesktopUrl(data.url) != null) }); }
1791 break;
1792 }
1793 case 'amtconfig': {
1794 // Perform Intel AMT activation and/or configuration
1795 if ((apftunnel != null) || (amt == null) || (typeof data.user != 'string') || (typeof data.pass != 'string')) break;
1796 amt.getMeiState(15, function (state) {
1797 if ((apftunnel != null) || (amt == null)) return;
1798 if ((state == null) || (state.ProvisioningState == null)) return;
1799 if ((state.UUID == null) || (state.UUID.length != 36)) return; // Bad UUID
1800 getAmtOsDnsSuffix(state, function () {
1801 var apfarg = {
1802 mpsurl: mesh.ServerUrl.replace('/agent.ashx', '/apf.ashx'),
1803 mpsuser: data.user, // Agent user name
1804 mpspass: data.pass, // Encrypted login cookie
1805 mpskeepalive: 60000,
1806 clientname: state.OsHostname,
1807 clientaddress: '127.0.0.1',
1808 clientuuid: state.UUID,
1809 conntype: 2, // 0 = CIRA, 1 = Relay, 2 = LMS. The correct value is 2 since we are performing an LMS relay, other values for testing.
1810 meiState: state // MEI state will be passed to MPS server
1811 };
1812 addAmtEvent('LMS tunnel start.');
1813 apftunnel = require('amt-apfclient')({ debug: false }, apfarg);
1814 apftunnel.onJsonControl = handleApfJsonControl;
1815 apftunnel.onChannelClosed = function () { addAmtEvent('LMS tunnel closed.'); apftunnel = null; }
1816 try { apftunnel.connect(); } catch (ex) { }
1817 });
1818 });
1819 break;
1820 }
1821 case 'getScript': {
1822 // Received a configuration script from the server
1823 sendConsoleText('getScript: ' + JSON.stringify(data));
1824 break;
1825 }
1826 case 'sysinfo': {
1827 // Fetch system information
1828 getSystemInformation(function (results) {
1829 if ((results != null) && (data.hash != results.hash)) { mesh.SendCommand({ action: 'sysinfo', sessionid: this.sessionid, data: results }); }
1830 });
1831 break;
1832 }
1833 case 'ping': { mesh.SendCommand('{"action":"pong"}'); break; }
1834 case 'pong': { break; }
1835 case 'plugin': {
1836 try { require(data.plugin).consoleaction(data, data.rights, data.sessionid, this); } catch (ex) { throw ex; }
1837 break;
1838 }
1839 case 'coredump':
1840 // Set the current agent coredump situation.s
1841 if (data.value === true) {
1842 if (process.platform == 'win32') {
1843 // TODO: This replace() below is not ideal, would be better to remove the .exe at the end instead of replace.
1844 process.coreDumpLocation = process.execPath.replace('.exe', '.dmp');
1845 } else {
1846 process.coreDumpLocation = (process.cwd() != '//') ? (process.cwd() + 'core') : null;
1847 }
1848 } else if (data.value === false) {
1849 process.coreDumpLocation = null;
1850 }
1851 break;
1852 case 'getcoredump':
1853 // Ask the agent if a core dump is currently available, if yes, also return the hash of the agent.
1854 var r = { action: 'getcoredump', value: (process.coreDumpLocation != null) };
1855 var coreDumpPath = null;
1856 if (process.platform == 'win32') { coreDumpPath = process.coreDumpLocation; } else { coreDumpPath = (process.cwd() != '//') ? fs.existsSync(process.cwd() + 'core') : null; }
1857 if ((coreDumpPath != null) && (fs.existsSync(coreDumpPath))) {
1858 try {
1859 var coredate = fs.statSync(coreDumpPath).mtime;
1860 var coretime = new Date(coredate).getTime();
1861 var agenttime = new Date(fs.statSync(process.execPath).mtime).getTime();
1862 if (coretime > agenttime) { r.exists = (db.Get('CoreDumpTime') != coredate); }
1863 } catch (ex) { }
1864 }
1865 if (r.exists == true) {
1866 r.agenthashhex = getSHA384FileHash(process.execPath).toString('hex'); // Hash of current agent
1867 r.corehashhex = getSHA384FileHash(coreDumpPath).toString('hex'); // Hash of core dump file
1868 }
1869 mesh.SendCommand(JSON.stringify(r));
1870 break;
1871 case 'meshToolInfo':
1872 if (data.pipe == true) { delete data.pipe; delete data.action; data.cmd = 'meshToolInfo'; broadcastToRegisteredApps(data); }
1873 if (data.tag == 'info') { sendConsoleText(JSON.stringify(data, null, 2)); }
1874 if (data.tag == 'install') {
1875 data.func = function (options, success) {
1876 sendConsoleText('Download of MeshCentral Assistant ' + (success ? 'succeed' : 'failed'));
1877 if (success) {
1878 // TODO: Install & Run
1879 }
1880 }
1881 data.filename = 'MeshAssistant.exe';
1882 downloadFile(data);
1883 }
1884 break;
1885 case 'getUserImage':
1886 if (data.pipe == true) { delete data.pipe; delete data.action; data.cmd = 'getUserImage'; broadcastToRegisteredApps(data); }
1887 if (data.tag == 'info') { sendConsoleText(JSON.stringify(data, null, 2)); }
1888 if (data.promise != null && require('MeshAgent')._promises[data.promise] != null) {
1889 var p = require('MeshAgent')._promises[data.promise];
1890 delete require('MeshAgent')._promises[data.promise];
1891 p.resolve(data.image);
1892 }
1893 break;
1894 case 'wget': // Server uses this command to tell the agent to download a file using HTTPS/GET and place it in a given path. This is used for one-to-many file uploads.
1895 agentFileHttpPendingRequests.push(data);
1896 serverFetchFile();
1897 break;
1898 case 'serverInfo': // Server information
1899 obj.serverInfo = data;
1900 delete obj.serverInfo.action;
1901 break;
1902 case 'errorlog': // Return agent error log
1903 try { mesh.SendCommand(JSON.stringify({ action: 'errorlog', log: require('util-agentlog').read(data.startTime) })); } catch (ex) { }
1904 break;
1905 default:
1906 // Unknown action, ignore it.
1907 break;
1908 }
1909 }
1910}
1911
1912// On non-Windows platforms, we need to query the DHCP server for the DNS suffix
1913function getAmtOsDnsSuffix(mestate, func) {
1914 if ((process.platform == 'win32') || (mestate.net0 == null) || (mestate.net0.mac == null)) { func(mestate); return; }
1915 try { require('linux-dhcp') } catch (ex) { func(mestate); return; }
1916 require('linux-dhcp').client.info(mestate.net0.mac).then(function (d) {
1917 if ((typeof d.options == 'object') && (typeof d.options.domainname == 'string')) { mestate.OsDnsSuffix = d.options.domainname; }
1918 func(mestate);
1919 }, function (e) {
1920 console.log('DHCP error', e);
1921 func(mestate);
1922 });
1923}
1924
1925// Download a file from the server and check the hash.
1926// This download is similar to the one used for meshcore self-update.
1927var trustedDownloads = {};
1928function downloadFile(downloadoptions) {
1929 var options = require('http').parseUri(downloadoptions.url);
1930 options.rejectUnauthorized = false;
1931 options.checkServerIdentity = function checkServerIdentity(certs) {
1932 // If the tunnel certificate matches the control channel certificate, accept the connection
1933 try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.digest == certs[0].digest) return; } catch (ex) { }
1934 try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.fingerprint == certs[0].fingerprint) return; } catch (ex) { }
1935 // Check that the certificate is the one expected by the server, fail if not.
1936 if (checkServerIdentity.servertlshash == null) { if (require('MeshAgent').ServerInfo == null || require('MeshAgent').ServerInfo.ControlChannelCertificate == null) return; throw new Error('BadCert'); }
1937 if (certs[0].digest == null) return;
1938 if ((checkServerIdentity.servertlshash != null) && (checkServerIdentity.servertlshash.toLowerCase() != certs[0].digest.split(':').join('').toLowerCase())) { throw new Error('BadCert') }
1939 }
1940 //options.checkServerIdentity.servertlshash = downloadoptions.serverhash;
1941 trustedDownloads[downloadoptions.name] = downloadoptions;
1942 trustedDownloads[downloadoptions.name].dl = require('https').get(options);
1943 trustedDownloads[downloadoptions.name].dl.on('error', function (e) { downloadoptions.func(downloadoptions, false); delete trustedDownloads[downloadoptions.name]; });
1944 trustedDownloads[downloadoptions.name].dl.on('response', function (img) {
1945 this._file = require('fs').createWriteStream(trustedDownloads[downloadoptions.name].filename, { flags: 'wb' });
1946 this._filehash = require('SHA384Stream').create();
1947 this._filehash.on('hash', function (h) { if ((downloadoptions.hash != null) && (downloadoptions.hash.toLowerCase() != h.toString('hex').toLowerCase())) { downloadoptions.func(downloadoptions, false); delete trustedDownloads[downloadoptions.name]; return; } downloadoptions.func(downloadoptions, true); });
1948 img.pipe(this._file);
1949 img.pipe(this._filehash);
1950 });
1951}
1952
1953// Handle APF JSON control commands
1954function handleApfJsonControl(data) {
1955 if (data.action == 'console') { addAmtEvent(data.msg); } // Add console message to AMT event log
1956 if (data.action == 'mestate') { amt.getMeiState(15, function (state) { apftunnel.updateMeiState(state); }); } // Update the MEI state
1957 if (data.action == 'close') { try { apftunnel.disconnect(); } catch (ex) { } apftunnel = null; } // Close the CIRA-LMS connection
1958 if (amt.amtMei != null) {
1959 if (data.action == 'deactivate') { // Request CCM deactivation
1960 amt.amtMei.unprovision(1, function (status) { if (apftunnel) apftunnel.sendMeiDeactivationState(status); }); // 0 = Success
1961 }
1962 if (data.action == 'startTlsHostConfig') { // Request start of host based TLS ACM activation
1963 amt.amtMei.startConfigurationHBased(Buffer.from(data.hash, 'hex'), data.hostVpn, data.dnsSuffixList, function (response) { apftunnel.sendStartTlsHostConfigResponse(response); });
1964 }
1965 if (data.action == 'stopConfiguration') { // Request Intel AMT stop configuration.
1966 amt.amtMei.stopConfiguration(function (status) { apftunnel.sendStopConfigurationResponse(status); });
1967 }
1968 }
1969}
1970
1971// Agent just get a file from the server and save it locally.
1972function serverFetchFile() {
1973 if ((Object.keys(agentFileHttpRequests).length > 4) || (agentFileHttpPendingRequests.length == 0)) return; // No more than 4 active HTTPS requests to the server.
1974 var data = agentFileHttpPendingRequests.shift();
1975 if ((data.overwrite !== true) && fs.existsSync(data.path)) return; // Don't overwrite an existing file.
1976 if (data.createFolder) { try { fs.mkdirSync(data.folder); } catch (ex) { } } // If requested, create the local folder.
1977 data.url = 'http' + getServerTargetUrlEx('*/').substring(2);
1978 var agentFileHttpOptions = http.parseUri(data.url);
1979 agentFileHttpOptions.path = data.urlpath;
1980
1981 // Perform manual server TLS certificate checking based on the certificate hash given by the server.
1982 agentFileHttpOptions.rejectUnauthorized = 0;
1983 agentFileHttpOptions.checkServerIdentity = function checkServerIdentity(certs) {
1984 // If the tunnel certificate matches the control channel certificate, accept the connection
1985 try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.digest == certs[0].digest) return; } catch (ex) { }
1986 try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.fingerprint == certs[0].fingerprint) return; } catch (ex) { }
1987 // Check that the certificate is the one expected by the server, fail if not.
1988 if ((checkServerIdentity.servertlshash != null) && (checkServerIdentity.servertlshash.toLowerCase() != certs[0].digest.split(':').join('').toLowerCase())) { throw new Error('BadCert') }
1989 }
1990 agentFileHttpOptions.checkServerIdentity.servertlshash = data.servertlshash;
1991
1992 if (agentFileHttpOptions == null) return;
1993 var agentFileHttpRequest = http.request(agentFileHttpOptions,
1994 function (response) {
1995 response.xparent = this;
1996 try {
1997 response.xfile = fs.createWriteStream(this.xpath, { flags: 'wbN' })
1998 response.pipe(response.xfile);
1999 response.end = function () { delete agentFileHttpRequests[this.xparent.xurlpath]; delete this.xparent; serverFetchFile(); }
2000 } catch (ex) { delete agentFileHttpRequests[this.xurlpath]; delete response.xparent; serverFetchFile(); return; }
2001 }
2002 );
2003 agentFileHttpRequest.on('error', function (ex) { sendConsoleText(ex); delete agentFileHttpRequests[this.xurlpath]; serverFetchFile(); });
2004 agentFileHttpRequest.end();
2005 agentFileHttpRequest.xurlpath = data.urlpath;
2006 agentFileHttpRequest.xpath = data.path;
2007 agentFileHttpRequests[data.urlpath] = agentFileHttpRequest;
2008}
2009
2010// Called when a file changed in the file system
2011/*
2012function onFileWatcher(a, b) {
2013 console.log('onFileWatcher', a, b, this.path);
2014 var response = getDirectoryInfo(this.path);
2015 if ((response != undefined) && (response != null)) { this.tunnel.s.write(JSON.stringify(response)); }
2016}
2017*/
2018
2019// Replace all key name spaces with _ in an object recursively.
2020// This is a workaround since require('computer-identifiers').get() returns key names with spaces in them on Linux.
2021function replaceSpacesWithUnderscoresRec(o) {
2022 if (typeof o != 'object') return;
2023 for (var i in o) { if (i.indexOf(' ') >= 0) { o[i.split(' ').join('_')] = o[i]; delete o[i]; } replaceSpacesWithUnderscoresRec(o[i]); }
2024}
2025
2026function getSystemInformation(func) {
2027 try {
2028 var results = { hardware: require('computer-identifiers').get() }; // Hardware info
2029 if (results.hardware && results.hardware.windows) {
2030 // Remove extra entries and things that change quickly
2031 var x = results.hardware.windows.osinfo;
2032 try { delete x.FreePhysicalMemory; } catch (ex) { }
2033 try { delete x.FreeSpaceInPagingFiles; } catch (ex) { }
2034 try { delete x.FreeVirtualMemory; } catch (ex) { }
2035 try { delete x.LocalDateTime; } catch (ex) { }
2036 try { delete x.MaxProcessMemorySize; } catch (ex) { }
2037 try { delete x.TotalVirtualMemorySize; } catch (ex) { }
2038 try { delete x.TotalVisibleMemorySize; } catch (ex) { }
2039 try {
2040 if (results.hardware.windows.memory) { for (var i in results.hardware.windows.memory) { delete results.hardware.windows.memory[i].Node; } }
2041 if (results.hardware.windows.osinfo) {
2042 delete results.hardware.windows.osinfo.Node;
2043 results.hardware.windows.osinfo.Domain = getDomainInfo().Domain;
2044 results.hardware.windows.osinfo.PartOfDomain = getDomainInfo().PartOfDomain;
2045 results.hardware.windows.osinfo.DomainState = getJoinState();
2046 }
2047 if (results.hardware.windows.partitions) { for (var i in results.hardware.windows.partitions) { delete results.hardware.windows.partitions[i].Node; } }
2048 } catch (ex) { }
2049 if (x.LastBootUpTime) { // detect windows uptime
2050 var thedate = {
2051 year: parseInt(x.LastBootUpTime.substring(0, 4)),
2052 month: parseInt(x.LastBootUpTime.substring(4, 6)) - 1, // Months are 0-based in JavaScript (0 - January, 11 - December)
2053 day: parseInt(x.LastBootUpTime.substring(6, 8)),
2054 hours: parseInt(x.LastBootUpTime.substring(8, 10)),
2055 minutes: parseInt(x.LastBootUpTime.substring(10, 12)),
2056 seconds: parseInt(x.LastBootUpTime.substring(12, 14)),
2057 };
2058 var thelastbootuptime = new Date(thedate.year, thedate.month, thedate.day, thedate.hours, thedate.minutes, thedate.seconds);
2059 meshCoreObj.lastbootuptime = thelastbootuptime.getTime(); // store the last boot up time in coreinfo for columns
2060 meshCoreObjChanged();
2061 var nowtime = new Date();
2062 var differenceInMilliseconds = Math.abs(thelastbootuptime - nowtime);
2063 if (differenceInMilliseconds < 300000) { // computer uptime less than 5 minutes
2064 MeshServerLogEx(159, [thelastbootuptime.toString()], "Device Powered On", null);
2065 }
2066 }
2067 }
2068 if(results.hardware && results.hardware.linux) {
2069 if(results.hardware.linux.LastBootUpTime) {
2070 var thelastbootuptime = new Date(results.hardware.linux.LastBootUpTime);
2071 meshCoreObj.lastbootuptime = thelastbootuptime.getTime(); // store the last boot up time in coreinfo for columns
2072 meshCoreObjChanged();
2073 var nowtime = new Date();
2074 var differenceInMilliseconds = Math.abs(thelastbootuptime - nowtime);
2075 if (differenceInMilliseconds < 300000) { // computer uptime less than 5 minutes
2076 MeshServerLogEx(159, [thelastbootuptime.toString()], "Device Powered On", null);
2077 }
2078 }
2079 }
2080 if(results.hardware && results.hardware.darwin){
2081 if(results.hardware.darwin.LastBootUpTime) {
2082 var thelastbootuptime = new Date(results.hardware.darwin.LastBootUpTime * 1000); // must times by 1000 even tho timestamp is correct?
2083 meshCoreObj.lastbootuptime = thelastbootuptime.getTime(); // store the last boot up time in coreinfo for columns
2084 meshCoreObjChanged();
2085 var nowtime = new Date();
2086 var differenceInMilliseconds = Math.abs(thelastbootuptime - nowtime);
2087 if (differenceInMilliseconds < 300000) { // computer uptime less than 5 minutes
2088 MeshServerLogEx(159, [thelastbootuptime.toString()], "Device Powered On", null);
2089 }
2090 }
2091 }
2092 results.hardware.agentvers = process.versions;
2093 results.hardware.network = { dns: require('os').dns() };
2094 replaceSpacesWithUnderscoresRec(results);
2095 var hasher = require('SHA384Stream').create();
2096
2097 // On Windows platforms, get volume information - Needs more testing.
2098 if (process.platform == 'win32')
2099 {
2100 results.pendingReboot = require('win-info').pendingReboot(); // Pending reboot
2101 if (require('win-volumes').volumes_promise != null)
2102 {
2103 var p = require('win-volumes').volumes_promise();
2104 p.then(function (res)
2105 {
2106 results.hardware.windows.volumes = cleanGetBitLockerVolumeInfo(res);
2107 results.hash = hasher.syncHash(JSON.stringify(results)).toString('hex');
2108 func(results);
2109 });
2110 }
2111 else
2112 {
2113 results.hash = hasher.syncHash(JSON.stringify(results)).toString('hex');
2114 func(results);
2115 }
2116 }
2117 else
2118 {
2119 results.hash = hasher.syncHash(JSON.stringify(results)).toString('hex');
2120 func(results);
2121 }
2122
2123 } catch (ex) { func(null, ex); }
2124}
2125
2126// Get a formated response for a given directory path
2127function getDirectoryInfo(reqpath) {
2128 var response = { path: reqpath, dir: [] };
2129 if (((reqpath == undefined) || (reqpath == '')) && (process.platform == 'win32')) {
2130 // List all the drives in the root, or the root itself
2131 var results = null;
2132 try { results = fs.readDrivesSync(); } catch (ex) { }
2133 if (results != null) {
2134 for (var i = 0; i < results.length; ++i) {
2135 var drive = { n: results[i].name, t: 1, dt: results[i].type, s: (results[i].size ? results[i].size : 0), f: (results[i].free ? results[i].free : 0) };
2136 response.dir.push(drive);
2137 }
2138 }
2139 } else {
2140 // List all the files and folders in this path
2141 if (reqpath == '') { reqpath = '/'; }
2142 var results = null, xpath = obj.path.join(reqpath, '*');
2143 //if (process.platform == "win32") { xpath = xpath.split('/').join('\\'); }
2144 try { results = fs.readdirSync(xpath); } catch (ex) { }
2145 try { if ((results != null) && (results.length == 0) && (fs.existsSync(reqpath) == false)) { results = null; } } catch (ex) { }
2146 if (results != null) {
2147 for (var i = 0; i < results.length; ++i) {
2148 if ((results[i] != '.') && (results[i] != '..')) {
2149 var stat = null, p = obj.path.join(reqpath, results[i]);
2150 //if (process.platform == "win32") { p = p.split('/').join('\\'); }
2151 try { stat = fs.statSync(p); } catch (ex) { } // TODO: Get file size/date
2152 if ((stat != null) && (stat != undefined)) {
2153 if (stat.isDirectory() == true) {
2154 response.dir.push({ n: results[i], t: 2, d: stat.mtime });
2155 } else {
2156 response.dir.push({ n: results[i], t: 3, s: stat.size, d: stat.mtime });
2157 }
2158 }
2159 }
2160 }
2161 } else {
2162 response.dir = null;
2163 }
2164 }
2165 return response;
2166}
2167
2168function tunnel_s_finalized()
2169{
2170 console.info1('Tunnel Socket Finalized');
2171}
2172
2173
2174function tunnel_onIdleTimeout()
2175{
2176 this.ping();
2177 this.setTimeout(require('MeshAgent').idleTimeout * 1000);
2178}
2179
2180// Tunnel callback operations
2181function onTunnelUpgrade(response, s, head)
2182{
2183
2184 this.s = s;
2185 s.once('~', tunnel_s_finalized);
2186 s.httprequest = this;
2187 s.end = onTunnelClosed;
2188 s.tunnel = this;
2189 s.descriptorMetadata = "MeshAgent_relayTunnel";
2190
2191
2192 if (require('MeshAgent').idleTimeout != null)
2193 {
2194 s.setTimeout(require('MeshAgent').idleTimeout * 1000);
2195 s.on('timeout', tunnel_onIdleTimeout);
2196 }
2197
2198 //sendConsoleText('onTunnelUpgrade - ' + this.tcpport + ' - ' + this.udpport);
2199
2200 if (this.tcpport != null) {
2201 // This is a TCP relay connection, pause now and try to connect to the target.
2202 s.pause();
2203 s.data = onTcpRelayServerTunnelData;
2204 var connectionOptions = { port: parseInt(this.tcpport) };
2205 if (this.tcpaddr != null) { connectionOptions.host = this.tcpaddr; } else { connectionOptions.host = '127.0.0.1'; }
2206 s.tcprelay = net.createConnection(connectionOptions, onTcpRelayTargetTunnelConnect);
2207 s.tcprelay.peerindex = this.index;
2208
2209 // Add the TCP session to the count and update the server
2210 if (s.httprequest.userid != null) {
2211 var userid = getUserIdAndGuestNameFromHttpRequest(s.httprequest);
2212 if (tunnelUserCount.tcp[userid] == null) { tunnelUserCount.tcp[userid] = 1; } else { tunnelUserCount.tcp[userid]++; }
2213 try { mesh.SendCommand({ action: 'sessions', type: 'tcp', value: tunnelUserCount.tcp }); } catch (ex) { }
2214 broadcastSessionsToRegisteredApps();
2215 }
2216 }
2217 if (this.udpport != null) {
2218 // This is a UDP relay connection, get the UDP socket setup. // TODO: ***************
2219 s.data = onUdpRelayServerTunnelData;
2220 s.udprelay = require('dgram').createSocket({ type: 'udp4' });
2221 s.udprelay.bind({ port: 0 });
2222 s.udprelay.peerindex = this.index;
2223 s.udprelay.on('message', onUdpRelayTargetTunnelConnect);
2224 s.udprelay.udpport = this.udpport;
2225 s.udprelay.udpaddr = this.udpaddr;
2226 s.udprelay.first = true;
2227
2228 // Add the UDP session to the count and update the server
2229 if (s.httprequest.userid != null) {
2230 var userid = getUserIdAndGuestNameFromHttpRequest(s.httprequest);
2231 if (tunnelUserCount.udp[userid] == null) { tunnelUserCount.udp[userid] = 1; } else { tunnelUserCount.udp[userid]++; }
2232 try { mesh.SendCommand({ action: 'sessions', type: 'udp', value: tunnelUserCount.tcp }); } catch (ex) { }
2233 broadcastSessionsToRegisteredApps();
2234 }
2235 }
2236 else {
2237 // This is a normal connect for KVM/Terminal/Files
2238 s.data = onTunnelData;
2239 }
2240}
2241
2242// If the HTTP Request has a guest name, we need to form a userid that includes the guest name in hex.
2243// This is so we can tell the server that a session is for a given userid/guest sharing pair.
2244function getUserIdAndGuestNameFromHttpRequest(request) {
2245 if (request.guestname == null) return request.userid; else return request.guestuserid + '/guest:' + Buffer.from(request.guestname).toString('base64');
2246}
2247
2248// Called when UDP relay data is received // TODO****
2249function onUdpRelayTargetTunnelConnect(data) {
2250 var peerTunnel = tunnels[this.peerindex];
2251 peerTunnel.s.write(data);
2252}
2253
2254// Called when we get data from the server for a TCP relay (We have to skip the first received 'c' and pipe the rest)
2255function onUdpRelayServerTunnelData(data) {
2256 if (this.udprelay.first === true) {
2257 delete this.udprelay.first; // Skip the first 'c' that is received.
2258 } else {
2259 this.udprelay.send(data, parseInt(this.udprelay.udpport), this.udprelay.udpaddr ? this.udprelay.udpaddr : '127.0.0.1');
2260 }
2261}
2262
2263// Called when the TCP relay target is connected
2264function onTcpRelayTargetTunnelConnect() {
2265 var peerTunnel = tunnels[this.peerindex];
2266 this.pipe(peerTunnel.s); // Pipe Target --> Server
2267 peerTunnel.s.first = true;
2268 peerTunnel.s.resume();
2269}
2270
2271// Called when we get data from the server for a TCP relay (We have to skip the first received 'c' and pipe the rest)
2272function onTcpRelayServerTunnelData(data) {
2273 if (this.first == true) {
2274 this.first = false;
2275 this.pipe(this.tcprelay, { dataTypeSkip: 1 }); // Pipe Server --> Target (don't pipe text type websocket frames)
2276 }
2277}
2278
2279function onTunnelClosed()
2280{
2281 if (this.httprequest._dispatcher != null && this.httprequest.term == null)
2282 {
2283 // Windows Dispatcher was created to spawn a child connection, but the child didn't connect yet, so we have to shutdown the dispatcher, otherwise the child may end up hanging
2284 if (this.httprequest._dispatcher.close) { this.httprequest._dispatcher.close(); }
2285 this.httprequest._dispatcher = null;
2286 }
2287
2288 if (this.tunnel)
2289 {
2290 if (tunnels[this.httprequest.index] == null)
2291 {
2292 this.tunnel.s = null;
2293 this.tunnel = null;
2294 return;
2295 }
2296 }
2297
2298 var tunnel = tunnels[this.httprequest.index];
2299 if (tunnel == null) return; // Stop duplicate calls.
2300
2301 // Perform display locking on disconnect
2302 if ((this.httprequest.protocol == 2) && (this.httprequest.autolock === true)) {
2303 // Look for a TSID
2304 var tsid = null;
2305 if ((this.httprequest.xoptions != null) && (typeof this.httprequest.xoptions.tsid == 'number')) { tsid = this.httprequest.xoptions.tsid; }
2306
2307 // Lock the current user out of the desktop
2308 MeshServerLogEx(53, null, "Locking remote user out of desktop", this.httprequest);
2309 lockDesktop(tsid);
2310 }
2311
2312 // If this is a routing session, clean up and send the new session counts.
2313 if (this.httprequest.userid != null) {
2314 if (this.httprequest.tcpport != null) {
2315 var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest);
2316 if (tunnelUserCount.tcp[userid] != null) { tunnelUserCount.tcp[userid]--; if (tunnelUserCount.tcp[userid] <= 0) { delete tunnelUserCount.tcp[userid]; } }
2317 try { mesh.SendCommand({ action: 'sessions', type: 'tcp', value: tunnelUserCount.tcp }); } catch (ex) { }
2318 broadcastSessionsToRegisteredApps();
2319 } else if (this.httprequest.udpport != null) {
2320 var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest);
2321 if (tunnelUserCount.udp[userid] != null) { tunnelUserCount.udp[userid]--; if (tunnelUserCount.udp[userid] <= 0) { delete tunnelUserCount.udp[userid]; } }
2322 try { mesh.SendCommand({ action: 'sessions', type: 'udp', value: tunnelUserCount.udp }); } catch (ex) { }
2323 broadcastSessionsToRegisteredApps();
2324 }
2325 }
2326
2327 try {
2328 // Sent tunnel statistics to the server, only send this if compression was used.
2329 if ((this.bytesSent_uncompressed) && (this.bytesSent_uncompressed.toString() != this.bytesSent_actual.toString())) {
2330 mesh.SendCommand({
2331 action: 'tunnelCloseStats',
2332 url: tunnel.url,
2333 userid: tunnel.userid,
2334 protocol: tunnel.protocol,
2335 sessionid: tunnel.sessionid,
2336 sent: this.bytesSent_uncompressed.toString(),
2337 sentActual: this.bytesSent_actual.toString(),
2338 sentRatio: this.bytesSent_ratio,
2339 received: this.bytesReceived_uncompressed.toString(),
2340 receivedActual: this.bytesReceived_actual.toString(),
2341 receivedRatio: this.bytesReceived_ratio
2342 });
2343 }
2344 } catch (ex) { }
2345
2346 //sendConsoleText("Tunnel #" + this.httprequest.index + " closed. Sent -> " + this.bytesSent_uncompressed + ' bytes (uncompressed), ' + this.bytesSent_actual + ' bytes (actual), ' + this.bytesSent_ratio + '% compression', this.httprequest.sessionid);
2347
2348
2349 /*
2350 // Close the watcher if required
2351 if (this.httprequest.watcher != undefined) {
2352 //console.log('Closing watcher: ' + this.httprequest.watcher.path);
2353 //this.httprequest.watcher.close(); // TODO: This line causes the agent to crash!!!!
2354 delete this.httprequest.watcher;
2355 }
2356 */
2357
2358 // If there is a upload or download active on this connection, close the file
2359 if (this.httprequest.uploadFile) { fs.closeSync(this.httprequest.uploadFile); delete this.httprequest.uploadFile; delete this.httprequest.uploadFileid; delete this.httprequest.uploadFilePath; delete this.httprequest.uploadFileSize; }
2360 if (this.httprequest.downloadFile) { delete this.httprequest.downloadFile; }
2361
2362 // Clean up WebRTC
2363 if (this.webrtc != null) {
2364 if (this.webrtc.rtcchannel) { try { this.webrtc.rtcchannel.close(); } catch (ex) { } this.webrtc.rtcchannel.removeAllListeners('data'); this.webrtc.rtcchannel.removeAllListeners('end'); delete this.webrtc.rtcchannel; }
2365 if (this.webrtc.websocket) { delete this.webrtc.websocket; }
2366 try { this.webrtc.close(); } catch (ex) { }
2367 this.webrtc.removeAllListeners('connected');
2368 this.webrtc.removeAllListeners('disconnected');
2369 this.webrtc.removeAllListeners('dataChannel');
2370 delete this.webrtc;
2371 }
2372
2373 // Clean up WebSocket
2374 delete tunnels[this.httprequest.index];
2375 tunnel = null;
2376 this.tunnel.s = null;
2377 this.tunnel = null;
2378 this.removeAllListeners('data');
2379}
2380function onTunnelSendOk() { /*sendConsoleText("Tunnel #" + this.index + " SendOK.", this.sessionid);*/ }
2381
2382function terminal_onconnection (c)
2383{
2384 if (this.httprequest.connectionPromise.completed)
2385 {
2386 c.end();
2387 }
2388 else
2389 {
2390 this.httprequest.connectionPromise._res(c);
2391 }
2392}
2393function terminal_user_onconnection(c)
2394{
2395 console.info1('completed-2: ' + this.connectionPromise.completed);
2396
2397 if (this.connectionPromise.completed)
2398 {
2399 c.end();
2400 }
2401 else
2402 {
2403 this.connectionPromise._res(c);
2404 }
2405}
2406function terminal_stderr_ondata(c)
2407{
2408 this.stdout.write(c);
2409}
2410function terminal_onend()
2411{
2412 this.httprequest.process.kill();
2413}
2414
2415function terminal_onexit()
2416{
2417 this.tunnel.end();
2418}
2419function terminal_onfinalized()
2420{
2421 this.httprequest = null;
2422 console.info1('Dispatcher Finalized');
2423}
2424function terminal_end()
2425{
2426 if (this.httprequest == null) { return; }
2427 if (this.httprequest.tpromise._consent) { this.httprequest.tpromise._consent.close(); }
2428 if (this.httprequest.connectionPromise) { this.httprequest.connectionPromise._rej('Closed'); }
2429
2430 // Remove the terminal session to the count to update the server
2431 if (this.httprequest.userid != null)
2432 {
2433 var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest);
2434 if (tunnelUserCount.terminal[userid] != null) { tunnelUserCount.terminal[userid]--; if (tunnelUserCount.terminal[userid] <= 0) { delete tunnelUserCount.terminal[userid]; } }
2435 try { mesh.SendCommand({ action: 'sessions', type: 'terminal', value: tunnelUserCount.terminal }); } catch (ex) { }
2436 broadcastSessionsToRegisteredApps();
2437 }
2438
2439 if (process.platform == 'win32')
2440 {
2441 // Unpipe the web socket
2442 this.unpipe(this.httprequest._term);
2443 if (this.httprequest._term) { this.httprequest._term.unpipe(this); }
2444
2445 // Unpipe the WebRTC channel if needed (This will also be done when the WebRTC channel ends).
2446 if (this.rtcchannel)
2447 {
2448 this.rtcchannel.unpipe(this.httprequest._term);
2449 if (this.httprequest._term) { this.httprequest._term.unpipe(this.rtcchannel); }
2450 }
2451
2452 // Clean up
2453 if (this.httprequest._term) { this.httprequest._term.end(); }
2454 this.httprequest._term = null;
2455 this.httprequest._dispatcher = null;
2456 }
2457
2458 this.httprequest = null;
2459
2460}
2461
2462function terminal_consent_ask(ws) {
2463 ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 }));
2464 var consentMessage = currentTranslation['terminalConsent'].replace(/\{0\}/g, ws.httprequest.realname).replace(/\{1\}/g, ws.httprequest.username);
2465 var consentTitle = 'MeshCentral';
2466 if (ws.httprequest.soptions != null) {
2467 if (ws.httprequest.soptions.consentTitle != null) { consentTitle = ws.httprequest.soptions.consentTitle; }
2468 if (ws.httprequest.soptions.consentMsgTerminal != null) { consentMessage = ws.httprequest.soptions.consentMsgTerminal.replace(/\{0\}/g, ws.httprequest.realname).replace(/\{1\}/g, ws.httprequest.username); }
2469 }
2470 if (process.platform == 'win32') {
2471 var enhanced = false;
2472 if (ws.httprequest.oldStyle === false) {
2473 try { require('win-userconsent'); enhanced = true; } catch (ex) { }
2474 }
2475 if (enhanced) {
2476 var ipr = server_getUserImage(ws.httprequest.userid);
2477 ipr.consentTitle = consentTitle;
2478 ipr.consentMessage = consentMessage;
2479 ipr.consentTimeout = ws.httprequest.consentTimeout;
2480 ipr.consentAutoAccept = ws.httprequest.consentAutoAccept;
2481 ipr.username = ws.httprequest.realname;
2482 ipr.tsid = ws.tsid;
2483 ipr.translations = { Allow: currentTranslation['allow'], Deny: currentTranslation['deny'], Auto: currentTranslation['autoAllowForFive'], Caption: consentMessage };
2484 ws.httprequest.tpromise._consent = ipr.then(function (img) {
2485 this.consent = require('win-userconsent').create(this.consentTitle, this.consentMessage, this.username, { b64Image: img.split(',').pop(), uid: this.tsid, timeout: this.consentTimeout * 1000, timeoutAutoAccept: this.consentAutoAccept, translations: this.translations, background: color_options.background, foreground: color_options.foreground });
2486 this.__childPromise.close = this.consent.close.bind(this.consent);
2487 return (this.consent);
2488 });
2489 } else {
2490 ws.httprequest.tpromise._consent = require('message-box').create(consentTitle, consentMessage, ws.httprequest.consentTimeout);
2491 }
2492 } else {
2493 ws.httprequest.tpromise._consent = require('message-box').create(consentTitle, consentMessage, ws.httprequest.consentTimeout);
2494 }
2495 ws.httprequest.tpromise._consent.retPromise = ws.httprequest.tpromise;
2496 ws.httprequest.tpromise._consent.then(function (always) {
2497 if (always && process.platform == 'win32') { server_set_consentTimer(this.retPromise.httprequest.userid); }
2498 // Success
2499 MeshServerLogEx(27, null, "Local user accepted remote terminal request (" + this.retPromise.httprequest.remoteaddr + ")", this.retPromise.that.httprequest);
2500 this.retPromise.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null, msgid: 0 }));
2501 this.retPromise._consent = null;
2502 this.retPromise._res();
2503 }, function (e) {
2504 if (this.retPromise.that) {
2505 if(this.retPromise.that.httprequest){ // User Consent Denied
2506 MeshServerLogEx(28, null, "Local user rejected remote terminal request (" + this.retPromise.that.httprequest.remoteaddr + ")", this.retPromise.that.httprequest);
2507 } else { } // Connection was closed server side, maybe log some messages somewhere?
2508 this.retPromise._consent = null;
2509 this.retPromise.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
2510 } else { } // no websocket, maybe log some messages somewhere?
2511 this.retPromise._rej(e.toString());
2512 });
2513}
2514
2515function terminal_promise_connection_rejected(e)
2516{
2517 // FAILED to connect terminal
2518 this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
2519 this.ws.end();
2520}
2521
2522function terminal_promise_connection_resolved(term)
2523{
2524 this._internal.completedArgs = [];
2525
2526 // SUCCESS
2527 var stdoutstream;
2528 var stdinstream;
2529 if (process.platform == 'win32')
2530 {
2531 this.ws.httprequest._term = term;
2532 this.ws.httprequest._term.tunnel = this.ws;
2533 stdoutstream = stdinstream = term;
2534 }
2535 else
2536 {
2537 term.descriptorMetadata = 'Remote Terminal';
2538 this.ws.httprequest.process = term;
2539 this.ws.httprequest.process.tunnel = this.ws;
2540 term.stderr.stdout = term.stdout;
2541 term.stderr.on('data', terminal_stderr_ondata);
2542 stdoutstream = term.stdout;
2543 stdinstream = term.stdin;
2544 this.ws.prependListener('end', terminal_onend);
2545 term.prependListener('exit', terminal_onexit);
2546 }
2547
2548 this.ws.removeAllListeners('data');
2549 this.ws.on('data', onTunnelControlData);
2550
2551 stdoutstream.pipe(this.ws, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
2552 this.ws.pipe(stdinstream, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
2553
2554 // Add the terminal session to the count to update the server
2555 if (this.ws.httprequest.userid != null)
2556 {
2557 var userid = getUserIdAndGuestNameFromHttpRequest(this.ws.httprequest);
2558 if (tunnelUserCount.terminal[userid] == null) { tunnelUserCount.terminal[userid] = 1; } else { tunnelUserCount.terminal[userid]++; }
2559 try { mesh.SendCommand({ action: 'sessions', type: 'terminal', value: tunnelUserCount.terminal }); } catch (ex) { }
2560 broadcastSessionsToRegisteredApps();
2561 }
2562
2563 // Toast Notification, if required
2564 if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 2))
2565 {
2566 // User Notifications is required
2567 var notifyMessage = currentTranslation['terminalNotify'].replace(/\{0\}/g, this.ws.httprequest.realname ? this.ws.httprequest.realname : this.ws.httprequest.username);
2568 var notifyTitle = "MeshCentral";
2569 if (this.ws.httprequest.soptions != null)
2570 {
2571 if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; }
2572 if (this.ws.httprequest.soptions.notifyMsgTerminal != null) { notifyMessage = this.ws.httprequest.soptions.notifyMsgTerminal.replace(/\{0\}/g, this.ws.httprequest.realname).replace(/\{1\}/g, this.ws.httprequest.username); }
2573 }
2574 try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (ex) { }
2575 }
2576 this.ws = null;
2577}
2578function terminal_promise_consent_rejected(e)
2579{
2580 // DO NOT start terminal
2581 if (this.that) {
2582 if(this.that.httprequest){ // User Consent Denied
2583 if ((this.that.httprequest.oldStyle === true) && (this.that.httprequest.consentAutoAccept === true) && (e.toString() != "7")) {
2584 terminal_promise_consent_resolved.call(this); // oldStyle prompt timed out and User Consent is not required so connect anyway
2585 return;
2586 }
2587 } else { } // Connection was closed server side, maybe log some messages somewhere?
2588 this.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
2589 this.that.end();
2590
2591 this.that = null;
2592 this.httprequest = null;
2593 } else { } // no websocket, maybe log some messages somewhere?
2594}
2595function promise_init(res, rej) { this._res = res; this._rej = rej; }
2596function terminal_userpromise_resolved(u)
2597{
2598
2599 var that = this.that;
2600 if (u.Active.length > 0)
2601 {
2602 var tmp;
2603 var username = '"' + u.Active[0].Domain + '\\' + u.Active[0].Username + '"';
2604
2605
2606 if (require('win-virtual-terminal').supported)
2607 {
2608 // ConPTY PseudoTerminal
2609 tmp = 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: [this.cols, this.rows] } });
2610 }
2611 else
2612 {
2613 // Legacy Terminal
2614 tmp = 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: [this.cols, this.rows] } });
2615 }
2616 that.httprequest._dispatcher = tmp;
2617 that.httprequest._dispatcher.connectionPromise = that.httprequest.connectionPromise;
2618 that.httprequest._dispatcher.on('connection', terminal_user_onconnection);
2619 that.httprequest._dispatcher.on('~', terminal_onfinalized);
2620 }
2621 this.that = null;
2622 that = null;
2623}
2624
2625function terminal_promise_consent_resolved()
2626{
2627 this.httprequest.connectionPromise = new promise(promise_init);
2628 this.httprequest.connectionPromise.ws = this.that;
2629
2630 // Start Terminal
2631 if (process.platform == 'win32')
2632 {
2633 try
2634 {
2635 var cols = 80, rows = 25;
2636 if (this.httprequest.xoptions)
2637 {
2638 if (this.httprequest.xoptions.rows) { rows = this.httprequest.xoptions.rows; }
2639 if (this.httprequest.xoptions.cols) { cols = this.httprequest.xoptions.cols; }
2640 }
2641
2642 if ((this.httprequest.protocol == 1) || (this.httprequest.protocol == 6))
2643 {
2644 // Admin Terminal
2645 if (require('win-virtual-terminal').supported)
2646 {
2647 // ConPTY PseudoTerminal
2648 // this.httprequest._term = require('win-virtual-terminal')[this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'](80, 25);
2649
2650 // The above line is commented out, because there is a bug with ClosePseudoConsole() API, so this is the workaround
2651 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] } });
2652 this.httprequest._dispatcher.httprequest = this.httprequest;
2653 this.httprequest._dispatcher.on('connection', terminal_onconnection);
2654 this.httprequest._dispatcher.on('~', terminal_onfinalized);
2655 }
2656 else
2657 {
2658 // Legacy Terminal
2659 this.httprequest.connectionPromise._res(require('win-terminal')[this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'](cols, rows));
2660 }
2661 }
2662 else
2663 {
2664 // Logged in user
2665 var userPromise = require('user-sessions').enumerateUsers();
2666 userPromise.that = this;
2667 userPromise.cols = cols;
2668 userPromise.rows = rows;
2669 userPromise.then(terminal_userpromise_resolved);
2670 }
2671 } catch (ex)
2672 {
2673 this.httprequest.connectionPromise._rej('Failed to start remote terminal session, ' + ex.toString());
2674 }
2675 }
2676 else
2677 {
2678 try
2679 {
2680 var bash = fs.existsSync('/bin/bash') ? '/bin/bash' : false;
2681 var sh = fs.existsSync('/bin/sh') ? '/bin/sh' : false;
2682 var login = process.platform == 'linux' ? '/bin/login' : '/usr/bin/login';
2683
2684 var env = { HISTCONTROL: 'ignoreboth' };
2685 if (process.env['LANG']) { env['LANG'] = process.env['LANG']; }
2686 if (process.env['PATH']) { env['PATH'] = process.env['PATH']; }
2687 if (this.httprequest.xoptions)
2688 {
2689 if (this.httprequest.xoptions.rows) { env.LINES = ('' + this.httprequest.xoptions.rows); }
2690 if (this.httprequest.xoptions.cols) { env.COLUMNS = ('' + this.httprequest.xoptions.cols); }
2691 }
2692 var options = { type: childProcess.SpawnTypes.TERM, uid: (this.httprequest.protocol == 8) ? require('user-sessions').consoleUid() : null, env: env };
2693 if (this.httprequest.xoptions && this.httprequest.xoptions.requireLogin)
2694 {
2695 if (!require('fs').existsSync(login)) { throw ('Unable to spawn login process'); }
2696 this.httprequest.connectionPromise._res(childProcess.execFile(login, ['login'], options)); // Start login shell
2697 }
2698 else if (bash)
2699 {
2700 var p = childProcess.execFile(bash, ['bash'], options); // Start bash
2701 // Spaces at the beginning of lines are needed to hide commands from the command history
2702 if ((obj.serverInfo.termlaunchcommand != null) && (typeof obj.serverInfo.termlaunchcommand[process.platform] == 'string'))
2703 {
2704 if (obj.serverInfo.termlaunchcommand[process.platform] != '') { p.stdin.write(obj.serverInfo.termlaunchcommand[process.platform]); }
2705 } else if (process.platform == 'linux') { p.stdin.write(' alias ls=\'ls --color=auto\';clear\n'); }
2706 this.httprequest.connectionPromise._res(p);
2707 }
2708 else if (sh)
2709 {
2710 var p = childProcess.execFile(sh, ['sh'], options); // Start sh
2711 // Spaces at the beginning of lines are needed to hide commands from the command history
2712 if ((obj.serverInfo.termlaunchcommand != null) && (typeof obj.serverInfo.termlaunchcommand[process.platform] == 'string'))
2713 {
2714 if (obj.serverInfo.termlaunchcommand[process.platform] != '') { p.stdin.write(obj.serverInfo.termlaunchcommand[process.platform]); }
2715 } else if (process.platform == 'linux') { p.stdin.write(' alias ls=\'ls --color=auto\';clear\n'); }
2716 this.httprequest.connectionPromise._res(p);
2717 }
2718 else
2719 {
2720 this.httprequest.connectionPromise._rej('Failed to start remote terminal session, no shell found');
2721 }
2722 } catch (ex)
2723 {
2724 this.httprequest.connectionPromise._rej('Failed to start remote terminal session, ' + ex.toString());
2725 }
2726 }
2727
2728 this.httprequest.connectionPromise.then(terminal_promise_connection_resolved, terminal_promise_connection_rejected);
2729 this.that = null;
2730 this.httprequest = null;
2731}
2732function tunnel_kvm_end()
2733{
2734 --this.desktop.kvm.connectionCount;
2735
2736 // Remove ourself from the list of remote desktop session
2737 var i = this.desktop.kvm.tunnels.indexOf(this);
2738 if (i >= 0) { this.desktop.kvm.tunnels.splice(i, 1); }
2739
2740 // Send a metadata update to all desktop sessions
2741 var users = {};
2742 if (this.httprequest.desktop.kvm.tunnels != null)
2743 {
2744 for (var i in this.httprequest.desktop.kvm.tunnels)
2745 {
2746 try
2747 {
2748 var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest.desktop.kvm.tunnels[i].httprequest);
2749 if (users[userid] == null) { users[userid] = 1; } else { users[userid]++; }
2750 } catch (ex) { sendConsoleText(ex); }
2751 }
2752 for (var i in this.httprequest.desktop.kvm.tunnels)
2753 {
2754 try { this.httprequest.desktop.kvm.tunnels[i].write(JSON.stringify({ ctrlChannel: '102938', type: 'metadata', users: users })); } catch (ex) { }
2755 }
2756 tunnelUserCount.desktop = users;
2757 try { mesh.SendCommand({ action: 'sessions', type: 'kvm', value: users }); } catch (ex) { }
2758 broadcastSessionsToRegisteredApps();
2759 }
2760
2761 // Unpipe the web socket
2762 try
2763 {
2764 this.unpipe(this.httprequest.desktop.kvm);
2765 this.httprequest.desktop.kvm.unpipe(this);
2766 } catch (ex) { }
2767
2768 // Unpipe the WebRTC channel if needed (This will also be done when the WebRTC channel ends).
2769 if (this.rtcchannel)
2770 {
2771 try
2772 {
2773 this.rtcchannel.unpipe(this.httprequest.desktop.kvm);
2774 this.httprequest.desktop.kvm.unpipe(this.rtcchannel);
2775 }
2776 catch (ex) { }
2777 }
2778
2779 // Place wallpaper back if needed
2780 // TODO
2781
2782 if (this.desktop.kvm.connectionCount == 0)
2783 {
2784 // Display a toast message. This may not be supported on all platforms.
2785 // try { require('toaster').Toast('MeshCentral', 'Remote Desktop Control Ended.'); } catch (ex) { }
2786
2787 this.httprequest.desktop.kvm.end();
2788 if (this.httprequest.desktop.kvm.connectionBar)
2789 {
2790 this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close');
2791 this.httprequest.desktop.kvm.connectionBar.close();
2792 this.httprequest.desktop.kvm.connectionBar = null;
2793 }
2794 } else
2795 {
2796 for (var i in this.httprequest.desktop.kvm.users)
2797 {
2798 if ((this.httprequest.desktop.kvm.users[i] == this.httprequest.username) && this.httprequest.desktop.kvm.connectionBar)
2799 {
2800 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; } }
2801 this.httprequest.desktop.kvm.users.splice(i, 1);
2802 this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close');
2803 this.httprequest.desktop.kvm.connectionBar.close();
2804 this.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(this.httprequest.privacybartext.replace(/\{0\}/g, this.httprequest.desktop.kvm.rusers.join(', ')).replace(/\{1\}/g, this.httprequest.desktop.kvm.users.join(', ')).replace(/'/g, "\\'\\"), require('MeshAgent')._tsid, color_options);
2805 this.httprequest.desktop.kvm.connectionBar.httprequest = this.httprequest;
2806 this.httprequest.desktop.kvm.connectionBar.on('close', function ()
2807 {
2808 MeshServerLogEx(29, null, "Remote Desktop Connection forcefully closed by local user (" + this.httprequest.remoteaddr + ")", this.httprequest);
2809 for (var i in this.httprequest.desktop.kvm._pipedStreams)
2810 {
2811 this.httprequest.desktop.kvm._pipedStreams[i].end();
2812 }
2813 this.httprequest.desktop.kvm.end();
2814 });
2815 break;
2816 }
2817 }
2818 }
2819
2820 if(this.httprequest.desktop.kvm.connectionBar)
2821 {
2822 console.info1('Setting ConnectionBar request to NULL');
2823 this.httprequest.desktop.kvm.connectionBar.httprequest = null;
2824 }
2825
2826 this.httprequest = null;
2827 this.desktop.tunnel = null;
2828}
2829
2830function kvm_tunnel_consentpromise_closehandler()
2831{
2832 if (this._consentpromise && this._consentpromise.close) { this._consentpromise.close(); }
2833}
2834
2835function kvm_consent_ok(ws) {
2836 // User Consent Prompt is not required because no user is present
2837 if (ws.httprequest.consent && (ws.httprequest.consent & 1)){
2838 // User Notifications is required
2839 MeshServerLogEx(35, null, "Started remote desktop with toast notification (" + ws.httprequest.remoteaddr + ")", ws.httprequest);
2840 var notifyMessage = currentTranslation['desktopNotify'].replace(/\{0\}/g, ws.httprequest.realname);
2841 var notifyTitle = "MeshCentral";
2842 if (ws.httprequest.soptions != null) {
2843 if (ws.httprequest.soptions.notifyTitle != null) { notifyTitle = ws.httprequest.soptions.notifyTitle; }
2844 if (ws.httprequest.soptions.notifyMsgDesktop != null) { notifyMessage = ws.httprequest.soptions.notifyMsgDesktop.replace(/\{0\}/g, ws.httprequest.realname).replace(/\{1\}/g, ws.httprequest.username); }
2845 }
2846 try { require('toaster').Toast(notifyTitle, notifyMessage, ws.tsid); } catch (ex) { }
2847 } else {
2848 MeshServerLogEx(36, null, "Started remote desktop without notification (" + ws.httprequest.remoteaddr + ")", ws.httprequest);
2849 }
2850 if (ws.httprequest.consent && (ws.httprequest.consent & 0x40)) {
2851 // Connection Bar is required
2852 if (ws.httprequest.desktop.kvm.connectionBar) {
2853 ws.httprequest.desktop.kvm.connectionBar.removeAllListeners('close');
2854 ws.httprequest.desktop.kvm.connectionBar.close();
2855 }
2856 try {
2857 ws.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(ws.httprequest.privacybartext.replace(/\{0\}/g, ws.httprequest.desktop.kvm.rusers.join(', ')).replace(/\{1\}/g, ws.httprequest.desktop.kvm.users.join(', ')).replace(/'/g, "\\'\\"), require('MeshAgent')._tsid, color_options);
2858 MeshServerLogEx(31, null, "Remote Desktop Connection Bar Activated/Updated (" + ws.httprequest.remoteaddr + ")", ws.httprequest);
2859 } catch (ex) {
2860 MeshServerLogEx(32, null, "Remote Desktop Connection Bar Failed or not Supported (" + ws.httprequest.remoteaddr + ")", ws.httprequest);
2861 }
2862 if (ws.httprequest.desktop.kvm.connectionBar) {
2863 ws.httprequest.desktop.kvm.connectionBar.state = {
2864 userid: ws.httprequest.userid,
2865 xuserid: ws.httprequest.xuserid,
2866 username: ws.httprequest.username,
2867 sessionid: ws.httprequest.sessionid,
2868 remoteaddr: ws.httprequest.remoteaddr,
2869 guestname: ws.httprequest.guestname,
2870 desktop: ws.httprequest.desktop
2871 };
2872 ws.httprequest.desktop.kvm.connectionBar.on('close', function () {
2873 console.info1('Connection Bar Forcefully closed');
2874 MeshServerLogEx(29, null, "Remote Desktop Connection forcefully closed by local user (" + this.state.remoteaddr + ")", this.state);
2875 for (var i in this.state.desktop.kvm._pipedStreams) {
2876 this.state.desktop.kvm._pipedStreams[i].end();
2877 }
2878 this.state.desktop.kvm.end();
2879 });
2880 }
2881 }
2882 ws.httprequest.desktop.kvm.pipe(ws, { dataTypeSkip: 1 });
2883 if (ws.httprequest.autolock) {
2884 destopLockHelper_pipe(ws.httprequest);
2885 }
2886}
2887
2888function kvm_consent_ask(ws){
2889 // Send a console message back using the console channel, "\n" is supported.
2890 ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 }));
2891 var consentMessage = currentTranslation['desktopConsent'].replace(/\{0\}/g, ws.httprequest.realname).replace(/\{1\}/g, ws.httprequest.username);
2892 var consentTitle = 'MeshCentral';
2893 if (ws.httprequest.soptions != null) {
2894 if (ws.httprequest.soptions.consentTitle != null) { consentTitle = ws.httprequest.soptions.consentTitle; }
2895 if (ws.httprequest.soptions.consentMsgDesktop != null) { consentMessage = ws.httprequest.soptions.consentMsgDesktop.replace(/\{0\}/g, ws.httprequest.realname).replace(/\{1\}/g, ws.httprequest.username); }
2896 }
2897 var pr;
2898 if (process.platform == 'win32') {
2899 var enhanced = false;
2900 if (ws.httprequest.oldStyle === false) {
2901 try { require('win-userconsent'); enhanced = true; } catch (ex) { }
2902 }
2903 if (enhanced) {
2904 var ipr = server_getUserImage(ws.httprequest.userid);
2905 ipr.consentTitle = consentTitle;
2906 ipr.consentMessage = consentMessage;
2907 ipr.consentTimeout = ws.httprequest.consentTimeout;
2908 ipr.consentAutoAccept = ws.httprequest.consentAutoAccept;
2909 ipr.tsid = ws.tsid;
2910 ipr.username = ws.httprequest.realname;
2911 ipr.translation = { Allow: currentTranslation['allow'], Deny: currentTranslation['deny'], Auto: currentTranslation['autoAllowForFive'], Caption: consentMessage };
2912 pr = ipr.then(function (img) {
2913 this.consent = require('win-userconsent').create(this.consentTitle, this.consentMessage, this.username, { b64Image: img.split(',').pop(), uid: this.tsid, timeout: this.consentTimeout * 1000, timeoutAutoAccept: this.consentAutoAccept, translations: this.translation, background: color_options.background, foreground: color_options.foreground });
2914 this.__childPromise.close = this.consent.close.bind(this.consent);
2915 return (this.consent);
2916 });
2917 } else {
2918 pr = require('message-box').create(consentTitle, consentMessage, ws.httprequest.consentTimeout, null, ws.tsid);
2919 }
2920 } else {
2921 pr = require('message-box').create(consentTitle, consentMessage, ws.httprequest.consentTimeout, null, ws.tsid);
2922 }
2923 pr.ws = ws;
2924 ws.pause();
2925 ws._consentpromise = pr;
2926 ws.prependOnceListener('end', kvm_tunnel_consentpromise_closehandler);
2927 pr.then(kvm_consentpromise_resolved, kvm_consentpromise_rejected);
2928}
2929
2930function kvm_consentpromise_rejected(e)
2931{
2932 if (this.ws) {
2933 if(this.ws.httprequest){ // User Consent Denied
2934 if ((this.ws.httprequest.oldStyle === true) && (this.ws.httprequest.consentAutoAccept === true) && (e.toString() != "7")) {
2935 kvm_consentpromise_resolved.call(this); // oldStyle prompt timed out and User Consent is not required so connect anyway
2936 return;
2937 }
2938 MeshServerLogEx(34, null, "Failed to start remote desktop after local user rejected (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
2939 } else { } // Connection was closed server side, maybe log some messages somewhere?
2940 this.ws._consentpromise = null;
2941 this.ws.end(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
2942 this.ws = null;
2943 } else { } // no websocket, maybe log some messages somewhere?
2944}
2945function kvm_consentpromise_resolved(always)
2946{
2947 if (always && process.platform=='win32') { server_set_consentTimer(this.ws.httprequest.userid); }
2948
2949 // Success
2950 this.ws._consentpromise = null;
2951 MeshServerLogEx(30, null, "Starting remote desktop after local user accepted (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
2952 this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null, msgid: 0 }));
2953 if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 1))
2954 {
2955 // User Notifications is required
2956 var notifyMessage = currentTranslation['desktopNotify'].replace(/\{0\}/g, this.ws.httprequest.realname);
2957 var notifyTitle = "MeshCentral";
2958 if (this.ws.httprequest.soptions != null)
2959 {
2960 if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; }
2961 if (this.ws.httprequest.soptions.notifyMsgDesktop != null) { notifyMessage = this.ws.httprequest.soptions.notifyMsgDesktop.replace(/\{0\}/g, this.ws.httprequest.realname).replace(/\{1\}/g, this.ws.httprequest.username); }
2962 }
2963 try { require('toaster').Toast(notifyTitle, notifyMessage, tsid); } catch (ex) { }
2964 }
2965 if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 0x40))
2966 {
2967 // Connection Bar is required
2968 if (this.ws.httprequest.desktop.kvm.connectionBar)
2969 {
2970 this.ws.httprequest.desktop.kvm.connectionBar.removeAllListeners('close');
2971 this.ws.httprequest.desktop.kvm.connectionBar.close();
2972 }
2973 try
2974 {
2975 this.ws.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(this.ws.httprequest.privacybartext.replace(/\{0\}/g, this.ws.httprequest.desktop.kvm.rusers.join(', ')).replace(/\{1\}/g, this.ws.httprequest.desktop.kvm.users.join(', ')).replace(/'/g, "\\'\\"), require('MeshAgent')._tsid, color_options);
2976 MeshServerLogEx(31, null, "Remote Desktop Connection Bar Activated/Updated (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
2977 } catch (ex)
2978 {
2979 if (process.platform != 'darwin')
2980 {
2981 MeshServerLogEx(32, null, "Remote Desktop Connection Bar Failed or Not Supported (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
2982 }
2983 }
2984 try {
2985 if (this.ws.httprequest.desktop.kvm.connectionBar) {
2986 this.ws.httprequest.desktop.kvm.connectionBar.httprequest = this.ws.httprequest;
2987 this.ws.httprequest.desktop.kvm.connectionBar.on('close', function () {
2988 MeshServerLogEx(29, null, "Remote Desktop Connection forcefully closed by local user (" + this.httprequest.remoteaddr + ")", this.httprequest);
2989 for (var i in this.httprequest.desktop.kvm._pipedStreams) {
2990 this.httprequest.desktop.kvm._pipedStreams[i].end();
2991 }
2992 this.httprequest.desktop.kvm.end();
2993 });
2994 }
2995 }
2996 catch (ex)
2997 {
2998 if (process.platform != 'darwin')
2999 {
3000 MeshServerLogEx(32, null, "Failed2(" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
3001 }
3002 }
3003 }
3004 this.ws.httprequest.desktop.kvm.pipe(this.ws, { dataTypeSkip: 1 });
3005 if (this.ws.httprequest.autolock)
3006 {
3007 destopLockHelper_pipe(this.ws.httprequest);
3008 }
3009 this.ws.resume();
3010 this.ws = null;
3011}
3012
3013function files_consent_ok(ws){
3014 // User Consent Prompt is not required
3015 if (ws.httprequest.consent && (ws.httprequest.consent & 4)) {
3016 // User Notifications is required
3017 MeshServerLogEx(42, null, "Started remote files with toast notification (" + ws.httprequest.remoteaddr + ")", ws.httprequest);
3018 var notifyMessage = currentTranslation['fileNotify'].replace(/\{0\}/g, ws.httprequest.realname);
3019 var notifyTitle = "MeshCentral";
3020 if (ws.httprequest.soptions != null) {
3021 if (ws.httprequest.soptions.notifyTitle != null) { notifyTitle = ws.httprequest.soptions.notifyTitle; }
3022 if (ws.httprequest.soptions.notifyMsgFiles != null) { notifyMessage = ws.httprequest.soptions.notifyMsgFiles.replace(/\{0\}/g, ws.httprequest.realname).replace(/\{1\}/g, ws.httprequest.username); }
3023 }
3024 try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (ex) { }
3025 } else {
3026 MeshServerLogEx(43, null, "Started remote files without notification (" + ws.httprequest.remoteaddr + ")", ws.httprequest);
3027 }
3028 ws.resume();
3029}
3030
3031function files_consent_ask(ws){
3032 // Send a console message back using the console channel, "\n" is supported.
3033 ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 }));
3034 var consentMessage = currentTranslation['fileConsent'].replace(/\{0\}/g, ws.httprequest.realname).replace(/\{1\}/g, ws.httprequest.username);
3035 var consentTitle = 'MeshCentral';
3036
3037 if (ws.httprequest.soptions != null) {
3038 if (ws.httprequest.soptions.consentTitle != null) { consentTitle = ws.httprequest.soptions.consentTitle; }
3039 if (ws.httprequest.soptions.consentMsgFiles != null) { consentMessage = ws.httprequest.soptions.consentMsgFiles.replace(/\{0\}/g, ws.httprequest.realname).replace(/\{1\}/g, ws.httprequest.username); }
3040 }
3041 var pr;
3042 if (process.platform == 'win32') {
3043 var enhanced = false;
3044 if (ws.httprequest.oldStyle === false) {
3045 try { require('win-userconsent'); enhanced = true; } catch (ex) { }
3046 }
3047 if (enhanced) {
3048 var ipr = server_getUserImage(ws.httprequest.userid);
3049 ipr.consentTitle = consentTitle;
3050 ipr.consentMessage = consentMessage;
3051 ipr.consentTimeout = ws.httprequest.consentTimeout;
3052 ipr.consentAutoAccept = ws.httprequest.consentAutoAccept;
3053 ipr.username = ws.httprequest.realname;
3054 ipr.tsid = ws.tsid;
3055 ipr.translations = { Allow: currentTranslation['allow'], Deny: currentTranslation['deny'], Auto: currentTranslation['autoAllowForFive'], Caption: consentMessage };
3056 pr = ipr.then(function (img) {
3057 this.consent = require('win-userconsent').create(this.consentTitle, this.consentMessage, this.username, { b64Image: img.split(',').pop(), uid: this.tsid, timeout: this.consentTimeout * 1000, timeoutAutoAccept: this.consentAutoAccept, translations: this.translations, background: color_options.background, foreground: color_options.foreground });
3058 this.__childPromise.close = this.consent.close.bind(this.consent);
3059 return (this.consent);
3060 });
3061 } else {
3062 pr = require('message-box').create(consentTitle, consentMessage, ws.httprequest.consentTimeout, null);
3063 }
3064 } else {
3065 pr = require('message-box').create(consentTitle, consentMessage, ws.httprequest.consentTimeout, null);
3066 }
3067 pr.ws = ws;
3068 ws.pause();
3069 ws._consentpromise = pr;
3070 ws.prependOnceListener('end', files_tunnel_endhandler);
3071 pr.then(files_consentpromise_resolved, files_consentpromise_rejected);
3072}
3073
3074function files_consentpromise_resolved(always)
3075{
3076 if (always && process.platform == 'win32') { server_set_consentTimer(this.ws.httprequest.userid); }
3077
3078 // Success
3079 this.ws._consentpromise = null;
3080 MeshServerLogEx(40, null, "Starting remote files after local user accepted (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
3081 this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null }));
3082 if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 4))
3083 {
3084 // User Notifications is required
3085 var notifyMessage = currentTranslation['fileNotify'].replace(/\{0\}/g, this.ws.httprequest.realname);
3086 var notifyTitle = "MeshCentral";
3087 if (this.ws.httprequest.soptions != null)
3088 {
3089 if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; }
3090 if (this.ws.httprequest.soptions.notifyMsgFiles != null) { notifyMessage = this.ws.httprequest.soptions.notifyMsgFiles.replace(/\{0\}/g, this.ws.httprequest.realname).replace(/\{1\}/g, this.ws.httprequest.username); }
3091 }
3092 try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (ex) { }
3093 }
3094 this.ws.resume();
3095 this.ws = null;
3096}
3097function files_consentpromise_rejected(e)
3098{
3099 if (this.ws) {
3100 if(this.ws.httprequest){ // User Consent Denied
3101 if ((this.ws.httprequest.oldStyle === true) && (this.ws.httprequest.consentAutoAccept === true) && (e.toString() != "7")) {
3102 files_consentpromise_resolved.call(this); // oldStyle prompt timed out and User Consent is not required so connect anyway
3103 return;
3104 }
3105 MeshServerLogEx(41, null, "Failed to start remote files after local user rejected (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
3106 } else { } // Connection was closed server side, maybe log some messages somewhere?
3107 this.ws._consentpromise = null;
3108 this.ws.end(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
3109 this.ws = null;
3110 } else { } // no websocket, maybe log some messages somewhere?
3111}
3112function files_tunnel_endhandler()
3113{
3114 if (this._consentpromise && this._consentpromise.close) { this._consentpromise.close(); }
3115}
3116
3117function onTunnelData(data)
3118{
3119 //sendConsoleText('OnTunnelData, ' + data.length + ', ' + typeof data + ', ' + data);
3120
3121 // If this is upload data, save it to file
3122 if ((this.httprequest.uploadFile) && (typeof data == 'object') && (data[0] != 123)) {
3123 // Save the data to file being uploaded.
3124 if (data[0] == 0) {
3125 // If data starts with zero, skip the first byte. This is used to escape binary file data from JSON.
3126 this.httprequest.uploadFileSize += (data.length - 1);
3127 try { fs.writeSync(this.httprequest.uploadFile, data, 1, data.length - 1); } catch (ex) { sendConsoleText('FileUpload Error'); this.write(Buffer.from(JSON.stringify({ action: 'uploaderror' }))); return; } // Write to the file, if there is a problem, error out.
3128 } else {
3129 // If data does not start with zero, save as-is.
3130 this.httprequest.uploadFileSize += data.length;
3131 try { fs.writeSync(this.httprequest.uploadFile, data); } catch (ex) { sendConsoleText('FileUpload Error'); this.write(Buffer.from(JSON.stringify({ action: 'uploaderror' }))); return; } // Write to the file, if there is a problem, error out.
3132 }
3133 this.write(Buffer.from(JSON.stringify({ action: 'uploadack', reqid: this.httprequest.uploadFileid }))); // Ask for more data.
3134 return;
3135 }
3136
3137 if (this.httprequest.state == 0) {
3138 // Check if this is a relay connection
3139 if ((data == 'c') || (data == 'cr')) {
3140 this.httprequest.state = 1;
3141 //sendConsoleText("Tunnel #" + this.httprequest.index + " now active", this.httprequest.sessionid);
3142 }
3143 }
3144 else {
3145 // Handle tunnel data
3146 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
3147 // Take a look at the protocol
3148 if ((data.length > 3) && (data[0] == '{')) { onTunnelControlData(data, this); return; }
3149 this.httprequest.protocol = parseInt(data);
3150 if (typeof this.httprequest.protocol != 'number') { this.httprequest.protocol = 0; }
3151
3152 // See if this protocol request is allowed.
3153 if ((this.httprequest.soptions != null) && (this.httprequest.soptions.usages != null) && (this.httprequest.soptions.usages.indexOf(this.httprequest.protocol) == -1)) { this.httprequest.protocol = 0; }
3154
3155 if (this.httprequest.protocol == 10) {
3156 //
3157 // Basic file transfer
3158 //
3159 var stats = null;
3160 if ((process.platform != 'win32') && (this.httprequest.xoptions.file.startsWith('/') == false)) { this.httprequest.xoptions.file = '/' + this.httprequest.xoptions.file; }
3161 try { stats = require('fs').statSync(this.httprequest.xoptions.file) } catch (ex) { }
3162 try { if (stats) { this.httprequest.downloadFile = fs.createReadStream(this.httprequest.xoptions.file, { flags: 'rbN' }); } } catch (ex) { }
3163 if (this.httprequest.downloadFile) {
3164 MeshServerLogEx(106, [this.httprequest.xoptions.file, stats.size], 'Download: \"' + this.httprequest.xoptions.file + '\", Size: ' + stats.size, this.httprequest);
3165 //sendConsoleText('BasicFileTransfer, ok, ' + this.httprequest.xoptions.file + ', ' + JSON.stringify(stats));
3166 this.write(JSON.stringify({ op: 'ok', size: stats.size }));
3167 this.httprequest.downloadFile.pipe(this);
3168 this.httprequest.downloadFile.end = function () { }
3169 } else {
3170 //sendConsoleText('BasicFileTransfer, cancel, ' + this.httprequest.xoptions.file);
3171 this.write(JSON.stringify({ op: 'cancel' }));
3172 }
3173 }
3174 else if ((this.httprequest.protocol == 1) || (this.httprequest.protocol == 6) || (this.httprequest.protocol == 8) || (this.httprequest.protocol == 9)) {
3175 //
3176 // Remote Terminal
3177 //
3178
3179 // Check user access rights for terminal
3180 if (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) || ((this.httprequest.rights != 0xFFFFFFFF) && ((this.httprequest.rights & MESHRIGHT_NOTERMINAL) != 0)))
3181 {
3182 // Disengage this tunnel, user does not have the rights to do this!!
3183 this.httprequest.protocol = 999999;
3184 this.httprequest.s.end();
3185 sendConsoleText("Error: No Terminal Control Rights.");
3186 return;
3187 }
3188
3189 this.descriptorMetadata = "Remote Terminal";
3190
3191 // Look for a TSID
3192 var tsid = null;
3193 if ((this.httprequest.xoptions != null) && (typeof this.httprequest.xoptions.tsid == 'number')) { tsid = this.httprequest.xoptions.tsid; }
3194 require('MeshAgent')._tsid = tsid;
3195 this.tsid = tsid;
3196
3197 if (process.platform == 'win32')
3198 {
3199 if (!require('win-terminal').PowerShellCapable() && (this.httprequest.protocol == 6 || this.httprequest.protocol == 9)) {
3200 this.httprequest.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: 'PowerShell is not supported on this version of windows', msgid: 1 }));
3201 this.httprequest.s.end();
3202 return;
3203 }
3204 }
3205
3206 var prom = require('promise');
3207 this.httprequest.tpromise = new prom(promise_init);
3208 this.httprequest.tpromise.that = this;
3209 this.httprequest.tpromise.httprequest = this.httprequest;
3210 this.end = terminal_end;
3211
3212 // Perform User-Consent if needed.
3213 if (this.httprequest.consent && (this.httprequest.consent & 16)) {
3214 // User asked for consent so now we check if we can auto accept if no user is present/loggedin
3215 if (this.httprequest.consentAutoAcceptIfNoUser || this.httprequest.consentAutoAcceptIfTerminalNoUser || this.httprequest.consentAutoAcceptIfLocked || this.httprequest.consentAutoAcceptIfTerminalLocked) {
3216 var p = require('user-sessions').enumerateUsers();
3217 p.sessionid = this.httprequest.sessionid;
3218 p.ws = this;
3219 p.then(function (u) {
3220 var v = [];
3221 for (var i in u) {
3222 if (u[i].State == 'Active') { v.push({ tsid: i, type: u[i].StationName, user: u[i].Username, domain: u[i].Domain }); }
3223 }
3224 var autoAccept = false;
3225
3226 // Check if we should auto-accept because no user is present
3227 if ((this.ws.httprequest.consentAutoAcceptIfNoUser || this.ws.httprequest.consentAutoAcceptIfTerminalNoUser) && (v.length == 0)) {
3228 autoAccept = true;
3229 }
3230
3231 // Check if we should auto-accept because all users are locked
3232 if ((this.ws.httprequest.consentAutoAcceptIfLocked || this.ws.httprequest.consentAutoAcceptIfTerminalLocked) && (v.length > 0)) {
3233 var allUsersLocked = true;
3234 if (!meshCoreObj.lusers || meshCoreObj.lusers.length == 0) {
3235 // No locked users list available, assume users are not locked
3236 allUsersLocked = false;
3237 } else {
3238 for (var i in v) {
3239 var username = v[i].domain ? (v[i].domain + '\\' + v[i].user) : v[i].user;
3240 if (meshCoreObj.lusers.indexOf(username) == -1) {
3241 allUsersLocked = false;
3242 break;
3243 }
3244 }
3245 }
3246 if (allUsersLocked) { autoAccept = true; }
3247 }
3248
3249 if (autoAccept) {
3250 this.ws.httprequest.tpromise._res();
3251 } else {
3252 // User is present and not all locked, so we still need consent
3253 terminal_consent_ask(this.ws);
3254 }
3255 });
3256 } else {
3257 terminal_consent_ask(this);
3258 }
3259 } else {
3260 // User-Consent is not required, so just resolve this promise
3261 this.httprequest.tpromise._res();
3262 }
3263 this.httprequest.tpromise.then(terminal_promise_consent_resolved, terminal_promise_consent_rejected);
3264 }
3265 else if (this.httprequest.protocol == 2)
3266 {
3267 //
3268 // Remote Desktop
3269 //
3270
3271 // Check user access rights for desktop
3272 if ((((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) && ((this.httprequest.rights & MESHRIGHT_REMOTEVIEW) == 0)) || ((this.httprequest.rights != 0xFFFFFFFF) && ((this.httprequest.rights & MESHRIGHT_NODESKTOP) != 0))) {
3273 // Disengage this tunnel, user does not have the rights to do this!!
3274 this.httprequest.protocol = 999999;
3275 this.httprequest.s.end();
3276 sendConsoleText("Error: No Desktop Control Rights.");
3277 return;
3278 }
3279
3280 this.descriptorMetadata = "Remote KVM";
3281
3282 // Look for a TSID
3283 var tsid = null;
3284 if ((this.httprequest.xoptions != null) && (typeof this.httprequest.xoptions.tsid == 'number')) { tsid = this.httprequest.xoptions.tsid; }
3285 require('MeshAgent')._tsid = tsid;
3286 this.tsid = tsid;
3287
3288 // If MacOS, Wake up device with caffeinate
3289 if(process.platform == 'darwin'){
3290 try {
3291 var options = {};
3292 try { options.uid = require('user-sessions').consoleUid(); } catch (ex) { }
3293 options.type = require('child_process').SpawnTypes.TERM;
3294 var replydata = "";
3295 var cmdchild = require('child_process').execFile('/usr/bin/caffeinate', ['caffeinate', '-u', '-t', '10'], options);
3296 cmdchild.descriptorMetadata = 'UserCommandsShell';
3297 cmdchild.stdout.on('data', function (c) { replydata += c.toString(); });
3298 cmdchild.stderr.on('data', function (c) { replydata + c.toString(); });
3299 cmdchild.on('exit', function () { delete cmdchild; });
3300 } catch(err) { }
3301 }
3302 // Remote desktop using native pipes
3303 this.httprequest.desktop = { state: 0, kvm: mesh.getRemoteDesktopStream(tsid), tunnel: this };
3304 this.httprequest.desktop.kvm.parent = this.httprequest.desktop;
3305 this.desktop = this.httprequest.desktop;
3306
3307 // Add ourself to the list of remote desktop sessions
3308 if (this.httprequest.desktop.kvm.tunnels == null) { this.httprequest.desktop.kvm.tunnels = []; }
3309 this.httprequest.desktop.kvm.tunnels.push(this);
3310
3311 // Send a metadata update to all desktop sessions
3312 var users = {};
3313 if (this.httprequest.desktop.kvm.tunnels != null)
3314 {
3315 for (var i in this.httprequest.desktop.kvm.tunnels)
3316 {
3317 try {
3318 var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest.desktop.kvm.tunnels[i].httprequest);
3319 if (users[userid] == null) { users[userid] = 1; } else { users[userid]++; }
3320 } catch (ex) { sendConsoleText(ex); }
3321 }
3322 for (var i in this.httprequest.desktop.kvm.tunnels)
3323 {
3324 try { this.httprequest.desktop.kvm.tunnels[i].write(JSON.stringify({ ctrlChannel: '102938', type: 'metadata', users: users })); } catch (ex) { }
3325 }
3326 tunnelUserCount.desktop = users;
3327 try { mesh.SendCommand({ action: 'sessions', type: 'kvm', value: users }); } catch (ex) { }
3328 broadcastSessionsToRegisteredApps();
3329 }
3330
3331 this.end = tunnel_kvm_end;
3332
3333 if (this.httprequest.desktop.kvm.hasOwnProperty('connectionCount')) {
3334 this.httprequest.desktop.kvm.connectionCount++;
3335 this.httprequest.desktop.kvm.rusers.push(this.httprequest.realname);
3336 this.httprequest.desktop.kvm.users.push(this.httprequest.username);
3337 this.httprequest.desktop.kvm.rusers.sort();
3338 this.httprequest.desktop.kvm.users.sort();
3339 } else {
3340 this.httprequest.desktop.kvm.connectionCount = 1;
3341 this.httprequest.desktop.kvm.rusers = [this.httprequest.realname];
3342 this.httprequest.desktop.kvm.users = [this.httprequest.username];
3343 }
3344
3345 if ((this.httprequest.desktopviewonly != true) && ((this.httprequest.rights == 0xFFFFFFFF) || (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) != 0) && ((this.httprequest.rights & MESHRIGHT_REMOTEVIEW) == 0))))
3346 {
3347 // If we have remote control rights, pipe the KVM input
3348 this.pipe(this.httprequest.desktop.kvm, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. Pipe the Browser --> KVM input.
3349 }
3350 else
3351 {
3352 // We need to only pipe non-mouse & non-keyboard inputs.
3353 // sendConsoleText('Warning: No Remote Desktop Input Rights.');
3354 // TODO!!!
3355 }
3356
3357 // Perform notification if needed. Toast messages may not be supported on all platforms.
3358 if (this.httprequest.consent && (this.httprequest.consent & 8)) {
3359
3360 // User asked for consent but now we check if can auto accept if no user is present
3361 if (this.httprequest.consentAutoAcceptIfNoUser || this.httprequest.consentAutoAcceptIfDesktopNoUser || this.httprequest.consentAutoAcceptIfLocked || this.httprequest.consentAutoAcceptIfDesktopLocked) {
3362 // Get list of users to check if we any actual users logged in, and if users logged in, we still need consent
3363 var p = require('user-sessions').enumerateUsers();
3364 p.sessionid = this.httprequest.sessionid;
3365 p.ws = this;
3366 p.then(function (u) {
3367 var v = [];
3368 for (var i in u) {
3369 if (u[i].State == 'Active') { v.push({ tsid: i, type: u[i].StationName, user: u[i].Username, domain: u[i].Domain }); }
3370 }
3371 var autoAccept = false;
3372
3373 // Check if we can auto-accept because no user is present
3374 if ((this.ws.httprequest.consentAutoAcceptIfNoUser || this.ws.httprequest.consentAutoAcceptIfDesktopNoUser) && (v.length == 0)) {
3375 // No user is present, auto accept
3376 autoAccept = true;
3377 }
3378
3379 // Check if we can auto-accept because all users are locked
3380 if ((this.ws.httprequest.consentAutoAcceptIfLocked || this.ws.httprequest.consentAutoAcceptIfDesktopLocked) && (v.length > 0)) {
3381 var allUsersLocked = true;
3382 if (!meshCoreObj.lusers || meshCoreObj.lusers.length == 0) {
3383 // No locked users list available, assume users are not locked
3384 allUsersLocked = false;
3385 } else {
3386 for (var i in v) {
3387 var username = v[i].domain ? (v[i].domain + '\\' + v[i].user) : v[i].user;
3388 if (meshCoreObj.lusers.indexOf(username) == -1) {
3389 allUsersLocked = false;
3390 break;
3391 }
3392 }
3393 }
3394 if (allUsersLocked) { autoAccept = true; }
3395 }
3396
3397 if (autoAccept) {
3398 kvm_consent_ok(this.ws);
3399 } else {
3400 // User is present and not all locked, so we still need consent
3401 kvm_consent_ask(this.ws);
3402 }
3403 });
3404 } else {
3405 // User Consent Prompt is required
3406 kvm_consent_ask(this);
3407 }
3408 } else {
3409 // User Consent Prompt is not required
3410 kvm_consent_ok(this);
3411 }
3412
3413 this.removeAllListeners('data');
3414 this.on('data', onTunnelControlData);
3415 //this.write('MeshCore KVM Hello!1');
3416 } else if (this.httprequest.protocol == 5) {
3417 //
3418 // Remote Files
3419 //
3420
3421 // Check user access rights for files
3422 if (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) || ((this.httprequest.rights != 0xFFFFFFFF) && ((this.httprequest.rights & MESHRIGHT_NOFILES) != 0))) {
3423 // Disengage this tunnel, user does not have the rights to do this!!
3424 this.httprequest.protocol = 999999;
3425 this.httprequest.s.end();
3426 sendConsoleText("Error: No files control rights.");
3427 return;
3428 }
3429
3430 this.descriptorMetadata = "Remote Files";
3431
3432 // Look for a TSID
3433 var tsid = null;
3434 if ((this.httprequest.xoptions != null) && (typeof this.httprequest.xoptions.tsid == 'number')) { tsid = this.httprequest.xoptions.tsid; }
3435 require('MeshAgent')._tsid = tsid;
3436 this.tsid = tsid;
3437
3438 // Add the files session to the count to update the server
3439 if (this.httprequest.userid != null) {
3440 var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest);
3441 if (tunnelUserCount.files[userid] == null) { tunnelUserCount.files[userid] = 1; } else { tunnelUserCount.files[userid]++; }
3442 try { mesh.SendCommand({ action: 'sessions', type: 'files', value: tunnelUserCount.files }); } catch (ex) { }
3443 broadcastSessionsToRegisteredApps();
3444 }
3445
3446 this.end = function ()
3447 {
3448 // Remove the files session from the count to update the server
3449 if (this.httprequest.userid != null) {
3450 var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest);
3451 if (tunnelUserCount.files[userid] != null) { tunnelUserCount.files[userid]--; if (tunnelUserCount.files[userid] <= 0) { delete tunnelUserCount.files[userid]; } }
3452 try { mesh.SendCommand({ action: 'sessions', type: 'files', value: tunnelUserCount.files }); } catch (ex) { }
3453 broadcastSessionsToRegisteredApps();
3454 }
3455 };
3456
3457 // Perform notification if needed. Toast messages may not be supported on all platforms.
3458 if (this.httprequest.consent && (this.httprequest.consent & 32)) {
3459 // User asked for consent so now we check if we can auto accept if no user is present/loggedin
3460 if (this.httprequest.consentAutoAcceptIfNoUser || this.httprequest.consentAutoAcceptIfFileNoUser || this.httprequest.consentAutoAcceptIfLocked || this.httprequest.consentAutoAcceptIfFileLocked) {
3461 var p = require('user-sessions').enumerateUsers();
3462 p.sessionid = this.httprequest.sessionid;
3463 p.ws = this;
3464 p.then(function (u) {
3465 var v = [];
3466 for (var i in u) {
3467 if (u[i].State == 'Active') { v.push({ tsid: i, type: u[i].StationName, user: u[i].Username, domain: u[i].Domain }); }
3468 }
3469 var autoAccept = false;
3470
3471 // Check if we should auto-accept because no user is present
3472 if ((this.ws.httprequest.consentAutoAcceptIfNoUser || this.ws.httprequest.consentAutoAcceptIfFileNoUser) && (v.length == 0)) {
3473 autoAccept = true;
3474 }
3475
3476 // Check if we should auto-accept because all users are locked
3477 if ((this.ws.httprequest.consentAutoAcceptIfLocked || this.ws.httprequest.consentAutoAcceptIfFileLocked) && (v.length > 0)) {
3478 var allUsersLocked = true;
3479 if (!meshCoreObj.lusers || meshCoreObj.lusers.length == 0) {
3480 // No locked users list available, assume users are not locked
3481 allUsersLocked = false;
3482 } else {
3483 for (var i in v) {
3484 var username = v[i].domain ? (v[i].domain + '\\' + v[i].user) : v[i].user;
3485 if (meshCoreObj.lusers.indexOf(username) == -1) {
3486 allUsersLocked = false;
3487 break;
3488 }
3489 }
3490 }
3491 if (allUsersLocked) { autoAccept = true; }
3492 }
3493
3494 if (autoAccept) {
3495 // User Consent Prompt is not required
3496 files_consent_ok(this.ws);
3497 } else {
3498 // User is present and not all locked, so we still need consent
3499 files_consent_ask(this.ws);
3500 }
3501 });
3502 } else {
3503 // User Consent Prompt is required
3504 files_consent_ask(this);
3505 }
3506 } else {
3507 // User Consent Prompt is not required
3508 files_consent_ok(this);
3509 }
3510
3511 // Setup files
3512 // NOP
3513 }
3514 } else if (this.httprequest.protocol == 1) {
3515 // Send data into terminal stdin
3516 //this.write(data); // Echo back the keys (Does not seem to be a good idea)
3517 } else if (this.httprequest.protocol == 2) {
3518 // Send data into remote desktop
3519 if (this.httprequest.desktop.state == 0) {
3520 this.write(Buffer.from(String.fromCharCode(0x11, 0xFE, 0x00, 0x00, 0x4D, 0x45, 0x53, 0x48, 0x00, 0x00, 0x00, 0x00, 0x02)));
3521 this.httprequest.desktop.state = 1;
3522 } else {
3523 this.httprequest.desktop.write(data);
3524 }
3525 } else if (this.httprequest.protocol == 5) {
3526 // Process files commands
3527 var cmd = null;
3528 try { cmd = JSON.parse(data); } catch (ex) { };
3529 if (cmd == null) { return; }
3530 if ((cmd.ctrlChannel == '102938') || ((cmd.type == 'offer') && (cmd.sdp != null))) { onTunnelControlData(cmd, this); return; } // If this is control data, handle it now.
3531 if (cmd.action == undefined) { return; }
3532 //sendConsoleText('CMD: ' + JSON.stringify(cmd));
3533
3534 if ((cmd.path != null) && (process.platform != 'win32') && (cmd.path[0] != '/')) { cmd.path = '/' + cmd.path; } // Add '/' to paths on non-windows
3535 //console.log(objToString(cmd, 0, ' '));
3536 switch (cmd.action) {
3537 case 'ls': {
3538 /*
3539 // Close the watcher if required
3540 var samepath = ((this.httprequest.watcher != undefined) && (cmd.path == this.httprequest.watcher.path));
3541 if ((this.httprequest.watcher != undefined) && (samepath == false)) {
3542 //console.log('Closing watcher: ' + this.httprequest.watcher.path);
3543 //this.httprequest.watcher.close(); // TODO: This line causes the agent to crash!!!!
3544 delete this.httprequest.watcher;
3545 }
3546 */
3547
3548 // Send the folder content to the browser
3549 var response = getDirectoryInfo(cmd.path);
3550 response.reqid = cmd.reqid;
3551 this.write(Buffer.from(JSON.stringify(response)));
3552
3553 /*
3554 // Start the directory watcher
3555 if ((cmd.path != '') && (samepath == false)) {
3556 var watcher = fs.watch(cmd.path, onFileWatcher);
3557 watcher.tunnel = this.httprequest;
3558 watcher.path = cmd.path;
3559 this.httprequest.watcher = watcher;
3560 //console.log('Starting watcher: ' + this.httprequest.watcher.path);
3561 }
3562 */
3563 break;
3564 }
3565 case 'mkdir': {
3566 // Create a new empty folder
3567 fs.mkdirSync(cmd.path);
3568 MeshServerLogEx(44, [cmd.path], "Create folder: \"" + cmd.path + "\"", this.httprequest);
3569 break;
3570 }
3571 case 'mkfile': {
3572 // Create a new empty file
3573 fs.closeSync(fs.openSync(cmd.path, 'w'));
3574 MeshServerLogEx(164, [cmd.path], "Create file: \"" + cmd.path + "\"", this.httprequest);
3575 break;
3576 }
3577 case 'rm': {
3578 // Delete, possibly recursive delete
3579 for (var i in cmd.delfiles) {
3580 var p = obj.path.join(cmd.path, cmd.delfiles[i]), delcount = 0;
3581 try { delcount = deleteFolderRecursive(p, cmd.rec); } catch (ex) { }
3582 if ((delcount == 1) && !cmd.rec) {
3583 MeshServerLogEx(45, [p], "Delete: \"" + p + "\"", this.httprequest);
3584 } else {
3585 if (cmd.rec) {
3586 MeshServerLogEx(46, [p, delcount], "Delete recursive: \"" + p + "\", " + delcount + " element(s) removed", this.httprequest);
3587 } else {
3588 MeshServerLogEx(47, [p, delcount], "Delete: \"" + p + "\", " + delcount + " element(s) removed", this.httprequest);
3589 }
3590 }
3591 }
3592 break;
3593 }
3594 case 'open': {
3595 // Open the local file/folder on the users desktop
3596 if (cmd.path) {
3597 MeshServerLogEx(20, [cmd.path], "Opening: " + cmd.path, cmd);
3598 openFileOnDesktop(cmd.path);
3599 }
3600 }
3601 case 'markcoredump': {
3602 // If we are asking for the coredump file, set the right path.
3603 var coreDumpPath = null;
3604 if (process.platform == 'win32') {
3605 if (fs.existsSync(process.coreDumpLocation)) { coreDumpPath = process.coreDumpLocation; }
3606 } else {
3607 if ((process.cwd() != '//') && fs.existsSync(process.cwd() + 'core')) { coreDumpPath = process.cwd() + 'core'; }
3608 }
3609 if (coreDumpPath != null) { db.Put('CoreDumpTime', require('fs').statSync(coreDumpPath).mtime); }
3610 break;
3611 }
3612 case 'rename':
3613 {
3614 // Rename a file or folder
3615 var oldfullpath = obj.path.join(cmd.path, cmd.oldname);
3616 var newfullpath = obj.path.join(cmd.path, cmd.newname);
3617 MeshServerLogEx(48, [oldfullpath, cmd.newname], 'Rename: \"' + oldfullpath + '\" to \"' + cmd.newname + '\"', this.httprequest);
3618 try { fs.renameSync(oldfullpath, newfullpath); } catch (ex) { console.log(ex); }
3619 break;
3620 }
3621 case 'findfile':
3622 {
3623 // Search for files
3624 var r = require('file-search').find('"' + cmd.path + '"', cmd.filter);
3625 if (!r.cancel) { r.cancel = function cancel() { this.child.kill(); }; }
3626 this._search = r;
3627 r.socket = this;
3628 r.socket.reqid = cmd.reqid; // Search request id. This is used to send responses and cancel the request.
3629 r.socket.path = cmd.path; // Search path
3630 r.on('result', function (str) { try { this.socket.write(Buffer.from(JSON.stringify({ action: 'findfile', r: str.substring(this.socket.path.length), reqid: this.socket.reqid }))); } catch (ex) { } });
3631 r.then(function () { try { this.socket.write(Buffer.from(JSON.stringify({ action: 'findfile', r: null, reqid: this.socket.reqid }))); } catch (ex) { } });
3632 break;
3633 }
3634 case 'cancelfindfile':
3635 {
3636 if (this._search) { this._search.cancel(); this._search = null; }
3637 break;
3638 }
3639 case 'download':
3640 {
3641 // Download a file
3642 var sendNextBlock = 0;
3643 if (cmd.sub == 'start') { // Setup the download
3644 if ((cmd.path == null) && (cmd.ask == 'coredump')) { // If we are asking for the coredump file, set the right path.
3645 if (process.platform == 'win32') {
3646 if (fs.existsSync(process.coreDumpLocation)) { cmd.path = process.coreDumpLocation; }
3647 } else {
3648 if ((process.cwd() != '//') && fs.existsSync(process.cwd() + 'core')) { cmd.path = process.cwd() + 'core'; }
3649 }
3650 }
3651 MeshServerLogEx((cmd.ask == 'coredump') ? 104 : 49, [cmd.path], 'Download: \"' + cmd.path + '\"', this.httprequest);
3652 if ((cmd.path == null) || (this.filedownload != null)) { this.write({ action: 'download', sub: 'cancel', id: this.filedownload.id }); delete this.filedownload; }
3653 this.filedownload = { id: cmd.id, path: cmd.path, ptr: 0 }
3654 try { this.filedownload.f = fs.openSync(this.filedownload.path, 'rbN'); } catch (ex) { this.write({ action: 'download', sub: 'cancel', id: this.filedownload.id }); delete this.filedownload; }
3655 if (this.filedownload) { this.write({ action: 'download', sub: 'start', id: cmd.id }); }
3656 } else if ((this.filedownload != null) && (cmd.id == this.filedownload.id)) { // Download commands
3657 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; }
3658 }
3659 // Send the next download block(s)
3660 if (sendNextBlock > 0) {
3661 sendNextBlock--;
3662 var buf = Buffer.alloc(16384);
3663 var len = fs.readSync(this.filedownload.f, buf, 4, 16380, null);
3664 this.filedownload.ptr += len;
3665 if (len < 16380) { buf.writeInt32BE(0x01000001, 0); fs.closeSync(this.filedownload.f); delete this.filedownload; sendNextBlock = 0; } else { buf.writeInt32BE(0x01000000, 0); }
3666 this.write(buf.slice(0, len + 4)); // Write as binary
3667 }
3668 break;
3669 }
3670 case 'upload':
3671 {
3672 // Upload a file, browser to agent
3673 if (this.httprequest.uploadFile != null) { fs.closeSync(this.httprequest.uploadFile); delete this.httprequest.uploadFile; }
3674 if (cmd.path == undefined) break;
3675 var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path;
3676 this.httprequest.uploadFilePath = filepath;
3677 this.httprequest.uploadFileSize = 0;
3678 try { this.httprequest.uploadFile = fs.openSync(filepath, cmd.append ? 'abN' : 'wbN'); } catch (ex) { this.write(Buffer.from(JSON.stringify({ action: 'uploaderror', reqid: cmd.reqid }))); break; }
3679 this.httprequest.uploadFileid = cmd.reqid;
3680 if (this.httprequest.uploadFile) { this.write(Buffer.from(JSON.stringify({ action: 'uploadstart', reqid: this.httprequest.uploadFileid }))); }
3681 break;
3682 }
3683 case 'uploaddone':
3684 {
3685 // Indicates that an upload is done
3686 if (this.httprequest.uploadFile) {
3687 MeshServerLogEx(105, [this.httprequest.uploadFilePath, this.httprequest.uploadFileSize], 'Upload: \"' + this.httprequest.uploadFilePath + '\", Size: ' + this.httprequest.uploadFileSize, this.httprequest);
3688 fs.closeSync(this.httprequest.uploadFile);
3689 this.write(Buffer.from(JSON.stringify({ action: 'uploaddone', reqid: this.httprequest.uploadFileid }))); // Indicate that we closed the file.
3690 delete this.httprequest.uploadFile;
3691 delete this.httprequest.uploadFileid;
3692 delete this.httprequest.uploadFilePath;
3693 delete this.httprequest.uploadFileSize;
3694 }
3695 break;
3696 }
3697 case 'uploadcancel':
3698 {
3699 // Indicates that an upload is canceled
3700 if (this.httprequest.uploadFile) {
3701 fs.closeSync(this.httprequest.uploadFile);
3702 fs.unlinkSync(this.httprequest.uploadFilePath);
3703 this.write(Buffer.from(JSON.stringify({ action: 'uploadcancel', reqid: this.httprequest.uploadFileid }))); // Indicate that we closed the file.
3704 delete this.httprequest.uploadFile;
3705 delete this.httprequest.uploadFileid;
3706 delete this.httprequest.uploadFilePath;
3707 delete this.httprequest.uploadFileSize;
3708 }
3709 break;
3710 }
3711 case 'uploadhash':
3712 {
3713 // Hash a file
3714 var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path;
3715 var h = null;
3716 try { h = getSHA384FileHash(filepath); } catch (ex) { sendConsoleText(ex); }
3717 this.write(Buffer.from(JSON.stringify({ action: 'uploadhash', reqid: cmd.reqid, path: cmd.path, name: cmd.name, tag: cmd.tag, hash: (h ? h.toString('hex') : null) })));
3718 break
3719 }
3720 case 'copy':
3721 {
3722 // Copy a bunch of files from scpath to dspath
3723 for (var i in cmd.names) {
3724 var sc = obj.path.join(cmd.scpath, cmd.names[i]), ds = obj.path.join(cmd.dspath, cmd.names[i]);
3725 MeshServerLogEx(51, [sc, ds], 'Copy: \"' + sc + '\" to \"' + ds + '\"', this.httprequest);
3726 if (sc != ds) { try { fs.copyFileSync(sc, ds); } catch (ex) { } }
3727 }
3728 break;
3729 }
3730 case 'move':
3731 {
3732 // Move a bunch of files from scpath to dspath
3733 for (var i in cmd.names) {
3734 var sc = obj.path.join(cmd.scpath, cmd.names[i]), ds = obj.path.join(cmd.dspath, cmd.names[i]);
3735 MeshServerLogEx(52, [sc, ds], 'Move: \"' + sc + '\" to \"' + ds + '\"', this.httprequest);
3736 if (sc != ds) { try { fs.copyFileSync(sc, ds); fs.unlinkSync(sc); } catch (ex) { } }
3737 }
3738 break;
3739 }
3740 case 'zip':
3741 // Zip a bunch of files
3742 if (this.zip != null) return; // Zip operating is currently running, exit now.
3743
3744 // Check that the specified files exist & build full paths
3745 var fp, stat, p = [];
3746 for (var i in cmd.files) { fp = cmd.path + '/' + cmd.files[i]; stat = null; try { stat = fs.statSync(fp); } catch (ex) { } if (stat != null) { p.push(fp); } }
3747 if (p.length == 0) return; // No files, quit now.
3748
3749 // Setup file compression
3750 var ofile = cmd.path + '/' + cmd.output;
3751 this.write(Buffer.from(JSON.stringify({ action: 'dialogmessage', msg: 'zipping' })));
3752 this.zipfile = ofile;
3753 delete this.zipcancel;
3754 var out = require('fs').createWriteStream(ofile, { flags: 'wb' });
3755 out.xws = this;
3756 out.on('close', function () {
3757 this.xws.write(Buffer.from(JSON.stringify({ action: 'dialogmessage', msg: null })));
3758 this.xws.write(Buffer.from(JSON.stringify({ action: 'refresh' })));
3759 if (this.xws.zipcancel === true) { fs.unlinkSync(this.xws.zipfile); } // Delete the complete file.
3760 delete this.xws.zipcancel;
3761 delete this.xws.zipfile;
3762 delete this.xws.zip;
3763 });
3764 this.zip = require('zip-writer').write({ files: p, basePath: cmd.path });
3765 this.zip.xws = this;
3766 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));
3767 this.zip.pipe(out);
3768 break;
3769 case 'unzip':
3770 if (this.unzip != null) return; // Unzip operating is currently running, exit now.
3771 this.unzip = require('zip-reader').read(cmd.input);
3772 this.unzip._dest = cmd.dest;
3773 this.unzip.xws = this;
3774 this.unzip.then(function (zipped) {
3775 this.xws.write(Buffer.from(JSON.stringify({ action: 'dialogmessage', msg: 'unzipping' })));
3776 zipped.xws = this.xws;
3777 zipped.extractAll(this._dest).then(function () { // finished extracting
3778 zipped.xws.write(Buffer.from(JSON.stringify({ action: 'dialogmessage', msg: null })));
3779 zipped.xws.write(Buffer.from(JSON.stringify({ action: 'refresh' })));
3780 delete zipped.xws.unzip;
3781 }, function (e) { // error extracting
3782 zipped.xws.write(Buffer.from(JSON.stringify({ action: 'dialogmessage', msg: 'unziperror', error: e })));
3783 delete zipped.xws.unzip;
3784 });
3785 }, function (e) { this.xws.write(Buffer.from(JSON.stringify({ action: 'dialogmessage', msg: 'unziperror', error: e }))); delete this.xws.unzip });
3786 break;
3787 case 'cancel':
3788 // Cancel zip operation if present
3789 try { this.zipcancel = true; this.zip.cancel(function () { }); } catch (ex) { }
3790 this.zip = null;
3791 break;
3792 default:
3793 // Unknown action, ignore it.
3794 break;
3795 }
3796 } else if (this.httprequest.protocol == 7) { // Plugin data exchange
3797 var cmd = null;
3798 try { cmd = JSON.parse(data); } catch (ex) { };
3799 if (cmd == null) { return; }
3800 if ((cmd.ctrlChannel == '102938') || ((cmd.type == 'offer') && (cmd.sdp != null))) { onTunnelControlData(cmd, this); return; } // If this is control data, handle it now.
3801 if (cmd.action == undefined) return;
3802
3803 switch (cmd.action) {
3804 case 'plugin': {
3805 try { require(cmd.plugin).consoleaction(cmd, null, null, this); } catch (ex) { throw ex; }
3806 break;
3807 }
3808 default: {
3809 // probably shouldn't happen, but just in case this feature is expanded
3810 }
3811 }
3812
3813 }
3814 //sendConsoleText("Got tunnel #" + this.httprequest.index + " data: " + data, this.httprequest.sessionid);
3815 }
3816}
3817
3818// Delete a directory with a files and directories within it
3819function deleteFolderRecursive(path, rec) {
3820 var count = 0;
3821 if (fs.existsSync(path)) {
3822 if (rec == true) {
3823 fs.readdirSync(obj.path.join(path, '*')).forEach(function (file, index) {
3824 var curPath = obj.path.join(path, file);
3825 if (fs.statSync(curPath).isDirectory()) { // recurse
3826 count += deleteFolderRecursive(curPath, true);
3827 } else { // delete file
3828 fs.unlinkSync(curPath);
3829 count++;
3830 }
3831 });
3832 }
3833 fs.unlinkSync(path);
3834 count++;
3835 }
3836 return count;
3837}
3838
3839// Called when receiving control data on WebRTC
3840function onTunnelWebRTCControlData(data) {
3841 if (typeof data != 'string') return;
3842 var obj;
3843 try { obj = JSON.parse(data); } catch (ex) { sendConsoleText('Invalid control JSON on WebRTC: ' + data); return; }
3844 if (obj.type == 'close') {
3845 //sendConsoleText('Tunnel #' + this.xrtc.websocket.tunnel.index + ' WebRTC control close');
3846 try { this.close(); } catch (ex) { }
3847 try { this.xrtc.close(); } catch (ex) { }
3848 }
3849}
3850
3851function tunnel_webrtc_onEnd()
3852{
3853 // The WebRTC channel closed, unpipe the KVM now. This is also done when the web socket closes.
3854 //sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC data channel closed');
3855 if (this.websocket.desktop && this.websocket.desktop.kvm)
3856 {
3857 try
3858 {
3859 this.unpipe(this.websocket.desktop.kvm);
3860 this.websocket.httprequest.desktop.kvm.unpipe(this);
3861 } catch (ex) { }
3862 }
3863 this.httprequest = null;
3864 this.websocket = null;
3865}
3866function tunnel_webrtc_DataChannel_OnFinalized()
3867{
3868 console.info1('WebRTC DataChannel Finalized');
3869}
3870function tunnel_webrtc_OnDataChannel(rtcchannel)
3871{
3872 //sendConsoleText('WebRTC Datachannel open, protocol: ' + this.websocket.httprequest.protocol);
3873 //rtcchannel.maxFragmentSize = 32768;
3874 rtcchannel.xrtc = this;
3875 rtcchannel.websocket = this.websocket;
3876 this.rtcchannel = rtcchannel;
3877 this.rtcchannel.once('~', tunnel_webrtc_DataChannel_OnFinalized);
3878 this.websocket.rtcchannel = rtcchannel;
3879 this.websocket.rtcchannel.on('data', onTunnelWebRTCControlData);
3880 this.websocket.rtcchannel.on('end', tunnel_webrtc_onEnd);
3881 this.websocket.write('{\"ctrlChannel\":\"102938\",\"type\":\"webrtc0\"}'); // Indicate we are ready for WebRTC switch-over.
3882}
3883
3884function tunnel_webrtc_OnFinalized()
3885{
3886 console.info1('WebRTC Connection Finalized');
3887}
3888
3889// Called when receiving control data on websocket
3890function onTunnelControlData(data, ws) {
3891 var obj;
3892 if (ws == null) { ws = this; }
3893 if (typeof data == 'string') { try { obj = JSON.parse(data); } catch (ex) { sendConsoleText('Invalid control JSON: ' + data); return; } }
3894 else if (typeof data == 'object') { obj = data; } else { return; }
3895 //sendConsoleText('onTunnelControlData(' + ws.httprequest.protocol + '): ' + JSON.stringify(data));
3896 //console.log('onTunnelControlData: ' + JSON.stringify(data));
3897
3898 switch (obj.type) {
3899 case 'lock': {
3900 // Look for a TSID
3901 var tsid = null;
3902 if ((ws.httprequest.xoptions != null) && (typeof ws.httprequest.xoptions.tsid == 'number')) { tsid = ws.httprequest.xoptions.tsid; }
3903
3904 // Lock the current user out of the desktop
3905 MeshServerLogEx(53, null, "Locking remote user out of desktop", ws.httprequest);
3906 lockDesktop(tsid);
3907 break;
3908 }
3909 case 'autolock': {
3910 // Set the session to auto lock on disconnect
3911 if (obj.value === true) {
3912 ws.httprequest.autolock = true;
3913 if (ws.httprequest.unlockerHelper == null) {
3914 destopLockHelper_pipe(ws.httprequest);
3915 }
3916 }
3917 else {
3918 delete ws.httprequest.autolock;
3919 }
3920 break;
3921 }
3922 case 'options': {
3923 // These are additional connection options passed in the control channel.
3924 //sendConsoleText('options: ' + JSON.stringify(obj));
3925 delete obj.type;
3926 ws.httprequest.xoptions = obj;
3927
3928 // Set additional user consent options if present
3929 if ((obj != null) && (typeof obj.consent == 'number')) { ws.httprequest.consent |= obj.consent; }
3930
3931 // Set autolock
3932 if ((obj != null) && (obj.autolock === true)) {
3933 ws.httprequest.autolock = true;
3934 if (ws.httprequest.unlockerHelper == null) {
3935 destopLockHelper_pipe(ws.httprequest);
3936 }
3937 }
3938
3939 break;
3940 }
3941 case 'close': {
3942 // We received the close on the websocket
3943 //sendConsoleText('Tunnel #' + ws.tunnel.index + ' WebSocket control close');
3944 try { ws.close(); } catch (ex) { }
3945 break;
3946 }
3947 case 'termsize': {
3948 // Indicates a change in terminal size
3949 if (process.platform == 'win32') {
3950 if (ws.httprequest._dispatcher == null) return;
3951 //sendConsoleText('Win32-TermSize: ' + obj.cols + 'x' + obj.rows);
3952 if (ws.httprequest._dispatcher.invoke) { ws.httprequest._dispatcher.invoke('resizeTerminal', [obj.cols, obj.rows]); }
3953 } else {
3954 if (ws.httprequest.process == null || ws.httprequest.process.pty == 0) return;
3955 //sendConsoleText('Linux Resize: ' + obj.cols + 'x' + obj.rows);
3956
3957 if (ws.httprequest.process.tcsetsize) { ws.httprequest.process.tcsetsize(obj.rows, obj.cols); }
3958 }
3959 break;
3960 }
3961 case 'webrtc0': { // Browser indicates we can start WebRTC switch-over.
3962 if (ws.httprequest.protocol == 1)
3963 { // Terminal
3964 // 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
3965 if (process.platform == 'win32') {
3966 ws.httprequest._term.unpipe(ws);
3967 } else {
3968 ws.httprequest.process.stdout.unpipe(ws);
3969 ws.httprequest.process.stderr.unpipe(ws);
3970 }
3971 } else if (ws.httprequest.protocol == 2) { // Desktop
3972 // 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
3973 ws.httprequest.desktop.kvm.unpipe(ws);
3974 } else
3975 {
3976 // Switch things around so all WebRTC data goes to onTunnelData().
3977 ws.rtcchannel.httprequest = ws.httprequest;
3978 ws.rtcchannel.removeAllListeners('data');
3979 ws.rtcchannel.on('data', onTunnelData);
3980 }
3981 ws.write("{\"ctrlChannel\":\"102938\",\"type\":\"webrtc1\"}"); // End of data marker
3982 break;
3983 }
3984 case 'webrtc1':
3985 {
3986 if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6))
3987 { // Terminal
3988 // Switch the user input from websocket to webrtc at this point.
3989 if (process.platform == 'win32') {
3990 ws.unpipe(ws.httprequest._term);
3991 ws.rtcchannel.pipe(ws.httprequest._term, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
3992 } else {
3993 ws.unpipe(ws.httprequest.process.stdin);
3994 ws.rtcchannel.pipe(ws.httprequest.process.stdin, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
3995 }
3996 ws.resume(); // Resume the websocket to keep receiving control data
3997 }
3998 else if (ws.httprequest.protocol == 2)
3999 { // Desktop
4000 // Switch the user input from websocket to webrtc at this point.
4001 ws.unpipe(ws.httprequest.desktop.kvm);
4002 if ((ws.httprequest.desktopviewonly != true) && ((ws.httprequest.rights == 0xFFFFFFFF) || (((ws.httprequest.rights & MESHRIGHT_REMOTECONTROL) != 0) && ((ws.httprequest.rights & MESHRIGHT_REMOTEVIEW) == 0)))) {
4003 // If we have remote control rights, pipe the KVM input
4004 try { ws.webrtc.rtcchannel.pipe(ws.httprequest.desktop.kvm, { dataTypeSkip: 1, end: false }); } catch (ex) { sendConsoleText('EX2'); } // 0 = Binary, 1 = Text.
4005 } else {
4006 // We need to only pipe non-mouse & non-keyboard inputs.
4007 // sendConsoleText('Warning: No Remote Desktop Input Rights.');
4008 // TODO!!!
4009 }
4010 ws.resume(); // Resume the websocket to keep receiving control data
4011 }
4012 ws.write('{\"ctrlChannel\":\"102938\",\"type\":\"webrtc2\"}'); // Indicates we will no longer get any data on websocket, switching to WebRTC at this point.
4013 break;
4014 }
4015 case 'webrtc2': {
4016 // Other side received websocket end of data marker, start sending data on WebRTC channel
4017 if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) { // Terminal
4018 if (process.platform == 'win32') {
4019 ws.httprequest._term.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
4020 } else {
4021 ws.httprequest.process.stdout.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
4022 ws.httprequest.process.stderr.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
4023 }
4024 } else if (ws.httprequest.protocol == 2) { // Desktop
4025 ws.httprequest.desktop.kvm.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
4026 }
4027 break;
4028 }
4029 case 'offer': {
4030 // This is a WebRTC offer.
4031 if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) return; // TODO: Terminal is currently broken with WebRTC. Reject WebRTC upgrade for now.
4032 ws.webrtc = rtc.createConnection();
4033 ws.webrtc.once('~', tunnel_webrtc_OnFinalized);
4034 ws.webrtc.websocket = ws;
4035 //ws.webrtc.on('connected', function () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC connected');*/ });
4036 //ws.webrtc.on('disconnected', function () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC disconnected');*/ });
4037 ws.webrtc.on('dataChannel', tunnel_webrtc_OnDataChannel);
4038
4039 var sdp = null;
4040 try { sdp = ws.webrtc.setOffer(obj.sdp); } catch (ex) { }
4041 if (sdp != null) { ws.write({ type: 'answer', ctrlChannel: '102938', sdp: sdp }); }
4042 break;
4043 }
4044 case 'ping': {
4045 ws.write("{\"ctrlChannel\":\"102938\",\"type\":\"pong\"}"); // Send pong response
4046 break;
4047 }
4048 case 'pong': { // NOP
4049 break;
4050 }
4051 case 'rtt': {
4052 ws.write({ type: 'rtt', ctrlChannel: '102938', time: obj.time });
4053 break;
4054 }
4055 }
4056}
4057
4058// Console state
4059var consoleWebSockets = {};
4060var consoleHttpRequest = null;
4061
4062// Console HTTP response
4063function consoleHttpResponse(response) {
4064 response.data = function (data) { sendConsoleText(rstr2hex(buf2rstr(data)), this.sessionid); consoleHttpRequest = null; }
4065 response.close = function () { sendConsoleText('httprequest.response.close', this.sessionid); consoleHttpRequest = null; }
4066}
4067
4068// Open a local file on current user's desktop
4069function openFileOnDesktop(file) {
4070 var child = null;
4071 try {
4072 switch (process.platform) {
4073 case 'win32':
4074 var uid = require('user-sessions').consoleUid();
4075 var user = require('user-sessions').getUsername(uid);
4076 var domain = require('user-sessions').getDomain(uid);
4077 var task = { name: 'MeshChatTask', user: user, domain: domain, execPath: (require('fs').statSync(file).isDirectory() ? process.env['windir'] + '\\explorer.exe' : file) };
4078 if (require('fs').statSync(file).isDirectory()) task.arguments = [file];
4079 try {
4080 require('win-tasks').addTask(task);
4081 require('win-tasks').getTask({ name: 'MeshChatTask' }).run();
4082 require('win-tasks').deleteTask('MeshChatTask');
4083 return (true);
4084 }
4085 catch (ex) {
4086 var taskoptions = { env: { _target: (require('fs').statSync(file).isDirectory() ? process.env['windir'] + '\\explorer.exe' : file), _user: '"' + domain + '\\' + user + '"' }, _args: "" };
4087 if (require('fs').statSync(file).isDirectory()) taskoptions.env._args = file;
4088 for (var c1e in process.env) {
4089 taskoptions.env[c1e] = process.env[c1e];
4090 }
4091 var child = require('child_process').execFile(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', ['powershell', '-noprofile', '-nologo', '-command', '-'], taskoptions);
4092 child.stderr.on('data', function (c) { });
4093 child.stdout.on('data', function (c) { });
4094 child.stdin.write('SCHTASKS /CREATE /F /TN MeshChatTask /SC ONCE /ST 00:00 ');
4095 if (user) { child.stdin.write('/RU $env:_user '); }
4096 child.stdin.write('/TR "$env:_target $env:_args"\r\n');
4097 child.stdin.write('$ts = New-Object -ComObject Schedule.service\r\n');
4098 child.stdin.write('$ts.connect()\r\n');
4099 child.stdin.write('$tsfolder = $ts.getfolder("\\")\r\n');
4100 child.stdin.write('$task = $tsfolder.GetTask("MeshChatTask")\r\n');
4101 child.stdin.write('$taskdef = $task.Definition\r\n');
4102 child.stdin.write('$taskdef.Settings.StopIfGoingOnBatteries = $false\r\n');
4103 child.stdin.write('$taskdef.Settings.DisallowStartIfOnBatteries = $false\r\n');
4104 child.stdin.write('$taskdef.Actions.Item(1).Path = $env:_target\r\n');
4105 child.stdin.write('$taskdef.Actions.Item(1).Arguments = $env:_args\r\n');
4106 child.stdin.write('$tsfolder.RegisterTaskDefinition($task.Name, $taskdef, 4, $null, $null, $null)\r\n');
4107 child.stdin.write('SCHTASKS /RUN /TN MeshChatTask\r\n');
4108 child.stdin.write('SCHTASKS /DELETE /F /TN MeshChatTask\r\nexit\r\n');
4109 child.waitExit();
4110 }
4111 break;
4112 case 'linux':
4113 child = require('child_process').execFile('/usr/bin/xdg-open', ['xdg-open', file], { uid: require('user-sessions').consoleUid() });
4114 break;
4115 case 'darwin':
4116 child = require('child_process').execFile('/usr/bin/open', ['open', file]);
4117 break;
4118 default:
4119 // Unknown platform, ignore this command.
4120 break;
4121 }
4122 } catch (ex) { }
4123 return child;
4124}
4125
4126// Open a web browser to a specified URL on current user's desktop
4127function openUserDesktopUrl(url) {
4128 if ((url.toLowerCase().startsWith('http://') == false) && (url.toLowerCase().startsWith('https://') == false)) { return null; }
4129 var child = null;
4130 try {
4131 switch (process.platform) {
4132 case 'win32':
4133 var uid = require('user-sessions').consoleUid();
4134 var user = require('user-sessions').getUsername(uid);
4135 var domain = require('user-sessions').getDomain(uid);
4136 var task = { name: 'MeshChatTask', user: user, domain: domain, execPath: process.env['windir'] + '\\system32\\cmd.exe', arguments: ['/C START ' + url.split('&').join('^&')] };
4137
4138 try {
4139 require('win-tasks').addTask(task);
4140 require('win-tasks').getTask({ name: 'MeshChatTask' }).run();
4141 require('win-tasks').deleteTask('MeshChatTask');
4142 return (true);
4143 }
4144 catch (ex) {
4145 var taskoptions = { env: { _target: process.env['windir'] + '\\system32\\cmd.exe', _args: '/C START ' + url.split('&').join('^&'), _user: '"' + domain + '\\' + user + '"' } };
4146 for (var c1e in process.env) {
4147 taskoptions.env[c1e] = process.env[c1e];
4148 }
4149 var child = require('child_process').execFile(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', ['powershell', '-noprofile', '-nologo', '-command', '-'], taskoptions);
4150 child.stderr.on('data', function (c) { });
4151 child.stdout.on('data', function (c) { });
4152 child.stdin.write('SCHTASKS /CREATE /F /TN MeshChatTask /SC ONCE /ST 00:00 ');
4153 if (user) { child.stdin.write('/RU $env:_user '); }
4154 child.stdin.write('/TR "$env:_target $env:_args"\r\n');
4155 child.stdin.write('$ts = New-Object -ComObject Schedule.service\r\n');
4156 child.stdin.write('$ts.connect()\r\n');
4157 child.stdin.write('$tsfolder = $ts.getfolder("\\")\r\n');
4158 child.stdin.write('$task = $tsfolder.GetTask("MeshChatTask")\r\n');
4159 child.stdin.write('$taskdef = $task.Definition\r\n');
4160 child.stdin.write('$taskdef.Settings.StopIfGoingOnBatteries = $false\r\n');
4161 child.stdin.write('$taskdef.Settings.DisallowStartIfOnBatteries = $false\r\n');
4162 child.stdin.write('$taskdef.Actions.Item(1).Path = $env:_target\r\n');
4163 child.stdin.write('$taskdef.Actions.Item(1).Arguments = $env:_args\r\n');
4164 child.stdin.write('$tsfolder.RegisterTaskDefinition($task.Name, $taskdef, 4, $null, $null, $null)\r\n');
4165
4166 child.stdin.write('SCHTASKS /RUN /TN MeshChatTask\r\n');
4167 child.stdin.write('SCHTASKS /DELETE /F /TN MeshChatTask\r\nexit\r\n');
4168 child.waitExit();
4169 }
4170 break;
4171 case 'linux':
4172 child = require('child_process').execFile('/usr/bin/xdg-open', ['xdg-open', url], { uid: require('user-sessions').consoleUid() });
4173 break;
4174 case 'darwin':
4175 child = require('child_process').execFile('/usr/bin/open', ['open', url], { uid: require('user-sessions').consoleUid() });
4176 break;
4177 default:
4178 // Unknown platform, ignore this command.
4179 break;
4180 }
4181 } catch (ex) { }
4182 return child;
4183}
4184
4185// Process a mesh agent console command
4186function processConsoleCommand(cmd, args, rights, sessionid) {
4187 try {
4188 var response = null;
4189 switch (cmd) {
4190 case 'help': { // Displays available commands
4191 var fin = '', f = '', availcommands = 'domain,translations,agentupdate,errorlog,msh,timerinfo,coreinfo,coreinfoupdate,coredump,service,fdsnapshot,fdcount,startupoptions,';
4192 availcommands += 'alert,agentsize,versions,help,info,osinfo,args,print,type,dbkeys,dbget,dbset,dbcompact,eval,parseuri,httpget,wslist,plugin,wsconnect,wssend,wsclose,notify,';
4193 availcommands += 'ls,ps,kill,netinfo,location,power,wakeonlan,setdebug,smbios,rawsmbios,toast,lock,users,openurl,getscript,getclip,setclip,log,cpuinfo,sysinfo';
4194 availcommands += 'apf,scanwifi,wallpaper,agentmsg,task,uninstallagent,display,openfile';
4195 if (require('os').dns != null) { availcommands += ',dnsinfo'; }
4196 try { require('linux-dhcp'); availcommands += ',dhcp'; } catch (ex) { }
4197 if (process.platform == 'win32') {
4198 availcommands += ',bitlocker,cs,wpfhwacceleration,uac,volumes,rdpport,deskbackground,domaininfo';
4199 if (bcdOK()) { availcommands += ',safemode'; }
4200 if (require('notifybar-desktop').DefaultPinned != null) { availcommands += ',privacybar'; }
4201 try { require('win-utils'); availcommands += ',taskbar'; } catch (ex) { }
4202 try { require('win-info'); availcommands += ',installedapps,qfe,defender,av,installedstoreapps'; } catch (ex) { }
4203 }
4204 if (amt != null) { availcommands += ',amt,amtconfig,amtevents'; }
4205 if (process.platform != 'freebsd') { availcommands += ',vm'; }
4206 if (require('MeshAgent').maxKvmTileSize != null) { availcommands += ',kvmmode'; }
4207 try { require('zip-reader'); availcommands += ',zip,unzip'; } catch (ex) { }
4208
4209 availcommands = availcommands.split(',').sort();
4210 while (availcommands.length > 0) {
4211 if (f.length > 90) { fin += (f + ',\r\n'); f = ''; }
4212 f += (((f != '') ? ', ' : ' ') + availcommands.shift());
4213 }
4214 if (f != '') { fin += f; }
4215 response = "Available commands: \r\n" + fin + ".";
4216 break;
4217 }
4218 case 'mousetrails':
4219 try { require('win-deskutils'); } catch (ex) { response = 'Unknown command "mousetrails", type "help" for list of available commands.'; break; }
4220 var id = require('user-sessions').getProcessOwnerName(process.pid).tsid == 0 ? 1 : null;
4221 switch (args['_'].length)
4222 {
4223 case 0:
4224 var trails = require('win-deskutils').mouse.getTrails(id);
4225 response = trails == 0 ? 'MouseTrails Disabled' : ('MouseTrails enabled (' + trails + ')');
4226 response += '\nTo change setting, specify a positive integer, where 0 is disable: mousetrails [n]';
4227 break;
4228 case 1:
4229 var trails = parseInt(args['_'][0]);
4230 require('win-deskutils').mouse.setTrails(trails, id);
4231 trails = require('win-deskutils').mouse.getTrails(id);
4232 response = trails == 0 ? 'MouseTrails Disabled' : ('MouseTrails enabled (' + trails + ')');
4233 break;
4234 default:
4235 response = 'Proper usage: mousetrails [n]';
4236 break;
4237 }
4238 break;
4239 case 'deskbackground':
4240 try { require('win-deskutils'); } catch (ex) { response = 'Unknown command "deskbackground", type "help" for list of available commands.'; break; }
4241 var id = require('user-sessions').getProcessOwnerName(process.pid).tsid == 0 ? 1 : null;
4242 switch (args['_'].length)
4243 {
4244 case 0:
4245 response = 'Desktop Background: ' + require('win-deskutils').background.get(id);
4246 break;
4247 case 1:
4248 require('win-deskutils').background.set(args['_'][0], id);
4249 response = 'Desktop Background: ' + require('win-deskutils').background.get(id);
4250 break;
4251 default:
4252 response = 'Proper usage: deskbackground [path]';
4253 break;
4254 }
4255 break;
4256 case 'taskbar':
4257 try { require('win-utils'); } catch (ex) { response = 'Unknown command "taskbar", type "help" for list of available commands.'; break; }
4258 switch (args['_'].length) {
4259 case 1:
4260 case 2:
4261 {
4262 var tsid = parseInt(args['_'][1]);
4263 if (isNaN(tsid)) { tsid = require('user-sessions').consoleUid(); }
4264 sendConsoleText('Changing TaskBar AutoHide status. Please wait...', sessionid);
4265 try {
4266 var result = require('win-utils').taskBar.autoHide(tsid, args['_'][0].toLowerCase() == 'hide');
4267 response = 'Current Status of TaskBar AutoHide: ' + result;
4268 } catch (ex) { response = 'Unable to change TaskBar settings'; }
4269 }
4270 break;
4271 default:
4272 {
4273 response = 'Proper usage: taskbar HIDE|SHOW [TSID]';
4274 break;
4275 }
4276 }
4277 break;
4278 case 'privacybar':
4279 if (process.platform != 'win32' || require('notifybar-desktop').DefaultPinned == null) {
4280 response = 'Unknown command "privacybar", type "help" for list of available commands.';
4281 }
4282 else {
4283 switch (args['_'].length) {
4284 default:
4285 // Show Help
4286 response = "Current Default Pinned State: " + (require('notifybar-desktop').DefaultPinned ? "PINNED" : "UNPINNED") + '\r\n';
4287 response += "To set default pinned state:\r\n privacybar [PINNED|UNPINNED]\r\n";
4288 break;
4289 case 1:
4290 switch (args['_'][0].toUpperCase()) {
4291 case 'PINNED':
4292 require('notifybar-desktop').DefaultPinned = true;
4293 response = "privacybar default pinned state is: PINNED";
4294 break;
4295 case 'UNPINNED':
4296 require('notifybar-desktop').DefaultPinned = false;
4297 response = "privacybar default pinned state is: UNPINNED";
4298 break;
4299 default:
4300 response = "INVALID parameter: " + args['_'][0].toUpperCase();
4301 break;
4302 }
4303 break;
4304 }
4305 }
4306 break;
4307 case 'domain':
4308 response = getDomainInfo();
4309 break;
4310 case 'domaininfo':
4311 {
4312 if (process.platform != 'win32') {
4313 response = 'Unknown command "domaininfo", type "help" for list of available commands.';
4314 break;
4315 }
4316 if (global._domainQuery != null) {
4317 response = "There is already an outstanding Domain Controller Query... Please try again later...";
4318 break;
4319 }
4320
4321 sendConsoleText('Querying Domain Controller... This can take up to 60 seconds. Please wait...', sessionid);
4322 global._domainQuery = require('win-wmi').queryAsync('ROOT\\CIMV2', 'SELECT * FROM Win32_NTDomain');
4323 global._domainQuery.session = sessionid;
4324 global._domainQuery.then(function (v) {
4325 var results = [];
4326 if (Array.isArray(v)) {
4327 var i;
4328 var r;
4329 for (i = 0; i < v.length; ++i) {
4330 r = {};
4331 if (v[i].DomainControllerAddress != null) { r.DomainControllerAddress = v[i].DomainControllerAddress.split('\\').pop(); }
4332 if (r.DomainControllerName != null) { r.DomainControllerName = v[i].DomainControllerName.split('\\').pop(); }
4333 r.DomainGuid = v[i].DomainGuid;
4334 r.DomainName = v[i].DomainName;
4335 if (r.DomainGuid != null) {
4336 results.push(r);
4337 }
4338 }
4339 }
4340 if (results.length > 0) {
4341 sendConsoleText('Domain Controller Results:', this.session);
4342 sendConsoleText(JSON.stringify(results, null, 1), this.session);
4343 sendConsoleText('End of results...', this.session);
4344 }
4345 else {
4346 sendConsoleText('Domain Controller: No results returned. Is the domain controller reachable?', this.session);
4347 }
4348 global._domainQuery = null;
4349 });
4350 break;
4351 }
4352 case 'translations': {
4353 response = JSON.stringify(coretranslations, null, 2);
4354 break;
4355 }
4356 case 'volumes':
4357 response = JSON.stringify(require('win-volumes').getVolumes(), null, 1);
4358 break;
4359 case 'bitlocker':
4360 if (process.platform == 'win32') {
4361 if (require('win-volumes').volumes_promise != null) {
4362 var p = require('win-volumes').volumes_promise();
4363 p.then(function (res) { sendConsoleText(JSON.stringify(cleanGetBitLockerVolumeInfo(res), null, 1), this.session); });
4364 }
4365 }
4366 break;
4367 case 'dhcp': // This command is only supported on Linux, this is because Linux does not give us the DNS suffix for each network adapter independently so we have to ask the DHCP server.
4368 {
4369 try { require('linux-dhcp'); } catch (ex) { response = 'Unknown command "dhcp", type "help" for list of available commands.'; break; }
4370 if (args['_'].length == 0) {
4371 var j = require('os').networkInterfaces();
4372 var ifcs = [];
4373 for (var i in j) {
4374 for (var z in j[i]) {
4375 if (j[i][z].status == 'up' && j[i][z].type != 'loopback' && j[i][z].address != null) {
4376 ifcs.push('"' + i + '"');
4377 break;
4378 }
4379 }
4380 }
4381 response = 'Proper usage: dhcp [' + ifcs.join(' | ') + ']';
4382 }
4383 else {
4384 require('linux-dhcp').client.info(args['_'][0]).
4385 then(function (d) {
4386 sendConsoleText(JSON.stringify(d, null, 1), sessionid);
4387 },
4388 function (e) {
4389 sendConsoleText(e, sessionid);
4390 });
4391 }
4392 break;
4393 }
4394 case 'cs':
4395 if (process.platform != 'win32') {
4396 response = 'Unknown command "cs", type "help" for list of available commands.';
4397 break;
4398 }
4399 switch (args['_'].length) {
4400 case 0:
4401 try {
4402 var cs = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'System\\CurrentControlSet\\Control\\Power', 'CsEnabled');
4403 response = "Connected Standby: " + (cs == 1 ? "ENABLED" : "DISABLED");
4404 } catch (ex) {
4405 response = "This machine does not support Connected Standby";
4406 }
4407 break;
4408 case 1:
4409 if ((args['_'][0].toUpperCase() != 'ENABLE' && args['_'][0].toUpperCase() != 'DISABLE')) {
4410 response = "Proper usage:\r\n cs [ENABLE|DISABLE]";
4411 }
4412 else {
4413 try {
4414 var cs = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'System\\CurrentControlSet\\Control\\Power', 'CsEnabled');
4415 require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'System\\CurrentControlSet\\Control\\Power', 'CsEnabled', args['_'][0].toUpperCase() == 'ENABLE' ? 1 : 0);
4416
4417 cs = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'System\\CurrentControlSet\\Control\\Power', 'CsEnabled');
4418 response = "Connected Standby: " + (cs == 1 ? "ENABLED" : "DISABLED");
4419 } catch (ex) {
4420 response = "This machine does not support Connected Standby";
4421 }
4422 }
4423 break;
4424 default:
4425 response = "Proper usage:\r\n cs [ENABLE|DISABLE]";
4426 break;
4427 }
4428 break;
4429 case 'assistant':
4430 if (process.platform == 'win32') {
4431 // Install MeshCentral Assistant on this device
4432 response = "Usage: Assistant [info|install|uninstall]";
4433 if (args['_'].length == 1) {
4434 if ((args['_'][0] == 'install') || (args['_'][0] == 'info')) { response = ''; require('MeshAgent').SendCommand({ action: 'meshToolInfo', sessionid: sessionid, name: 'MeshCentralAssistant', cookie: true, tag: args['_'][0] }); }
4435 // TODO: Uninstall
4436 }
4437 } else {
4438 response = "MeshCentral Assistant is not supported on this platform.";
4439 }
4440 break;
4441 case 'userimage':
4442 require('MeshAgent').SendCommand({ action: 'getUserImage', sessionid: sessionid, userid: args['_'][0], tag: 'info' });
4443 response = 'ok';
4444 break;
4445 case 'agentupdate':
4446 require('MeshAgent').SendCommand({ action: 'agentupdate', sessionid: sessionid });
4447 break;
4448 case 'agentupdateex':
4449 // Perform an direct agent update without requesting any information from the server, this should not typically be used.
4450 if (args['_'].length == 1) {
4451 if (args['_'][0].startsWith('https://')) { agentUpdate_Start(args['_'][0], { sessionid: sessionid }); } else { response = "Usage: agentupdateex https://server/path"; }
4452 } else {
4453 agentUpdate_Start(null, { sessionid: sessionid });
4454 }
4455 break;
4456 case 'errorlog':
4457 switch (args['_'].length) {
4458 case 0:
4459 // All Error Logs
4460 response = JSON.stringify(require('util-agentlog').read(), null, 1);
4461 break;
4462 case 1:
4463 // Error Logs, by either count or timestamp
4464 response = JSON.stringify(require('util-agentlog').read(parseInt(args['_'][0])), null, 1);
4465 break;
4466 default:
4467 response = "Proper usage:\r\n errorlog [lastCount|linuxEpoch]";
4468 break;
4469 }
4470 break;
4471 case 'msh':
4472 if (args['_'].length == 0) {
4473 response = JSON.stringify(_MSH(), null, 2);
4474 } else if (args['_'].length > 3) {
4475 response = 'Proper usage: msh [get|set|delete]\r\nmsh get MeshServer\r\nmsh set abc "xyz"\r\nmsh delete abc';
4476 } else {
4477 var mshFileName = process.execPath.replace('.exe','') + '.msh';
4478 switch (args['_'][0].toLocaleLowerCase()) {
4479 case 'get':
4480 if (typeof args['_'][1] != 'string' || args['_'].length > 2) {
4481 response = 'Proper usage: msh get MeshServer';
4482 } else if(_MSH()[args['_'][1]]) {
4483 response = _MSH()[args['_'][1]];
4484 } else {
4485 response = "Unknown Value: " + args['_'][1];
4486 }
4487 break;
4488 case 'set':
4489 if (typeof args['_'][1] != 'string' || typeof args['_'][2] != 'string') {
4490 response = 'Proper usage: msh set abc "xyz"';
4491 } else {
4492 var jsonToSave = _MSH();
4493 jsonToSave[args['_'][1]] = args['_'][2];
4494 var updatedContent = '';
4495 for (var key in jsonToSave) {
4496 if (jsonToSave.hasOwnProperty(key)) {
4497 updatedContent += key + '=' + jsonToSave[key] + '\n';
4498 }
4499 }
4500 try {
4501 require('fs').writeFileSync(mshFileName, updatedContent);
4502 response = "msh set " + args['_'][1] + " successful"
4503 } catch (ex) {
4504 response = "msh set " + args['_'][1] + " unsuccessful";
4505 }
4506 }
4507 break;
4508 case 'delete':
4509 if (typeof args['_'][1] != 'string') {
4510 response = 'Proper usage: msh delete abc';
4511 } else {
4512 var jsonToSave = _MSH();
4513 delete jsonToSave[args['_'][1]];
4514 var updatedContent = '';
4515 for (var key in jsonToSave) {
4516 if (jsonToSave.hasOwnProperty(key)) {
4517 updatedContent += key + '=' + jsonToSave[key] + '\n';
4518 }
4519 }
4520 try {
4521 require('fs').writeFileSync(mshFileName, updatedContent);
4522 response = "msh delete " + args['_'][1] + " successful"
4523 } catch (ex) {
4524 response = "msh delete " + args['_'][1] + " unsuccessful";
4525 }
4526 }
4527 break;
4528 default:
4529 response = 'Proper usage: msh [get|set|delete]\r\nmsh get MeshServer\r\nmsh set abc "xyz"\r\nmsh delete abc';
4530 break;
4531 }
4532 }
4533 break;
4534 case 'dnsinfo':
4535 if (require('os').dns == null) {
4536 response = "Unknown command \"" + cmd + "\", type \"help\" for list of available commands.";
4537 }
4538 else {
4539 response = 'DNS Servers: ';
4540 var dns = require('os').dns();
4541 for (var i = 0; i < dns.length; ++i) {
4542 if (i > 0) { response += ', '; }
4543 response += dns[i];
4544 }
4545 }
4546 break;
4547 case 'timerinfo':
4548 response = require('ChainViewer').getTimerInfo();
4549 break;
4550 case 'rdpport':
4551 if (process.platform != 'win32') {
4552 response = 'Unknown command "rdpport", type "help" for list of available commands.';
4553 return;
4554 }
4555 if (args['_'].length == 0) {
4556 response = 'Proper usage: rdpport [get|default|PORTNUMBER]';
4557 } else {
4558 switch (args['_'][0].toLocaleLowerCase()) {
4559 case 'get':
4560 var rdpport = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'System\\CurrentControlSet\\Control\\Terminal Server\\WinStations\\RDP-Tcp', 'PortNumber');
4561 response = "Current RDP Port Set To: " + rdpport + '\r\n';
4562 break;
4563 case 'default':
4564 try {
4565 require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'System\\CurrentControlSet\\Control\\Terminal Server\\WinStations\\RDP-Tcp', 'PortNumber', 3389);
4566 response = 'RDP Port Set To 3389, Please Dont Forget To Restart Your Computer To Fully Apply';
4567 } catch (ex) {
4568 response = 'Unable to Set RDP Port To: 3389';
4569 }
4570 break;
4571 default:
4572 if (isNaN(parseFloat(args['_'][0]))){
4573 response = 'Proper usage: rdpport [get|default|PORTNUMBER]';
4574 } else if(parseFloat(args['_'][0]) < 0 || args['_'][0] > 65535) {
4575 response = 'RDP Port Must Be More Than 0 And Less Than 65535';
4576 } else {
4577 try {
4578 require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'System\\CurrentControlSet\\Control\\Terminal Server\\WinStations\\RDP-Tcp', 'PortNumber', parseFloat(args['_'][0]));
4579 response = 'RDP Port Set To ' + args['_'][0] + ', Please Dont Forget To Restart Your Computer To Fully Apply';
4580 } catch (ex) {
4581 response = 'Unable to Set RDP Port To: '+args['_'][0];
4582 }
4583 }
4584 break;
4585 }
4586 }
4587 break;
4588 case 'find':
4589 if (args['_'].length <= 1) {
4590 response = "Proper usage:\r\n find root criteria [criteria2] [criteria n...]";
4591 }
4592 else {
4593 var root = args['_'][0];
4594 var p = args['_'].slice(1);
4595 var r = require('file-search').find(root, p);
4596 r.sid = sessionid;
4597 r.on('result', function (str) { sendConsoleText(str, this.sid); });
4598 r.then(function () { sendConsoleText('*** End Results ***', this.sid); });
4599 response = "Find: [" + root + "] " + JSON.stringify(p);
4600 }
4601 break;
4602 case 'coreinfo': {
4603 response = JSON.stringify(meshCoreObj, null, 2);
4604 break;
4605 }
4606 case 'coreinfoupdate': {
4607 sendPeriodicServerUpdate(null, true);
4608 response = "Core Info Update Requested"
4609 break;
4610 }
4611 case 'agentmsg': {
4612 if (args['_'].length == 0) {
4613 response = "Proper usage:\r\n agentmsg add \"[message]\" [iconIndex]\r\n agentmsg remove [id]\r\n agentmsg list"; // Display usage
4614 } else {
4615 if ((args['_'][0] == 'add') && (args['_'].length > 1)) {
4616 var msgID, iconIndex = 0;
4617 if (args['_'].length >= 3) { try { iconIndex = parseInt(args['_'][2]); } catch (ex) { } }
4618 if (typeof iconIndex != 'number') { iconIndex = 0; }
4619 msgID = sendAgentMessage(args['_'][1], iconIndex);
4620 response = 'Agent message: ' + msgID + ' added.';
4621 } else if ((args['_'][0] == 'remove') && (args['_'].length > 1)) {
4622 var r = removeAgentMessage(args['_'][1]);
4623 response = 'Message ' + (r ? 'removed' : 'NOT FOUND');
4624 } else if (args['_'][0] == 'list') {
4625 response = JSON.stringify(sendAgentMessage(), null, 2);
4626 }
4627 broadcastSessionsToRegisteredApps();
4628 }
4629 break;
4630 }
4631 case 'clearagentmsg': {
4632 removeAgentMessage();
4633 broadcastSessionsToRegisteredApps();
4634 break;
4635 }
4636 case 'coredump':
4637 if (args['_'].length != 1) {
4638 response = "Proper usage: coredump on|off|status|clear"; // Display usage
4639 } else {
4640 switch (args['_'][0].toLowerCase()) {
4641 case 'on':
4642 process.coreDumpLocation = (process.platform == 'win32') ? (process.execPath.replace('.exe', '.dmp')) : (process.execPath + '.dmp');
4643 response = 'coredump is now on';
4644 break;
4645 case 'off':
4646 process.coreDumpLocation = null;
4647 response = 'coredump is now off';
4648 break;
4649 case 'status':
4650 response = 'coredump is: ' + ((process.coreDumpLocation == null) ? 'off' : 'on');
4651 if (process.coreDumpLocation != null) {
4652 if (process.platform == 'win32') {
4653 if (fs.existsSync(process.coreDumpLocation)) {
4654 response += '\r\n CoreDump present at: ' + process.coreDumpLocation;
4655 response += '\r\n CoreDump Time: ' + new Date(fs.statSync(process.coreDumpLocation).mtime).getTime();
4656 response += '\r\n Agent Time : ' + new Date(fs.statSync(process.execPath).mtime).getTime();
4657 }
4658 } else {
4659 if ((process.cwd() != '//') && fs.existsSync(process.cwd() + 'core')) {
4660 response += '\r\n CoreDump present at: ' + process.cwd() + 'core';
4661 response += '\r\n CoreDump Time: ' + new Date(fs.statSync(process.cwd() + 'core').mtime).getTime();
4662 response += '\r\n Agent Time : ' + new Date(fs.statSync(process.execPath).mtime).getTime();
4663 }
4664 }
4665 }
4666 break;
4667 case 'clear':
4668 db.Put('CoreDumpTime', null);
4669 response = 'coredump db cleared';
4670 break;
4671 default:
4672 response = "Proper usage: coredump on|off|status"; // Display usage
4673 break;
4674 }
4675 }
4676 break;
4677 case 'service':
4678 if (args['_'].length != 1) {
4679 response = "Proper usage: service status|restart"; // Display usage
4680 } else {
4681 var svcname = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent';
4682 try {
4683 svcname = require('MeshAgent').serviceName;
4684 } catch (ex) { }
4685 var s = require('service-manager').manager.getService(svcname);
4686 switch (args['_'][0].toLowerCase()) {
4687 case 'status':
4688 response = 'Service ' + (s.isRunning() ? (s.isMe() ? '[SELF]' : '[RUNNING]') : ('[NOT RUNNING]'));
4689 break;
4690 case 'restart':
4691 if (s.isMe()) {
4692 s.restart();
4693 } else {
4694 response = 'Restarting another agent instance is not allowed';
4695 }
4696 break;
4697 default:
4698 response = "Proper usage: service status|restart"; // Display usage
4699 break;
4700 }
4701 if (process.platform == 'win32') { s.close(); }
4702 }
4703 break;
4704 case 'zip':
4705 if (args['_'].length == 0) {
4706 response = "Proper usage: zip (output file name), input1 [, input n]"; // Display usage
4707 } else {
4708 var p = args['_'].join(' ').split(',');
4709 var ofile = p.shift();
4710 sendConsoleText('Writing ' + ofile + '...');
4711 var out = require('fs').createWriteStream(ofile, { flags: 'wb' });
4712 out.fname = ofile;
4713 out.sessionid = sessionid;
4714 out.on('close', function () { sendConsoleText('DONE writing ' + this.fname, this.sessionid); });
4715 var zip = require('zip-writer').write({ files: p });
4716 zip.pipe(out);
4717 }
4718 break;
4719 case 'unzip':
4720 if (args['_'].length == 0) {
4721 response = "Proper usage: unzip input,destination"; // Display usage
4722 } else {
4723 var p = args['_'].join(' ').split(',');
4724 if (p.length != 2) { response = "Proper usage: unzip input,destination"; break; } // Display usage
4725 var prom = require('zip-reader').read(p[0].trim());
4726 prom._dest = p[1].trim();
4727 prom.self = this;
4728 prom.sessionid = sessionid;
4729 prom.then(function (zipped) {
4730 sendConsoleText('Extracting to ' + this._dest + '...', this.sessionid);
4731 zipped.extractAll(this._dest).then(function () { sendConsoleText('finished unzipping', this.sessionid); }, function (e) { sendConsoleText('Error unzipping: ' + e, this.sessionid); }).parentPromise.sessionid = this.sessionid;
4732 }, function (e) { sendConsoleText('Error unzipping: ' + e, this.sessionid); });
4733 }
4734 break;
4735 case 'setbattery':
4736 // require('MeshAgent').SendCommand({ action: 'battery', state: 'dc', level: 55 });
4737 if ((args['_'].length > 0) && ((args['_'][0] == 'ac') || (args['_'][0] == 'dc'))) {
4738 var b = { action: 'battery', state: args['_'][0] };
4739 if (args['_'].length == 2) { b.level = parseInt(args['_'][1]); }
4740 require('MeshAgent').SendCommand(b);
4741 } else {
4742 require('MeshAgent').SendCommand({ action: 'battery' });
4743 }
4744 break;
4745 case 'fdsnapshot':
4746 require('ChainViewer').getSnapshot().then(function (c) { sendConsoleText(c, this.sessionid); }).parentPromise.sessionid = sessionid;
4747 break;
4748 case 'fdcount':
4749 require('DescriptorEvents').getDescriptorCount().then(
4750 function (c) {
4751 sendConsoleText('Descriptor Count: ' + c, this.sessionid);
4752 }, function (e) {
4753 sendConsoleText('Error fetching descriptor count: ' + e, this.sessionid);
4754 }).parentPromise.sessionid = sessionid;
4755 break;
4756 case 'uac':
4757 if (process.platform != 'win32') {
4758 response = 'Unknown command "uac", type "help" for list of available commands.';
4759 break;
4760 }
4761 if (args['_'].length != 1) {
4762 response = 'Proper usage: uac [get|interactive|secure]';
4763 }
4764 else {
4765 switch (args['_'][0].toUpperCase()) {
4766 case 'GET':
4767 var secd = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System', 'PromptOnSecureDesktop');
4768 response = "UAC mode: " + (secd == 0 ? "Interactive Desktop" : "Secure Desktop");
4769 break;
4770 case 'INTERACTIVE':
4771 try {
4772 require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System', 'PromptOnSecureDesktop', 0);
4773 response = 'UAC mode changed to: Interactive Desktop';
4774 } catch (ex) {
4775 response = "Unable to change UAC Mode";
4776 }
4777 break;
4778 case 'SECURE':
4779 try {
4780 require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System', 'PromptOnSecureDesktop', 1);
4781 response = 'UAC mode changed to: Secure Desktop';
4782 } catch (ex) {
4783 response = "Unable to change UAC Mode";
4784 }
4785 break;
4786 default:
4787 response = 'Proper usage: uac [get|interactive|secure]';
4788 break;
4789 }
4790 }
4791 break;
4792 case 'vm':
4793 response = 'Virtual Machine = ' + require('computer-identifiers').isVM();
4794 break;
4795 case 'startupoptions':
4796 response = JSON.stringify(require('MeshAgent').getStartupOptions());
4797 break;
4798 case 'kvmmode':
4799 if (require('MeshAgent').maxKvmTileSize == null) {
4800 response = "Unknown command \"kvmmode\", type \"help\" for list of available commands.";
4801 }
4802 else {
4803 if (require('MeshAgent').maxKvmTileSize == 0) {
4804 response = 'KVM Mode: Full JUMBO';
4805 }
4806 else {
4807 response = 'KVM Mode: ' + (require('MeshAgent').maxKvmTileSize <= 65500 ? 'NO JUMBO' : 'Partial JUMBO');
4808 response += (', TileLimit: ' + (require('MeshAgent').maxKvmTileSize < 1024 ? (require('MeshAgent').maxKvmTileSize + ' bytes') : (Math.round(require('MeshAgent').maxKvmTileSize / 1024) + ' Kbytes')));
4809 }
4810 }
4811 break;
4812 case 'alert':
4813 if (args['_'].length == 0) {
4814 response = "Proper usage: alert TITLE, CAPTION [, TIMEOUT]"; // Display usage
4815 }
4816 else {
4817 var p = args['_'].join(' ').split(',');
4818 if (p.length < 2) {
4819 response = "Proper usage: alert TITLE, CAPTION [, TIMEOUT]"; // Display usage
4820 }
4821 else {
4822 this._alert = require('message-box').create(p[0], p[1], p.length == 3 ? parseInt(p[2]) : 9999, 1);
4823 }
4824 }
4825 break;
4826 case 'agentsize':
4827 var actualSize = Math.floor(require('fs').statSync(process.execPath).size / 1024);
4828 if (process.platform == 'win32') {
4829 // Check the Agent Uninstall MetaData for correctness, as the installer may have written an incorrect value
4830 var writtenSize = 0;
4831 var serviceName = (_MSH().serviceName ? _MSH().serviceName : (require('_agentNodeId').serviceName() ? require('_agentNodeId').serviceName() : 'Mesh Agent'));
4832 try { writtenSize = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + serviceName, 'EstimatedSize'); } catch (ex) { response = ex; }
4833 if (writtenSize != actualSize) {
4834 response = "Size updated from: " + writtenSize + " to: " + actualSize;
4835 try { require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + serviceName, 'EstimatedSize', actualSize); } catch (ex) { response = ex; }
4836 } else
4837 { response = "Agent Size: " + actualSize + " kb"; }
4838 } else
4839 { response = "Agent Size: " + actualSize + " kb"; }
4840 break;
4841 case 'versions':
4842 response = JSON.stringify(process.versions, null, ' ');
4843 break;
4844 case 'wpfhwacceleration':
4845 if (process.platform != 'win32') { throw ("wpfhwacceleration setting is only supported on Windows"); }
4846 if (args['_'].length != 1) {
4847 response = "Proper usage: wpfhwacceleration (ON|OFF|STATUS)"; // Display usage
4848 }
4849 else {
4850 var reg = require('win-registry');
4851 var uname = require('user-sessions').getUsername(require('user-sessions').consoleUid());
4852 var key = reg.usernameToUserKey(uname);
4853
4854 switch (args['_'][0].toUpperCase()) {
4855 default:
4856 response = "Proper usage: wpfhwacceleration (ON|OFF|STATUS|DEFAULT)"; // Display usage
4857 break;
4858 case 'ON':
4859 try {
4860 reg.WriteKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration', 0);
4861 response = "OK";
4862 } catch (ex) { response = "FAILED"; }
4863 break;
4864 case 'OFF':
4865 try {
4866 reg.WriteKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration', 1);
4867 response = 'OK';
4868 } catch (ex) { response = 'FAILED'; }
4869 break;
4870 case 'STATUS':
4871 var s;
4872 try { s = reg.QueryKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration') == 1 ? 'DISABLED' : 'ENABLED'; } catch (ex) { s = 'DEFAULT'; }
4873 response = "WPF Hardware Acceleration: " + s;
4874 break;
4875 case 'DEFAULT':
4876 try { reg.DeleteKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration'); } catch (ex) { }
4877 response = 'OK';
4878 break;
4879 }
4880 }
4881 break;
4882 case 'tsid':
4883 if (process.platform == 'win32') {
4884 if (args['_'].length != 1) {
4885 response = "TSID: " + (require('MeshAgent')._tsid == null ? "console" : require('MeshAgent')._tsid);
4886 } else {
4887 var i = parseInt(args['_'][0]);
4888 require('MeshAgent')._tsid = (isNaN(i) ? null : i);
4889 response = "TSID set to: " + (require('MeshAgent')._tsid == null ? "console" : require('MeshAgent')._tsid);
4890 }
4891 } else
4892 { response = "TSID command only supported on Windows"; }
4893 break;
4894 case 'activeusers':
4895 if (process.platform == 'win32') {
4896 var p = require('user-sessions').enumerateUsers();
4897 p.sessionid = sessionid;
4898 p.then(function (u) {
4899 var v = [];
4900 var ret = getDomainInfo();
4901 for (var i in u) {
4902 if (u[i].State == 'Active') {
4903 if ((u[i].Domain != null && u[i].Domain == 'AzureAD') || getJoinState() == 1){
4904 var userobj = getLogonCacheKeys();
4905 if(userobj && userobj.length > 0){
4906 for (var j = 0; j < userobj.length; j++) {
4907 if (userobj[j] && userobj[j].SAM && userobj[j].SAM[0].trim() === u[i].Username) {
4908 u[i].UPN = userobj[j].UPN
4909 break;
4910 }
4911 }
4912 }
4913 } else if (u[i].Domain != null) {
4914 if (ret != null && ret.PartOfDomain === true) {
4915 u[i].UPN = u[i].Username + '@' + ret.Domain;
4916 } else if (getJoinState() == 4) { // One account with Microsoft Account
4917 var userobj = getLogonCacheKeys();
4918 if(userobj && userobj.length > 0){
4919 for (var j = 0; j < userobj.length; j++) {
4920 if (userobj[j] && userobj[j].SAM && userobj[j].SAM.length == 0 && userobj[j].UPN && userobj[j].UPN != '') {
4921 u[i].UPN = userobj[j].UPN;
4922 break;
4923 }
4924 }
4925 }
4926 }
4927 }
4928 if (u[i].UPN == null) { u[i].UPN = ''; }
4929 v.push({ tsid: i, type: u[i].StationName, user: u[i].Username, domain: u[i].Domain, upn: u[i].UPN });
4930 }
4931 }
4932 sendConsoleText(JSON.stringify(v, null, 1), this.sessionid);
4933 });
4934 } else
4935 { response = "activeusers command only supported on Windows"; }
4936 break;
4937 case 'wallpaper':
4938 if (process.platform != 'win32' && !(process.platform == 'linux' && require('linux-gnome-helpers').available)) {
4939 response = "wallpaper command not supported on this platform";
4940 }
4941 else {
4942 if (args['_'].length != 1) {
4943 response = 'Proper usage: wallpaper (GET|TOGGLE)'; // Display usage
4944 }
4945 else {
4946 switch (args['_'][0].toUpperCase()) {
4947 default:
4948 response = 'Proper usage: wallpaper (GET|TOGGLE)'; // Display usage
4949 break;
4950 case 'GET':
4951 case 'TOGGLE':
4952 if (process.platform == 'win32') {
4953 var id = require('user-sessions').getProcessOwnerName(process.pid).tsid == 0 ? 1 : 0;
4954 var child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0='], { type: id });
4955 child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
4956 child.stderr.on('data', function () { });
4957 child.waitExit();
4958 var current = child.stdout.str.trim();
4959 if (args['_'][0].toUpperCase() == 'GET') {
4960 response = current;
4961 break;
4962 }
4963 if (current != '') {
4964 require('MeshAgent')._wallpaper = current;
4965 response = 'Wallpaper cleared';
4966 } else {
4967 response = 'Wallpaper restored';
4968 }
4969 child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0=', current != '' ? '""' : require('MeshAgent')._wallpaper], { type: id });
4970 child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
4971 child.stderr.on('data', function () { });
4972 child.waitExit();
4973 }
4974 else {
4975 var id = require('user-sessions').consoleUid();
4976 var current = require('linux-gnome-helpers').getDesktopWallpaper(id);
4977 if (args['_'][0].toUpperCase() == 'GET') {
4978 response = current;
4979 break;
4980 }
4981 if (current != '/dev/null') {
4982 require('MeshAgent')._wallpaper = current;
4983 response = 'Wallpaper cleared';
4984 } else {
4985 response = 'Wallpaper restored';
4986 }
4987 require('linux-gnome-helpers').setDesktopWallpaper(id, current != '/dev/null' ? undefined : require('MeshAgent')._wallpaper);
4988 }
4989 break;
4990 }
4991 }
4992 }
4993 break;
4994 case 'safemode':
4995 if (process.platform != 'win32') {
4996 response = 'safemode only supported on Windows Platforms'
4997 }
4998 else {
4999 if (!bcdOK()) {
5000 response = 'safemode not supported on 64 bit Windows from a 32 bit process'
5001 break;
5002 }
5003 if (args['_'].length != 1) {
5004 response = 'Proper usage: safemode (ON|OFF|STATUS)'; // Display usage
5005 }
5006 else {
5007 var svcname = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent';
5008 try {
5009 svcname = require('MeshAgent').serviceName;
5010 } catch (ex) { }
5011
5012 switch (args['_'][0].toUpperCase()) {
5013 default:
5014 response = 'Proper usage: safemode (ON|OFF|STATUS)'; // Display usage
5015 break;
5016 case 'ON':
5017 require('win-bcd').setKey('safeboot', 'Network');
5018 require('win-bcd').enableSafeModeService(svcname);
5019 break;
5020 case 'OFF':
5021 require('win-bcd').deleteKey('safeboot');
5022 break;
5023 case 'STATUS':
5024 var nextboot = require('win-bcd').getKey('safeboot');
5025 if (nextboot) {
5026 switch (nextboot) {
5027 case 'Network':
5028 case 'network':
5029 nextboot = 'SAFE_MODE_NETWORK';
5030 break;
5031 default:
5032 nextboot = 'SAFE_MODE';
5033 break;
5034 }
5035 }
5036 response = 'Current: ' + require('win-bcd').bootMode + ', NextBoot: ' + (nextboot ? nextboot : 'NORMAL');
5037 break;
5038 }
5039 }
5040 }
5041 break;
5042 /*
5043 case 'border':
5044 {
5045 if ((args['_'].length == 1) && (args['_'][0] == 'on')) {
5046 if (meshCoreObj.users.length > 0) {
5047 obj.borderManager.Start(meshCoreObj.users[0]);
5048 response = 'Border blinking is on.';
5049 } else {
5050 response = 'Cannot turn on border blinking, no logged in users.';
5051 }
5052 } else if ((args['_'].length == 1) && (args['_'][0] == 'off')) {
5053 obj.borderManager.Stop();
5054 response = 'Border blinking is off.';
5055 } else {
5056 response = 'Proper usage: border "on|off"'; // Display correct command usage
5057 }
5058 }
5059 break;
5060 */
5061 case 'av':
5062 if (process.platform == 'win32') {
5063 // Windows Command: "wmic /Namespace:\\root\SecurityCenter2 Path AntiVirusProduct get /FORMAT:CSV"
5064 response = JSON.stringify(require('win-info').av(), null, 1);
5065 } else {
5066 response = 'Not supported on the platform';
5067 }
5068 break;
5069 case 'defender':
5070 if (process.platform == 'win32') {
5071 // Windows Command: "wmic /Namespace:\\root\SecurityCenter2 Path AntiVirusProduct get /FORMAT:CSV"
5072 response = JSON.stringify(require('win-info').defender(), null, 1);
5073 } else {
5074 response = 'Not supported on the platform';
5075 }
5076 break;
5077 case 'log':
5078 if (args['_'].length != 1) { response = 'Proper usage: log "sample text"'; } else { MeshServerLog(args['_'][0]); response = 'ok'; }
5079 break;
5080 case 'getclip':
5081 if (require('MeshAgent').isService) {
5082 require('clipboard').dispatchRead().then(function (str) { sendConsoleText(str, sessionid); });
5083 } else {
5084 require('clipboard').read().then(function (str) { sendConsoleText(str, sessionid); });
5085 }
5086 break;
5087 case 'setclip': {
5088 if (pendingSetClip) {
5089 response = 'Busy';
5090 } else if (args['_'].length != 1) {
5091 response = 'Proper usage: setclip "sample text"';
5092 } else {
5093 if (require('MeshAgent').isService) {
5094 if (process.platform != 'win32') {
5095 require('clipboard').dispatchWrite(args['_'][0]);
5096 }
5097 else {
5098 var clipargs = args['_'][0];
5099 var uid = require('user-sessions').consoleUid();
5100 var user = require('user-sessions').getUsername(uid);
5101 var domain = require('user-sessions').getDomain(uid);
5102 user = (domain + '\\' + user);
5103
5104 if (this._dispatcher) { this._dispatcher.close(); }
5105 this._dispatcher = require('win-dispatcher').dispatch({ user: user, modules: [{ name: 'clip-dispatch', script: "module.exports = { dispatch: function dispatch(val) { require('clipboard')(val); process.exit(); } };" }], launch: { module: 'clip-dispatch', method: 'dispatch', args: [clipargs] } });
5106 this._dispatcher.parent = this;
5107 //require('events').setFinalizerMetadata.call(this._dispatcher, 'clip-dispatch');
5108 pendingSetClip = true;
5109 this._dispatcher.on('connection', function (c) {
5110 this._c = c;
5111 this._c.root = this.parent;
5112 this._c.on('end', function ()
5113 {
5114 pendingSetClip = false;
5115 try { this.root._dispatcher.close(); } catch (ex) { }
5116 this.root._dispatcher = null;
5117 this.root = null;
5118 });
5119 });
5120 }
5121 response = 'Setting clipboard to: "' + args['_'][0] + '"';
5122 }
5123 else {
5124 require('clipboard')(args['_'][0]); response = 'Setting clipboard to: "' + args['_'][0] + '"';
5125 }
5126 }
5127 break;
5128 }
5129 case 'openurl': {
5130 if (args['_'].length != 1) { response = 'Proper usage: openurl (url)'; } // Display usage
5131 else { if (openUserDesktopUrl(args['_'][0]) == null) { response = 'Failed.'; } else { response = 'Success.'; } }
5132 break;
5133 }
5134 case 'openfile': {
5135 if (args['_'].length != 1) { response = 'Proper usage: openfile (filepath)'; } // Display usage
5136 else { if (openFileOnDesktop(args['_'][0]) == null) { response = 'Failed.'; } else { response = 'Success.'; } }
5137 break;
5138 }
5139 case 'users': {
5140 if (meshCoreObj.users == null) { response = 'Active users are unknown.'; } else { response = 'Active Users: ' + meshCoreObj.users.join(', ') + '.'; }
5141 require('user-sessions').enumerateUsers().then(function (u) {
5142 var ret = getDomainInfo();
5143 for (var i in u) {
5144 if ((u[i].Domain != null && u[i].Domain == 'AzureAD') || getJoinState() == 1){
5145 var userobj = getLogonCacheKeys();
5146 if(userobj && userobj.length > 0){
5147 for (var j = 0; j < userobj.length; j++) {
5148 var a = userobj[j];
5149 if (a && a.SAM && a.SAM[0].trim() === u[i].Username) {
5150 u[i].UPN = a.UPN
5151 break;
5152 }
5153 }
5154 }
5155 } else if (u[i].Domain != null) {
5156 if (ret != null && ret.PartOfDomain === true) {
5157 u[i].UPN = u[i].Username + '@' + ret.Domain;
5158 } else if (getJoinState() == 4) { // One account with Microsoft Account
5159 var userobj = getLogonCacheKeys();
5160 if(userobj && userobj.length > 0){
5161 for (var j = 0; j < userobj.length; j++) {
5162 var a = userobj[j];
5163 if (a && a.SAM && a.SAM.length == 0 && a.UPN && a.UPN != '') {
5164 u[i].UPN = a.UPN;
5165 break;
5166 }
5167 }
5168 }
5169 }
5170 }
5171 if (u[i].UPN == null) { u[i].UPN = ''; }
5172 sendConsoleText(u[i]);
5173 }
5174 });
5175 break;
5176 }
5177 case 'kvmusers':
5178 response = JSON.stringify(require('kvm-helper').users(), null, 1);
5179 break;
5180 case 'toast': {
5181 if (args['_'].length < 1) { response = 'Proper usage: toast "message"'; } else {
5182 if (require('MeshAgent')._tsid == null) {
5183 require('toaster').Toast('MeshCentral', args['_'][0]).then(sendConsoleText, sendConsoleText);
5184 }
5185 else {
5186 require('toaster').Toast('MeshCentral', args['_'][0], require('MeshAgent')._tsid).then(sendConsoleText, sendConsoleText);
5187 }
5188 }
5189 break;
5190 }
5191 case 'setdebug': {
5192 if (args['_'].length < 1) { response = 'Proper usage: setdebug (target), 0 = Disabled, 1 = StdOut, 2 = This Console, * = All Consoles, 4 = WebLog, 8 = Logfile'; } // Display usage
5193 else { if (args['_'][0] == '*') { console.setDestination(2); } else { console.setDestination(parseInt(args['_'][0]), sessionid); } }
5194 break;
5195 }
5196 case 'ps': {
5197 processManager.getProcesses(function (plist) {
5198 var x = '';
5199 for (var i in plist) { x += i + ((plist[i].user) ? (', ' + plist[i].user) : '') + ', ' + plist[i].cmd + '\r\n'; }
5200 sendConsoleText(x, sessionid);
5201 });
5202 break;
5203 }
5204 case 'kill': {
5205 if ((args['_'].length < 1)) {
5206 response = 'Proper usage: kill [pid]'; // Display correct command usage
5207 } else {
5208 process.kill(parseInt(args['_'][0]));
5209 response = 'Killed process ' + args['_'][0] + '.';
5210 }
5211 break;
5212 }
5213 case 'smbios': {
5214 if (SMBiosTables == null) { response = 'SMBios tables not available.'; } else { response = objToString(SMBiosTables, 0, ' ', true); }
5215 break;
5216 }
5217 case 'rawsmbios': {
5218 if (SMBiosTablesRaw == null) { response = 'SMBios tables not available.'; } else {
5219 response = '';
5220 for (var i in SMBiosTablesRaw) {
5221 var header = false;
5222 for (var j in SMBiosTablesRaw[i]) {
5223 if (SMBiosTablesRaw[i][j].length > 0) {
5224 if (header == false) { response += ('Table type #' + i + ((require('smbios').smTableTypes[i] == null) ? '' : (', ' + require('smbios').smTableTypes[i]))) + '\r\n'; header = true; }
5225 response += (' ' + SMBiosTablesRaw[i][j].toString('hex')) + '\r\n';
5226 }
5227 }
5228 }
5229 }
5230 break;
5231 }
5232 case 'eval': { // Eval JavaScript
5233 if (args['_'].length < 1) {
5234 response = 'Proper usage: eval "JavaScript code"'; // Display correct command usage
5235 } else {
5236 response = JSON.stringify(mesh.eval(args['_'][0])); // This can only be run by trusted administrator.
5237 }
5238 break;
5239 }
5240 case 'uninstallagent': // Uninstall this agent
5241 var agentName = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent';
5242 try {
5243 agentName = require('MeshAgent').serviceName;
5244 } catch (ex) { }
5245
5246 if (!require('service-manager').manager.getService(agentName).isMe()) {
5247 response = 'Uininstall failed, this instance is not the service instance';
5248 } else {
5249 try { diagnosticAgent_uninstall(); } catch (ex) { }
5250 var js = "require('service-manager').manager.getService('" + agentName + "').stop(); require('service-manager').manager.uninstallService('" + agentName + "'); process.exit();";
5251 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 });
5252 }
5253 break;
5254 case 'notify': { // Send a notification message to the mesh
5255 if (args['_'].length != 1) {
5256 response = 'Proper usage: notify "message" [--session]'; // Display correct command usage
5257 } else {
5258 var notification = { action: 'msg', type: 'notify', value: args['_'][0], tag: 'console' };
5259 if (args.session) { notification.sessionid = sessionid; } // If "--session" is specified, notify only this session, if not, the server will notify the mesh
5260 mesh.SendCommand(notification); // no sessionid or userid specified, notification will go to the entire mesh
5261 response = "ok";
5262 }
5263 break;
5264 }
5265 case 'cpuinfo': { // Return system information
5266 // CPU & memory utilization
5267 pr = require('sysinfo').cpuUtilization();
5268 pr.sessionid = sessionid;
5269 pr.then(function (data) {
5270 sendConsoleText(JSON.stringify(
5271 {
5272 cpu: data,
5273 memory: require('sysinfo').memUtilization(),
5274 thermals: require('sysinfo').thermals == null ? [] : require('sysinfo').thermals()
5275 }, null, 1), this.sessionid);
5276 }, function (e) {
5277 sendConsoleText(e);
5278 });
5279 break;
5280 }
5281 case 'sysinfo': { // Return system information
5282 getSystemInformation(function (results, err) {
5283 if (results == null) {
5284 sendConsoleText(err, this.sessionid);
5285 } else {
5286 sendConsoleText(JSON.stringify(results, null, 1), this.sessionid);
5287 mesh.SendCommand({ action: 'sysinfo', sessionid: this.sessionid, data: results });
5288 }
5289 });
5290 break;
5291 }
5292 case 'info': { // Return information about the agent and agent core module
5293 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 + '.';
5294 if (amt != null) { response += '\r\nBuilt-in LMS: ' + ['Disabled', 'Connecting..', 'Connected'][amt.lmsstate] + '.'; }
5295 if (meshCoreObj.osdesc) { response += '\r\nOS: ' + meshCoreObj.osdesc + '.'; }
5296 response += '\r\nModules: ' + addedModules.join(', ') + '.';
5297 response += '\r\nServer Connection: ' + mesh.isControlChannelConnected + ', State: ' + meshServerConnectionState + '.';
5298 var oldNodeId = db.Get('OldNodeId');
5299 if (oldNodeId != null) { response += '\r\nOldNodeID: ' + oldNodeId + '.'; }
5300 response += '\r\nNode ID: ' + Buffer.from(require('_agentNodeId')(), 'hex').toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
5301 if (process.platform == 'linux' || process.platform == 'freebsd') { response += '\r\nX11 support: ' + require('monitor-info').kvm_x11_support + '.'; }
5302 response += '\r\nApplication Location: ' + process.cwd();
5303 //response += '\r\Debug Console: ' + debugConsole + '.';
5304 break;
5305 }
5306 case 'osinfo': { // Return the operating system information
5307 var i = 1;
5308 if (args['_'].length > 0) { i = parseInt(args['_'][0]); if (i > 8) { i = 8; } response = 'Calling ' + i + ' times.'; }
5309 for (var j = 0; j < i; j++) {
5310 var pr = require('os').name();
5311 pr.sessionid = sessionid;
5312 pr.then(function (v) {
5313 sendConsoleText("OS: " + v + (process.platform == 'win32' ? (require('win-virtual-terminal').supported ? ' [ConPTY: YES]' : ' [ConPTY: NO]') : ''), this.sessionid);
5314 });
5315 }
5316 break;
5317 }
5318 case 'args': { // Displays parsed command arguments
5319 response = 'args ' + objToString(args, 0, ' ', true);
5320 break;
5321 }
5322 case 'print': { // Print a message on the mesh agent console, does nothing when running in the background
5323 var r = [];
5324 for (var i in args['_']) { r.push(args['_'][i]); }
5325 console.log(r.join(' '));
5326 response = 'Message printed on agent console.';
5327 break;
5328 }
5329 case 'type': { // Returns the content of a file
5330 if (args['_'].length == 0) {
5331 response = 'Proper usage: type (filepath) [maxlength]'; // Display correct command usage
5332 } else {
5333 var max = 4096;
5334 if ((args['_'].length > 1) && (typeof args['_'][1] == 'number')) { max = args['_'][1]; }
5335 if (max > 4096) max = 4096;
5336 var buf = Buffer.alloc(max), fd = fs.openSync(args['_'][0], "r"), r = fs.readSync(fd, buf, 0, max); // Read the file content
5337 response = buf.toString();
5338 var i = response.indexOf('\n');
5339 if ((i > 0) && (response[i - 1] != '\r')) { response = response.split('\n').join('\r\n'); }
5340 if (r == max) response += '...';
5341 fs.closeSync(fd);
5342 }
5343 break;
5344 }
5345 case 'dbkeys': { // Return all data store keys
5346 response = JSON.stringify(db.Keys);
5347 break;
5348 }
5349 case 'dbget': { // Return the data store value for a given key
5350 if (db == null) { response = 'Database not accessible.'; break; }
5351 if (args['_'].length != 1) {
5352 response = 'Proper usage: dbget (key)'; // Display the value for a given database key
5353 } else {
5354 response = db.Get(args['_'][0]);
5355 }
5356 break;
5357 }
5358 case 'dbset': { // Set a data store key and value pair
5359 if (db == null) { response = 'Database not accessible.'; break; }
5360 if (args['_'].length != 2) {
5361 response = 'Proper usage: dbset (key) (value)'; // Set a database key
5362 } else {
5363 var r = db.Put(args['_'][0], args['_'][1]);
5364 response = 'Key set: ' + r;
5365 }
5366 break;
5367 }
5368 case 'dbcompact': { // Compact the data store
5369 if (db == null) { response = 'Database not accessible.'; break; }
5370 var r = db.Compact();
5371 response = 'Database compacted: ' + r;
5372 break;
5373 }
5374 case 'httpget': {
5375 if (consoleHttpRequest != null) {
5376 response = 'HTTP operation already in progress.';
5377 } else {
5378 if (args['_'].length != 1) {
5379 response = 'Proper usage: httpget (url)';
5380 } else {
5381 var options = http.parseUri(args['_'][0]);
5382 options.method = 'GET';
5383 if (options == null) {
5384 response = 'Invalid url.';
5385 } else {
5386 try { consoleHttpRequest = http.request(options, consoleHttpResponse); } catch (ex) { response = 'Invalid HTTP GET request'; }
5387 consoleHttpRequest.sessionid = sessionid;
5388 if (consoleHttpRequest != null) {
5389 consoleHttpRequest.end();
5390 response = 'HTTPGET ' + options.protocol + '//' + options.host + ':' + options.port + options.path;
5391 }
5392 }
5393 }
5394 }
5395 break;
5396 }
5397 case 'wslist': { // List all web sockets
5398 response = '';
5399 for (var i in consoleWebSockets) {
5400 var httprequest = consoleWebSockets[i];
5401 response += 'Websocket #' + i + ', ' + httprequest.url + '\r\n';
5402 }
5403 if (response == '') { response = 'no websocket sessions.'; }
5404 break;
5405 }
5406 case 'wsconnect': { // Setup a web socket
5407 if (args['_'].length == 0) {
5408 response = 'Proper usage: wsconnect (url)\r\nFor example: wsconnect wss://localhost:443/meshrelay.ashx?id=abc'; // Display correct command usage
5409 } else {
5410 var httprequest = null;
5411 try {
5412 var options = http.parseUri(args['_'][0].split('$').join('%24').split('@').join('%40')); // Escape the $ and @ characters in the URL
5413 options.rejectUnauthorized = 0;
5414 httprequest = http.request(options);
5415 } catch (ex) { response = 'Invalid HTTP websocket request'; }
5416 if (httprequest != null) {
5417 httprequest.upgrade = onWebSocketUpgrade;
5418 httprequest.on('error', function (e) { sendConsoleText("ERROR: Unable to connect to: " + this.url + ", " + JSON.stringify(e)); });
5419
5420 var index = 1;
5421 while (consoleWebSockets[index]) { index++; }
5422 httprequest.sessionid = sessionid;
5423 httprequest.index = index;
5424 httprequest.url = args['_'][0];
5425 consoleWebSockets[index] = httprequest;
5426 response = 'New websocket session #' + index;
5427 }
5428 }
5429 break;
5430 }
5431 case 'wssend': { // Send data on a web socket
5432 if (args['_'].length == 0) {
5433 response = 'Proper usage: wssend (socketnumber)\r\n'; // Display correct command usage
5434 for (var i in consoleWebSockets) {
5435 var httprequest = consoleWebSockets[i];
5436 response += 'Websocket #' + i + ', ' + httprequest.url + '\r\n';
5437 }
5438 } else {
5439 var i = parseInt(args['_'][0]);
5440 var httprequest = consoleWebSockets[i];
5441 if (httprequest != undefined) {
5442 httprequest.s.write(args['_'][1]);
5443 response = 'ok';
5444 } else {
5445 response = 'Invalid web socket number';
5446 }
5447 }
5448 break;
5449 }
5450 case 'wsclose': { // Close a websocket
5451 if (args['_'].length == 0) {
5452 response = 'Proper usage: wsclose (socketnumber)'; // Display correct command usage
5453 } else {
5454 var i = parseInt(args['_'][0]);
5455 var httprequest = consoleWebSockets[i];
5456 if (httprequest != undefined) {
5457 if (httprequest.s != null) { httprequest.s.end(); } else { httprequest.end(); }
5458 response = 'ok';
5459 } else {
5460 response = 'Invalid web socket number';
5461 }
5462 }
5463 break;
5464 }
5465 case 'tunnels': { // Show the list of current tunnels
5466 response = '';
5467 for (var i in tunnels) {
5468 response += 'Tunnel #' + i + ', ' + tunnels[i].protocol; //tunnels[i].url
5469 if (tunnels[i].userid) { response += ', ' + tunnels[i].userid; }
5470 if (tunnels[i].guestname) { response += '/' + tunnels[i].guestname; }
5471 response += '\r\n'
5472 }
5473 if (response == '') { response = 'No websocket sessions.'; }
5474 break;
5475 }
5476 case 'ls': { // Show list of files and folders
5477 response = '';
5478 var xpath = '*';
5479 if (args['_'].length > 0) { xpath = obj.path.join(args['_'][0], '*'); }
5480 response = 'List of ' + xpath + '\r\n';
5481 var results = fs.readdirSync(xpath);
5482 for (var i = 0; i < results.length; ++i) {
5483 var stat = null, p = obj.path.join(args['_'][0], results[i]);
5484 try { stat = fs.statSync(p); } catch (ex) { }
5485 if ((stat == null) || (stat == undefined)) {
5486 response += (results[i] + "\r\n");
5487 } else {
5488 response += (results[i] + " " + ((stat.isDirectory()) ? "(Folder)" : "(File)") + "\r\n");
5489 }
5490 }
5491 break;
5492 }
5493 case 'lsx': { // Show list of files and folders
5494 response = objToString(getDirectoryInfo(args['_'][0]), 0, ' ', true);
5495 break;
5496 }
5497 case 'lock': { // Lock the current user out of the desktop
5498 lockDesktop();
5499 break;
5500 }
5501 case 'amt': { // Show Intel AMT status
5502 if (amt != null) {
5503 amt.getMeiState(9, function (state) {
5504 var resp = "Intel AMT not detected.";
5505 if (state != null) { resp = objToString(state, 0, ' ', true); }
5506 sendConsoleText(resp, sessionid);
5507 });
5508 } else {
5509 response = "Intel AMT not detected.";
5510 }
5511 break;
5512 }
5513 case 'netinfo': { // Show network interface information
5514 var interfaces = require('os').networkInterfaces();
5515 if (process.platform == 'win32') {
5516 try {
5517 var ret = require('win-wmi').query('ROOT\\CIMV2', 'SELECT InterfaceIndex,NetConnectionID,Speed FROM Win32_NetworkAdapter', ['InterfaceIndex','NetConnectionID','Speed']);
5518 if (ret[0]) {
5519 var speedMap = {};
5520 for (var i = 0; i < ret.length; i++) speedMap[ret[i].InterfaceIndex] = ret[i].Speed;
5521 var adapterNames = Object.keys(interfaces);
5522 for (var j = 0; j < adapterNames.length; j++) {
5523 var theinterfaces = interfaces[adapterNames[j]];
5524 for (var k = 0; k < theinterfaces.length; k++) {
5525 var iface = theinterfaces[k], speed = speedMap[iface.index] || 0;
5526 iface.speed = parseInt(speed); // bits per seconds
5527 }
5528 }
5529 }
5530 } catch(ex) { }
5531 } else if (process.platform == 'linux') {
5532 var adapterNames = Object.keys(interfaces);
5533 for (var i = 0; i < adapterNames.length; i++) {
5534 var ifaceName = adapterNames[i];
5535 try {
5536 var speedStr = require('fs').readFileSync('/sys/class/net/' + ifaceName + '/speed').toString();
5537 if ((speedStr.trim() != "") && (speedStr.trim() != "-1")) {
5538 var theinterfaces = interfaces[ifaceName];
5539 for (var k = 0; k < theinterfaces.length; k++) {
5540 var iface = theinterfaces[k];
5541 iface.speed = parseInt(speedStr) * 1000000; // bits per seconds
5542 }
5543 }
5544 } catch(ex) { }
5545 }
5546 }
5547 response = objToString(interfaces, 0, ' ', true);
5548 break;
5549 }
5550 case 'wakeonlan': { // Send wake-on-lan
5551 if ((args['_'].length != 1) || (args['_'][0].length != 12)) {
5552 response = 'Proper usage: wakeonlan [mac], for example "wakeonlan 010203040506".';
5553 } else {
5554 var count = sendWakeOnLanEx([args['_'][0]]);
5555 sendWakeOnLanEx([args['_'][0]]);
5556 sendWakeOnLanEx([args['_'][0]]);
5557 response = 'Sending wake-on-lan on ' + count + ' interface(s).';
5558 }
5559 break;
5560 }
5561 case 'display': {
5562 if (args['_'].length != 1) {
5563 response = 'Proper usage: display (sleep | awake)';
5564 } else {
5565 var sleepawake = [args['_'][0]];
5566 if(sleepawake=='sleep'){
5567 require('power-monitor').sleepDisplay()
5568 }else if(sleepawake=='awake'){
5569 require('power-monitor').wakeDisplay()
5570
5571 }
5572 response = 'Setting Display To ' + sleepawake;
5573 }
5574 break;
5575 }
5576 case 'sendall': { // Send a message to all consoles on this mesh
5577 sendConsoleText(args['_'].join(' '));
5578 break;
5579 }
5580 case 'power': { // Execute a power action on this computer
5581 if (mesh.ExecPowerState == undefined) {
5582 response = 'Power command not supported on this agent.';
5583 } else {
5584 if ((args['_'].length == 0) || isNaN(Number(args['_'][0]))) {
5585 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
5586 } else {
5587 var r = mesh.ExecPowerState(Number(args['_'][0]), Number(args['_'][1]));
5588 response = 'Power action executed with return code: ' + r + '.';
5589 }
5590 }
5591 break;
5592 }
5593 case 'location': {
5594 getIpLocationData(function (location) {
5595 sendConsoleText(objToString({ action: 'iplocation', type: 'publicip', value: location }, 0, ' '));
5596 });
5597 break;
5598 }
5599 case 'parseuri': {
5600 response = JSON.stringify(http.parseUri(args['_'][0]));
5601 break;
5602 }
5603 case 'scanwifi': {
5604 if (wifiScanner != null) {
5605 var wifiPresent = wifiScanner.hasWireless;
5606 if (wifiPresent) { response = "Perfoming Wifi scan..."; wifiScanner.Scan(); } else { response = "Wifi absent."; }
5607 } else
5608 { response = "Wifi module not present."; }
5609 break;
5610 }
5611 case 'modules': {
5612 response = JSON.stringify(addedModules);
5613 break;
5614 }
5615 case 'listservices': {
5616 var services = require('service-manager').manager.enumerateService();
5617 response = JSON.stringify(services, null, 1);
5618 break;
5619 }
5620 case 'getscript': {
5621 if (args['_'].length != 1) {
5622 response = "Proper usage: getscript [scriptNumber].";
5623 } else {
5624 mesh.SendCommand({ action: 'getScript', type: args['_'][0] });
5625 }
5626 break;
5627 }
5628 case 'diagnostic':
5629 {
5630 if (!mesh.DAIPC.listening) {
5631 response = 'Unable to bind to Diagnostic IPC, most likely because the path (' + process.cwd() + ') is not on a local file system';
5632 break;
5633 }
5634 var diag = diagnosticAgent_installCheck();
5635 if (diag) {
5636 if (args['_'].length == 1 && args['_'][0] == 'uninstall') {
5637 diagnosticAgent_uninstall();
5638 response = 'Diagnostic Agent uninstalled';
5639 }
5640 else {
5641 response = 'Diagnostic Agent installed at: ' + diag.appLocation();
5642 }
5643 }
5644 else {
5645 if (args['_'].length == 1 && args['_'][0] == 'install') {
5646 diag = diagnosticAgent_installCheck(true);
5647 if (diag) {
5648 response = 'Diagnostic agent was installed at: ' + diag.appLocation();
5649 }
5650 else {
5651 response = 'Diagnostic agent installation failed';
5652 }
5653 }
5654 else {
5655 response = 'Diagnostic Agent Not installed. To install: diagnostic install';
5656 }
5657 }
5658 if (diag) { diag.close(); diag = null; }
5659 break;
5660 }
5661 case 'amtevents': {
5662 if ((args['_'].length == 1) && (args['_'][0] == 'on')) { obj.showamtevent = true; response = 'Intel AMT configuration events live view enabled.'; }
5663 else if ((args['_'].length == 1) && (args['_'][0] == 'off')) { delete obj.showamtevent; response = 'Intel AMT configuration events live view disabled.'; }
5664 else if (obj.amtevents == null) { response = 'No events.'; } else { response = obj.amtevents.join('\r\n'); }
5665 break;
5666 }
5667 case 'amtconfig': {
5668 if (amt == null) { response = 'Intel AMT not detected.'; break; }
5669 if (!obj.showamtevent) { obj.showamtevent = true; require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: 'Enabled live view of Intel AMT configuration events, \"amtevents off\" to disable.' }); }
5670 if (apftunnel != null) { response = 'Intel AMT server tunnel already active'; break; }
5671 require('MeshAgent').SendCommand({ action: 'amtconfig' }); // Request that the server give us a server authentication cookie to start the APF session.
5672 break;
5673 }
5674 case 'apf': {
5675 if (meshCoreObj.intelamt !== null) {
5676 if (args['_'].length == 1) {
5677 var connType = -1, connTypeStr = args['_'][0].toLowerCase();
5678 if (connTypeStr == 'lms') { connType = 2; }
5679 if (connTypeStr == 'relay') { connType = 1; }
5680 if (connTypeStr == 'cira') { connType = 0; }
5681 if (connTypeStr == 'off') { connType = -2; }
5682 if (connType >= 0) { // Connect
5683 var apfarg = {
5684 mpsurl: mesh.ServerUrl.replace('agent.ashx', 'apf.ashx'),
5685 mpsuser: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16).replace(/\+/g, '@').replace(/\//g, '$'),
5686 mpspass: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16).replace(/\+/g, '@').replace(/\//g, '$'),
5687 mpskeepalive: 60000,
5688 clientname: require('os').hostname(),
5689 clientaddress: '127.0.0.1',
5690 clientuuid: meshCoreObj.intelamt.UUID,
5691 conntype: connType // 0 = CIRA, 1 = Relay, 2 = LMS. The correct value is 2 since we are performing an LMS relay, other values for testing.
5692 };
5693 if ((apfarg.clientuuid == null) || (apfarg.clientuuid.length != 36)) {
5694 response = "Unable to get Intel AMT UUID: " + apfarg.clientuuid;
5695 } else {
5696 apftunnel = require('amt-apfclient')({ debug: false }, apfarg);
5697 apftunnel.onJsonControl = handleApfJsonControl;
5698 apftunnel.onChannelClosed = function () { apftunnel = null; }
5699 try {
5700 apftunnel.connect();
5701 response = "Started APF tunnel";
5702 } catch (ex) {
5703 response = JSON.stringify(ex);
5704 }
5705 }
5706 } else if (connType == -2) { // Disconnect
5707 try {
5708 apftunnel.disconnect();
5709 response = "Stopped APF tunnel";
5710 } catch (ex) {
5711 response = JSON.stringify(ex);
5712 }
5713 apftunnel = null;
5714 } else {
5715 response = "Invalid command.\r\nUse: apf lms|relay|cira|off";
5716 }
5717 } else {
5718 response = "APF tunnel is " + (apftunnel == null ? "off" : "on") + "\r\nUse: apf lms|relay|cira|off";
5719 }
5720 } else {
5721 response = "APF tunnel requires Intel AMT";
5722 }
5723 break;
5724 }
5725 case 'plugin': {
5726 if (typeof args['_'][0] == 'string') {
5727 try {
5728 // Pass off the action to the plugin
5729 // for plugin creators, you'll want to have a plugindir/modules_meshcore/plugin.js
5730 // to control the output / actions here.
5731 response = require(args['_'][0]).consoleaction(args, rights, sessionid, mesh);
5732 } catch (ex) {
5733 response = "There was an error in the plugin (" + ex + ")";
5734 }
5735 } else {
5736 response = "Proper usage: plugin [pluginName] [args].";
5737 }
5738 break;
5739 }
5740 case 'installedapps': {
5741 if(process.platform == 'win32'){
5742 require('win-info').installedApps().then(function (apps){ sendConsoleText(JSON.stringify(apps,null,1)); });
5743 }
5744 break;
5745 }
5746 case 'installedstoreapps': {
5747 if(process.platform == 'win32'){
5748 var apps = require('win-info').installedStoreApps();
5749 if (apps[0]) {
5750 sendConsoleText(JSON.stringify(apps,null,1));
5751 };
5752 }
5753 break;
5754 }
5755 case 'qfe': {
5756 if(process.platform == 'win32'){
5757 var qfe = require('win-info').qfe();
5758 sendConsoleText(JSON.stringify(qfe,null,1));
5759 }
5760 break;
5761 }
5762 default: { // This is an unknown command, return an error message
5763 response = "Unknown command \"" + cmd + "\", type \"help\" for list of available commands.";
5764 break;
5765 }
5766 }
5767 } catch (ex) { response = "Command returned an exception error: " + ex; console.log(ex); }
5768 if (response != null) { sendConsoleText(response, sessionid); }
5769}
5770
5771// Send a mesh agent console command
5772function sendConsoleText(text, sessionid) {
5773 if (typeof text == 'object') { text = JSON.stringify(text); }
5774 if (debugConsole && ((sessionid == null) || (sessionid == 'pipe'))) { broadcastToRegisteredApps({ cmd: 'console', value: text }); }
5775 if (sessionid != 'pipe') { require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: text, sessionid: sessionid }); }
5776}
5777
5778function removeAgentMessage(msgid) {
5779 var ret = false;
5780 if (msgid == null) {
5781 // Delete all messages
5782 sendAgentMessage.messages = [];
5783 ret = true;
5784 }
5785 else {
5786 var i = sendAgentMessage.messages.findIndex(function (v) { return (v.id == msgid); });
5787 if (i >= 0) {
5788 sendAgentMessage.messages.splice(i, 1);
5789 ret = true;
5790 }
5791 }
5792 if (ret) { sendAgentMessage(); }
5793 return (ret);
5794}
5795
5796// Send a mesh agent message to server, placing a bubble/badge on the agent device
5797function sendAgentMessage(msg, icon, serverid, first) {
5798 if (sendAgentMessage.messages == null) {
5799 sendAgentMessage.messages = [];
5800 }
5801
5802 if (arguments.length > 0) {
5803 if (first == null || (serverid && first && sendAgentMessage.messages.findIndex(function (v) { return (v.msgid == serverid); }) < 0)) {
5804 sendAgentMessage.messages.push({ msg: msg, icon: icon, msgid: serverid });
5805 sendAgentMessage.messages.peek().id = sendAgentMessage.messages.peek()._hashCode();
5806 }
5807 }
5808
5809 var p = {}, i;
5810 for (i = 0; i < sendAgentMessage.messages.length; ++i) {
5811 p[i] = sendAgentMessage.messages[i];
5812 }
5813 try {
5814 require('MeshAgent').SendCommand({ action: 'sessions', type: 'msg', value: p });
5815 } catch (ex) { }
5816 return (arguments.length > 0 ? sendAgentMessage.messages.peek().id : sendAgentMessage.messages);
5817}
5818function getOpenDescriptors() {
5819 var r = [];
5820 switch (process.platform) {
5821 case "freebsd": {
5822 var child = require('child_process').execFile('/bin/sh', ['sh']);
5823 child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
5824 child.stderr.on('data', function (c) { });
5825
5826 child.stdin.write("procstat -f " + process.pid + " | tr '\\n' '`' | awk -F'`' '");
5827 child.stdin.write('{');
5828 child.stdin.write(' DEL="";');
5829 child.stdin.write(' printf "[";');
5830 child.stdin.write(' for(i=1;i<NF;++i)');
5831 child.stdin.write(' {');
5832 child.stdin.write(' A=split($i,B," ");');
5833 child.stdin.write(' if(B[3] ~ /^[0-9]/)');
5834 child.stdin.write(' {');
5835 child.stdin.write(' printf "%s%s", DEL, B[3];');
5836 child.stdin.write(' DEL=",";');
5837 child.stdin.write(' }');
5838 child.stdin.write(' }');
5839 child.stdin.write(' printf "]";');
5840 child.stdin.write("}'");
5841
5842 child.stdin.write('\nexit\n');
5843 child.waitExit();
5844
5845 try { r = JSON.parse(child.stdout.str.trim()); } catch (ex) { }
5846 break;
5847 }
5848 case "linux": {
5849 var child = require('child_process').execFile('/bin/sh', ['sh']);
5850 child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
5851 child.stderr.on('data', function (c) { });
5852
5853 child.stdin.write("ls /proc/" + process.pid + "/fd | tr '\\n' '`' | awk -F'`' '");
5854 child.stdin.write('{');
5855 child.stdin.write(' printf "[";');
5856 child.stdin.write(' DEL="";');
5857 child.stdin.write(' for(i=1;i<NF;++i)');
5858 child.stdin.write(' {');
5859 child.stdin.write(' printf "%s%s",DEL,$i;');
5860 child.stdin.write(' DEL=",";');
5861 child.stdin.write(' }');
5862 child.stdin.write(' printf "]";');
5863 child.stdin.write("}'");
5864 child.stdin.write('\nexit\n');
5865 child.waitExit();
5866
5867 try { r = JSON.parse(child.stdout.str.trim()); } catch (ex) { }
5868 break;
5869 }
5870 }
5871 return r;
5872}
5873function closeDescriptors(libc, descriptors) {
5874 var fd = null;
5875 while (descriptors.length > 0) {
5876 fd = descriptors.pop();
5877 if (fd > 2) {
5878 libc.close(fd);
5879 }
5880 }
5881}
5882function linux_execv(name, agentfilename, sessionid) {
5883 var libs = require('monitor-info').getLibInfo('libc');
5884 var libc = null;
5885
5886 if ((libs.length == 0 || libs.length == null) && require('MeshAgent').ARCHID == 33) {
5887 var child = require('child_process').execFile('/bin/sh', ['sh']);
5888 child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
5889 child.stderr.str = ''; child.stderr.on('data', function (c) { this.str += c.toString(); });
5890 child.stdin.write("ls /lib/libc.* | tr '\\n' '`' | awk -F'`' '{ " + ' printf "["; DEL=""; for(i=1;i<NF;++i) { printf "%s{\\"path\\":\\"%s\\"}",DEL,$i; DEL=""; } printf "]"; }\'\nexit\n');
5891 child.waitExit();
5892
5893 try {
5894 libs = JSON.parse(child.stdout.str.trim());
5895 } catch (ex) { }
5896 }
5897
5898 while (libs.length > 0) {
5899 try {
5900 libc = require('_GenericMarshal').CreateNativeProxy(libs.pop().path);
5901 break;
5902 } catch (ex) {
5903 libc = null;
5904 continue;
5905 }
5906 }
5907 if (libc != null) {
5908 try {
5909 libc.CreateMethod('execv');
5910 libc.CreateMethod('close');
5911 } catch (ex) {
5912 libc = null;
5913 }
5914 }
5915
5916 if (libc == null) {
5917 // Couldn't find libc.so, fallback to using service manager to restart agent
5918 if (sessionid != null) { sendConsoleText('Restarting service via service-manager...', sessionid) }
5919 try {
5920 // restart service
5921 var s = require('service-manager').manager.getService(name);
5922 s.restart();
5923 } catch (ex) {
5924 sendConsoleText('Self Update encountered an error trying to restart service', sessionid);
5925 sendAgentMessage('Self Update encountered an error trying to restart service', 3);
5926 }
5927 return;
5928 }
5929
5930 if (sessionid != null) { sendConsoleText('Restarting service via execv()...', sessionid) }
5931
5932 var i;
5933 var args;
5934 var argarr = [process.execPath];
5935 var argtmp = [];
5936 var path = require('_GenericMarshal').CreateVariable(process.execPath);
5937
5938 if (require('MeshAgent').getStartupOptions != null) {
5939 var options = require('MeshAgent').getStartupOptions();
5940 for (i in options) {
5941 argarr.push('--' + i + '="' + options[i] + '"');
5942 }
5943 }
5944
5945 args = require('_GenericMarshal').CreateVariable((1 + argarr.length) * require('_GenericMarshal').PointerSize);
5946 for (i = 0; i < argarr.length; ++i) {
5947 var arg = require('_GenericMarshal').CreateVariable(argarr[i]);
5948 argtmp.push(arg);
5949 arg.pointerBuffer().copy(args.toBuffer(), i * require('_GenericMarshal').PointerSize);
5950 }
5951
5952 var descriptors = getOpenDescriptors();
5953 closeDescriptors(libc, descriptors);
5954
5955 libc.execv(path, args);
5956 if (sessionid != null) { sendConsoleText('Self Update failed because execv() failed', sessionid) }
5957 sendAgentMessage('Self Update failed because execv() failed', 3);
5958}
5959
5960function bsd_execv(name, agentfilename, sessionid) {
5961 var child = require('child_process').execFile('/bin/sh', ['sh']);
5962 child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
5963 child.stderr.str = ''; child.stderr.on('data', function (c) { this.str += c.toString(); });
5964 child.stdin.write("cat /usr/lib/libc.so | awk '");
5965 child.stdin.write('{');
5966 child.stdin.write(' a=split($0, tok, "(");');
5967 child.stdin.write(' if(a>1)');
5968 child.stdin.write(' {');
5969 child.stdin.write(' split(tok[2], b, ")");');
5970 child.stdin.write(' split(b[1], c, " ");');
5971 child.stdin.write(' print c[1];');
5972 child.stdin.write(' }');
5973 child.stdin.write("}'\nexit\n");
5974 child.waitExit();
5975 if (child.stdout.str.trim() == '') {
5976 if (sessionid != null) { sendConsoleText('Self Update failed because cannot find libc.so', sessionid) }
5977 sendAgentMessage('Self Update failed because cannot find libc.so', 3);
5978 return;
5979 }
5980
5981 var libc = null;
5982 try {
5983 libc = require('_GenericMarshal').CreateNativeProxy(child.stdout.str.trim());
5984 libc.CreateMethod('execv');
5985 libc.CreateMethod('close');
5986 } catch (ex) {
5987 if (sessionid != null) { sendConsoleText('Self Update failed: ' + ex.toString(), sessionid) }
5988 sendAgentMessage('Self Update failed: ' + ex.toString(), 3);
5989 return;
5990 }
5991
5992 var path = require('_GenericMarshal').CreateVariable(process.execPath);
5993 var argarr = [process.execPath];
5994 var args, i, argtmp = [];
5995 var options = require('MeshAgent').getStartupOptions();
5996 for (i in options) {
5997 argarr.push('--' + i + '="' + options[i] + '"');
5998 }
5999 args = require('_GenericMarshal').CreateVariable((1 + argarr.length) * require('_GenericMarshal').PointerSize);
6000 for (i = 0; i < argarr.length; ++i) {
6001 var arg = require('_GenericMarshal').CreateVariable(argarr[i]);
6002 argtmp.push(arg);
6003 arg.pointerBuffer().copy(args.toBuffer(), i * require('_GenericMarshal').PointerSize);
6004 }
6005
6006 if (sessionid != null) { sendConsoleText('Restarting service via execv()', sessionid) }
6007
6008 var descriptors = getOpenDescriptors();
6009 closeDescriptors(libc, descriptors);
6010
6011 libc.execv(path, args);
6012 if (sessionid != null) { sendConsoleText('Self Update failed because execv() failed', sessionid) }
6013 sendAgentMessage('Self Update failed because execv() failed', 3);
6014}
6015
6016function windows_execve(name, agentfilename, sessionid) {
6017 var libc;
6018 try {
6019 libc = require('_GenericMarshal').CreateNativeProxy('msvcrt.dll');
6020 libc.CreateMethod('_wexecve');
6021 } catch (ex) {
6022 sendConsoleText('Self Update failed because msvcrt.dll is missing', sessionid);
6023 sendAgentMessage('Self Update failed because msvcrt.dll is missing', 3);
6024 return;
6025 }
6026
6027 var cmd = require('_GenericMarshal').CreateVariable(process.env['windir'] + '\\system32\\cmd.exe', { wide: true });
6028 var args = require('_GenericMarshal').CreateVariable(3 * require('_GenericMarshal').PointerSize);
6029 var arg1 = require('_GenericMarshal').CreateVariable('cmd.exe', { wide: true });
6030 var arg2 = require('_GenericMarshal').CreateVariable('/C net stop "' + name + '" & "' + process.cwd() + agentfilename + '.update.exe" -b64exec ' + 'dHJ5CnsKICAgIHZhciBzZXJ2aWNlTG9jYXRpb24gPSBwcm9jZXNzLmFyZ3YucG9wKCkudG9Mb3dlckNhc2UoKTsKICAgIHJlcXVpcmUoJ3Byb2Nlc3MtbWFuYWdlcicpLmVudW1lcmF0ZVByb2Nlc3NlcygpLnRoZW4oZnVuY3Rpb24gKHByb2MpCiAgICB7CiAgICAgICAgZm9yICh2YXIgcCBpbiBwcm9jKQogICAgICAgIHsKICAgICAgICAgICAgaWYgKHByb2NbcF0ucGF0aCAmJiAocHJvY1twXS5wYXRoLnRvTG93ZXJDYXNlKCkgPT0gc2VydmljZUxvY2F0aW9uKSkKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgcHJvY2Vzcy5raWxsKHByb2NbcF0ucGlkKTsKICAgICAgICAgICAgfQogICAgICAgIH0KICAgICAgICBwcm9jZXNzLmV4aXQoKTsKICAgIH0pOwp9CmNhdGNoIChlKQp7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQ==' +
6031 ' "' + process.execPath + '" & copy "' + process.cwd() + agentfilename + '.update.exe" "' + process.execPath + '" & net start "' + name + '" & erase "' + process.cwd() + agentfilename + '.update.exe"', { wide: true });
6032
6033 arg1.pointerBuffer().copy(args.toBuffer());
6034 arg2.pointerBuffer().copy(args.toBuffer(), require('_GenericMarshal').PointerSize);
6035
6036 libc._wexecve(cmd, args, 0);
6037}
6038
6039// Start a JavaScript based Agent Self-Update
6040function agentUpdate_Start(updateurl, updateoptions) {
6041 // If this value is null
6042 var sessionid = (updateoptions != null) ? updateoptions.sessionid : null; // If this is null, messages will be broadcast. Otherwise they will be unicasted
6043
6044 // If the url starts with *, switch it to use the same protoco, host and port as the control channel.
6045 if (updateurl != null) {
6046 updateurl = getServerTargetUrlEx(updateurl);
6047 if (updateurl.startsWith("wss://")) { updateurl = "https://" + updateurl.substring(6); }
6048 }
6049
6050 if (agentUpdate_Start._selfupdate != null) {
6051 // We were already called, so we will ignore this duplicate request
6052 if (sessionid != null) { sendConsoleText('Self update already in progress...', sessionid); }
6053 }
6054 else {
6055 if (agentUpdate_Start._retryCount == null) { agentUpdate_Start._retryCount = 0; }
6056 if (require('MeshAgent').ARCHID == null && updateurl == null) {
6057 // This agent doesn't have the ability to tell us which ARCHID it is, so we don't know which agent to pull
6058 sendConsoleText('Unable to initiate update, agent ARCHID is not defined', sessionid);
6059 }
6060 else {
6061 var agentfilename = process.execPath.split(process.platform == 'win32' ? '\\' : '/').pop(); // Local File Name, ie: MeshAgent.exe
6062 var name = require('MeshAgent').serviceName;
6063 if (name == null) { name = (process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'); } // This is an older agent that doesn't expose the service name, so use the default
6064 try {
6065 var s = require('service-manager').manager.getService(name);
6066 if (!s.isMe()) {
6067 if (process.platform == 'win32') { s.close(); }
6068 sendConsoleText('Self Update cannot continue, this agent is not an instance of (' + name + ')', sessionid);
6069 return;
6070 }
6071 if (process.platform == 'win32') { s.close(); }
6072 }
6073 catch (ex) {
6074 sendConsoleText('Self Update Failed because this agent is not an instance of (' + name + ')', sessionid);
6075 sendAgentMessage('Self Update Failed because this agent is not an instance of (' + name + ')', 3);
6076 return;
6077 }
6078
6079 if ((sessionid != null) && (updateurl != null)) { sendConsoleText('Downloading update from: ' + updateurl, sessionid); }
6080 var options = require('http').parseUri(updateurl != null ? updateurl : require('MeshAgent').ServerUrl);
6081 options.protocol = 'https:';
6082 if (updateurl == null) { options.path = ('/meshagents?id=' + require('MeshAgent').ARCHID); sendConsoleText('Downloading update from: ' + options.path, sessionid); }
6083 options.rejectUnauthorized = false;
6084 options.checkServerIdentity = function checkServerIdentity(certs) {
6085 // If the tunnel certificate matches the control channel certificate, accept the connection
6086 try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.digest == certs[0].digest) return; } catch (ex) { }
6087 try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.fingerprint == certs[0].fingerprint) return; } catch (ex) { }
6088
6089 // Check that the certificate is the one expected by the server, fail if not.
6090 if (checkServerIdentity.servertlshash == null) {
6091 if (require('MeshAgent').ServerInfo == null || require('MeshAgent').ServerInfo.ControlChannelCertificate == null) { return; }
6092 sendConsoleText('Self Update failed, because the url cannot be verified: ' + updateurl, sessionid);
6093 sendAgentMessage('Self Update failed, because the url cannot be verified: ' + updateurl, 3);
6094 throw new Error('BadCert');
6095 }
6096 if (certs[0].digest == null) { return; }
6097 if ((checkServerIdentity.servertlshash != null) && (checkServerIdentity.servertlshash.toLowerCase() != certs[0].digest.split(':').join('').toLowerCase())) {
6098 sendConsoleText('Self Update failed, because the supplied certificate does not match', sessionid);
6099 sendAgentMessage('Self Update failed, because the supplied certificate does not match', 3);
6100 throw new Error('BadCert')
6101 }
6102 }
6103 options.checkServerIdentity.servertlshash = (updateoptions != null ? updateoptions.tlshash : null);
6104 agentUpdate_Start._selfupdate = require('https').get(options);
6105 agentUpdate_Start._selfupdate.on('error', function (e) {
6106 sendConsoleText('Self Update failed, because there was a problem trying to download the update from ' + updateurl, sessionid);
6107 sendAgentMessage('Self Update failed, because there was a problem trying to download the update from ' + updateurl, 3);
6108 agentUpdate_Start._selfupdate = null;
6109 });
6110 agentUpdate_Start._selfupdate.on('response', function (img) {
6111 this._file = require('fs').createWriteStream(agentfilename + (process.platform == 'win32' ? '.update.exe' : '.update'), { flags: 'wb' });
6112 this._filehash = require('SHA384Stream').create();
6113 this._filehash.on('hash', function (h) {
6114 if (updateoptions != null && updateoptions.hash != null) {
6115 if (updateoptions.hash.toLowerCase() == h.toString('hex').toLowerCase()) {
6116 if (sessionid != null) { sendConsoleText('Download complete. HASH verified.', sessionid); }
6117 } else {
6118 agentUpdate_Start._retryCount++;
6119 sendConsoleText('Self Update FAILED because the downloaded agent FAILED hash check (' + agentUpdate_Start._retryCount + '), URL: ' + updateurl, sessionid);
6120 sendConsoleText(updateoptions.hash + " != " + h.toString('hex'));
6121 sendAgentMessage('Self Update FAILED because the downloaded agent FAILED hash check (' + agentUpdate_Start._retryCount + '), URL: ' + updateurl, 3);
6122 agentUpdate_Start._selfupdate = null;
6123
6124 if (agentUpdate_Start._retryCount < 4) {
6125 // Retry the download again
6126 sendConsoleText('Self Update will try again in 60 seconds...', sessionid);
6127 agentUpdate_Start._timeout = setTimeout(agentUpdate_Start, 60000, updateurl, updateoptions);
6128 }
6129 else {
6130 sendConsoleText('Self Update giving up, too many failures...', sessionid);
6131 sendAgentMessage('Self Update giving up, too many failures...', 3);
6132 }
6133 return;
6134 }
6135 }
6136 else {
6137 sendConsoleText('Download complete. HASH=' + h.toString('hex'), sessionid);
6138 }
6139
6140 // Send an indication to the server that we got the update download correctly.
6141 try { require('MeshAgent').SendCommand({ action: 'agentupdatedownloaded' }); } catch (ex) { }
6142
6143 if (sessionid != null) { sendConsoleText('Updating and restarting agent...', sessionid); }
6144 if (process.platform == 'win32') {
6145 // Use _wexecve() equivalent to perform the update
6146 windows_execve(name, agentfilename, sessionid);
6147 }
6148 else {
6149 var m = require('fs').statSync(process.execPath).mode;
6150 require('fs').chmodSync(process.cwd() + agentfilename + '.update', m);
6151
6152 // remove binary
6153 require('fs').unlinkSync(process.execPath);
6154
6155 // copy update
6156 require('fs').copyFileSync(process.cwd() + agentfilename + '.update', process.execPath);
6157 require('fs').chmodSync(process.execPath, m);
6158
6159 // erase update
6160 require('fs').unlinkSync(process.cwd() + agentfilename + '.update');
6161
6162 switch (process.platform) {
6163 case 'freebsd':
6164 bsd_execv(name, agentfilename, sessionid);
6165 break;
6166 case 'linux':
6167 linux_execv(name, agentfilename, sessionid);
6168 break;
6169 default:
6170 try {
6171 // restart service
6172 var s = require('service-manager').manager.getService(name);
6173 s.restart();
6174 }
6175 catch (ex) {
6176 sendConsoleText('Self Update encountered an error trying to restart service', sessionid);
6177 sendAgentMessage('Self Update encountered an error trying to restart service', 3);
6178 }
6179 break;
6180 }
6181 }
6182 });
6183 img.pipe(this._file);
6184 img.pipe(this._filehash);
6185 });
6186 }
6187 }
6188}
6189
6190
6191
6192
6193// Called before the process exits
6194//process.exit = function (code) { console.log("Exit with code: " + code.toString()); }
6195
6196// Called when the server connection state changes
6197function handleServerConnection(state) {
6198 meshServerConnectionState = state;
6199 if (meshServerConnectionState == 0) {
6200 // Server disconnected
6201 if (selfInfoUpdateTimer != null) { clearInterval(selfInfoUpdateTimer); selfInfoUpdateTimer = null; }
6202 lastSelfInfo = null;
6203 } else {
6204 // Server connected, send mesh core information
6205 if (require('MeshAgent').ServerInfo == null || require('MeshAgent').ServerInfo.ControlChannelCertificate == null) {
6206 // Outdated Agent, will have insecure tunnels
6207 sendAgentMessage("This agent has an outdated certificate validation mechanism, consider updating.", 3, 118);
6208 }
6209 else if (global._MSH == null) {
6210 sendAgentMessage("This is an old agent version, consider updating.", 3, 117);
6211 }
6212
6213 var oldNodeId = db.Get('OldNodeId');
6214 if (oldNodeId != null) { mesh.SendCommand({ action: 'mc1migration', oldnodeid: oldNodeId }); }
6215
6216 // Send SMBios tables if present
6217 if (SMBiosTablesRaw != null) { mesh.SendCommand({ action: 'smbios', value: SMBiosTablesRaw }); }
6218
6219 // Update the server on with basic info, logged in users and more advanced stuff, like Intel ME and Network Settings
6220 meInfoStr = null;
6221 LastPeriodicServerUpdate = null;
6222 sendPeriodicServerUpdate(null, true);
6223 if (selfInfoUpdateTimer == null) {
6224 selfInfoUpdateTimer = setInterval(sendPeriodicServerUpdate, 1200000); // 20 minutes
6225 selfInfoUpdateTimer.metadata = 'meshcore (InfoUpdate Timer)';
6226 }
6227
6228 // Send any state messages
6229 if (Object.keys(tunnelUserCount.msg).length > 0) {
6230 sendAgentMessage();
6231 broadcastSessionsToRegisteredApps();
6232 }
6233
6234 // Send update of registered applications to the server
6235 updateRegisteredAppsToServer();
6236 }
6237
6238 // Send server state update to registered applications
6239 broadcastToRegisteredApps({ cmd: 'serverstate', value: meshServerConnectionState, url: require('MeshAgent').ConnectedServer });
6240}
6241
6242// Update the server with the latest network interface information
6243var sendNetworkUpdateNagleTimer = null;
6244function sendNetworkUpdateNagle() { if (sendNetworkUpdateNagleTimer != null) { clearTimeout(sendNetworkUpdateNagleTimer); sendNetworkUpdateNagleTimer = null; } sendNetworkUpdateNagleTimer = setTimeout(sendNetworkUpdate, 5000); }
6245function sendNetworkUpdate(force) {
6246 sendNetworkUpdateNagleTimer = null;
6247
6248 try {
6249 // Update the network interfaces information data
6250 var netInfo = { netif2: require('os').networkInterfaces() };
6251 if (process.platform == 'win32') {
6252 try {
6253 var ret = require('win-wmi').query('ROOT\\CIMV2', 'SELECT InterfaceIndex,NetConnectionID,Speed FROM Win32_NetworkAdapter', ['InterfaceIndex','NetConnectionID','Speed']);
6254 if (ret[0]) {
6255 var speedMap = {};
6256 for (var i = 0; i < ret.length; i++) speedMap[ret[i].InterfaceIndex] = ret[i].Speed;
6257 var adapterNames = Object.keys(netInfo.netif2);
6258 for (var j = 0; j < adapterNames.length; j++) {
6259 var interfaces = netInfo.netif2[adapterNames[j]];
6260 for (var k = 0; k < interfaces.length; k++) {
6261 var iface = interfaces[k], speed = speedMap[iface.index] || 0;
6262 iface.speed = parseInt(speed); // bits per seconds
6263 }
6264 }
6265 }
6266 } catch(ex) { }
6267 } else if (process.platform == 'linux') {
6268 var adapterNames = Object.keys(netInfo.netif2);
6269 for (var i = 0; i < adapterNames.length; i++) {
6270 var ifaceName = adapterNames[i];
6271 try {
6272 var speedStr = require('fs').readFileSync('/sys/class/net/' + ifaceName + '/speed').toString();
6273 if ((speedStr.trim() != "") && (speedStr.trim() != "-1")) {
6274 var theinterfaces = netInfo.netif2[ifaceName];
6275 for (var k = 0; k < theinterfaces.length; k++) {
6276 var iface = theinterfaces[k];
6277 iface.speed = parseInt(speedStr) * 1000000; // bits per seconds
6278 }
6279 }
6280 } catch(ex) { }
6281 }
6282 }
6283 if (netInfo.netif2) {
6284 netInfo.action = 'netinfo';
6285 var netInfoStr = JSON.stringify(netInfo);
6286 if ((force == true) || (clearGatewayMac(netInfoStr) != clearGatewayMac(lastNetworkInfo))) { mesh.SendCommand(netInfo); lastNetworkInfo = netInfoStr; }
6287 }
6288 } catch (ex) { }
6289}
6290
6291// Called periodically to check if we need to send updates to the server
6292function sendPeriodicServerUpdate(flags, force) {
6293 if (meshServerConnectionState == 0) return; // Not connected to server, do nothing.
6294 if (!flags) { flags = 0xFFFFFFFF; }
6295 if (!force) { force = false; }
6296
6297 // If we have a connected MEI, get Intel ME information
6298 if ((flags & 1) && (amt != null) && (amt.state == 2)) {
6299 delete meshCoreObj.intelamt;
6300 amt.getMeiState(9, function (meinfo) {
6301 meshCoreObj.intelamt = meinfo;
6302 meshCoreObj.intelamt.microlms = amt.lmsstate;
6303 meshCoreObjChanged();
6304 });
6305 }
6306
6307 // Update network information
6308 if (flags & 2) { sendNetworkUpdateNagle(false); }
6309
6310 // Update anti-virus information
6311 if ((flags & 4) && (process.platform == 'win32')) {
6312 // Windows Command: "wmic /Namespace:\\root\SecurityCenter2 Path AntiVirusProduct get /FORMAT:CSV"
6313 try { meshCoreObj.av = require('win-info').av(); meshCoreObjChanged(); } catch (ex) { av = null; } // Antivirus
6314 //if (process.platform == 'win32') { try { meshCoreObj.pr = require('win-info').pendingReboot(); meshCoreObjChanged(); } catch (ex) { meshCoreObj.pr = null; } } // Pending reboot
6315 }
6316 if (process.platform == 'win32') {
6317 if (require('MeshAgent')._securitycenter == null) {
6318 try {
6319 require('MeshAgent')._securitycenter = require('win-securitycenter').status();
6320 meshCoreObj['wsc'] = require('MeshAgent')._securitycenter; // Windows Security Central (WSC)
6321 require('win-securitycenter').on('changed', function () {
6322 require('MeshAgent')._securitycenter = require('win-securitycenter').status();
6323 meshCoreObj['wsc'] = require('MeshAgent')._securitycenter; // Windows Security Central (WSC)
6324 require('MeshAgent').SendCommand({ action: 'coreinfo', wsc: require('MeshAgent')._securitycenter });
6325 });
6326 } catch (ex) { }
6327 }
6328
6329 // Get Defender Information
6330 try {
6331 meshCoreObj.defender = require('win-info').defender();
6332 meshCoreObjChanged();
6333 } catch (ex) { }
6334 }
6335
6336 // Send available data right now
6337 if (force) {
6338 meshCoreObj = sortObjRec(meshCoreObj);
6339 var x = JSON.stringify(meshCoreObj);
6340 if (x != LastPeriodicServerUpdate) {
6341 LastPeriodicServerUpdate = x;
6342 mesh.SendCommand(meshCoreObj);
6343 }
6344 }
6345}
6346
6347// Sort the names in an object
6348function sortObject(obj) { return Object.keys(obj).sort().reduce(function(a, v) { a[v] = obj[v]; return a; }, {}); }
6349
6350// Fix the incoming data and cut down how much data we use
6351function cleanGetBitLockerVolumeInfo(volumes) {
6352 for (var i in volumes) {
6353 const v = volumes[i];
6354 if (typeof v.size == 'string') { v.size = parseInt(v.size); }
6355 if (typeof v.sizeremaining == 'string') { v.sizeremaining = parseInt(v.sizeremaining); }
6356 if (v.identifier == '') { delete v.identifier; }
6357 if (v.name == '') { delete v.name; }
6358 if (v.removable != true) { delete v.removable; }
6359 if (v.cdrom != true) { delete v.cdrom; }
6360 if (v.protectionStatus == 'On') { v.protectionStatus = true; } else { delete v.protectionStatus; }
6361 if (v.volumeStatus == 'FullyDecrypted') { delete v.volumeStatus; }
6362 if (v.recoveryPassword == '') { delete v.recoveryPassword; }
6363 }
6364 return sortObject(volumes);
6365}
6366
6367// Once we are done collecting all the data, send to server if needed
6368var LastPeriodicServerUpdate = null;
6369var PeriodicServerUpdateNagleTimer = null;
6370function meshCoreObjChanged() {
6371 if (PeriodicServerUpdateNagleTimer == null) {
6372 PeriodicServerUpdateNagleTimer = setTimeout(meshCoreObjChangedEx, 500);
6373 }
6374}
6375function meshCoreObjChangedEx() {
6376 PeriodicServerUpdateNagleTimer = null;
6377 meshCoreObj = sortObjRec(meshCoreObj);
6378 var x = JSON.stringify(meshCoreObj);
6379 if (x != LastPeriodicServerUpdate) {
6380 try { LastPeriodicServerUpdate = x; mesh.SendCommand(meshCoreObj); } catch (ex) { }
6381 }
6382}
6383
6384function sortObjRec(o) { if ((typeof o != 'object') || (Array.isArray(o))) return o; for (var i in o) { if (typeof o[i] == 'object') { o[i] = sortObjRec(o[i]); } } return sortObj(o); }
6385function sortObj(o) { return Object.keys(o).sort().reduce(function (result, key) { result[key] = o[key]; return result; }, {}); }
6386
6387function onWebSocketClosed() { sendConsoleText("WebSocket #" + this.httprequest.index + " closed.", this.httprequest.sessionid); delete consoleWebSockets[this.httprequest.index]; }
6388function onWebSocketData(data) { sendConsoleText("Got WebSocket #" + this.httprequest.index + " data: " + data, this.httprequest.sessionid); }
6389function onWebSocketSendOk() { sendConsoleText("WebSocket #" + this.index + " SendOK.", this.sessionid); }
6390
6391function onWebSocketUpgrade(response, s, head) {
6392 sendConsoleText("WebSocket #" + this.index + " connected.", this.sessionid);
6393 this.s = s;
6394 s.httprequest = this;
6395 s.end = onWebSocketClosed;
6396 s.data = onWebSocketData;
6397}
6398
6399mesh.AddCommandHandler(handleServerCommand);
6400mesh.AddConnectHandler(handleServerConnection);
6401