master
js 7,042 lines 386 KB
Raw
1 /*
2 Copyright 2018-2022 Intel Corporation
3
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15 */
16
17 process.on('uncaughtException', function (ex) {
18 require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: "uncaughtException1: " + ex });
19 });
20 if (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
26 var promise = require('promise');
27
28 // Mesh Rights
29 var MNG_ERROR = 65;
30 var MESHRIGHT_EDITMESH = 1;
31 var MESHRIGHT_MANAGEUSERS = 2;
32 var MESHRIGHT_MANAGECOMPUTERS = 4;
33 var MESHRIGHT_REMOTECONTROL = 8;
34 var MESHRIGHT_AGENTCONSOLE = 16;
35 var MESHRIGHT_SERVERFILES = 32;
36 var MESHRIGHT_WAKEDEVICE = 64;
37 var MESHRIGHT_SETNOTES = 128;
38 var MESHRIGHT_REMOTEVIEW = 256; // Remote View Only
39 var MESHRIGHT_NOTERMINAL = 512;
40 var MESHRIGHT_NOFILES = 1024;
41 var MESHRIGHT_NOAMT = 2048;
42 var MESHRIGHT_LIMITEDINPUT = 4096;
43 var MESHRIGHT_LIMITEVENTS = 8192;
44 var MESHRIGHT_CHATNOTIFY = 16384;
45 var MESHRIGHT_UNINSTALL = 32768;
46 var MESHRIGHT_NODESKTOP = 65536;
47
48 var 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 //
54 function __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
65 if (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
77 function 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 }
84 function 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
140 function getLoggedOnUserBySessionId(sessionId) {
141 try {
142 const result = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine,
143 ("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Authentication\\LogonUI\\SessionData\\" + sessionId), 'LoggedOnUser'
144 );
145
146 if (result) {
147 return result.replace(/^[^\\]+\\/, '');
148 }
149
150 return null;
151
152 } catch (err) {
153 return null;
154 }
155 }
156
157 function getLogonCacheKeys() {
158 var registry = require('win-registry');
159 var HKLM = registry.HKEY.LocalMachine;
160
161 var userObj = [];
162
163 function readSubKeys(path) {
164 var vals = registry.QueryKey(HKLM, path);
165 if (!vals) return;
166
167 // Extract IdentityName, SAMName, SID if they exist
168 var identityName = null, samName = null, sid = null;
169
170 for (var i = 0; i in vals.values; i++) {
171 if (vals.values[i].toLowerCase() === 'identityname' && identityName === null) {
172 identityName = registry.QueryKey(HKLM, path, vals.values[i]);
173 }
174 if (vals.values[i].toLowerCase() === 'samname' && samName === null) {
175 samName = registry.QueryKey(HKLM, path, vals.values[i]);
176 }
177 if (vals.values[i].toLowerCase() === 'sid' && sid === null) {
178 sid = registry.QueryKey(HKLM, path, vals.values[i]);
179 }
180 }
181
182 // If IdentityName exists, add to userObj
183 if (identityName) {
184 userObj.push({
185 UPN: identityName,
186 SAM: samName,
187 SID: sid
188 });
189 }
190
191 // Recurse into subkeys if any
192 if (vals.subkeys && vals.subkeys.length > 0) {
193 for (var j = 0; j < vals.subkeys.length; j++) {
194 readSubKeys(path + '\\' + vals.subkeys[j]);
195 }
196 }
197 }
198
199 // Start recursion from the LogonCache root
200 readSubKeys('SOFTWARE\\Microsoft\\IdentityStore\\LogonCache');
201
202 var grouped = {};
203
204 function pushUnique(arr, val) {
205 if (val && arr.indexOf(val) === -1) arr.push(val);
206 }
207
208 // Group by UPN and merge values
209 for (var i = 0; i < userObj.length; i++) {
210 var u = userObj[i];
211
212 if (!grouped[u.UPN]) grouped[u.UPN] = {UPN: u.UPN, SID: [], SAM: []};
213
214 pushUnique(grouped[u.UPN].SID, u.SID);
215 pushUnique(grouped[u.UPN].SAM, u.SAM);
216 }
217
218 userObj = [];
219 // Convert grouped object to array
220 for (var k in grouped) if (grouped.hasOwnProperty(k)) userObj.push(grouped[k]);
221
222 return userObj;
223
224 }
225
226 function getJoinState() {
227 if (process.platform != 'win32') { return -1; }
228 var isAzureAD = false;
229 var isOnPrem = false;
230 var isHybrid = false;
231 var isMicrosoft = false;
232 // 1 Azure AD / Entra ID
233 try {
234 const joinInfo = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'SYSTEM\\CurrentControlSet\\Control\\CloudDomainJoin\\JoinInfo');
235 isAzureAD = Array.isArray(joinInfo.subkeys) && joinInfo.subkeys.length > 0;
236 } catch (e) {}
237 // 2 On-prem AD
238 try {
239 const tcpip = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters','Domain');
240 isOnPrem = !!(tcpip !== "" || null);
241 } catch (e) {}
242 // 3 Hybrid AD
243 isHybrid = isAzureAD && isOnPrem;
244 // 4 Microsoft Account
245 try {
246 const userAccounts = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'SOFTWARE\\Microsoft\\IdentityStore\\LogonCache\\D7F9888F-E3FC-49b0-9EA6-A85B5F392A4F');
247 isMicrosoft = Array.isArray(userAccounts.subkeys) && userAccounts.subkeys.length > 0;
248 } catch (e) {}
249 if (isMicrosoft) return 4;
250 if (isHybrid) return 3;
251 if (isOnPrem) return 2;
252 if (isAzureAD) return 1;
253 return 0;
254
255 }
256
257
258 try {
259 Object.defineProperty(Array.prototype, 'findIndex', {
260 value: function (func) {
261 var i = 0;
262 for (i = 0; i < this.length; ++i) {
263 if (func(this[i], i, this)) {
264 return (i);
265 }
266 }
267 return (-1);
268 }
269 });
270 } catch (ex) { }
271
272 if (require('MeshAgent').ARCHID == null) {
273 var id = null;
274 switch (process.platform) {
275 case 'win32':
276 id = require('_GenericMarshal').PointerSize == 4 ? 3 : 4;
277 break;
278 case 'freebsd':
279 id = require('_GenericMarshal').PointerSize == 4 ? 31 : 30;
280 break;
281 case 'darwin':
282 try {
283 id = require('os').arch() == 'x64' ? 16 : 29;
284 } catch (ex) { id = 16; }
285 break;
286 }
287 if (id != null) { Object.defineProperty(require('MeshAgent'), 'ARCHID', { value: id }); }
288 }
289
290 function setDefaultCoreTranslation(obj, field, value) {
291 if (obj[field] == null || obj[field] == '') { obj[field] = value; }
292 }
293
294 function getCoreTranslation() {
295 var ret = {};
296 if (global.coretranslations != null) {
297 try {
298 var lang = require('util-language').current;
299 if (coretranslations[lang] == null) { lang = lang.split('-')[0]; }
300 if (coretranslations[lang] == null) { lang = 'en'; }
301 if (coretranslations[lang] != null) { ret = coretranslations[lang]; }
302 }
303 catch (ex) { }
304 }
305
306 setDefaultCoreTranslation(ret, 'allow', 'Allow');
307 setDefaultCoreTranslation(ret, 'deny', 'Deny');
308 setDefaultCoreTranslation(ret, 'autoAllowForFive', 'Auto accept all connections for next 5 minutes');
309 setDefaultCoreTranslation(ret, 'terminalConsent', '{0} requesting remote terminal access. Grant access?');
310 setDefaultCoreTranslation(ret, 'desktopConsent', '{0} requesting remote desktop access. Grant access?');
311 setDefaultCoreTranslation(ret, 'fileConsent', '{0} requesting remote file Access. Grant access?');
312 setDefaultCoreTranslation(ret, 'terminalNotify', '{0} started a remote terminal session.');
313 setDefaultCoreTranslation(ret, 'desktopNotify', '{0} started a remote desktop session.');
314 setDefaultCoreTranslation(ret, 'fileNotify', '{0} started a remote file session.');
315 setDefaultCoreTranslation(ret, 'privacyBar', 'Sharing desktop with: {0}');
316
317 return (ret);
318 }
319 var currentTranslation = getCoreTranslation();
320
321 try {
322 require('kvm-helper');
323 }
324 catch (e) {
325 var j =
326 {
327 users: function () {
328 var r = {};
329 require('user-sessions').Current(function (c) { r = c; });
330 if (process.platform != 'win32') {
331 for (var i in r) {
332 r[i].SessionId = r[i].uid;
333 }
334 }
335 return (r);
336 }
337 };
338 addModuleObject('kvm-helper', j);
339 }
340
341
342 function lockDesktop(uid) {
343 switch (process.platform) {
344 case 'linux':
345 if (uid != null) {
346 var name = require('user-sessions').getUsername(uid);
347 var child = require('child_process').execFile('/bin/sh', ['sh']);
348 child.stdout.str = ''; child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });
349 child.stderr.str = ''; child.stderr.on('data', function (chunk) { this.str += chunk.toString(); });
350 child.stdin.write('loginctl show-user -p Sessions ' + name + " | awk '{");
351 child.stdin.write('gsub(/^Sessions=/,"",$0);');
352 child.stdin.write('cmd = sprintf("loginctl lock-session %s",$0);');
353 child.stdin.write('system(cmd);');
354 child.stdin.write("}'\nexit\n");
355 child.waitExit();
356 }
357 else {
358 var child = require('child_process').execFile('/bin/sh', ['sh']);
359 child.stdout.str = ''; child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });
360 child.stderr.str = ''; child.stderr.on('data', function (chunk) { this.str += chunk.toString(); });
361 child.stdin.write('loginctl lock-sessions\nexit\n');
362 child.waitExit();
363 }
364 break;
365 case 'win32':
366 {
367 var options = { type: 1, uid: uid };
368 var child = require('child_process').execFile(process.env['windir'] + '\\system32\\cmd.exe', ['/c', 'RunDll32.exe user32.dll,LockWorkStation'], options);
369 child.waitExit();
370 }
371 break;
372 default:
373 break;
374 }
375 }
376 var writable = require('stream').Writable;
377 function destopLockHelper_pipe(httprequest) {
378 if (process.platform != 'linux' && process.platform != 'freebsd') { return; }
379
380 if (httprequest.unlockerHelper == null && httprequest.desktop != null && httprequest.desktop.kvm != null) {
381 httprequest.unlockerHelper = new writable(
382 {
383 'write': function (chunk, flush) {
384 if (chunk.readUInt16BE(0) == 65) {
385 delete this.request.autolock;
386 }
387 flush();
388 return (true);
389 },
390 'final': function (flush) {
391 flush();
392 }
393 });
394 httprequest.unlockerHelper.request = httprequest;
395 httprequest.desktop.kvm.pipe(httprequest.unlockerHelper);
396 }
397 }
398
399 var obj = { serverInfo: {} };
400 var agentFileHttpRequests = {}; // Currently active agent HTTPS GET requests from the server.
401 var agentFileHttpPendingRequests = []; // Pending HTTPS GET requests from the server.
402 var debugConsole = (global._MSH && (_MSH().debugConsole == 1));
403
404 var color_options =
405 {
406 background: (global._MSH != null) ? global._MSH().background : '0,54,105',
407 foreground: (global._MSH != null) ? global._MSH().foreground : '255,255,255'
408 };
409
410 if (process.platform == 'win32' && require('user-sessions').isRoot()) {
411 // Check the Agent Uninstall MetaData for correctness, as the installer may have written an incorrect value
412 try {
413 var writtenSize = 0, actualSize = Math.floor(require('fs').statSync(process.execPath).size / 1024);
414 var serviceName = (_MSH().serviceName ? _MSH().serviceName : (require('_agentNodeId').serviceName() ? require('_agentNodeId').serviceName() : 'Mesh Agent'));
415 try { writtenSize = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + serviceName, 'EstimatedSize'); } catch (ex) { }
416 if (writtenSize != actualSize) { try { require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + serviceName, 'EstimatedSize', actualSize); } catch (ex) { } }
417 } catch (ex) { }
418
419 // Check to see if we are the Installed Mesh Agent Service, if we are, make sure we can run in Safe Mode
420 var svcname = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent';
421 try {
422 svcname = require('MeshAgent').serviceName;
423 } catch (ex) { }
424
425 try {
426 var meshCheck = false;
427 try { meshCheck = require('service-manager').manager.getService(svcname).isMe(); } catch (ex) { }
428 if (meshCheck && require('win-bcd').isSafeModeService && !require('win-bcd').isSafeModeService(svcname)) { require('win-bcd').enableSafeModeService(svcname); }
429 } catch (ex) { }
430
431 // Check the Agent Uninstall MetaData for DisplayVersion and update if not the same and only on windows
432 if (process.platform == 'win32') {
433 try {
434 var writtenDisplayVersion = 0, actualDisplayVersion = process.versions.commitDate.toString();
435 var serviceName = (_MSH().serviceName ? _MSH().serviceName : (require('_agentNodeId').serviceName() ? require('_agentNodeId').serviceName() : 'Mesh Agent'));
436 try { writtenDisplayVersion = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + serviceName, 'DisplayVersion'); } catch (ex) { }
437 if (writtenDisplayVersion != actualDisplayVersion) { try { require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + serviceName, 'DisplayVersion', actualDisplayVersion); } catch (ex) { } }
438 } catch (ex) { }
439 }
440 }
441
442 if (process.platform != 'win32') {
443 var ch = require('child_process');
444 ch._execFile = ch.execFile;
445 ch.execFile = function execFile(path, args, options) {
446 if (options && options.type && options.type == ch.SpawnTypes.TERM && options.env) {
447 options.env['TERM'] = 'xterm-256color';
448 }
449 return (this._execFile(path, args, options));
450 };
451 }
452
453
454 if (process.platform == 'darwin' && !process.versions) {
455 // This is an older MacOS Agent, so we'll need to check the service definition so that Auto-Update will function correctly
456 var child = require('child_process').execFile('/bin/sh', ['sh']);
457 child.stdout.str = '';
458 child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });
459 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, \">\"); ");
460 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]; } }");
461 child.stdin.write(" else { split(c[1], ka, \"/\"); if(ka[1]==\"true\") {print \"ALWAYS\";} } }'\nexit\n");
462 child.waitExit();
463 if (child.stdout.str.trim() == 'Crashed') {
464 child = require('child_process').execFile('/bin/sh', ['sh']);
465 child.stdout.str = '';
466 child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });
467 child.stdin.write("launchctl list | grep 'meshagent' | awk '{ if($3==\"meshagent\"){print $1;}}'\nexit\n");
468 child.waitExit();
469
470 if (parseInt(child.stdout.str.trim()) == process.pid) {
471 // The currently running MeshAgent is us, so we can continue with the update
472 var plist = require('fs').readFileSync('/Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist').toString();
473 var tokens = plist.split('<key>KeepAlive</key>');
474 if (tokens[1].split('>')[0].split('<')[1] == 'dict') {
475 var tmp = tokens[1].split('</dict>');
476 tmp.shift();
477 tokens[1] = '\n <true/>' + tmp.join('</dict>');
478 tokens = tokens.join('<key>KeepAlive</key>');
479
480 require('fs').writeFileSync('/Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist', tokens);
481
482 var fix = '';
483 fix += ("function macosRepair()\n");
484 fix += ("{\n");
485 fix += (" var child = require('child_process').execFile('/bin/sh', ['sh']);\n");
486 fix += (" child.stdout.str = '';\n");
487 fix += (" child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });\n");
488 fix += (" child.stderr.on('data', function (chunk) { });\n");
489 fix += (" child.stdin.write('launchctl unload /Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist\\n');\n");
490 fix += (" child.stdin.write('launchctl load /Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist\\n');\n");
491 fix += (" child.stdin.write('rm /Library/LaunchDaemons/meshagentRepair.plist\\n');\n");
492 fix += (" child.stdin.write('rm " + process.cwd() + "/macosRepair.js\\n');\n");
493 fix += (" child.stdin.write('launchctl stop meshagentRepair\\nexit\\n');\n");
494 fix += (" child.waitExit();\n");
495 fix += ("}\n");
496 fix += ("macosRepair();\n");
497 fix += ("process.exit();\n");
498 require('fs').writeFileSync(process.cwd() + '/macosRepair.js', fix);
499
500 var plist = '<?xml version="1.0" encoding="UTF-8"?>\n';
501 plist += '<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n';
502 plist += '<plist version="1.0">\n';
503 plist += ' <dict>\n';
504 plist += ' <key>Label</key>\n';
505 plist += (' <string>meshagentRepair</string>\n');
506 plist += ' <key>ProgramArguments</key>\n';
507 plist += ' <array>\n';
508 plist += (' <string>' + process.execPath + '</string>\n');
509 plist += ' <string>macosRepair.js</string>\n';
510 plist += ' </array>\n';
511 plist += ' <key>WorkingDirectory</key>\n';
512 plist += (' <string>' + process.cwd() + '</string>\n');
513 plist += ' <key>RunAtLoad</key>\n';
514 plist += ' <true/>\n';
515 plist += ' </dict>\n';
516 plist += '</plist>';
517 require('fs').writeFileSync('/Library/LaunchDaemons/meshagentRepair.plist', plist);
518
519 child = require('child_process').execFile('/bin/sh', ['sh']);
520 child.stdout.str = '';
521 child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });
522 child.stdin.write("launchctl load /Library/LaunchDaemons/meshagentRepair.plist\nexit\n");
523 child.waitExit();
524 }
525 }
526 }
527 }
528
529 // Add an Intel AMT event to the log
530 function addAmtEvent(msg) {
531 if (obj.amtevents == null) { obj.amtevents = []; }
532 var d = new Date(), e = zeroPad(d.getHours(), 2) + ':' + zeroPad(d.getMinutes(), 2) + ':' + zeroPad(d.getSeconds(), 2) + ', ' + msg;
533 obj.amtevents.push(e);
534 if (obj.amtevents.length > 100) { obj.amtevents.splice(0, obj.amtevents.length - 100); }
535 if (obj.showamtevent) { require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: e }); }
536 }
537 function zeroPad(num, size) { var s = '000000000' + num; return s.substr(s.length - size); }
538 function trimResults(val) {
539 var i, x;
540 for (i = 0; i < val.length; ++i) {
541 for (x in val[i]) {
542 if (x.startsWith('_')) {
543 delete val[i][x];
544 } else {
545 if (val[i][x] == null || val[i][x] == 0) { delete val[i][x]; }
546 }
547 }
548 }
549 }
550
551
552 // Create Secure IPC for Diagnostic Agent Communications
553 obj.DAIPC = require('net').createServer();
554 if (process.platform != 'win32') { try { require('fs').unlinkSync(process.cwd() + '/DAIPC'); } catch (ex) { } }
555 obj.DAIPC.IPCPATH = process.platform == 'win32' ? ('\\\\.\\pipe\\' + require('_agentNodeId')() + '-DAIPC') : (process.cwd() + '/DAIPC');
556 try { obj.DAIPC.listen({ path: obj.DAIPC.IPCPATH, writableAll: true, maxConnections: 5 }); } catch (ex) { }
557 obj.DAIPC._daipc = [];
558 obj.DAIPC.on('connection', function (c) {
559 c._send = function (j) {
560 var data = JSON.stringify(j);
561 var packet = Buffer.alloc(data.length + 4);
562 packet.writeUInt32LE(data.length + 4, 0);
563 Buffer.from(data).copy(packet, 4);
564 this.write(packet);
565 };
566 this._daipc.push(c);
567 c.parent = this;
568 c.on('end', function () { removeRegisteredApp(this); });
569 c.on('data', function (chunk) {
570 if (chunk.length < 4) { this.unshift(chunk); return; }
571 var len = chunk.readUInt32LE(0);
572 if (len > 8192) { removeRegisteredApp(this); this.end(); return; }
573 if (chunk.length < len) { this.unshift(chunk); return; }
574
575 var data = chunk.slice(4, len);
576 try { data = JSON.parse(data.toString()); } catch (ex) { }
577 if ((data == null) || (typeof data.cmd != 'string')) return;
578
579 try {
580 switch (data.cmd) {
581 case 'requesthelp':
582 if (this._registered == null) return;
583 sendConsoleText('Request Help (' + this._registered + '): ' + data.value);
584 var help = {};
585 help[this._registered] = data.value;
586 try { mesh.SendCommand({ action: 'sessions', type: 'help', value: help }); } catch (ex) { }
587 MeshServerLogEx(98, [this._registered, data.value], "Help Requested, user: " + this._registered + ", details: " + data.value, null);
588 break;
589 case 'cancelhelp':
590 if (this._registered == null) return;
591 sendConsoleText('Cancel Help (' + this._registered + ')');
592 try { mesh.SendCommand({ action: 'sessions', type: 'help', value: {} }); } catch (ex) { }
593 break;
594 case 'register':
595 if (typeof data.value == 'string') {
596 this._registered = data.value;
597 var apps = {};
598 apps[data.value] = 1;
599 try { mesh.SendCommand({ action: 'sessions', type: 'app', value: apps }); } catch (ex) { }
600 this._send({ cmd: 'serverstate', value: meshServerConnectionState, url: require('MeshAgent').ConnectedServer, amt: (amt != null) });
601 }
602 break;
603 case 'query':
604 switch (data.value) {
605 case 'connection':
606 data.result = require('MeshAgent').ConnectedServer;
607 this._send(data);
608 break;
609 case 'descriptors':
610 require('ChainViewer').getSnapshot().then(function (f) {
611 this.tag.payload.result = f;
612 this.tag.ipc._send(this.tag.payload);
613 }).parentPromise.tag = { ipc: this, payload: data };
614 break;
615 case 'timerinfo':
616 data.result = require('ChainViewer').getTimerInfo();
617 this._send(data);
618 break;
619 }
620 break;
621 case 'amtstate':
622 if (amt == null) return;
623 var func = function amtStateFunc(state) { if (state != null) { amtStateFunc.pipe._send({ cmd: 'amtstate', value: state }); } }
624 func.pipe = this;
625 amt.getMeiState(11, func);
626 break;
627 case 'sessions':
628 this._send({ cmd: 'sessions', sessions: tunnelUserCount });
629 break;
630 case 'meshToolInfo':
631 try { mesh.SendCommand({ action: 'meshToolInfo', name: data.name, hash: data.hash, cookie: data.cookie ? true : false, pipe: true }); } catch (ex) { }
632 break;
633 case 'getUserImage':
634 try { mesh.SendCommand({ action: 'getUserImage', userid: data.userid, pipe: true }); } catch (ex) { }
635 break;
636 case 'console':
637 if (debugConsole) {
638 var args = splitArgs(data.value);
639 processConsoleCommand(args[0].toLowerCase(), parseArgs(args), 0, 'pipe');
640 }
641 break;
642 }
643 }
644 catch (ex) { removeRegisteredApp(this); this.end(); return; }
645 });
646 });
647
648 // Send current sessions to registered apps
649 function broadcastSessionsToRegisteredApps(x) {
650 var p = {}, i;
651 for (i = 0; sendAgentMessage.messages != null && i < sendAgentMessage.messages.length; ++i) {
652 p[i] = sendAgentMessage.messages[i];
653 }
654 tunnelUserCount.msg = p;
655 broadcastToRegisteredApps({ cmd: 'sessions', sessions: tunnelUserCount });
656 tunnelUserCount.msg = {};
657 }
658
659 // Send this object to all registered local applications
660 function broadcastToRegisteredApps(x) {
661 if ((obj.DAIPC == null) || (obj.DAIPC._daipc == null)) return;
662 for (var i in obj.DAIPC._daipc) {
663 if (obj.DAIPC._daipc[i]._registered != null) { obj.DAIPC._daipc[i]._send(x); }
664 }
665 }
666
667 // Send this object to a specific registered local applications
668 function sendToRegisteredApp(appid, x) {
669 if ((obj.DAIPC == null) || (obj.DAIPC._daipc == null)) return;
670 for (var i in obj.DAIPC._daipc) { if (obj.DAIPC._daipc[i]._registered == appid) { obj.DAIPC._daipc[i]._send(x); } }
671 }
672
673 // Send list of registered apps to the server
674 function updateRegisteredAppsToServer() {
675 if ((obj.DAIPC == null) || (obj.DAIPC._daipc == null)) return;
676 var apps = {};
677 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]++; } }
678 try { mesh.SendCommand({ action: 'sessions', type: 'app', value: apps }); } catch (ex) { }
679 }
680
681 // Remove a registered app
682 function removeRegisteredApp(pipe) {
683 for (var i = obj.DAIPC._daipc.length - 1; i >= 0; i--) { if (obj.DAIPC._daipc[i] === pipe) { obj.DAIPC._daipc.splice(i, 1); } }
684 if (pipe._registered != null) updateRegisteredAppsToServer();
685 }
686
687 function diagnosticAgent_uninstall() {
688 require('service-manager').manager.uninstallService('meshagentDiagnostic');
689 require('task-scheduler').delete('meshagentDiagnostic/periodicStart'); // TODO: Using "delete" here breaks the minifier since this is a reserved keyword
690 }
691 function diagnosticAgent_installCheck(install) {
692 try {
693 var diag = require('service-manager').manager.getService('meshagentDiagnostic');
694 return (diag);
695 } catch (ex) { }
696 if (!install) { return null; }
697
698 var svc = null;
699 try {
700 require('service-manager').manager.installService(
701 {
702 name: 'meshagentDiagnostic',
703 displayName: "Mesh Agent Diagnostic Service",
704 description: "Mesh Agent Diagnostic Service",
705 servicePath: process.execPath,
706 parameters: ['-recovery']
707 //files: [{ newName: 'diagnostic.js', _buffer: Buffer.from('LyoNCkNvcHlyaWdodCAyMDE5IEludGVsIENvcnBvcmF0aW9uDQoNCkxpY2Vuc2VkIHVuZGVyIHRoZSBBcGFjaGUgTGljZW5zZSwgVmVyc2lvbiAyLjAgKHRoZSAiTGljZW5zZSIpOw0KeW91IG1heSBub3QgdXNlIHRoaXMgZmlsZSBleGNlcHQgaW4gY29tcGxpYW5jZSB3aXRoIHRoZSBMaWNlbnNlLg0KWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0DQoNCiAgICBodHRwOi8vd3d3LmFwYWNoZS5vcmcvbGljZW5zZXMvTElDRU5TRS0yLjANCg0KVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQ0KZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywNCldJVEhPVVQgV0FSUkFOVElFUyBPUiBDT05ESVRJT05TIE9GIEFOWSBLSU5ELCBlaXRoZXIgZXhwcmVzcyBvciBpbXBsaWVkLg0KU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZA0KbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuDQoqLw0KDQp2YXIgaG9zdCA9IHJlcXVpcmUoJ3NlcnZpY2UtaG9zdCcpLmNyZWF0ZSgnbWVzaGFnZW50RGlhZ25vc3RpYycpOw0KdmFyIFJlY292ZXJ5QWdlbnQgPSByZXF1aXJlKCdNZXNoQWdlbnQnKTsNCg0KaG9zdC5vbignc2VydmljZVN0YXJ0JywgZnVuY3Rpb24gKCkNCnsNCiAgICBjb25zb2xlLnNldERlc3RpbmF0aW9uKGNvbnNvbGUuRGVzdGluYXRpb25zLkxPR0ZJTEUpOw0KICAgIGhvc3Quc3RvcCA9IGZ1bmN0aW9uKCkNCiAgICB7DQogICAgICAgIHJlcXVpcmUoJ3NlcnZpY2UtbWFuYWdlcicpLm1hbmFnZXIuZ2V0U2VydmljZSgnbWVzaGFnZW50RGlhZ25vc3RpYycpLnN0b3AoKTsNCiAgICB9DQogICAgUmVjb3ZlcnlBZ2VudC5vbignQ29ubmVjdGVkJywgZnVuY3Rpb24gKHN0YXR1cykNCiAgICB7DQogICAgICAgIGlmIChzdGF0dXMgPT0gMCkNCiAgICAgICAgew0KICAgICAgICAgICAgY29uc29sZS5sb2coJ0RpYWdub3N0aWMgQWdlbnQ6IFNlcnZlciBjb25uZWN0aW9uIGxvc3QuLi4nKTsNCiAgICAgICAgICAgIHJldHVybjsNCiAgICAgICAgfQ0KICAgICAgICBjb25zb2xlLmxvZygnRGlhZ25vc3RpYyBBZ2VudDogQ29ubmVjdGlvbiBFc3RhYmxpc2hlZCB3aXRoIFNlcnZlcicpOw0KICAgICAgICBzdGFydCgpOw0KICAgIH0pOw0KfSk7DQpob3N0Lm9uKCdub3JtYWxTdGFydCcsIGZ1bmN0aW9uICgpDQp7DQogICAgaG9zdC5zdG9wID0gZnVuY3Rpb24gKCkNCiAgICB7DQogICAgICAgIHByb2Nlc3MuZXhpdCgpOw0KICAgIH0NCiAgICBjb25zb2xlLmxvZygnTm9uIFNlcnZpY2UgTW9kZScpOw0KICAgIFJlY292ZXJ5QWdlbnQub24oJ0Nvbm5lY3RlZCcsIGZ1bmN0aW9uIChzdGF0dXMpDQogICAgew0KICAgICAgICBpZiAoc3RhdHVzID09IDApDQogICAgICAgIHsNCiAgICAgICAgICAgIGNvbnNvbGUubG9nKCdEaWFnbm9zdGljIEFnZW50OiBTZXJ2ZXIgY29ubmVjdGlvbiBsb3N0Li4uJyk7DQogICAgICAgICAgICByZXR1cm47DQogICAgICAgIH0NCiAgICAgICAgY29uc29sZS5sb2coJ0RpYWdub3N0aWMgQWdlbnQ6IENvbm5lY3Rpb24gRXN0YWJsaXNoZWQgd2l0aCBTZXJ2ZXInKTsNCiAgICAgICAgc3RhcnQoKTsNCiAgICB9KTsNCn0pOw0KaG9zdC5vbignc2VydmljZVN0b3AnLCBmdW5jdGlvbiAoKSB7IHByb2Nlc3MuZXhpdCgpOyB9KTsNCmhvc3QucnVuKCk7DQoNCg0KZnVuY3Rpb24gc3RhcnQoKQ0Kew0KDQp9Ow0K', 'base64') }]
708 });
709 svc = require('service-manager').manager.getService('meshagentDiagnostic');
710 }
711 catch (ex) { return null; }
712 var proxyConfig = require('global-tunnel').proxyConfig;
713 var cert = require('MeshAgent').GenerateAgentCertificate('CN=MeshNodeDiagnosticCertificate');
714 var nodeid = require('tls').loadCertificate(cert.root).getKeyHash().toString('base64');
715 ddb = require('SimpleDataStore').Create(svc.appWorkingDirectory().replace('\\', '/') + '/meshagentDiagnostic.db');
716 ddb.Put('disableUpdate', '1');
717 ddb.Put('MeshID', Buffer.from(require('MeshAgent').ServerInfo.MeshID, 'hex'));
718 ddb.Put('ServerID', require('MeshAgent').ServerInfo.ServerID);
719 ddb.Put('MeshServer', require('MeshAgent').ServerInfo.ServerUri);
720 if (cert.root.pfx) { ddb.Put('SelfNodeCert', cert.root.pfx); }
721 if (cert.tls) { ddb.Put('SelfNodeTlsCert', cert.tls.pfx); }
722 if (proxyConfig) {
723 ddb.Put('WebProxy', proxyConfig.host + ':' + proxyConfig.port);
724 } else {
725 ddb.Put('ignoreProxyFile', '1');
726 }
727
728 require('MeshAgent').SendCommand({ action: 'diagnostic', value: { command: 'register', value: nodeid } });
729 require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: "Diagnostic Agent Registered [" + nodeid.length + "/" + nodeid + "]" });
730
731 delete ddb;
732
733 // Set a recurrent task, to run the Diagnostic Agent every 2 days
734 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' });
735 //require('task-scheduler').create({ name: 'meshagentDiagnostic/periodicStart', daily: '1', time: '17:16', service: 'meshagentDiagnostic' });
736
737 return (svc);
738 }
739
740 // Monitor the file 'batterystate.txt' in the agent's folder and sends battery update when this file is changed.
741 if ((require('fs').existsSync(process.cwd() + 'batterystate.txt')) && (require('fs').watch != null)) {
742 // Setup manual battery monitoring
743 require('MeshAgent')._batteryFileWatcher = require('fs').watch(process.cwd(), function () {
744 if (require('MeshAgent')._batteryFileTimer != null) return;
745 require('MeshAgent')._batteryFileTimer = setTimeout(function () {
746 try {
747 require('MeshAgent')._batteryFileTimer = null;
748 var data = null;
749 try { data = require('fs').readFileSync(process.cwd() + 'batterystate.txt').toString(); } catch (ex) { }
750 if ((data != null) && (data.length < 10)) {
751 data = data.split(',');
752 if ((data.length == 2) && ((data[0] == 'ac') || (data[0] == 'dc'))) {
753 var level = parseInt(data[1]);
754 if ((level >= 0) && (level <= 100)) { require('MeshAgent').SendCommand({ action: 'battery', state: data[0], level: level }); }
755 }
756 }
757 } catch (ex) { }
758 }, 1000);
759 });
760 }
761 else {
762 try {
763 // Setup normal battery monitoring
764 if (require('computer-identifiers').isBatteryPowered && require('computer-identifiers').isBatteryPowered()) {
765 require('MeshAgent')._battLevelChanged = function _battLevelChanged(val) {
766 _battLevelChanged.self._currentBatteryLevel = val;
767 _battLevelChanged.self.SendCommand({ action: 'battery', state: _battLevelChanged.self._currentPowerState, level: val });
768 };
769 require('MeshAgent')._battLevelChanged.self = require('MeshAgent');
770 require('MeshAgent')._powerChanged = function _powerChanged(val) {
771 _powerChanged.self._currentPowerState = (val == 'AC' ? 'ac' : 'dc');
772 _powerChanged.self.SendCommand({ action: 'battery', state: (val == 'AC' ? 'ac' : 'dc'), level: _powerChanged.self._currentBatteryLevel });
773 };
774 require('MeshAgent')._powerChanged.self = require('MeshAgent');
775 require('MeshAgent').on('Connected', function (status) {
776 if (status == 0) {
777 require('power-monitor').removeListener('acdc', this._powerChanged);
778 require('power-monitor').removeListener('batteryLevel', this._battLevelChanged);
779 } else {
780 require('power-monitor').on('acdc', this._powerChanged);
781 require('power-monitor').on('batteryLevel', this._battLevelChanged);
782 }
783 });
784 }
785 }
786 catch (ex) { }
787 }
788
789
790 // MeshAgent JavaScript Core Module. This code is sent to and running on the mesh agent.
791 var 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
792
793 // Get the operating system description string
794 try { require('os').name().then(function (v) { meshCoreObj.osdesc = v; meshCoreObjChanged(); }); } catch (ex) { }
795
796 // Setup logged in user monitoring (THIS IS BROKEN IN WIN7)
797 function onUserSessionChanged(user, locked) {
798 userSession.enumerateUsers().then(function (users) {
799 if (process.platform == 'linux') {
800 if (userSession._startTime == null) {
801 userSession._startTime = Date.now();
802 userSession._count = users.length;
803 }
804 else if (Date.now() - userSession._startTime < 10000 && users.length == userSession._count) {
805 userSession.removeAllListeners('changed');
806 return;
807 }
808 }
809
810 var u = [], a = users.Active;
811 if(meshCoreObj.lusers == null) { meshCoreObj.lusers = []; }
812 if(meshCoreObj.upnusers == null) { meshCoreObj.upnusers = []; }
813 var ret = getDomainInfo();
814 for (var i = 0; i < a.length; i++) {
815 var un = a[i].Domain ? (a[i].Domain + '\\' + a[i].Username) : (a[i].Username);
816 if (user && locked && (JSON.stringify(a[i]) === JSON.stringify(user))) { if (meshCoreObj.lusers.indexOf(un) == -1) { meshCoreObj.lusers.push(un); } }
817 else if (user && !locked && (JSON.stringify(a[i]) === JSON.stringify(user))) { meshCoreObj.lusers.splice(meshCoreObj.lusers.indexOf(un), 1); }
818 if (u.indexOf(un) == -1) { u.push(un); } // Only push users in the list once.
819 if ((a[i].Domain != null && a[i].Domain == 'AzureAD') || getJoinState() == 1 ){
820 var userobj = getLogonCacheKeys();
821 if(userobj && userobj.length > 0){
822 for (var j = 0; j < userobj.length; j++) {
823 if (userobj[j] && userobj[j].SAM && userobj[j].SAM[0].trim() === a[i].Username) {
824 meshCoreObj.upnusers.push(userobj[j].UPN);
825 break;
826 }
827 }
828 }
829 } else if (a[i].Domain != null) {
830 if (ret != null && ret.PartOfDomain === true) {
831 var loggedOnUser = getLoggedOnUserBySessionId(a[i].SessionId);
832 if(loggedOnUser == null || (/^[^@]+@[^@]+\.[^@]+$/.test(loggedOnUser) == false)){
833 loggedOnUser = a[i].Username + '@' + ret.Domain;
834 }
835 meshCoreObj.upnusers.push(loggedOnUser);
836 } else if (getJoinState() == 4) { // One account with Microsoft Account
837 var userobj = getLogonCacheKeys();
838 if(userobj && userobj.length > 0){
839 for (var j = 0; j < userobj.length; j++) {
840 if (userobj[j] && userobj[j].SAM && userobj[j].SAM.length == 0 && userobj[j].UPN && userobj[j].UPN != '') {
841 meshCoreObj.upnusers.push(userobj[j].UPN);
842 break;
843 }
844 }
845 }
846 }
847 }
848 }
849 meshCoreObj.lusers = meshCoreObj.lusers;
850 meshCoreObj.users = u;
851 meshCoreObjChanged();
852 });
853 }
854
855 try {
856 var userSession = require('user-sessions');
857 userSession.on('changed', function () { onUserSessionChanged(null, false); });
858 userSession.emit('changed');
859 userSession.on('locked', function (user) { if(user != undefined && user != null) { onUserSessionChanged(user, true); } });
860 userSession.on('unlocked', function (user) { if(user != undefined && user != null) { onUserSessionChanged(user, false); } });
861 } catch (ex) { }
862
863 var meshServerConnectionState = 0;
864 var tunnels = {};
865 var lastNetworkInfo = null;
866 var lastPublicLocationInfo = null;
867 var selfInfoUpdateTimer = null;
868 var http = require('http');
869 var net = require('net');
870 var fs = require('fs');
871 var rtc = require('ILibWebRTC');
872 var amt = null;
873 var processManager = require('process-manager');
874 var wifiScannerLib = null;
875 var wifiScanner = null;
876 var networkMonitor = null;
877 var nextTunnelIndex = 1;
878 var apftunnel = null;
879 var tunnelUserCount = { terminal: {}, files: {}, tcp: {}, udp: {}, msg: {} }; // List of userid->count sessions for terminal, files and TCP/UDP routing
880
881 // Add to the server event log
882 function MeshServerLog(msg, state) {
883 if (typeof msg == 'string') { msg = { action: 'log', msg: msg }; } else { msg.action = 'log'; }
884 if (state) {
885 if (state.userid) { msg.userid = state.userid; }
886 if (state.username) { msg.username = state.username; }
887 if (state.sessionid) { msg.sessionid = state.sessionid; }
888 if (state.remoteaddr) { msg.remoteaddr = state.remoteaddr; }
889 if (state.guestname) { msg.guestname = state.guestname; }
890 }
891 mesh.SendCommand(msg);
892 }
893
894 // Add to the server event log, use internationalized events
895 function MeshServerLogEx(id, args, msg, state) {
896 var msg = { action: 'log', msgid: id, msgArgs: args, msg: msg };
897 if (state) {
898 if (state.userid) { msg.userid = state.userid; }
899 if (state.xuserid) { msg.xuserid = state.xuserid; }
900 if (state.username) { msg.username = state.username; }
901 if (state.sessionid) { msg.sessionid = state.sessionid; }
902 if (state.remoteaddr) { msg.remoteaddr = state.remoteaddr; }
903 if (state.guestname) { msg.guestname = state.guestname; }
904 }
905 mesh.SendCommand(msg);
906 }
907
908 // Import libraries
909 db = require('SimpleDataStore').Shared();
910 sha = require('SHA256Stream');
911 mesh = require('MeshAgent');
912 childProcess = require('child_process');
913
914 if (mesh.hasKVM == 1) { // if the agent is compiled with KVM support
915 // Check if this computer supports a desktop
916 try {
917 if ((process.platform == 'win32') || (process.platform == 'darwin') || (require('monitor-info').kvm_x11_support)) {
918 meshCoreObj.caps |= 1; meshCoreObjChanged();
919 } else if (process.platform == 'linux' || process.platform == 'freebsd') {
920 require('monitor-info').on('kvmSupportDetected', function (value) { meshCoreObj.caps |= 1; meshCoreObjChanged(); });
921 }
922 } catch (ex) { }
923 }
924 mesh.DAIPC = obj.DAIPC;
925
926 /*
927 // Try to load up the network monitor
928 try {
929 networkMonitor = require('NetworkMonitor');
930 networkMonitor.on('change', function () { sendNetworkUpdateNagle(); });
931 networkMonitor.on('add', function (addr) { sendNetworkUpdateNagle(); });
932 networkMonitor.on('remove', function (addr) { sendNetworkUpdateNagle(); });
933 } catch (ex) { networkMonitor = null; }
934 */
935
936 // Fetch the SMBios Tables
937 var SMBiosTables = null;
938 var SMBiosTablesRaw = null;
939 try {
940 var SMBiosModule = null;
941 try { SMBiosModule = require('smbios'); } catch (ex) { }
942 if (SMBiosModule != null) {
943 SMBiosModule.get(function (data) {
944 if (data != null) {
945 SMBiosTablesRaw = data;
946 SMBiosTables = require('smbios').parse(data)
947 if (mesh.isControlChannelConnected) { mesh.SendCommand({ action: 'smbios', value: SMBiosTablesRaw }); }
948
949 // If SMBios tables say that Intel AMT is present, try to connect MEI
950 if (SMBiosTables.amtInfo && (SMBiosTables.amtInfo.AMT == true)) {
951 var amtmodule = require('amt-manage');
952 amt = new amtmodule(mesh, db, false);
953 amt.on('portBinding_LMS', function (map) { mesh.SendCommand({ action: 'lmsinfo', value: { ports: map.keys() } }); });
954 amt.on('stateChange_LMS', function (v) { if (!meshCoreObj.intelamt) { meshCoreObj.intelamt = {}; } meshCoreObj.intelamt.microlms = v; meshCoreObjChanged(); }); // 0 = Disabled, 1 = Connecting, 2 = Connected
955 amt.onStateChange = function (state) { if (state == 2) { sendPeriodicServerUpdate(1); } } // MEI State
956 amt.reset();
957 }
958 }
959 });
960 }
961 } catch (ex) { sendConsoleText("ex1: " + ex); }
962
963 // Try to load up the WIFI scanner
964 try {
965 var wifiScannerLib = require('wifi-scanner');
966 wifiScanner = new wifiScannerLib();
967 wifiScanner.on('accessPoint', function (data) { sendConsoleText("wifiScanner: " + data); });
968 } catch (ex) { wifiScannerLib = null; wifiScanner = null; }
969
970 // Get our location (lat/long) using our public IP address
971 var getIpLocationDataExInProgress = false;
972 var getIpLocationDataExCounts = [0, 0];
973 function getIpLocationDataEx(func) {
974 if (getIpLocationDataExInProgress == true) { return false; }
975 getIpLocationDataExInProgress = true;
976 getIpLocationDataExCounts[0]++;
977
978 function tryEndpoint(url, fallback) {
979 var options = http.parseUri(url);
980 options.method = 'GET';
981 http.request(options, function (resp) {
982 var geoData = '';
983 resp.data = function (chunk) { geoData += chunk; };
984 resp.end = function () {
985 try {
986 var result = JSON.parse(geoData);
987 if (result.ip && result.loc) {
988 getIpLocationDataExInProgress = false;
989 getIpLocationDataExCounts[1]++;
990 func(result);
991 return;
992 }
993 } catch (ex) { }
994 if (fallback) { fallback(); } else { done(null); }
995 };
996 if (resp.statusCode != 200) { if (fallback) { fallback(); } else { done(null); } }
997 }).on('error', function () {
998 if (fallback) { fallback(); } else { done(null); }
999 }).end();
1000 }
1001
1002 function done(result) {
1003 getIpLocationDataExInProgress = false;
1004 if (func) { func(result); }
1005 }
1006
1007 tryEndpoint('http://v6.ipinfo.io/json', function () {
1008 tryEndpoint('http://ipinfo.io/json', null);
1009 });
1010
1011 return true;
1012 }
1013
1014 // Remove all Gateway MAC addresses for interface list. This is useful because the gateway MAC is not always populated reliably.
1015 function clearGatewayMac(str) {
1016 if (typeof str != 'string') return null;
1017 var x = JSON.parse(str);
1018 for (var i in x.netif) { try { if (x.netif[i].gatewaymac) { delete x.netif[i].gatewaymac } } catch (ex) { } }
1019 return JSON.stringify(x);
1020 }
1021
1022 function getIpLocationData(func) {
1023 // Get the location information for the cache if possible
1024 var publicLocationInfo = db.Get('publicLocationInfo');
1025 if (publicLocationInfo != null) { publicLocationInfo = JSON.parse(publicLocationInfo); }
1026 if (publicLocationInfo == null) {
1027 // Nothing in the cache, fetch the data
1028 getIpLocationDataEx(function (locationData) {
1029 if (locationData != null) {
1030 publicLocationInfo = {};
1031 publicLocationInfo.netInfoStr = lastNetworkInfo;
1032 publicLocationInfo.locationData = locationData;
1033 var x = db.Put('publicLocationInfo', JSON.stringify(publicLocationInfo)); // Save to database
1034 if (func) func(locationData); // Report the new location
1035 }
1036 else {
1037 if (func) func(null); // Report no location
1038 }
1039 });
1040 }
1041 else {
1042 // Check the cache
1043 if (clearGatewayMac(publicLocationInfo.netInfoStr) == clearGatewayMac(lastNetworkInfo)) {
1044 // Cache match
1045 if (func) func(publicLocationInfo.locationData);
1046 }
1047 else {
1048 // Cache mismatch
1049 getIpLocationDataEx(function (locationData) {
1050 if (locationData != null) {
1051 publicLocationInfo = {};
1052 publicLocationInfo.netInfoStr = lastNetworkInfo;
1053 publicLocationInfo.locationData = locationData;
1054 var x = db.Put('publicLocationInfo', JSON.stringify(publicLocationInfo)); // Save to database
1055 if (func) func(locationData); // Report the new location
1056 }
1057 else {
1058 if (func) func(publicLocationInfo.locationData); // Can't get new location, report the old location
1059 }
1060 });
1061 }
1062 }
1063 }
1064
1065 // Polyfill String.endsWith
1066 if (!String.prototype.endsWith) {
1067 String.prototype.endsWith = function (searchString, position) {
1068 var subjectString = this.toString();
1069 if (typeof position !== 'number' || !isFinite(position) || Math.floor(position) !== position || position > subjectString.length) { position = subjectString.length; }
1070 position -= searchString.length;
1071 var lastIndex = subjectString.lastIndexOf(searchString, position);
1072 return lastIndex !== -1 && lastIndex === position;
1073 };
1074 }
1075
1076 // Polyfill path.join
1077 obj.path =
1078 {
1079 join: function () {
1080 var x = [];
1081 for (var i in arguments) {
1082 var w = arguments[i];
1083 if (w != null) {
1084 while (w.endsWith('/') || w.endsWith('\\')) { w = w.substring(0, w.length - 1); }
1085 if (i != 0) {
1086 while (w.startsWith('/') || w.startsWith('\\')) { w = w.substring(1); }
1087 }
1088 x.push(w);
1089 }
1090 }
1091 if (x.length == 0) return '/';
1092 return x.join('/');
1093 }
1094 };
1095
1096 // Replace a string with a number if the string is an exact number
1097 function toNumberIfNumber(x) { if ((typeof x == 'string') && (+parseInt(x) === x)) { x = parseInt(x); } return x; }
1098
1099 // Convert decimal to hex
1100 function char2hex(i) { return (i + 0x100).toString(16).substr(-2).toUpperCase(); }
1101
1102 // Convert a raw string to a hex string
1103 function rstr2hex(input) { var r = '', i; for (i = 0; i < input.length; i++) { r += char2hex(input.charCodeAt(i)); } return r; }
1104
1105 // Convert a buffer into a string
1106 function buf2rstr(buf) { var r = ''; for (var i = 0; i < buf.length; i++) { r += String.fromCharCode(buf[i]); } return r; }
1107
1108 // Convert a hex string to a raw string // TODO: Do this using Buffer(), will be MUCH faster
1109 function hex2rstr(d) {
1110 if (typeof d != "string" || d.length == 0) return '';
1111 var r = '', m = ('' + d).match(/../g), t;
1112 while (t = m.shift()) r += String.fromCharCode('0x' + t);
1113 return r
1114 }
1115
1116 // Convert an object to string with all functions
1117 function objToString(x, p, pad, ret) {
1118 if (ret == undefined) ret = '';
1119 if (p == undefined) p = 0;
1120 if (x == null) { return '[null]'; }
1121 if (p > 8) { return '[...]'; }
1122 if (x == undefined) { return '[undefined]'; }
1123 if (typeof x == 'string') { if (p == 0) return x; return '"' + x + '"'; }
1124 if (typeof x == 'buffer') { return '[buffer]'; }
1125 if (typeof x != 'object') { return x; }
1126 var r = '{' + (ret ? '\r\n' : ' ');
1127 for (var i in x) { if (i != '_ObjectID') { r += (addPad(p + 2, pad) + i + ': ' + objToString(x[i], p + 2, pad, ret) + (ret ? '\r\n' : ' ')); } }
1128 return r + addPad(p, pad) + '}';
1129 }
1130
1131 // Return p number of spaces
1132 function addPad(p, ret) { var r = ''; for (var i = 0; i < p; i++) { r += ret; } return r; }
1133
1134 // Split a string taking into account the quoats. Used for command line parsing
1135 function splitArgs(str) {
1136 var myArray = [], myRegexp = /[^\s"]+|"([^"]*)"/gi;
1137 do { var match = myRegexp.exec(str); if (match != null) { myArray.push(match[1] ? match[1] : match[0]); } } while (match != null);
1138 return myArray;
1139 }
1140
1141 // Parse arguments string array into an object
1142 function parseArgs(argv) {
1143 var results = { '_': [] }, current = null;
1144 for (var i = 1, len = argv.length; i < len; i++) {
1145 var x = argv[i];
1146 if (x.length > 2 && x[0] == '-' && x[1] == '-') {
1147 if (current != null) { results[current] = true; }
1148 current = x.substring(2);
1149 } else {
1150 if (current != null) { results[current] = toNumberIfNumber(x); current = null; } else { results['_'].push(toNumberIfNumber(x)); }
1151 }
1152 }
1153 if (current != null) { results[current] = true; }
1154 return results;
1155 }
1156
1157 // Get server target url with a custom path
1158 function getServerTargetUrl(path) {
1159 var x = mesh.ServerUrl;
1160 //sendConsoleText("mesh.ServerUrl: " + mesh.ServerUrl);
1161 if (x == null) { return null; }
1162 if (path == null) { path = ''; }
1163 x = http.parseUri(x);
1164 if (x == null) return null;
1165 return x.protocol + '//' + x.host + ':' + x.port + '/' + path;
1166 }
1167
1168 // Get server url. If the url starts with "*/..." change it, it not use the url as is.
1169 function getServerTargetUrlEx(url) {
1170 if (url.substring(0, 2) == '*/') { return getServerTargetUrl(url.substring(2)); }
1171 return url;
1172 }
1173
1174 function sendWakeOnLanEx_interval() {
1175 var t = require('MeshAgent').wakesockets;
1176 if (t.list.length == 0) {
1177 clearInterval(t);
1178 delete require('MeshAgent').wakesockets;
1179 return;
1180 }
1181
1182 var mac = t.list.shift().split(':').join('')
1183 var magic = 'FFFFFFFFFFFF';
1184 for (var x = 1; x <= 16; ++x) { magic += mac; }
1185 var magicbin = Buffer.from(magic, 'hex');
1186
1187 for (var i in t.sockets) {
1188 t.sockets[i].send(magicbin, 7, '255.255.255.255');
1189 //sendConsoleText('Sending wake packet on ' + JSON.stringify(t.sockets[i].address()));
1190 }
1191 }
1192 function sendWakeOnLanEx(hexMacList) {
1193 var ret = 0;
1194
1195 if (require('MeshAgent').wakesockets == null) {
1196 // Create a new interval timer
1197 require('MeshAgent').wakesockets = setInterval(sendWakeOnLanEx_interval, 10);
1198 require('MeshAgent').wakesockets.sockets = [];
1199 require('MeshAgent').wakesockets.list = hexMacList;
1200
1201 var interfaces = require('os').networkInterfaces();
1202 for (var adapter in interfaces) {
1203 if (interfaces.hasOwnProperty(adapter)) {
1204 for (var i = 0; i < interfaces[adapter].length; ++i) {
1205 var addr = interfaces[adapter][i];
1206 if ((addr.family == 'IPv4') && (addr.mac != '00:00:00:00:00:00')) {
1207 try {
1208 var socket = require('dgram').createSocket({ type: 'udp4' });
1209 socket.bind({ address: addr.address });
1210 socket.setBroadcast(true);
1211 socket.setMulticastInterface(addr.address);
1212 socket.setMulticastTTL(1);
1213 socket.descriptorMetadata = 'WoL (' + addr.address + ')';
1214 require('MeshAgent').wakesockets.sockets.push(socket);
1215 ++ret;
1216 }
1217 catch (ex) { }
1218 }
1219 }
1220 }
1221 }
1222 }
1223 else {
1224 // Append to an existing interval timer
1225 for (var i in hexMacList) {
1226 require('MeshAgent').wakesockets.list.push(hexMacList[i]);
1227 }
1228 ret = require('MeshAgent').wakesockets.sockets.length;
1229 }
1230
1231 return ret;
1232 }
1233
1234 function server_promise_default(res, rej) {
1235 this.resolve = res;
1236 this.reject = rej;
1237 }
1238 function server_getUserImage(userid) {
1239 var xpromise = require('promise');
1240 var ret = new xpromise(server_promise_default);
1241
1242 if (require('MeshAgent')._promises == null) { require('MeshAgent')._promises = {}; }
1243 require('MeshAgent')._promises[ret._hashCode()] = ret;
1244 require('MeshAgent').SendCommand({ action: 'getUserImage', userid: userid, promise: ret._hashCode(), sentDefault: true });
1245 return ret;
1246 }
1247 require('MeshAgent')._consentTimers = {};
1248 function server_set_consentTimer(id) {
1249 require('MeshAgent')._consentTimers[id] = new Date();
1250 }
1251 function server_check_consentTimer(id) {
1252 if (require('MeshAgent')._consentTimers[id] != null) {
1253 if ((new Date()) - require('MeshAgent')._consentTimers[id] < (60000 * 5)) return true;
1254 require('MeshAgent')._consentTimers[id] = null;
1255 }
1256 return false;
1257 }
1258
1259 function tunnel_finalized()
1260 {
1261 console.info1('Tunnel Request Finalized');
1262 }
1263 function tunnel_checkServerIdentity(certs)
1264 {
1265 /*
1266 try { sendConsoleText("certs[0].digest: " + certs[0].digest); } catch (ex) { sendConsoleText(ex); }
1267 try { sendConsoleText("certs[0].fingerprint: " + certs[0].fingerprint); } catch (ex) { sendConsoleText(ex); }
1268 try { sendConsoleText("control-digest: " + require('MeshAgent').ServerInfo.ControlChannelCertificate.digest); } catch (ex) { sendConsoleText(ex); }
1269 try { sendConsoleText("control-fingerprint: " + require('MeshAgent').ServerInfo.ControlChannelCertificate.fingerprint); } catch (ex) { sendConsoleText(ex); }
1270 */
1271
1272 // Check if this is an old agent, no certificate checks are possible in this situation. Display a warning.
1273 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; }
1274
1275 // If the tunnel certificate matches the control channel certificate, accept the connection
1276 if (require('MeshAgent').ServerInfo.ControlChannelCertificate.digest == certs[0].digest) return; // Control channel certificate matches using full cert hash
1277 if ((certs[0].fingerprint != null) && (require('MeshAgent').ServerInfo.ControlChannelCertificate.fingerprint == certs[0].fingerprint)) return; // Control channel certificate matches using public key hash
1278
1279 // Check that the certificate is the one expected by the server, fail if not.
1280 if ((tunnel_checkServerIdentity.servertlshash != null) && (tunnel_checkServerIdentity.servertlshash.toLowerCase() != certs[0].digest.split(':').join('').toLowerCase())) { throw new Error('BadCert') }
1281 }
1282
1283 function tunnel_onError()
1284 {
1285 sendConsoleText("ERROR: Unable to connect relay tunnel to: " + this.url + ", " + JSON.stringify(e));
1286 }
1287
1288 // Handle a mesh agent command
1289 function handleServerCommand(data) {
1290 if (typeof data == 'object') {
1291 // If this is a console command, parse it and call the console handler
1292 switch (data.action) {
1293 case 'agentupdate':
1294 agentUpdate_Start(data.url, { hash: data.hash, tlshash: data.servertlshash, sessionid: data.sessionid });
1295 break;
1296 case 'msg': {
1297 switch (data.type) {
1298 case 'console': { // Process a console command
1299 if ((typeof data.rights != 'number') || ((data.rights & 8) == 0) || ((data.rights & 16) == 0)) break; // Check console rights (Remote Control and Console)
1300 if (data.value && data.sessionid) {
1301 MeshServerLogEx(17, [data.value], "Processing console command: " + data.value, data);
1302 var args = splitArgs(data.value);
1303 processConsoleCommand(args[0].toLowerCase(), parseArgs(args), data.rights, data.sessionid);
1304 }
1305 break;
1306 }
1307 case 'tunnel':
1308 {
1309 if (data.value != null) { // Process a new tunnel connection request
1310 // Create a new tunnel object
1311 var xurl = getServerTargetUrlEx(data.value);
1312 if (xurl != null) {
1313 xurl = xurl.split('$').join('%24').split('@').join('%40'); // Escape the $ and @ characters
1314 var woptions = http.parseUri(xurl);
1315 woptions.perMessageDeflate = false;
1316 if (typeof data.perMessageDeflate == 'boolean') { woptions.perMessageDeflate = data.perMessageDeflate; }
1317
1318 // Perform manual server TLS certificate checking based on the certificate hash given by the server.
1319 woptions.rejectUnauthorized = 0;
1320 woptions.checkServerIdentity = tunnel_checkServerIdentity;
1321 woptions.checkServerIdentity.servertlshash = data.servertlshash;
1322
1323 //sendConsoleText(JSON.stringify(woptions));
1324 //sendConsoleText('TUNNEL: ' + JSON.stringify(data, null, 2));
1325
1326 var tunnel = http.request(woptions);
1327 tunnel.upgrade = onTunnelUpgrade;
1328 tunnel.on('error', tunnel_onError);
1329 tunnel.sessionid = data.sessionid;
1330 tunnel.rights = data.rights;
1331 tunnel.consent = data.consent;
1332 if (global._MSH && _MSH().LocalConsent != null) { tunnel.consent |= parseInt(_MSH().LocalConsent); }
1333 tunnel.privacybartext = data.privacybartext ? data.privacybartext : currentTranslation['privacyBar'];
1334 tunnel.username = data.username + (data.guestname ? (' - ' + data.guestname) : '');
1335 tunnel.realname = (data.realname ? data.realname : data.username) + (data.guestname ? (' - ' + data.guestname) : '');
1336 tunnel.guestuserid = data.guestuserid;
1337 tunnel.guestname = data.guestname;
1338 tunnel.userid = data.userid;
1339 if (server_check_consentTimer(tunnel.userid)) { tunnel.consent = (tunnel.consent & -57); } // Deleting Consent Requirement
1340 tunnel.desktopviewonly = data.desktopviewonly;
1341 tunnel.remoteaddr = data.remoteaddr;
1342 tunnel.state = 0;
1343 tunnel.url = xurl;
1344 tunnel.protocol = 0;
1345 tunnel.soptions = data.soptions;
1346 tunnel.consentTimeout = (tunnel.soptions && tunnel.soptions.consentTimeout) ? tunnel.soptions.consentTimeout : 30;
1347 tunnel.consentAutoAccept = (tunnel.soptions && (tunnel.soptions.consentAutoAccept === true));
1348 tunnel.consentAutoAcceptIfNoUser = (tunnel.soptions && (tunnel.soptions.consentAutoAcceptIfNoUser === true));
1349 tunnel.consentAutoAcceptIfDesktopNoUser = (tunnel.soptions && (tunnel.soptions.consentAutoAcceptIfDesktopNoUser === true));
1350 tunnel.consentAutoAcceptIfTerminalNoUser = (tunnel.soptions && (tunnel.soptions.consentAutoAcceptIfTerminalNoUser === true));
1351 tunnel.consentAutoAcceptIfFileNoUser = (tunnel.soptions && (tunnel.soptions.consentAutoAcceptIfFileNoUser === true));
1352 tunnel.consentAutoAcceptIfLocked = (tunnel.soptions && (tunnel.soptions.consentAutoAcceptIfLocked === true));
1353 tunnel.consentAutoAcceptIfDesktopLocked = (tunnel.soptions && (tunnel.soptions.consentAutoAcceptIfDesktopLocked === true));
1354 tunnel.consentAutoAcceptIfTerminalLocked = (tunnel.soptions && (tunnel.soptions.consentAutoAcceptIfTerminalLocked === true));
1355 tunnel.consentAutoAcceptIfFileLocked = (tunnel.soptions && (tunnel.soptions.consentAutoAcceptIfFileLocked === true));
1356 tunnel.oldStyle = (tunnel.soptions && tunnel.soptions.oldStyle) ? tunnel.soptions.oldStyle : false;
1357 tunnel.terminalUserVariable = (tunnel.soptions && tunnel.soptions.terminalUserVariable) ? tunnel.soptions.terminalUserVariable : false;
1358 tunnel.tcpaddr = data.tcpaddr;
1359 tunnel.tcpport = data.tcpport;
1360 tunnel.udpaddr = data.udpaddr;
1361 tunnel.udpport = data.udpport;
1362
1363 // Put the tunnel in the tunnels list
1364 var index = nextTunnelIndex++;
1365 tunnel.index = index;
1366 tunnels[index] = tunnel;
1367 tunnel.once('~', tunnel_finalized);
1368 tunnel.end();
1369
1370 //sendConsoleText('New tunnel connection #' + index + ': ' + tunnel.url + ', rights: ' + tunnel.rights, data.sessionid);
1371 }
1372 }
1373 break;
1374 }
1375 case 'endtunnel': {
1376 // Terminate one or more tunnels
1377 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
1378 for (var i in tunnels) {
1379 if ((tunnels[i].userid == data.xuserid) && (tunnels[i].guestname == data.guestname)) {
1380 var disconnect = false, msgid = 0;
1381 if ((data.protocol == 'kvm') && (tunnels[i].protocol == 2)) { msgid = 134; disconnect = true; }
1382 else if ((data.protocol == 'terminal') && (tunnels[i].protocol == 1)) { msgid = 135; disconnect = true; }
1383 else if ((data.protocol == 'files') && (tunnels[i].protocol == 5)) { msgid = 136; disconnect = true; }
1384 else if ((data.protocol == 'tcp') && (tunnels[i].tcpport != null)) { msgid = 137; disconnect = true; }
1385 else if ((data.protocol == 'udp') && (tunnels[i].udpport != null)) { msgid = 137; disconnect = true; }
1386 if (disconnect) {
1387 if (tunnels[i].s != null) { tunnels[i].s.end(); } else { tunnels[i].end(); }
1388
1389 // Log tunnel disconnection
1390 var xusername = data.xuserid.split('/')[2];
1391 if (data.guestname != null) { xusername += '/' + guestname; }
1392 MeshServerLogEx(msgid, [xusername], "Forcibly disconnected session of user: " + xusername, data);
1393 }
1394 }
1395 }
1396 break;
1397 }
1398 case 'messagebox': {
1399 // Display a message box
1400 if (data.title && data.msg) {
1401 MeshServerLogEx(18, [data.title, data.msg], "Displaying message box, title=" + data.title + ", message=" + data.msg, data);
1402 if (process.platform == 'win32') {
1403 if (global._clientmessage) {
1404 global._clientmessage.addMessage(data.msg);
1405 }
1406 else {
1407 try {
1408 require('win-dialog');
1409 var ipr = server_getUserImage(data.userid);
1410 ipr.title = data.title;
1411 ipr.message = data.msg;
1412 ipr.username = data.username;
1413 if (data.realname && (data.realname != '')) { ipr.username = data.realname; }
1414 ipr.timeout = (typeof data.timeout === 'number' ? data.timeout : 120000);
1415 global._clientmessage = ipr.then(function (img) {
1416 var options = { b64Image: img.split(',').pop(), background: color_options.background, foreground: color_options.foreground }
1417 if (this.timeout != 0) { options.timeout = this.timeout; }
1418 this.messagebox = require('win-dialog').create(this.title, this.message, this.username, options);
1419 this.__childPromise.addMessage = this.messagebox.addMessage.bind(this.messagebox);
1420 return (this.messagebox);
1421 });
1422
1423 global._clientmessage.then(function () { global._clientmessage = null; });
1424 }
1425 catch (z) {
1426 try { require('message-box').create(data.title, data.msg, 120).then(function () { }).catch(function () { }); } catch (ex) { }
1427 }
1428 }
1429 }
1430 else {
1431 try { require('message-box').create(data.title, data.msg, 120).then(function () { }).catch(function () { }); } catch (ex) { }
1432 }
1433 }
1434 break;
1435 }
1436 case 'ps': {
1437 // Return the list of running processes
1438 if (data.sessionid) {
1439 processManager.getProcesses(function (plist) {
1440 mesh.SendCommand({ action: 'msg', type: 'ps', value: JSON.stringify(plist), sessionid: data.sessionid });
1441 });
1442 }
1443 break;
1444 }
1445 case 'psinfo': {
1446 // Requestion details information about a process
1447 if (data.pid) {
1448 var info = {}; // TODO: Replace with real data. Feel free not to give all values if not available.
1449 try {
1450 info = processManager.getProcessInfo(data.pid);
1451 }catch(e){ }
1452 /*
1453 info.processUser = "User"; // String
1454 info.processDomain = "Domain"; // String
1455 info.cmd = "abc"; // String
1456 info.processName = "dummydata";
1457 info.privateMemorySize = 123; // Bytes
1458 info.virtualMemorySize = 123; // Bytes
1459 info.workingSet = 123; // Bytes
1460 info.totalProcessorTime = 123; // Seconds
1461 info.userProcessorTime = 123; // Seconds
1462 info.startTime = "2012-12-30T23:59:59.000Z"; // Time in UTC ISO format
1463 info.sessionId = 123; // Number
1464 info.privilegedProcessorTime = 123; // Seconds
1465 info.PriorityBoostEnabled = true; // Boolean
1466 info.peakWorkingSet = 123; // Bytes
1467 info.peakVirtualMemorySize = 123; // Bytes
1468 info.peakPagedMemorySize = 123; // Bytes
1469 info.pagedSystemMemorySize = 123; // Bytes
1470 info.pagedMemorySize = 123; // Bytes
1471 info.nonpagedSystemMemorySize = 123; // Bytes
1472 info.mainWindowTitle = "dummydata"; // String
1473 info.machineName = "dummydata"; // Only set this if machine name is not "."
1474 info.handleCount = 123; // Number
1475 */
1476 mesh.SendCommand({ action: 'msg', type: 'psinfo', pid: data.pid, sessionid: data.sessionid, value: info });
1477 }
1478 break;
1479 }
1480 case 'pskill': {
1481 // Kill a process
1482 if (data.value) {
1483 var info = data.value.split('|'), pid = data.value, msg = " (Unknown)";
1484 if (info.length > 1) { pid = info[0]; msg = " (" + info[1] + ")"; } else { info[1] = "Unknown"; }
1485 MeshServerLogEx(19, info, "Killing process " + pid + msg, data);
1486 try { process.kill(parseInt(pid)); } catch (ex) { sendConsoleText("pskill: " + JSON.stringify(ex)); }
1487 }
1488 break;
1489 }
1490 case 'service': {
1491 // return information about the service
1492 try {
1493 var service = require('service-manager').manager.getService(data.serviceName);
1494 if (service != null) {
1495 var reply = {
1496 name: (service.name ? service.name : ''),
1497 status: (service.status ? service.status : ''),
1498 startType: (service.startType ? service.startType : ''),
1499 failureActions: (service.failureActions ? service.failureActions : ''),
1500 installedDate: (service.installedDate ? service.installedDate : ''),
1501 installedBy: (service.installedBy ? service.installedBy : '') ,
1502 user: (service.user ? service.user : '')
1503 };
1504 if(reply.installedBy.indexOf('S-1-5') != -1) {
1505 var cmd = "(Get-WmiObject -Class win32_userAccount -Filter \"SID='"+service.installedBy+"'\").Caption";
1506 var replydata = "";
1507 var pws = require('child_process').execFile(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', ['powershell', '-noprofile', '-nologo', '-command', '-'], {});
1508 pws.descriptorMetadata = 'UserSIDPowerShell';
1509 pws.stdout.on('data', function (c) { replydata += c.toString(); });
1510 pws.stderr.on('data', function (c) { replydata += c.toString(); });
1511 pws.stdin.write(cmd + '\r\nexit\r\n');
1512 pws.on('exit', function () {
1513 if (replydata != "") reply.installedBy = replydata;
1514 mesh.SendCommand({ action: 'msg', type: 'service', value: JSON.stringify(reply), sessionid: data.sessionid });
1515 delete pws;
1516 });
1517 } else {
1518 mesh.SendCommand({ action: 'msg', type: 'service', value: JSON.stringify(reply), sessionid: data.sessionid });
1519 }
1520 }
1521 } catch (ex) {
1522 mesh.SendCommand({ action: 'msg', type: 'service', error: ex, sessionid: data.sessionid })
1523 }
1524 }
1525 case 'services': {
1526 // Return the list of installed services
1527 var services = null;
1528 try { services = require('service-manager').manager.enumerateService(); } catch (ex) { }
1529 if (services != null) { mesh.SendCommand({ action: 'msg', type: 'services', value: JSON.stringify(services), sessionid: data.sessionid }); }
1530 break;
1531 }
1532 case 'serviceStop': {
1533 // Stop a service
1534 try {
1535 var service = require('service-manager').manager.getService(data.serviceName);
1536 if (service != null) { service.stop(); }
1537 } catch (ex) { }
1538 break;
1539 }
1540 case 'serviceStart': {
1541 // Start a service
1542 try {
1543 var service = require('service-manager').manager.getService(data.serviceName);
1544 if (service != null) { service.start(); }
1545 } catch (ex) { }
1546 break;
1547 }
1548 case 'serviceRestart': {
1549 // Restart a service
1550 try {
1551 var service = require('service-manager').manager.getService(data.serviceName);
1552 if (service != null) { service.restart(); }
1553 } catch (ex) { }
1554 break;
1555 }
1556 case 'deskBackground':
1557 {
1558 // Toggle desktop background
1559 try {
1560 if (process.platform == 'win32') {
1561 var stype = require('user-sessions').getProcessOwnerName(process.pid).tsid == 0 ? 1 : 0;
1562 var sid = undefined;
1563 if (stype == 1) {
1564 if (require('MeshAgent')._tsid != null) {
1565 stype = 5;
1566 sid = require('MeshAgent')._tsid;
1567 }
1568 }
1569 var id = require('user-sessions').getProcessOwnerName(process.pid).tsid == 0 ? 1 : 0;
1570 var child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0='], { type: stype, uid: sid });
1571 child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
1572 child.stderr.on('data', function () { });
1573 child.waitExit();
1574 var current = child.stdout.str.trim();
1575 if (current != '') { require('MeshAgent')._wallpaper = current; }
1576 child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0=', current != '' ? '""' : require('MeshAgent')._wallpaper], { type: stype, uid: sid });
1577 child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
1578 child.stderr.on('data', function () { });
1579 child.waitExit();
1580 mesh.SendCommand({ action: 'msg', type: 'deskBackground', sessionid: data.sessionid, data: (current != '' ? "" : require('MeshAgent')._wallpaper), });
1581 } else {
1582 var id = require('user-sessions').consoleUid();
1583 var current = require('linux-gnome-helpers').getDesktopWallpaper(id);
1584 if (current != '/dev/null') { require('MeshAgent')._wallpaper = current; }
1585 require('linux-gnome-helpers').setDesktopWallpaper(id, current != '/dev/null' ? undefined : require('MeshAgent')._wallpaper);
1586 mesh.SendCommand({ action: 'msg', type: 'deskBackground', sessionid: data.sessionid, data: (current != '/dev/null' ? "" : require('MeshAgent')._wallpaper), });
1587 }
1588 } catch (ex) {
1589 sendConsoleText(ex);
1590 }
1591 break;
1592 }
1593 case 'openUrl': {
1594 // Open a local web browser and return success/fail
1595 MeshServerLogEx(20, [data.url], "Opening: " + data.url, data);
1596 sendConsoleText("OpenURL: " + data.url);
1597 if (data.url) { mesh.SendCommand({ action: 'msg', type: 'openUrl', url: data.url, sessionid: data.sessionid, success: (openUserDesktopUrl(data.url) != null) }); }
1598 break;
1599 }
1600 case 'getclip': {
1601 // Send the load clipboard back to the user
1602 //sendConsoleText('getClip: ' + JSON.stringify(data));
1603 if (require('MeshAgent').isService) {
1604 require('clipboard').dispatchRead().then(function (str) {
1605 if (str) {
1606 if (data.tag != 3) { MeshServerLogEx(21, [str.length], "Getting clipboard content, " + str.length + " byte(s)", data); }
1607 mesh.SendCommand({ action: 'msg', type: 'getclip', sessionid: data.sessionid, data: str, tag: data.tag });
1608 }
1609 });
1610 } else {
1611 require('clipboard').read().then(function (str) {
1612 if (str) {
1613 if (data.tag != 3) { MeshServerLogEx(21, [str.length], "Getting clipboard content, " + str.length + " byte(s)", data); }
1614 mesh.SendCommand({ action: 'msg', type: 'getclip', sessionid: data.sessionid, data: str, tag: data.tag });
1615 }
1616 });
1617 }
1618 break;
1619 }
1620 case 'setclip': {
1621 if (pendingSetClip) return;
1622 // Set the load clipboard to a user value
1623 if (typeof data.data == 'string') {
1624 MeshServerLogEx(22, [data.data.length], "Setting clipboard content, " + data.data.length + " byte(s)", data);
1625 if (require('MeshAgent').isService) {
1626 if (process.platform != 'win32') {
1627 require('clipboard').dispatchWrite(data.data);
1628 mesh.SendCommand({ action: 'msg', type: 'setclip', sessionid: data.sessionid, success: true });
1629 }
1630 else {
1631 var clipargs = data.data;
1632 var uid = require('user-sessions').consoleUid();
1633 var user = require('user-sessions').getUsername(uid);
1634 var domain = require('user-sessions').getDomain(uid);
1635 user = (domain + '\\' + user);
1636
1637 if (this._dispatcher) { this._dispatcher.close(); }
1638 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] } });
1639 this._dispatcher.parent = this;
1640 //require('events').setFinalizerMetadata.call(this._dispatcher, 'clip-dispatch');
1641 pendingSetClip = true;
1642 this._dispatcher.on('connection', function (c) {
1643 this._c = c;
1644 this._c.root = this.parent;
1645 this._c.on('end', function ()
1646 {
1647 pendingSetClip = false;
1648 try { this.root._dispatcher.close(); } catch (ex) { }
1649 this.root._dispatcher = null;
1650 this.root = null;
1651 mesh.SendCommand({ action: 'msg', type: 'setclip', sessionid: data.sessionid, success: true });
1652 });
1653 });
1654 }
1655 }
1656 else {
1657 require('clipboard')(data.data);
1658 mesh.SendCommand({ action: 'msg', type: 'setclip', sessionid: data.sessionid, success: true });
1659 } // Set the clipboard
1660 }
1661 break;
1662 }
1663 case 'userSessions': {
1664 mesh.SendCommand({ action: 'msg', type: 'userSessions', sessionid: data.sessionid, data: require('kvm-helper').users(), tag: data.tag });
1665 break;
1666 }
1667 case 'cpuinfo':
1668 // CPU & memory utilization
1669 var cpuuse = require('sysinfo').cpuUtilization();
1670 cpuuse.sessionid = data.sessionid;
1671 cpuuse.tag = data.tag;
1672 cpuuse.then(function (data) {
1673 mesh.SendCommand(JSON.stringify(
1674 {
1675 action: 'msg',
1676 type: 'cpuinfo',
1677 cpu: data,
1678 memory: require('sysinfo').memUtilization(),
1679 thermals: require('sysinfo').thermals == null ? [] : require('sysinfo').thermals(),
1680 sessionid: this.sessionid,
1681 tag: this.tag
1682 }));
1683 }, function (ex) { });
1684 break;
1685 case 'localapp':
1686 // Send a message to a local application
1687 sendConsoleText('localappMsg: ' + data.appid + ', ' + JSON.stringify(data.value));
1688 if (data.appid != null) { sendToRegisteredApp(data.appid, data.value); } else { broadcastToRegisteredApps(data.value); }
1689 break;
1690 case 'alertbox': {
1691 // Display an old style alert box
1692 if (data.title && data.msg) {
1693 MeshServerLogEx(158, [data.title, data.msg], "Displaying alert box, title=" + data.title + ", message=" + data.msg, data);
1694 try { require('message-box').create(data.title, data.msg, 9999, 1).then(function () { }).catch(function () { }); } catch (ex) { }
1695 }
1696 break;
1697 }
1698 case 'sysinfo': {
1699 // Send system information
1700 getSystemInformation(function (results) {
1701 if ((results != null) && (data.hash != results.hash)) { mesh.SendCommand({ action: 'sysinfo', sessionid: this.sessionid, data: results }); }
1702 });
1703 break;
1704 }
1705 default:
1706 // Unknown action, ignore it.
1707 break;
1708 }
1709 break;
1710 }
1711 case 'software': {
1712 var sendSoftwareResponse = function(responseData) {
1713 mesh.SendCommand({
1714 action: 'software',
1715 value: (typeof responseData === 'string') ? responseData : JSON.stringify(responseData),
1716 sessionid: data.sessionid
1717 });
1718 };
1719 if (data.type == 'installedapps') {
1720 if (process.platform == 'win32') {
1721 try {
1722 if (require('win-info').installedApps) {
1723 require('win-info').installedApps().then(sendSoftwareResponse).catch(function(e) { sendSoftwareResponse({ error: e.toString() }); });
1724 } else { sendSoftwareResponse({ error: "Not supported" }); }
1725 } catch (e) { sendSoftwareResponse({ error: e.toString() }); }
1726 } else if (process.platform == 'linux') {
1727 try {
1728 if (require('linux-info').packages) {
1729 require('linux-info').packages().then(sendSoftwareResponse).catch(function(e) { sendSoftwareResponse({ error: e.toString() }); });
1730 } else { sendSoftwareResponse({ error: "Not supported" }); }
1731 } catch (e) { sendSoftwareResponse({ error: e.toString() }); }
1732 } else if (process.platform == 'darwin') {
1733 try {
1734 if (require('mac-info').apps) {
1735 require('mac-info').apps().then(sendSoftwareResponse).catch(function(e) { sendSoftwareResponse({ error: e.toString() }); });
1736 } else { sendSoftwareResponse({ error: "Not supported" }); }
1737 } catch (e) { sendSoftwareResponse({ error: e.toString() }); }
1738 } else {
1739 sendSoftwareResponse({ success: false, error: "Not supported" });
1740 }
1741 } else if (data.type == 'installedstoreapps') {
1742 if (process.platform != 'win32') {
1743 sendSoftwareResponse({ success: false, error: "Installed Store Apps is only supported on Windows devices" });
1744 return;
1745 }
1746 try {
1747 if (require('win-info').installedStoreApps) {
1748 require('win-info').installedStoreApps().then(sendSoftwareResponse).catch(function(e) { sendSoftwareResponse({ error: e.toString() }); });
1749 }
1750 } catch (e) { sendSoftwareResponse({ error: e.toString() }); }
1751 } else if (data.type == 'uninstallapp' && (typeof data.value == 'string' && data.value != '')) {
1752 if (process.platform != 'win32') {
1753 sendSoftwareResponse({ success: false, error: "Uninstall is only supported on Windows devices" });
1754 return;
1755 }
1756 var base64Cmd = data.value.trim();
1757 var uninstallCmd = '';
1758 try {
1759 var b = Buffer.from(base64Cmd, 'base64');
1760 var decoded = b.toString();
1761 var lc = decoded ? decoded.toLowerCase() : '';
1762 if (decoded && decoded.length > 0 && (lc.indexOf('msiexec') >= 0 || lc.indexOf('.exe') >= 0)) { uninstallCmd = decoded; } else { uninstallCmd = base64Cmd; }
1763 } catch (e) { uninstallCmd = base64Cmd; }
1764 if (!uninstallCmd || uninstallCmd.trim() === '' || uninstallCmd.trim() === '\\') {
1765 sendSoftwareResponse({ success: false, error: 'No valid uninstall command available' });
1766 } else {
1767 var logDir = (process.env['ProgramData'] || 'C:\\ProgramData') + '\\MeshAgent';
1768 try { if (!require('fs').existsSync(logDir)) { require('fs').mkdirSync(logDir); } } catch (e) { }
1769 var logFile = logDir + '\\MeshAgent_Uninstall.log';
1770 var child_process = require('child_process');
1771 var cmdPath = process.env['windir'] + '\\system32\\cmd.exe';
1772 var writeLog = function(message, callback) {
1773 try {
1774 var timestamp = new Date().toISOString();
1775 var logLine = timestamp + ' | ' + message;
1776 logLine = logLine.replace(/"/g, '""').replace(/&/g, '^&').replace(/</g, '^<').replace(/>/g, '^>').replace(/\|/g, '^|');
1777 var logChild = child_process.execFile(cmdPath, ['cmd', '/c', 'echo ' + logLine + ' >> "' + logFile + '"'], { timeout: 5000 });
1778 logChild.on('exit', function() { if (callback) callback(); });
1779 } catch (e) { if (callback) callback(); }
1780 };
1781 try {
1782 writeLog('UNINSTALL START - Command: ' + uninstallCmd.replace(/\\/g, '/'));
1783 var originalCmd = uninstallCmd;
1784 if (uninstallCmd.toLowerCase().indexOf('msiexec') >= 0) {
1785 uninstallCmd = uninstallCmd.replace(/\/I\s*(\{[^}]+\})/gi, '/X $1'); // change /I to /X for uninstall, fixes 7zip example
1786 uninstallCmd = uninstallCmd.replace(/\/q[nbrf]?/gi, '');
1787 if (uninstallCmd.indexOf('/QN') < 0) { uninstallCmd = uninstallCmd + ' /QN /norestart'; }
1788 } else {
1789 if (uninstallCmd.toLowerCase().indexOf('/s') < 0) { uninstallCmd = uninstallCmd + ' /S /silent /SILENT /VERYSILENT /quiet /norestart'; }
1790 }
1791 uninstallCmd = uninstallCmd.replace(/\s+/g, ' ').trim();
1792 var child = child_process.execFile(cmdPath, ['cmd', '/c', uninstallCmd], { timeout: 300000 });
1793 child.stdout.str = ''; child.stderr.str = '';
1794 child._cmd = uninstallCmd;
1795 child.stdout.on('data', function(c) { this.str += c.toString(); });
1796 child.stderr.on('data', function(c) { this.str += c.toString(); });
1797 child.on('exit', function(code) {
1798 var success = (code === 0 || code === null || code === 3010);
1799 var status = success ? 'SUCCESS' : 'FAILED';
1800 writeLog('UNINSTALL ' + status + ' - ExitCode: ' + code);
1801 var result = { success: success, exitCode: code, command: child._cmd };
1802 if (child.stdout.str) result.stdout = child.stdout.str.trim().substring(0, 500);
1803 if (child.stderr.str) result.stderr = child.stderr.str.trim().substring(0, 500);
1804 sendSoftwareResponse(result);
1805 });
1806 sendSoftwareResponse({ status: 'Silent uninstall started', command: uninstallCmd });
1807 } catch (ex) {
1808 writeLog('UNINSTALL ERROR - ' + ex.toString());
1809 sendSoftwareResponse({ error: ex.toString() });
1810 }
1811 }
1812 } else if (data.type == 'uninstallstoreapp' && (typeof data.value === 'string' && data.value != '')) {
1813 if (process.platform != 'win32') {
1814 sendSoftwareResponse({ success: false, error: "Uninstall is only supported on Windows devices" });
1815 return;
1816 }
1817 var rawName = data.value.trim();
1818 var packageName = rawName.replace(/"/g, "").replace(/'/g, "");
1819 var logDir = (process.env['ProgramData'] || 'C:\\ProgramData') + '\\MeshAgent';
1820 try { if (!require('fs').existsSync(logDir)) { require('fs').mkdirSync(logDir); } } catch (e) { }
1821 var logFile = logDir + '\\MeshAgent_StoreUninstall.log';
1822 try {
1823 var psPath = (process.env['SystemRoot'] || 'C:\\Windows') + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe';
1824 var child_process = require('child_process');
1825 var completionMarker = '###DONE###' + Date.now();
1826 var child = child_process.execFile(psPath, ['powershell', '-NoProfile', '-NoLogo', '-ExecutionPolicy', 'Bypass', '-Command', '-'], {});
1827 child.stdout.str = ''; child.stderr.str = '';
1828 child._completed = false;
1829 child.stdout.on('data', function(c) {
1830 this.str += c.toString();
1831 if (!child._completed && this.str.indexOf(completionMarker) >= 0) {
1832 child._completed = true;
1833 var output = this.str.split(completionMarker)[0].trim();
1834 sendSoftwareResponse({ status: 'Finished', output: output });
1835 }
1836 });
1837 child.stderr.on('data', function(c) { this.str += c.toString(); });
1838 var script = [
1839 "$LogFile = '" + logFile + "'",
1840 "$TargetName = '" + packageName + "'",
1841 "function Write-Log { param([string]$Msg); $Line = \"$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - $Msg\"; Add-Content -Path $LogFile -Value $Line -ErrorAction SilentlyContinue; Write-Output $Msg }",
1842 "",
1843 "Write-Log \"STORE UNINSTALL START: $TargetName\"",
1844 "Write-Log \"Running as: $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)\"",
1845 "",
1846 "$foundCount = 0",
1847 "$pkgs = Get-AppxPackage -AllUsers -Name \"*$TargetName*\"",
1848 "if ($pkgs) {",
1849 " if ($pkgs -isnot [array]) { $pkgs = @($pkgs) }",
1850 " Write-Log \"Found (AllUsers): $($pkgs.Count) packages\"",
1851 " foreach ($p in $pkgs) {",
1852 " Write-Log \"Removing: $($p.PackageFullName)\"",
1853 " try {",
1854 " Remove-AppxPackage -Package $p.PackageFullName -AllUsers -ErrorAction Stop",
1855 " Write-Log \"SUCCESS: Removed with -AllUsers\"",
1856 " $foundCount++",
1857 " } catch {",
1858 " Write-Log \"WARN: AllUsers failed, trying current user only. Error: $($_.Exception.Message)\"",
1859 " try {",
1860 " Remove-AppxPackage -Package $p.PackageFullName -ErrorAction Stop",
1861 " Write-Log \"SUCCESS: Removed from CurrentUser\"",
1862 " $foundCount++",
1863 " } catch {",
1864 " Write-Log \"ERROR: Failed to remove $($p.PackageFullName). Error: $($_.Exception.Message)\"",
1865 " }",
1866 " }",
1867 " }",
1868 "} else { Write-Log \"Found (AllUsers): 0 packages\" }",
1869 "",
1870 "$prov = Get-AppxProvisionedPackage -Online | Where-Object { $_.PackageName -like \"*$TargetName*\" }",
1871 "if ($prov) {",
1872 " if ($prov -isnot [array]) { $prov = @($prov) }",
1873 " Write-Log \"Found provisioned: $($prov.Count) packages\"",
1874 " foreach ($pr in $prov) {",
1875 " Write-Log \"Deprovisioning: $($pr.DisplayName)\"",
1876 " try {",
1877 " Remove-AppxProvisionedPackage -Online -PackageName $pr.PackageName -ErrorAction Stop | Out-Null",
1878 " Write-Log \"SUCCESS: Deprovisioned\"",
1879 " } catch {",
1880 " Write-Log \"ERROR: Failed to deprovision. Error: $($_.Exception.Message)\"",
1881 " }",
1882 " }",
1883 "} else { Write-Log \"Found provisioned: 0 packages\" }",
1884 "",
1885 "'" + completionMarker + "'",
1886 "exit"
1887 ].join("\r\n");
1888 child.stdin.write(script + "\r\n");
1889 setTimeout(function() { if (!child._completed) { child.kill(); sendSoftwareResponse({ error: 'Timeout' }); } }, 60000);
1890 sendSoftwareResponse({ status: 'Store app removal started', package: packageName });
1891 } catch (ex) {
1892 sendSoftwareResponse({ error: ex.toString() });
1893 }
1894 }
1895 break;
1896 }
1897 case 'acmactivate': {
1898 if (amt != null) {
1899 MeshServerLogEx(23, null, "Attempting Intel AMT ACM mode activation", data);
1900 amt.setAcmResponse(data);
1901 }
1902 break;
1903 }
1904 case 'wakeonlan': {
1905 // Send wake-on-lan on all interfaces for all MAC addresses in data.macs array. The array is a list of HEX MAC addresses.
1906 //sendConsoleText("Server requesting wake-on-lan for: " + data.macs.join(', '));
1907 sendWakeOnLanEx(data.macs);
1908 sendWakeOnLanEx(data.macs);
1909 sendWakeOnLanEx(data.macs);
1910 break;
1911 }
1912 case 'runcommands': {
1913 if (mesh.cmdchild != null) { sendConsoleText("Run commands can't execute, already busy."); break; }
1914 if (!data.reply) sendConsoleText("Run commands (" + data.runAsUser + "): " + data.cmds);
1915
1916 // data.runAsUser: 0=Agent,1=UserOrAgent,2=UserOnly
1917 var options = {};
1918 if (data.runAsUser > 0) {
1919 try { options.uid = require('user-sessions').consoleUid(); } catch (ex) { }
1920 options.type = require('child_process').SpawnTypes.TERM;
1921 }
1922 if (data.runAsUser == 2) {
1923 if (options.uid == null) break;
1924 if (((require('user-sessions').minUid != null) && (options.uid < require('user-sessions').minUid()))) break; // This command can only run as user.
1925 }
1926 var replydata = "";
1927 if (process.platform == 'win32') {
1928 if (data.type == 1) {
1929 // Windows command shell
1930 mesh.cmdchild = require('child_process').execFile(process.env['windir'] + '\\system32\\cmd.exe', ['cmd'], options);
1931 mesh.cmdchild.descriptorMetadata = 'UserCommandsShell';
1932 mesh.cmdchild.stdout.on('data', function (c) { replydata += c.toString(); sendConsoleText(c.toString()); });
1933 mesh.cmdchild.stderr.on('data', function (c) { replydata += c.toString(); sendConsoleText(c.toString()); });
1934 mesh.cmdchild.stdin.write(data.cmds + '\r\nexit\r\n');
1935 mesh.cmdchild.on('exit', function () {
1936 if (data.reply) {
1937 mesh.SendCommand({ action: 'msg', type: 'runcommands', result: replydata, sessionid: data.sessionid, responseid: data.responseid });
1938 } else {
1939 sendConsoleText("Run commands completed.");
1940 }
1941 delete mesh.cmdchild;
1942 });
1943 } else if (data.type == 2) {
1944 // Windows Powershell
1945 mesh.cmdchild = require('child_process').execFile(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', ['powershell', '-noprofile', '-nologo', '-command', '-'], options);
1946 mesh.cmdchild.descriptorMetadata = 'UserCommandsPowerShell';
1947 mesh.cmdchild.stdout.on('data', function (c) { replydata += c.toString(); sendConsoleText(c.toString()); });
1948 mesh.cmdchild.stderr.on('data', function (c) { replydata += c.toString(); sendConsoleText(c.toString()); });
1949 mesh.cmdchild.stdin.write(data.cmds + '\r\nexit\r\n');
1950 mesh.cmdchild.on('exit', function () {
1951 if (data.reply) {
1952 mesh.SendCommand({ action: 'msg', type: 'runcommands', result: replydata, sessionid: data.sessionid, responseid: data.responseid });
1953 } else {
1954 sendConsoleText("Run commands completed.");
1955 }
1956 delete mesh.cmdchild;
1957 });
1958 }
1959 } else if (data.type == 3) {
1960 // Linux shell
1961 mesh.cmdchild = require('child_process').execFile('/bin/sh', ['sh'], options);
1962 mesh.cmdchild.descriptorMetadata = 'UserCommandsShell';
1963 mesh.cmdchild.stdout.on('data', function (c) { replydata += c.toString(); sendConsoleText(c.toString()); });
1964 mesh.cmdchild.stderr.on('data', function (c) { replydata += c.toString(); sendConsoleText(c.toString()); });
1965 mesh.cmdchild.stdin.write(data.cmds.split('\r').join('') + '\nexit\n');
1966 mesh.cmdchild.on('exit', function () {
1967 if (data.reply) {
1968 mesh.SendCommand({ action: 'msg', type: 'runcommands', result: replydata, sessionid: data.sessionid, responseid: data.responseid });
1969 } else {
1970 sendConsoleText("Run commands completed.");
1971 }
1972 delete mesh.cmdchild;
1973 });
1974 }
1975 break;
1976 }
1977 case 'uninstallagent':
1978 // Uninstall this agent
1979 var agentName = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent';
1980 try {
1981 agentName = require('MeshAgent').serviceName;
1982 } catch (ex) { }
1983
1984 if (require('service-manager').manager.getService(agentName).isMe()) {
1985 try { diagnosticAgent_uninstall(); } catch (ex) { }
1986 var js = "require('service-manager').manager.getService('" + agentName + "').stop(); require('service-manager').manager.uninstallService('" + agentName + "'); process.exit();";
1987 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 });
1988 }
1989 break;
1990 case 'poweraction': {
1991 // Server telling us to execute a power action
1992 if ((mesh.ExecPowerState != undefined) && (data.actiontype)) {
1993 var forced = 0;
1994 if (data.forced == 1) { forced = 1; }
1995 data.actiontype = parseInt(data.actiontype);
1996 MeshServerLogEx(25, [data.actiontype, forced], "Performing power action=" + data.actiontype + ", forced=" + forced, data);
1997 sendConsoleText("Performing power action=" + data.actiontype + ", forced=" + forced + '.');
1998 var r = mesh.ExecPowerState(data.actiontype, forced);
1999 sendConsoleText("ExecPowerState returned code: " + r);
2000 }
2001 break;
2002 }
2003 case 'iplocation': {
2004 // 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
2005 getIpLocationData(function (location) { mesh.SendCommand({ action: 'iplocation', type: 'publicip', value: location }); });
2006 break;
2007 }
2008 case 'toast': {
2009 // Display a toast message
2010 if (data.title && data.msg) {
2011 MeshServerLogEx(26, [data.title, data.msg], "Displaying toast message, title=" + data.title + ", message=" + data.msg, data);
2012 data.msg = data.msg.split('\r').join('\\r').split('\n').join('\\n');
2013 try { require('toaster').Toast(data.title, data.msg); } catch (ex) { }
2014 }
2015 break;
2016 }
2017 case 'openUrl': {
2018 // Open a local web browser and return success/fail
2019 //sendConsoleText('OpenURL: ' + data.url);
2020 MeshServerLogEx(20, [data.url], "Opening: " + data.url, data);
2021 if (data.url) { mesh.SendCommand({ action: 'openUrl', url: data.url, sessionid: data.sessionid, success: (openUserDesktopUrl(data.url) != null) }); }
2022 break;
2023 }
2024 case 'amtconfig': {
2025 // Perform Intel AMT activation and/or configuration
2026 if ((apftunnel != null) || (amt == null) || (typeof data.user != 'string') || (typeof data.pass != 'string')) break;
2027 amt.getMeiState(15, function (state) {
2028 if ((apftunnel != null) || (amt == null)) return;
2029 if ((state == null) || (state.ProvisioningState == null)) return;
2030 if ((state.UUID == null) || (state.UUID.length != 36)) return; // Bad UUID
2031 getAmtOsDnsSuffix(state, function () {
2032 var apfarg = {
2033 mpsurl: mesh.ServerUrl.replace('/agent.ashx', '/apf.ashx'),
2034 mpsuser: data.user, // Agent user name
2035 mpspass: data.pass, // Encrypted login cookie
2036 mpskeepalive: 60000,
2037 clientname: state.OsHostname,
2038 clientaddress: '127.0.0.1',
2039 clientuuid: state.UUID,
2040 conntype: 2, // 0 = CIRA, 1 = Relay, 2 = LMS. The correct value is 2 since we are performing an LMS relay, other values for testing.
2041 meiState: state // MEI state will be passed to MPS server
2042 };
2043 addAmtEvent('LMS tunnel start.');
2044 apftunnel = require('amt-apfclient')({ debug: false }, apfarg);
2045 apftunnel.onJsonControl = handleApfJsonControl;
2046 apftunnel.onChannelClosed = function () { addAmtEvent('LMS tunnel closed.'); apftunnel = null; }
2047 try { apftunnel.connect(); } catch (ex) { }
2048 });
2049 });
2050 break;
2051 }
2052 case 'getScript': {
2053 // Received a configuration script from the server
2054 sendConsoleText('getScript: ' + JSON.stringify(data));
2055 break;
2056 }
2057 case 'sysinfo': {
2058 // Fetch system information
2059 getSystemInformation(function (results) {
2060 if ((results != null) && (data.hash != results.hash)) { mesh.SendCommand({ action: 'sysinfo', sessionid: this.sessionid, data: results }); }
2061 });
2062 break;
2063 }
2064 case 'ping': { mesh.SendCommand('{"action":"pong"}'); break; }
2065 case 'pong': { break; }
2066 case 'plugin': {
2067 try { require(data.plugin).consoleaction(data, data.rights, data.sessionid, this); } catch (ex) { throw ex; }
2068 break;
2069 }
2070 case 'coredump':
2071 // Set the current agent coredump situation.s
2072 if (data.value === true) {
2073 if (process.platform == 'win32') {
2074 // TODO: This replace() below is not ideal, would be better to remove the .exe at the end instead of replace.
2075 process.coreDumpLocation = process.execPath.replace('.exe', '.dmp');
2076 } else {
2077 process.coreDumpLocation = (process.cwd() != '//') ? (process.cwd() + 'core') : null;
2078 }
2079 } else if (data.value === false) {
2080 process.coreDumpLocation = null;
2081 }
2082 break;
2083 case 'getcoredump':
2084 // Ask the agent if a core dump is currently available, if yes, also return the hash of the agent.
2085 var r = { action: 'getcoredump', value: (process.coreDumpLocation != null) };
2086 var coreDumpPath = null;
2087 if (process.platform == 'win32') { coreDumpPath = process.coreDumpLocation; } else { coreDumpPath = (process.cwd() != '//') ? fs.existsSync(process.cwd() + 'core') : null; }
2088 if ((coreDumpPath != null) && (fs.existsSync(coreDumpPath))) {
2089 try {
2090 var coredate = fs.statSync(coreDumpPath).mtime;
2091 var coretime = new Date(coredate).getTime();
2092 var agenttime = new Date(fs.statSync(process.execPath).mtime).getTime();
2093 if (coretime > agenttime) { r.exists = (db.Get('CoreDumpTime') != coredate); }
2094 } catch (ex) { }
2095 }
2096 if (r.exists == true) {
2097 r.agenthashhex = getSHA384FileHash(process.execPath).toString('hex'); // Hash of current agent
2098 r.corehashhex = getSHA384FileHash(coreDumpPath).toString('hex'); // Hash of core dump file
2099 }
2100 mesh.SendCommand(JSON.stringify(r));
2101 break;
2102 case 'meshToolInfo':
2103 if (data.pipe == true) { delete data.pipe; delete data.action; data.cmd = 'meshToolInfo'; broadcastToRegisteredApps(data); }
2104 if (data.tag == 'info') { sendConsoleText(JSON.stringify(data, null, 2)); }
2105 if (data.tag == 'install') {
2106 data.func = function (options, success) {
2107 sendConsoleText('Download of MeshCentral Assistant ' + (success ? 'succeed' : 'failed'));
2108 if (success) {
2109 // TODO: Install & Run
2110 }
2111 }
2112 data.filename = 'MeshAssistant.exe';
2113 downloadFile(data);
2114 }
2115 break;
2116 case 'getUserImage':
2117 if (data.pipe == true) { delete data.pipe; delete data.action; data.cmd = 'getUserImage'; broadcastToRegisteredApps(data); }
2118 if (data.tag == 'info') { sendConsoleText(JSON.stringify(data, null, 2)); }
2119 if (data.promise != null && require('MeshAgent')._promises[data.promise] != null) {
2120 var p = require('MeshAgent')._promises[data.promise];
2121 delete require('MeshAgent')._promises[data.promise];
2122 p.resolve(data.image);
2123 }
2124 break;
2125 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.
2126 agentFileHttpPendingRequests.push(data);
2127 serverFetchFile();
2128 break;
2129 case 'serverInfo': // Server information
2130 obj.serverInfo = data;
2131 delete obj.serverInfo.action;
2132 break;
2133 case 'errorlog': // Return agent error log
2134 try { mesh.SendCommand(JSON.stringify({ action: 'errorlog', log: require('util-agentlog').read(data.startTime) })); } catch (ex) { }
2135 break;
2136 default:
2137 // Unknown action, ignore it.
2138 break;
2139 }
2140 }
2141 }
2142
2143 // On non-Windows platforms, we need to query the DHCP server for the DNS suffix
2144 function getAmtOsDnsSuffix(mestate, func) {
2145 if ((process.platform == 'win32') || (mestate.net0 == null) || (mestate.net0.mac == null)) { func(mestate); return; }
2146 try { require('linux-dhcp') } catch (ex) { func(mestate); return; }
2147 require('linux-dhcp').client.info(mestate.net0.mac).then(function (d) {
2148 if ((typeof d.options == 'object') && (typeof d.options.domainname == 'string')) { mestate.OsDnsSuffix = d.options.domainname; }
2149 func(mestate);
2150 }, function (e) {
2151 console.log('DHCP error', e);
2152 func(mestate);
2153 });
2154 }
2155
2156 // Download a file from the server and check the hash.
2157 // This download is similar to the one used for meshcore self-update.
2158 var trustedDownloads = {};
2159 function downloadFile(downloadoptions) {
2160 var options = require('http').parseUri(downloadoptions.url);
2161 options.rejectUnauthorized = false;
2162 options.checkServerIdentity = function checkServerIdentity(certs) {
2163 // If the tunnel certificate matches the control channel certificate, accept the connection
2164 try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.digest == certs[0].digest) return; } catch (ex) { }
2165 try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.fingerprint == certs[0].fingerprint) return; } catch (ex) { }
2166 // Check that the certificate is the one expected by the server, fail if not.
2167 if (checkServerIdentity.servertlshash == null) { if (require('MeshAgent').ServerInfo == null || require('MeshAgent').ServerInfo.ControlChannelCertificate == null) return; throw new Error('BadCert'); }
2168 if (certs[0].digest == null) return;
2169 if ((checkServerIdentity.servertlshash != null) && (checkServerIdentity.servertlshash.toLowerCase() != certs[0].digest.split(':').join('').toLowerCase())) { throw new Error('BadCert') }
2170 }
2171 //options.checkServerIdentity.servertlshash = downloadoptions.serverhash;
2172 trustedDownloads[downloadoptions.name] = downloadoptions;
2173 trustedDownloads[downloadoptions.name].dl = require('https').get(options);
2174 trustedDownloads[downloadoptions.name].dl.on('error', function (e) { downloadoptions.func(downloadoptions, false); delete trustedDownloads[downloadoptions.name]; });
2175 trustedDownloads[downloadoptions.name].dl.on('response', function (img) {
2176 this._file = require('fs').createWriteStream(trustedDownloads[downloadoptions.name].filename, { flags: 'wb' });
2177 this._filehash = require('SHA384Stream').create();
2178 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); });
2179 img.pipe(this._file);
2180 img.pipe(this._filehash);
2181 });
2182 }
2183
2184 // Handle APF JSON control commands
2185 function handleApfJsonControl(data) {
2186 if (data.action == 'console') { addAmtEvent(data.msg); } // Add console message to AMT event log
2187 if (data.action == 'mestate') { amt.getMeiState(15, function (state) { apftunnel.updateMeiState(state); }); } // Update the MEI state
2188 if (data.action == 'close') { try { apftunnel.disconnect(); } catch (ex) { } apftunnel = null; } // Close the CIRA-LMS connection
2189 if (amt.amtMei != null) {
2190 if (data.action == 'deactivate') { // Request CCM deactivation
2191 amt.amtMei.unprovision(1, function (status) { if (apftunnel) apftunnel.sendMeiDeactivationState(status); }); // 0 = Success
2192 }
2193 if (data.action == 'startTlsHostConfig') { // Request start of host based TLS ACM activation
2194 amt.amtMei.startConfigurationHBased(Buffer.from(data.hash, 'hex'), data.hostVpn, data.dnsSuffixList, function (response) { apftunnel.sendStartTlsHostConfigResponse(response); });
2195 }
2196 if (data.action == 'stopConfiguration') { // Request Intel AMT stop configuration.
2197 amt.amtMei.stopConfiguration(function (status) { apftunnel.sendStopConfigurationResponse(status); });
2198 }
2199 }
2200 }
2201
2202 // Agent just get a file from the server and save it locally.
2203 function serverFetchFile() {
2204 if ((Object.keys(agentFileHttpRequests).length > 4) || (agentFileHttpPendingRequests.length == 0)) return; // No more than 4 active HTTPS requests to the server.
2205 var data = agentFileHttpPendingRequests.shift();
2206 if ((data.overwrite !== true) && fs.existsSync(data.path)) return; // Don't overwrite an existing file.
2207 if (data.createFolder) { try { fs.mkdirSync(data.folder); } catch (ex) { } } // If requested, create the local folder.
2208 data.url = 'http' + getServerTargetUrlEx('*/').substring(2);
2209 var agentFileHttpOptions = http.parseUri(data.url);
2210 agentFileHttpOptions.path = data.urlpath;
2211
2212 // Perform manual server TLS certificate checking based on the certificate hash given by the server.
2213 agentFileHttpOptions.rejectUnauthorized = 0;
2214 agentFileHttpOptions.checkServerIdentity = function checkServerIdentity(certs) {
2215 // If the tunnel certificate matches the control channel certificate, accept the connection
2216 try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.digest == certs[0].digest) return; } catch (ex) { }
2217 try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.fingerprint == certs[0].fingerprint) return; } catch (ex) { }
2218 // Check that the certificate is the one expected by the server, fail if not.
2219 if ((checkServerIdentity.servertlshash != null) && (checkServerIdentity.servertlshash.toLowerCase() != certs[0].digest.split(':').join('').toLowerCase())) { throw new Error('BadCert') }
2220 }
2221 agentFileHttpOptions.checkServerIdentity.servertlshash = data.servertlshash;
2222
2223 if (agentFileHttpOptions == null) return;
2224 var agentFileHttpRequest = http.request(agentFileHttpOptions,
2225 function (response) {
2226 response.xparent = this;
2227 try {
2228 response.xfile = fs.createWriteStream(this.xpath, { flags: 'wbN' })
2229 response.pipe(response.xfile);
2230 response.end = function () { delete agentFileHttpRequests[this.xparent.xurlpath]; delete this.xparent; serverFetchFile(); }
2231 } catch (ex) { delete agentFileHttpRequests[this.xurlpath]; delete response.xparent; serverFetchFile(); return; }
2232 }
2233 );
2234 agentFileHttpRequest.on('error', function (ex) { sendConsoleText(ex); delete agentFileHttpRequests[this.xurlpath]; serverFetchFile(); });
2235 agentFileHttpRequest.end();
2236 agentFileHttpRequest.xurlpath = data.urlpath;
2237 agentFileHttpRequest.xpath = data.path;
2238 agentFileHttpRequests[data.urlpath] = agentFileHttpRequest;
2239 }
2240
2241 // Called when a file changed in the file system
2242 /*
2243 function onFileWatcher(a, b) {
2244 console.log('onFileWatcher', a, b, this.path);
2245 var response = getDirectoryInfo(this.path);
2246 if ((response != undefined) && (response != null)) { this.tunnel.s.write(JSON.stringify(response)); }
2247 }
2248 */
2249
2250 // Replace all key name spaces with _ in an object recursively.
2251 // This is a workaround since require('computer-identifiers').get() returns key names with spaces in them on Linux.
2252 function replaceSpacesWithUnderscoresRec(o) {
2253 if (typeof o != 'object') return;
2254 for (var i in o) { if (i.indexOf(' ') >= 0) { o[i.split(' ').join('_')] = o[i]; delete o[i]; } replaceSpacesWithUnderscoresRec(o[i]); }
2255 }
2256
2257 function getSystemInformation(func) {
2258 try {
2259 var results = { hardware: require('computer-identifiers').get() }; // Hardware info
2260 if (results.hardware && results.hardware.windows) {
2261 // Remove extra entries and things that change quickly
2262 var x = results.hardware.windows.osinfo;
2263 try { delete x.FreePhysicalMemory; } catch (ex) { }
2264 try { delete x.FreeSpaceInPagingFiles; } catch (ex) { }
2265 try { delete x.FreeVirtualMemory; } catch (ex) { }
2266 try { delete x.LocalDateTime; } catch (ex) { }
2267 try { delete x.MaxProcessMemorySize; } catch (ex) { }
2268 try { delete x.TotalVirtualMemorySize; } catch (ex) { }
2269 try { delete x.TotalVisibleMemorySize; } catch (ex) { }
2270 try {
2271 if (results.hardware.windows.memory) { for (var i in results.hardware.windows.memory) { delete results.hardware.windows.memory[i].Node; } }
2272 if (results.hardware.windows.osinfo) {
2273 delete results.hardware.windows.osinfo.Node;
2274 results.hardware.windows.osinfo.Domain = getDomainInfo().Domain;
2275 results.hardware.windows.osinfo.PartOfDomain = getDomainInfo().PartOfDomain;
2276 results.hardware.windows.osinfo.DomainState = getJoinState();
2277 }
2278 if (results.hardware.windows.partitions) { for (var i in results.hardware.windows.partitions) { delete results.hardware.windows.partitions[i].Node; } }
2279 } catch (ex) { }
2280 if (x.LastBootUpTime) { // detect windows uptime
2281 var thedate = {
2282 year: parseInt(x.LastBootUpTime.substring(0, 4)),
2283 month: parseInt(x.LastBootUpTime.substring(4, 6)) - 1, // Months are 0-based in JavaScript (0 - January, 11 - December)
2284 day: parseInt(x.LastBootUpTime.substring(6, 8)),
2285 hours: parseInt(x.LastBootUpTime.substring(8, 10)),
2286 minutes: parseInt(x.LastBootUpTime.substring(10, 12)),
2287 seconds: parseInt(x.LastBootUpTime.substring(12, 14)),
2288 };
2289 var thelastbootuptime = new Date(thedate.year, thedate.month, thedate.day, thedate.hours, thedate.minutes, thedate.seconds);
2290 meshCoreObj.lastbootuptime = thelastbootuptime.getTime(); // store the last boot up time in coreinfo for columns
2291 meshCoreObjChanged();
2292 var nowtime = new Date();
2293 var differenceInMilliseconds = Math.abs(thelastbootuptime - nowtime);
2294 if (differenceInMilliseconds < 300000) { // computer uptime less than 5 minutes
2295 MeshServerLogEx(159, [thelastbootuptime.toString()], "Device Powered On", null);
2296 }
2297 }
2298 }
2299 if(results.hardware && results.hardware.linux) {
2300 if(results.hardware.linux.LastBootUpTime) {
2301 var thelastbootuptime = new Date(results.hardware.linux.LastBootUpTime);
2302 meshCoreObj.lastbootuptime = thelastbootuptime.getTime(); // store the last boot up time in coreinfo for columns
2303 meshCoreObjChanged();
2304 var nowtime = new Date();
2305 var differenceInMilliseconds = Math.abs(thelastbootuptime - nowtime);
2306 if (differenceInMilliseconds < 300000) { // computer uptime less than 5 minutes
2307 MeshServerLogEx(159, [thelastbootuptime.toString()], "Device Powered On", null);
2308 }
2309 }
2310 }
2311 if(results.hardware && results.hardware.darwin){
2312 if(results.hardware.darwin.LastBootUpTime) {
2313 var thelastbootuptime = new Date(results.hardware.darwin.LastBootUpTime * 1000); // must times by 1000 even tho timestamp is correct?
2314 meshCoreObj.lastbootuptime = thelastbootuptime.getTime(); // store the last boot up time in coreinfo for columns
2315 meshCoreObjChanged();
2316 var nowtime = new Date();
2317 var differenceInMilliseconds = Math.abs(thelastbootuptime - nowtime);
2318 if (differenceInMilliseconds < 300000) { // computer uptime less than 5 minutes
2319 MeshServerLogEx(159, [thelastbootuptime.toString()], "Device Powered On", null);
2320 }
2321 }
2322 }
2323 results.hardware.agentvers = process.versions;
2324 results.hardware.network = { dns: require('os').dns() };
2325 replaceSpacesWithUnderscoresRec(results);
2326 var hasher = require('SHA384Stream').create();
2327
2328 // On Windows platforms, get volume information - Needs more testing.
2329 if (process.platform == 'win32')
2330 {
2331 results.pendingReboot = require('win-info').pendingReboot(); // Pending reboot
2332 if (require('win-volumes').volumes_promise != null)
2333 {
2334 var p = require('win-volumes').volumes_promise();
2335 p.then(function (res)
2336 {
2337 results.hardware.windows.volumes = cleanGetBitLockerVolumeInfo(res);
2338 results.hash = hasher.syncHash(JSON.stringify(results)).toString('hex');
2339 func(results);
2340 });
2341 }
2342 else
2343 {
2344 results.hash = hasher.syncHash(JSON.stringify(results)).toString('hex');
2345 func(results);
2346 }
2347 }
2348 else
2349 {
2350 results.hash = hasher.syncHash(JSON.stringify(results)).toString('hex');
2351 func(results);
2352 }
2353
2354 } catch (ex) { func(null, ex); }
2355 }
2356
2357 // Get a formatted response for a given directory path
2358 function getDirectoryInfo(reqpath) {
2359 var response = { path: reqpath, dir: [] };
2360 if (((reqpath == undefined) || (reqpath == '')) && (process.platform == 'win32')) {
2361 // List all the drives in the root, or the root itself
2362 var results = null;
2363 try { results = fs.readDrivesSync(); } catch (ex) { }
2364 if (results != null) {
2365 for (var i = 0; i < results.length; ++i) {
2366 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) };
2367 response.dir.push(drive);
2368 }
2369 }
2370 } else {
2371 // List all the files and folders in this path
2372 if (reqpath == '') { reqpath = '/'; }
2373 var results = null, xpath = obj.path.join(reqpath, '*');
2374 //if (process.platform == "win32") { xpath = xpath.split('/').join('\\'); }
2375 try { results = fs.readdirSync(xpath); } catch (ex) { }
2376 try { if ((results != null) && (results.length == 0) && (fs.existsSync(reqpath) == false)) { results = null; } } catch (ex) { }
2377 if (results != null) {
2378 for (var i = 0; i < results.length; ++i) {
2379 if ((results[i] != '.') && (results[i] != '..')) {
2380 var stat = null, p = obj.path.join(reqpath, results[i]);
2381 //if (process.platform == "win32") { p = p.split('/').join('\\'); }
2382 try { stat = fs.statSync(p); } catch (ex) { } // TODO: Get file size/date
2383 if ((stat != null) && (stat != undefined)) {
2384 if (stat.isDirectory() == true) {
2385 response.dir.push({ n: results[i], t: 2, d: stat.mtime });
2386 } else {
2387 response.dir.push({ n: results[i], t: 3, s: stat.size, d: stat.mtime });
2388 }
2389 }
2390 }
2391 }
2392 } else {
2393 response.dir = null;
2394 }
2395 }
2396 return response;
2397 }
2398
2399 function tunnel_s_finalized()
2400 {
2401 console.info1('Tunnel Socket Finalized');
2402 }
2403
2404
2405 function tunnel_onIdleTimeout()
2406 {
2407 this.ping();
2408 this.setTimeout(require('MeshAgent').idleTimeout * 1000);
2409 }
2410
2411 // Tunnel callback operations
2412 function onTunnelUpgrade(response, s, head)
2413 {
2414
2415 this.s = s;
2416 s.once('~', tunnel_s_finalized);
2417 s.httprequest = this;
2418 s.end = onTunnelClosed;
2419 s.tunnel = this;
2420 s.descriptorMetadata = "MeshAgent_relayTunnel";
2421
2422
2423 if (require('MeshAgent').idleTimeout != null)
2424 {
2425 s.setTimeout(require('MeshAgent').idleTimeout * 1000);
2426 s.on('timeout', tunnel_onIdleTimeout);
2427 }
2428
2429 //sendConsoleText('onTunnelUpgrade - ' + this.tcpport + ' - ' + this.udpport);
2430
2431 if (this.tcpport != null) {
2432 // This is a TCP relay connection, pause now and try to connect to the target.
2433 s.pause();
2434 s.data = onTcpRelayServerTunnelData;
2435 var connectionOptions = { port: parseInt(this.tcpport) };
2436 if (this.tcpaddr != null) { connectionOptions.host = this.tcpaddr; } else { connectionOptions.host = '127.0.0.1'; }
2437 s.tcprelay = net.createConnection(connectionOptions, onTcpRelayTargetTunnelConnect);
2438 s.tcprelay.peerindex = this.index;
2439
2440 // Add the TCP session to the count and update the server
2441 if (s.httprequest.userid != null) {
2442 var userid = getUserIdAndGuestNameFromHttpRequest(s.httprequest);
2443 if (tunnelUserCount.tcp[userid] == null) { tunnelUserCount.tcp[userid] = 1; } else { tunnelUserCount.tcp[userid]++; }
2444 try { mesh.SendCommand({ action: 'sessions', type: 'tcp', value: tunnelUserCount.tcp }); } catch (ex) { }
2445 broadcastSessionsToRegisteredApps();
2446 }
2447 }
2448 if (this.udpport != null) {
2449 // This is a UDP relay connection, get the UDP socket setup. // TODO: ***************
2450 s.data = onUdpRelayServerTunnelData;
2451 s.udprelay = require('dgram').createSocket({ type: 'udp4' });
2452 s.udprelay.bind({ port: 0 });
2453 s.udprelay.peerindex = this.index;
2454 s.udprelay.on('message', onUdpRelayTargetTunnelConnect);
2455 s.udprelay.udpport = this.udpport;
2456 s.udprelay.udpaddr = this.udpaddr;
2457 s.udprelay.first = true;
2458
2459 // Add the UDP session to the count and update the server
2460 if (s.httprequest.userid != null) {
2461 var userid = getUserIdAndGuestNameFromHttpRequest(s.httprequest);
2462 if (tunnelUserCount.udp[userid] == null) { tunnelUserCount.udp[userid] = 1; } else { tunnelUserCount.udp[userid]++; }
2463 try { mesh.SendCommand({ action: 'sessions', type: 'udp', value: tunnelUserCount.tcp }); } catch (ex) { }
2464 broadcastSessionsToRegisteredApps();
2465 }
2466 }
2467 else {
2468 // This is a normal connect for KVM/Terminal/Files
2469 s.data = onTunnelData;
2470 }
2471 }
2472
2473 // If the HTTP Request has a guest name, we need to form a userid that includes the guest name in hex.
2474 // This is so we can tell the server that a session is for a given userid/guest sharing pair.
2475 function getUserIdAndGuestNameFromHttpRequest(request) {
2476 if (request.guestname == null) return request.userid; else return request.guestuserid + '/guest:' + Buffer.from(request.guestname).toString('base64');
2477 }
2478
2479 // Called when UDP relay data is received // TODO****
2480 function onUdpRelayTargetTunnelConnect(data) {
2481 var peerTunnel = tunnels[this.peerindex];
2482 peerTunnel.s.write(data);
2483 }
2484
2485 // Called when we get data from the server for a TCP relay (We have to skip the first received 'c' and pipe the rest)
2486 function onUdpRelayServerTunnelData(data) {
2487 if (this.udprelay.first === true) {
2488 delete this.udprelay.first; // Skip the first 'c' that is received.
2489 } else {
2490 this.udprelay.send(data, parseInt(this.udprelay.udpport), this.udprelay.udpaddr ? this.udprelay.udpaddr : '127.0.0.1');
2491 }
2492 }
2493
2494 // Called when the TCP relay target is connected
2495 function onTcpRelayTargetTunnelConnect() {
2496 var peerTunnel = tunnels[this.peerindex];
2497 this.pipe(peerTunnel.s); // Pipe Target --> Server
2498 peerTunnel.s.first = true;
2499 peerTunnel.s.resume();
2500 }
2501
2502 // Called when we get data from the server for a TCP relay (We have to skip the first received 'c' and pipe the rest)
2503 function onTcpRelayServerTunnelData(data) {
2504 if (this.first == true) {
2505 this.first = false;
2506 this.pipe(this.tcprelay, { dataTypeSkip: 1 }); // Pipe Server --> Target (don't pipe text type websocket frames)
2507 }
2508 }
2509
2510 function onTunnelClosed()
2511 {
2512 if (this.httprequest._dispatcher != null && this.httprequest.term == null)
2513 {
2514 // 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
2515 if (this.httprequest._dispatcher.close) { this.httprequest._dispatcher.close(); }
2516 this.httprequest._dispatcher = null;
2517 }
2518
2519 if (this.tunnel)
2520 {
2521 if (tunnels[this.httprequest.index] == null)
2522 {
2523 this.tunnel.s = null;
2524 this.tunnel = null;
2525 return;
2526 }
2527 }
2528
2529 var tunnel = tunnels[this.httprequest.index];
2530 if (tunnel == null) return; // Stop duplicate calls.
2531
2532 // Perform display locking on disconnect
2533 if ((this.httprequest.protocol == 2) && (this.httprequest.autolock === true)) {
2534 // Look for a TSID
2535 var tsid = null;
2536 if ((this.httprequest.xoptions != null) && (typeof this.httprequest.xoptions.tsid == 'number')) { tsid = this.httprequest.xoptions.tsid; }
2537
2538 // Lock the current user out of the desktop
2539 MeshServerLogEx(53, null, "Locking remote user out of desktop", this.httprequest);
2540 lockDesktop(tsid);
2541 }
2542
2543 // If this is a routing session, clean up and send the new session counts.
2544 if (this.httprequest.userid != null) {
2545 if (this.httprequest.tcpport != null) {
2546 var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest);
2547 if (tunnelUserCount.tcp[userid] != null) { tunnelUserCount.tcp[userid]--; if (tunnelUserCount.tcp[userid] <= 0) { delete tunnelUserCount.tcp[userid]; } }
2548 try { mesh.SendCommand({ action: 'sessions', type: 'tcp', value: tunnelUserCount.tcp }); } catch (ex) { }
2549 broadcastSessionsToRegisteredApps();
2550 } else if (this.httprequest.udpport != null) {
2551 var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest);
2552 if (tunnelUserCount.udp[userid] != null) { tunnelUserCount.udp[userid]--; if (tunnelUserCount.udp[userid] <= 0) { delete tunnelUserCount.udp[userid]; } }
2553 try { mesh.SendCommand({ action: 'sessions', type: 'udp', value: tunnelUserCount.udp }); } catch (ex) { }
2554 broadcastSessionsToRegisteredApps();
2555 }
2556 }
2557
2558 try {
2559 // Sent tunnel statistics to the server, only send this if compression was used.
2560 if ((this.bytesSent_uncompressed) && (this.bytesSent_uncompressed.toString() != this.bytesSent_actual.toString())) {
2561 mesh.SendCommand({
2562 action: 'tunnelCloseStats',
2563 url: tunnel.url,
2564 userid: tunnel.userid,
2565 protocol: tunnel.protocol,
2566 sessionid: tunnel.sessionid,
2567 sent: this.bytesSent_uncompressed.toString(),
2568 sentActual: this.bytesSent_actual.toString(),
2569 sentRatio: this.bytesSent_ratio,
2570 received: this.bytesReceived_uncompressed.toString(),
2571 receivedActual: this.bytesReceived_actual.toString(),
2572 receivedRatio: this.bytesReceived_ratio
2573 });
2574 }
2575 } catch (ex) { }
2576
2577 //sendConsoleText("Tunnel #" + this.httprequest.index + " closed. Sent -> " + this.bytesSent_uncompressed + ' bytes (uncompressed), ' + this.bytesSent_actual + ' bytes (actual), ' + this.bytesSent_ratio + '% compression', this.httprequest.sessionid);
2578
2579
2580 /*
2581 // Close the watcher if required
2582 if (this.httprequest.watcher != undefined) {
2583 //console.log('Closing watcher: ' + this.httprequest.watcher.path);
2584 //this.httprequest.watcher.close(); // TODO: This line causes the agent to crash!!!!
2585 delete this.httprequest.watcher;
2586 }
2587 */
2588
2589 // If there is a upload or download active on this connection, close the file
2590 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; }
2591 if (this.httprequest.downloadFile) { delete this.httprequest.downloadFile; }
2592
2593 // Clean up WebRTC
2594 if (this.webrtc != null) {
2595 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; }
2596 if (this.webrtc.websocket) { delete this.webrtc.websocket; }
2597 try { this.webrtc.close(); } catch (ex) { }
2598 this.webrtc.removeAllListeners('connected');
2599 this.webrtc.removeAllListeners('disconnected');
2600 this.webrtc.removeAllListeners('dataChannel');
2601 delete this.webrtc;
2602 }
2603
2604 // Clean up WebSocket
2605 delete tunnels[this.httprequest.index];
2606 tunnel = null;
2607 this.tunnel.s = null;
2608 this.tunnel = null;
2609 this.removeAllListeners('data');
2610 }
2611 function onTunnelSendOk() { /*sendConsoleText("Tunnel #" + this.index + " SendOK.", this.sessionid);*/ }
2612
2613 function terminal_onconnection (c)
2614 {
2615 if (this.httprequest.connectionPromise.completed)
2616 {
2617 c.end();
2618 }
2619 else
2620 {
2621 this.httprequest.connectionPromise._res(c);
2622 }
2623 }
2624 function terminal_user_onconnection(c)
2625 {
2626 console.info1('completed-2: ' + this.connectionPromise.completed);
2627
2628 if (this.connectionPromise.completed)
2629 {
2630 c.end();
2631 }
2632 else
2633 {
2634 this.connectionPromise._res(c);
2635 }
2636 }
2637 function terminal_stderr_ondata(c)
2638 {
2639 this.stdout.write(c);
2640 }
2641 function terminal_onend()
2642 {
2643 this.httprequest.process.kill();
2644 }
2645
2646 function terminal_onexit()
2647 {
2648 this.tunnel.end();
2649 }
2650 function terminal_onfinalized()
2651 {
2652 this.httprequest = null;
2653 console.info1('Dispatcher Finalized');
2654 }
2655 function terminal_end()
2656 {
2657 if (this.httprequest == null) { return; }
2658 if (this.httprequest.tpromise._consent) { this.httprequest.tpromise._consent.close(); }
2659 if (this.httprequest.connectionPromise) { this.httprequest.connectionPromise._rej('Closed'); }
2660
2661 // Remove the terminal session to the count to update the server
2662 if (this.httprequest.userid != null)
2663 {
2664 var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest);
2665 if (tunnelUserCount.terminal[userid] != null) { tunnelUserCount.terminal[userid]--; if (tunnelUserCount.terminal[userid] <= 0) { delete tunnelUserCount.terminal[userid]; } }
2666 try { mesh.SendCommand({ action: 'sessions', type: 'terminal', value: tunnelUserCount.terminal }); } catch (ex) { }
2667 broadcastSessionsToRegisteredApps();
2668 }
2669
2670 if (process.platform == 'win32')
2671 {
2672 // Unpipe the web socket
2673 this.unpipe(this.httprequest._term);
2674 if (this.httprequest._term) { this.httprequest._term.unpipe(this); }
2675
2676 // Unpipe the WebRTC channel if needed (This will also be done when the WebRTC channel ends).
2677 if (this.rtcchannel)
2678 {
2679 this.rtcchannel.unpipe(this.httprequest._term);
2680 if (this.httprequest._term) { this.httprequest._term.unpipe(this.rtcchannel); }
2681 }
2682
2683 // Clean up
2684 if (this.httprequest._term) { this.httprequest._term.end(); }
2685 this.httprequest._term = null;
2686 this.httprequest._dispatcher = null;
2687 }
2688
2689 this.httprequest = null;
2690
2691 }
2692
2693 function terminal_consent_ask(ws) {
2694 ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 }));
2695 var consentMessage = currentTranslation['terminalConsent'].replace(/\{0\}/g, ws.httprequest.realname).replace(/\{1\}/g, ws.httprequest.username);
2696 var consentTitle = 'MeshCentral';
2697 if (ws.httprequest.soptions != null) {
2698 if (ws.httprequest.soptions.consentTitle != null) { consentTitle = ws.httprequest.soptions.consentTitle; }
2699 if (ws.httprequest.soptions.consentMsgTerminal != null) { consentMessage = ws.httprequest.soptions.consentMsgTerminal.replace(/\{0\}/g, ws.httprequest.realname).replace(/\{1\}/g, ws.httprequest.username); }
2700 }
2701 if (process.platform == 'win32') {
2702 var enhanced = false;
2703 if (ws.httprequest.oldStyle === false) {
2704 try { require('win-userconsent'); enhanced = true; } catch (ex) { }
2705 }
2706 if (enhanced) {
2707 var ipr = server_getUserImage(ws.httprequest.userid);
2708 ipr.consentTitle = consentTitle;
2709 ipr.consentMessage = consentMessage;
2710 ipr.consentTimeout = ws.httprequest.consentTimeout;
2711 ipr.consentAutoAccept = ws.httprequest.consentAutoAccept;
2712 ipr.username = ws.httprequest.realname;
2713 ipr.tsid = ws.tsid;
2714 ipr.translations = { Allow: currentTranslation['allow'], Deny: currentTranslation['deny'], Auto: currentTranslation['autoAllowForFive'], Caption: consentMessage };
2715 ws.httprequest.tpromise._consent = ipr.then(function (img) {
2716 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 });
2717 this.__childPromise.close = this.consent.close.bind(this.consent);
2718 return (this.consent);
2719 });
2720 } else {
2721 ws.httprequest.tpromise._consent = require('message-box').create(consentTitle, consentMessage, ws.httprequest.consentTimeout);
2722 }
2723 } else {
2724 ws.httprequest.tpromise._consent = require('message-box').create(consentTitle, consentMessage, ws.httprequest.consentTimeout);
2725 }
2726 ws.httprequest.tpromise._consent.retPromise = ws.httprequest.tpromise;
2727 ws.httprequest.tpromise._consent.then(function (always) {
2728 if (always && process.platform == 'win32') { server_set_consentTimer(this.retPromise.httprequest.userid); }
2729 // Success
2730 MeshServerLogEx(27, null, "Local user accepted remote terminal request (" + this.retPromise.httprequest.remoteaddr + ")", this.retPromise.that.httprequest);
2731 this.retPromise.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null, msgid: 0 }));
2732 this.retPromise._consent = null;
2733 this.retPromise._res();
2734 }, function (e) {
2735 if (this.retPromise.that) {
2736 if(this.retPromise.that.httprequest){ // User Consent Denied
2737 MeshServerLogEx(28, null, "Local user rejected remote terminal request (" + this.retPromise.that.httprequest.remoteaddr + ")", this.retPromise.that.httprequest);
2738 } else { } // Connection was closed server side, maybe log some messages somewhere?
2739 this.retPromise._consent = null;
2740 this.retPromise.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
2741 } else { } // no websocket, maybe log some messages somewhere?
2742 this.retPromise._rej(e.toString());
2743 });
2744 }
2745
2746 function terminal_promise_connection_rejected(e)
2747 {
2748 // FAILED to connect terminal
2749 this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
2750 this.ws.end();
2751 }
2752
2753 function terminal_promise_connection_resolved(term)
2754 {
2755 this._internal.completedArgs = [];
2756
2757 // SUCCESS
2758 var stdoutstream;
2759 var stdinstream;
2760 if (process.platform == 'win32')
2761 {
2762 this.ws.httprequest._term = term;
2763 this.ws.httprequest._term.tunnel = this.ws;
2764 stdoutstream = stdinstream = term;
2765 }
2766 else
2767 {
2768 term.descriptorMetadata = 'Remote Terminal';
2769 this.ws.httprequest.process = term;
2770 this.ws.httprequest.process.tunnel = this.ws;
2771 term.stderr.stdout = term.stdout;
2772 term.stderr.on('data', terminal_stderr_ondata);
2773 stdoutstream = term.stdout;
2774 stdinstream = term.stdin;
2775 this.ws.prependListener('end', terminal_onend);
2776 term.prependListener('exit', terminal_onexit);
2777 }
2778
2779 this.ws.removeAllListeners('data');
2780 this.ws.on('data', onTunnelControlData);
2781
2782 stdoutstream.pipe(this.ws, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
2783 this.ws.pipe(stdinstream, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
2784
2785 // Add the terminal session to the count to update the server
2786 if (this.ws.httprequest.userid != null)
2787 {
2788 var userid = getUserIdAndGuestNameFromHttpRequest(this.ws.httprequest);
2789 if (tunnelUserCount.terminal[userid] == null) { tunnelUserCount.terminal[userid] = 1; } else { tunnelUserCount.terminal[userid]++; }
2790 try { mesh.SendCommand({ action: 'sessions', type: 'terminal', value: tunnelUserCount.terminal }); } catch (ex) { }
2791 broadcastSessionsToRegisteredApps();
2792 }
2793
2794 // Toast Notification, if required
2795 if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 2))
2796 {
2797 // User Notifications is required
2798 var notifyMessage = currentTranslation['terminalNotify'].replace(/\{0\}/g, this.ws.httprequest.realname ? this.ws.httprequest.realname : this.ws.httprequest.username);
2799 var notifyTitle = "MeshCentral";
2800 if (this.ws.httprequest.soptions != null)
2801 {
2802 if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; }
2803 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); }
2804 }
2805 try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (ex) { }
2806 }
2807 this.ws = null;
2808 }
2809 function terminal_promise_consent_rejected(e)
2810 {
2811 // DO NOT start terminal
2812 if (this.that) {
2813 if(this.that.httprequest){ // User Consent Denied
2814 if ((this.that.httprequest.oldStyle === true) && (this.that.httprequest.consentAutoAccept === true) && (e.toString() != "7")) {
2815 terminal_promise_consent_resolved.call(this); // oldStyle prompt timed out and User Consent is not required so connect anyway
2816 return;
2817 }
2818 } else { } // Connection was closed server side, maybe log some messages somewhere?
2819 this.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
2820 this.that.end();
2821
2822 this.that = null;
2823 this.httprequest = null;
2824 } else { } // no websocket, maybe log some messages somewhere?
2825 }
2826 function promise_init(res, rej) { this._res = res; this._rej = rej; }
2827 function terminal_userpromise_resolved(u)
2828 {
2829
2830 var that = this.that;
2831 if (u.Active.length > 0)
2832 {
2833 var tmp;
2834 var username = '"' + u.Active[0].Domain + '\\' + u.Active[0].Username + '"';
2835
2836
2837 if (require('win-virtual-terminal').supported)
2838 {
2839 // ConPTY PseudoTerminal
2840 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] } });
2841 }
2842 else
2843 {
2844 // Legacy Terminal
2845 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] } });
2846 }
2847 that.httprequest._dispatcher = tmp;
2848 that.httprequest._dispatcher.connectionPromise = that.httprequest.connectionPromise;
2849 that.httprequest._dispatcher.on('connection', terminal_user_onconnection);
2850 that.httprequest._dispatcher.on('~', terminal_onfinalized);
2851 }
2852 this.that = null;
2853 that = null;
2854 }
2855
2856 function terminal_promise_consent_resolved()
2857 {
2858 this.httprequest.connectionPromise = new promise(promise_init);
2859 this.httprequest.connectionPromise.ws = this.that;
2860
2861 // Start Terminal
2862 if (process.platform == 'win32')
2863 {
2864 try
2865 {
2866 var cols = 80, rows = 25;
2867 if (this.httprequest.xoptions)
2868 {
2869 if (this.httprequest.xoptions.rows) { rows = this.httprequest.xoptions.rows; }
2870 if (this.httprequest.xoptions.cols) { cols = this.httprequest.xoptions.cols; }
2871 }
2872
2873 if ((this.httprequest.protocol == 1) || (this.httprequest.protocol == 6))
2874 {
2875 // Admin Terminal
2876 if (require('win-virtual-terminal').supported)
2877 {
2878 // ConPTY PseudoTerminal
2879 // this.httprequest._term = require('win-virtual-terminal')[this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'](80, 25);
2880
2881 // The above line is commented out, because there is a bug with ClosePseudoConsole() API, so this is the workaround
2882 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] } });
2883 this.httprequest._dispatcher.httprequest = this.httprequest;
2884 this.httprequest._dispatcher.on('connection', terminal_onconnection);
2885 this.httprequest._dispatcher.on('~', terminal_onfinalized);
2886 }
2887 else
2888 {
2889 // Legacy Terminal
2890 this.httprequest.connectionPromise._res(require('win-terminal')[this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'](cols, rows));
2891 }
2892 }
2893 else
2894 {
2895 // Logged in user
2896 var userPromise = require('user-sessions').enumerateUsers();
2897 userPromise.that = this;
2898 userPromise.cols = cols;
2899 userPromise.rows = rows;
2900 userPromise.then(terminal_userpromise_resolved);
2901 }
2902 } catch (ex)
2903 {
2904 this.httprequest.connectionPromise._rej('Failed to start remote terminal session, ' + ex.toString());
2905 }
2906 }
2907 else
2908 {
2909 try
2910 {
2911 var bash = fs.existsSync('/bin/bash') ? '/bin/bash' : false;
2912 var sh = fs.existsSync('/bin/sh') ? '/bin/sh' : false;
2913 var login = process.platform == 'linux' ? '/bin/login' : '/usr/bin/login';
2914
2915 var env = { HISTCONTROL: 'ignoreboth' };
2916 if (process.env['LANG']) { env['LANG'] = process.env['LANG']; }
2917 if (process.env['PATH']) { env['PATH'] = process.env['PATH']; }
2918 if (typeof this.httprequest.terminalUserVariable == 'string' && this.httprequest.terminalUserVariable != '') {
2919 if (this.httprequest.terminalUserVariable == 'realname') {
2920 env['MESHCENTRAL_USER'] = (this.httprequest.realname ? this.httprequest.realname : 'unknown');
2921 } else if (this.httprequest.terminalUserVariable == 'identifier') {
2922 env['MESHCENTRAL_USER'] = (this.httprequest.userid ? this.httprequest.userid : (this.httprequest.guestuserid ? 'deviceshare:' + this.httprequest.guestuserid : 'unknown'));
2923 } else if (this.httprequest.terminalUserVariable == 'username') {
2924 env['MESHCENTRAL_USER'] = (this.httprequest.username ? this.httprequest.username : 'unknown');
2925 }
2926 }
2927 if (this.httprequest.xoptions)
2928 {
2929 if (this.httprequest.xoptions.rows) { env.LINES = ('' + this.httprequest.xoptions.rows); }
2930 if (this.httprequest.xoptions.cols) { env.COLUMNS = ('' + this.httprequest.xoptions.cols); }
2931 }
2932 var options = { type: childProcess.SpawnTypes.TERM, uid: (this.httprequest.protocol == 8) ? require('user-sessions').consoleUid() : null, env: env };
2933 if (this.httprequest.xoptions && this.httprequest.xoptions.requireLogin)
2934 {
2935 if (!require('fs').existsSync(login)) { throw ('Unable to spawn login process'); }
2936 this.httprequest.connectionPromise._res(childProcess.execFile(login, ['login'], options)); // Start login shell
2937 }
2938 else if (bash)
2939 {
2940 var p = childProcess.execFile(bash, ['bash'], options); // Start bash
2941 // Spaces at the beginning of lines are needed to hide commands from the command history
2942 if ((obj.serverInfo.termlaunchcommand != null) && (typeof obj.serverInfo.termlaunchcommand[process.platform] == 'string'))
2943 {
2944 if (obj.serverInfo.termlaunchcommand[process.platform] != '') { p.stdin.write(obj.serverInfo.termlaunchcommand[process.platform]); }
2945 } else if (process.platform == 'linux') { p.stdin.write(' alias ls=\'ls --color=auto\';clear\n'); }
2946 this.httprequest.connectionPromise._res(p);
2947 }
2948 else if (sh)
2949 {
2950 var p = childProcess.execFile(sh, ['sh'], options); // Start sh
2951 // Spaces at the beginning of lines are needed to hide commands from the command history
2952 if ((obj.serverInfo.termlaunchcommand != null) && (typeof obj.serverInfo.termlaunchcommand[process.platform] == 'string'))
2953 {
2954 if (obj.serverInfo.termlaunchcommand[process.platform] != '') { p.stdin.write(obj.serverInfo.termlaunchcommand[process.platform]); }
2955 } else if (process.platform == 'linux') { p.stdin.write(' alias ls=\'ls --color=auto\';clear\n'); }
2956 this.httprequest.connectionPromise._res(p);
2957 }
2958 else
2959 {
2960 this.httprequest.connectionPromise._rej('Failed to start remote terminal session, no shell found');
2961 }
2962 } catch (ex)
2963 {
2964 this.httprequest.connectionPromise._rej('Failed to start remote terminal session, ' + ex.toString());
2965 }
2966 }
2967
2968 this.httprequest.connectionPromise.then(terminal_promise_connection_resolved, terminal_promise_connection_rejected);
2969 this.that = null;
2970 this.httprequest = null;
2971 }
2972 function tunnel_kvm_end()
2973 {
2974 --this.desktop.kvm.connectionCount;
2975
2976 // Remove ourself from the list of remote desktop session
2977 var i = this.desktop.kvm.tunnels.indexOf(this);
2978 if (i >= 0) { this.desktop.kvm.tunnels.splice(i, 1); }
2979
2980 // Send a metadata update to all desktop sessions
2981 var users = {};
2982 if (this.httprequest.desktop.kvm.tunnels != null)
2983 {
2984 for (var i in this.httprequest.desktop.kvm.tunnels)
2985 {
2986 try
2987 {
2988 var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest.desktop.kvm.tunnels[i].httprequest);
2989 if (users[userid] == null) { users[userid] = 1; } else { users[userid]++; }
2990 } catch (ex) { sendConsoleText(ex); }
2991 }
2992 for (var i in this.httprequest.desktop.kvm.tunnels)
2993 {
2994 try { this.httprequest.desktop.kvm.tunnels[i].write(JSON.stringify({ ctrlChannel: '102938', type: 'metadata', users: users })); } catch (ex) { }
2995 }
2996 tunnelUserCount.desktop = users;
2997 try { mesh.SendCommand({ action: 'sessions', type: 'kvm', value: users }); } catch (ex) { }
2998 broadcastSessionsToRegisteredApps();
2999 }
3000
3001 // Unpipe the web socket
3002 try
3003 {
3004 this.unpipe(this.httprequest.desktop.kvm);
3005 this.httprequest.desktop.kvm.unpipe(this);
3006 } catch (ex) { }
3007
3008 // Unpipe the WebRTC channel if needed (This will also be done when the WebRTC channel ends).
3009 if (this.rtcchannel)
3010 {
3011 try
3012 {
3013 this.rtcchannel.unpipe(this.httprequest.desktop.kvm);
3014 this.httprequest.desktop.kvm.unpipe(this.rtcchannel);
3015 }
3016 catch (ex) { }
3017 }
3018
3019 // Place wallpaper back if needed
3020 // TODO
3021
3022 if (this.desktop.kvm.connectionCount == 0)
3023 {
3024 // Display a toast message. This may not be supported on all platforms.
3025 // try { require('toaster').Toast('MeshCentral', 'Remote Desktop Control Ended.'); } catch (ex) { }
3026
3027 this.httprequest.desktop.kvm.end();
3028 if (this.httprequest.desktop.kvm.connectionBar)
3029 {
3030 this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close');
3031 this.httprequest.desktop.kvm.connectionBar.close();
3032 this.httprequest.desktop.kvm.connectionBar = null;
3033 }
3034 } else
3035 {
3036 for (var i in this.httprequest.desktop.kvm.users)
3037 {
3038 if ((this.httprequest.desktop.kvm.users[i] == this.httprequest.username) && this.httprequest.desktop.kvm.connectionBar)
3039 {
3040 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; } }
3041 this.httprequest.desktop.kvm.users.splice(i, 1);
3042 this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close');
3043 this.httprequest.desktop.kvm.connectionBar.close();
3044 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);
3045 this.httprequest.desktop.kvm.connectionBar.httprequest = this.httprequest;
3046 this.httprequest.desktop.kvm.connectionBar.on('close', function ()
3047 {
3048 MeshServerLogEx(29, null, "Remote Desktop Connection forcefully closed by local user (" + this.httprequest.remoteaddr + ")", this.httprequest);
3049 for (var i in this.httprequest.desktop.kvm._pipedStreams)
3050 {
3051 this.httprequest.desktop.kvm._pipedStreams[i].end();
3052 }
3053 this.httprequest.desktop.kvm.end();
3054 });
3055 break;
3056 }
3057 }
3058 }
3059
3060 if(this.httprequest.desktop.kvm.connectionBar)
3061 {
3062 console.info1('Setting ConnectionBar request to NULL');
3063 this.httprequest.desktop.kvm.connectionBar.httprequest = null;
3064 }
3065
3066 this.httprequest = null;
3067 this.desktop.tunnel = null;
3068 }
3069
3070 function kvm_tunnel_consentpromise_closehandler()
3071 {
3072 if (this._consentpromise && this._consentpromise.close) { this._consentpromise.close(); }
3073 }
3074
3075 function kvm_consent_ok(ws) {
3076 // User Consent Prompt is not required because no user is present
3077 if (ws.httprequest.consent && (ws.httprequest.consent & 1)){
3078 // User Notifications is required
3079 MeshServerLogEx(35, null, "Started remote desktop with toast notification (" + ws.httprequest.remoteaddr + ")", ws.httprequest);
3080 var notifyMessage = currentTranslation['desktopNotify'].replace(/\{0\}/g, ws.httprequest.realname);
3081 var notifyTitle = "MeshCentral";
3082 if (ws.httprequest.soptions != null) {
3083 if (ws.httprequest.soptions.notifyTitle != null) { notifyTitle = ws.httprequest.soptions.notifyTitle; }
3084 if (ws.httprequest.soptions.notifyMsgDesktop != null) { notifyMessage = ws.httprequest.soptions.notifyMsgDesktop.replace(/\{0\}/g, ws.httprequest.realname).replace(/\{1\}/g, ws.httprequest.username); }
3085 }
3086 try { require('toaster').Toast(notifyTitle, notifyMessage, ws.tsid); } catch (ex) { }
3087 } else {
3088 MeshServerLogEx(36, null, "Started remote desktop without notification (" + ws.httprequest.remoteaddr + ")", ws.httprequest);
3089 }
3090 if (ws.httprequest.consent && (ws.httprequest.consent & 0x40)) {
3091 // Connection Bar is required
3092 if (ws.httprequest.desktop.kvm.connectionBar) {
3093 ws.httprequest.desktop.kvm.connectionBar.removeAllListeners('close');
3094 ws.httprequest.desktop.kvm.connectionBar.close();
3095 }
3096 try {
3097 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);
3098 MeshServerLogEx(31, null, "Remote Desktop Connection Bar Activated/Updated (" + ws.httprequest.remoteaddr + ")", ws.httprequest);
3099 } catch (ex) {
3100 MeshServerLogEx(32, null, "Remote Desktop Connection Bar Failed or not Supported (" + ws.httprequest.remoteaddr + ")", ws.httprequest);
3101 }
3102 if (ws.httprequest.desktop.kvm.connectionBar) {
3103 ws.httprequest.desktop.kvm.connectionBar.state = {
3104 userid: ws.httprequest.userid,
3105 xuserid: ws.httprequest.xuserid,
3106 username: ws.httprequest.username,
3107 sessionid: ws.httprequest.sessionid,
3108 remoteaddr: ws.httprequest.remoteaddr,
3109 guestname: ws.httprequest.guestname,
3110 desktop: ws.httprequest.desktop
3111 };
3112 ws.httprequest.desktop.kvm.connectionBar.on('close', function () {
3113 console.info1('Connection Bar Forcefully closed');
3114 MeshServerLogEx(29, null, "Remote Desktop Connection forcefully closed by local user (" + this.state.remoteaddr + ")", this.state);
3115 for (var i in this.state.desktop.kvm._pipedStreams) {
3116 this.state.desktop.kvm._pipedStreams[i].end();
3117 }
3118 this.state.desktop.kvm.end();
3119 });
3120 }
3121 }
3122 ws.httprequest.desktop.kvm.pipe(ws, { dataTypeSkip: 1 });
3123 if (ws.httprequest.autolock) {
3124 destopLockHelper_pipe(ws.httprequest);
3125 }
3126 }
3127
3128 function kvm_consent_ask(ws){
3129 // Send a console message back using the console channel, "\n" is supported.
3130 ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 }));
3131 var consentMessage = currentTranslation['desktopConsent'].replace(/\{0\}/g, ws.httprequest.realname).replace(/\{1\}/g, ws.httprequest.username);
3132 var consentTitle = 'MeshCentral';
3133 if (ws.httprequest.soptions != null) {
3134 if (ws.httprequest.soptions.consentTitle != null) { consentTitle = ws.httprequest.soptions.consentTitle; }
3135 if (ws.httprequest.soptions.consentMsgDesktop != null) { consentMessage = ws.httprequest.soptions.consentMsgDesktop.replace(/\{0\}/g, ws.httprequest.realname).replace(/\{1\}/g, ws.httprequest.username); }
3136 }
3137 var pr;
3138 if (process.platform == 'win32') {
3139 var enhanced = false;
3140 if (ws.httprequest.oldStyle === false) {
3141 try { require('win-userconsent'); enhanced = true; } catch (ex) { }
3142 }
3143 if (enhanced) {
3144 var ipr = server_getUserImage(ws.httprequest.userid);
3145 ipr.consentTitle = consentTitle;
3146 ipr.consentMessage = consentMessage;
3147 ipr.consentTimeout = ws.httprequest.consentTimeout;
3148 ipr.consentAutoAccept = ws.httprequest.consentAutoAccept;
3149 ipr.tsid = ws.tsid;
3150 ipr.username = ws.httprequest.realname;
3151 ipr.translation = { Allow: currentTranslation['allow'], Deny: currentTranslation['deny'], Auto: currentTranslation['autoAllowForFive'], Caption: consentMessage };
3152 pr = ipr.then(function (img) {
3153 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 });
3154 this.__childPromise.close = this.consent.close.bind(this.consent);
3155 return (this.consent);
3156 });
3157 } else {
3158 pr = require('message-box').create(consentTitle, consentMessage, ws.httprequest.consentTimeout, null, ws.tsid);
3159 }
3160 } else {
3161 pr = require('message-box').create(consentTitle, consentMessage, ws.httprequest.consentTimeout, null, ws.tsid);
3162 }
3163 pr.ws = ws;
3164 ws.pause();
3165 ws._consentpromise = pr;
3166 ws.prependOnceListener('end', kvm_tunnel_consentpromise_closehandler);
3167 pr.then(kvm_consentpromise_resolved, kvm_consentpromise_rejected);
3168 }
3169
3170 function kvm_consentpromise_rejected(e)
3171 {
3172 if (this.ws) {
3173 if(this.ws.httprequest){ // User Consent Denied
3174 if ((this.ws.httprequest.oldStyle === true) && (this.ws.httprequest.consentAutoAccept === true) && (e.toString() != "7")) {
3175 kvm_consentpromise_resolved.call(this); // oldStyle prompt timed out and User Consent is not required so connect anyway
3176 return;
3177 }
3178 MeshServerLogEx(34, null, "Failed to start remote desktop after local user rejected (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
3179 } else { } // Connection was closed server side, maybe log some messages somewhere?
3180 this.ws._consentpromise = null;
3181 this.ws.end(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
3182 this.ws = null;
3183 } else { } // no websocket, maybe log some messages somewhere?
3184 }
3185 function kvm_consentpromise_resolved(always)
3186 {
3187 if (always && process.platform=='win32') { server_set_consentTimer(this.ws.httprequest.userid); }
3188
3189 // Success
3190 this.ws._consentpromise = null;
3191 MeshServerLogEx(30, null, "Starting remote desktop after local user accepted (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
3192 this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null, msgid: 0 }));
3193 if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 1))
3194 {
3195 // User Notifications is required
3196 var notifyMessage = currentTranslation['desktopNotify'].replace(/\{0\}/g, this.ws.httprequest.realname);
3197 var notifyTitle = "MeshCentral";
3198 if (this.ws.httprequest.soptions != null)
3199 {
3200 if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; }
3201 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); }
3202 }
3203 try { require('toaster').Toast(notifyTitle, notifyMessage, tsid); } catch (ex) { }
3204 }
3205 if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 0x40))
3206 {
3207 // Connection Bar is required
3208 if (this.ws.httprequest.desktop.kvm.connectionBar)
3209 {
3210 this.ws.httprequest.desktop.kvm.connectionBar.removeAllListeners('close');
3211 this.ws.httprequest.desktop.kvm.connectionBar.close();
3212 }
3213 try
3214 {
3215 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);
3216 MeshServerLogEx(31, null, "Remote Desktop Connection Bar Activated/Updated (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
3217 } catch (ex)
3218 {
3219 if (process.platform != 'darwin')
3220 {
3221 MeshServerLogEx(32, null, "Remote Desktop Connection Bar Failed or Not Supported (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
3222 }
3223 }
3224 try {
3225 if (this.ws.httprequest.desktop.kvm.connectionBar) {
3226 this.ws.httprequest.desktop.kvm.connectionBar.httprequest = this.ws.httprequest;
3227 this.ws.httprequest.desktop.kvm.connectionBar.on('close', function () {
3228 MeshServerLogEx(29, null, "Remote Desktop Connection forcefully closed by local user (" + this.httprequest.remoteaddr + ")", this.httprequest);
3229 for (var i in this.httprequest.desktop.kvm._pipedStreams) {
3230 this.httprequest.desktop.kvm._pipedStreams[i].end();
3231 }
3232 this.httprequest.desktop.kvm.end();
3233 });
3234 }
3235 }
3236 catch (ex)
3237 {
3238 if (process.platform != 'darwin')
3239 {
3240 MeshServerLogEx(32, null, "Failed2(" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
3241 }
3242 }
3243 }
3244 this.ws.httprequest.desktop.kvm.pipe(this.ws, { dataTypeSkip: 1 });
3245 if (this.ws.httprequest.autolock)
3246 {
3247 destopLockHelper_pipe(this.ws.httprequest);
3248 }
3249 this.ws.resume();
3250 this.ws = null;
3251 }
3252
3253 function files_consent_ok(ws){
3254 // User Consent Prompt is not required
3255 if (ws.httprequest.consent && (ws.httprequest.consent & 4)) {
3256 // User Notifications is required
3257 MeshServerLogEx(42, null, "Started remote files with toast notification (" + ws.httprequest.remoteaddr + ")", ws.httprequest);
3258 var notifyMessage = currentTranslation['fileNotify'].replace(/\{0\}/g, ws.httprequest.realname);
3259 var notifyTitle = "MeshCentral";
3260 if (ws.httprequest.soptions != null) {
3261 if (ws.httprequest.soptions.notifyTitle != null) { notifyTitle = ws.httprequest.soptions.notifyTitle; }
3262 if (ws.httprequest.soptions.notifyMsgFiles != null) { notifyMessage = ws.httprequest.soptions.notifyMsgFiles.replace(/\{0\}/g, ws.httprequest.realname).replace(/\{1\}/g, ws.httprequest.username); }
3263 }
3264 try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (ex) { }
3265 } else {
3266 MeshServerLogEx(43, null, "Started remote files without notification (" + ws.httprequest.remoteaddr + ")", ws.httprequest);
3267 }
3268 ws.resume();
3269 }
3270
3271 function files_consent_ask(ws){
3272 // Send a console message back using the console channel, "\n" is supported.
3273 ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 }));
3274 var consentMessage = currentTranslation['fileConsent'].replace(/\{0\}/g, ws.httprequest.realname).replace(/\{1\}/g, ws.httprequest.username);
3275 var consentTitle = 'MeshCentral';
3276
3277 if (ws.httprequest.soptions != null) {
3278 if (ws.httprequest.soptions.consentTitle != null) { consentTitle = ws.httprequest.soptions.consentTitle; }
3279 if (ws.httprequest.soptions.consentMsgFiles != null) { consentMessage = ws.httprequest.soptions.consentMsgFiles.replace(/\{0\}/g, ws.httprequest.realname).replace(/\{1\}/g, ws.httprequest.username); }
3280 }
3281 var pr;
3282 if (process.platform == 'win32') {
3283 var enhanced = false;
3284 if (ws.httprequest.oldStyle === false) {
3285 try { require('win-userconsent'); enhanced = true; } catch (ex) { }
3286 }
3287 if (enhanced) {
3288 var ipr = server_getUserImage(ws.httprequest.userid);
3289 ipr.consentTitle = consentTitle;
3290 ipr.consentMessage = consentMessage;
3291 ipr.consentTimeout = ws.httprequest.consentTimeout;
3292 ipr.consentAutoAccept = ws.httprequest.consentAutoAccept;
3293 ipr.username = ws.httprequest.realname;
3294 ipr.tsid = ws.tsid;
3295 ipr.translations = { Allow: currentTranslation['allow'], Deny: currentTranslation['deny'], Auto: currentTranslation['autoAllowForFive'], Caption: consentMessage };
3296 pr = ipr.then(function (img) {
3297 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 });
3298 this.__childPromise.close = this.consent.close.bind(this.consent);
3299 return (this.consent);
3300 });
3301 } else {
3302 pr = require('message-box').create(consentTitle, consentMessage, ws.httprequest.consentTimeout, null);
3303 }
3304 } else {
3305 pr = require('message-box').create(consentTitle, consentMessage, ws.httprequest.consentTimeout, null);
3306 }
3307 pr.ws = ws;
3308 ws.pause();
3309 ws._consentpromise = pr;
3310 ws.prependOnceListener('end', files_tunnel_endhandler);
3311 pr.then(files_consentpromise_resolved, files_consentpromise_rejected);
3312 }
3313
3314 function files_consentpromise_resolved(always)
3315 {
3316 if (always && process.platform == 'win32') { server_set_consentTimer(this.ws.httprequest.userid); }
3317
3318 // Success
3319 this.ws._consentpromise = null;
3320 MeshServerLogEx(40, null, "Starting remote files after local user accepted (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
3321 this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null }));
3322 if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 4))
3323 {
3324 // User Notifications is required
3325 var notifyMessage = currentTranslation['fileNotify'].replace(/\{0\}/g, this.ws.httprequest.realname);
3326 var notifyTitle = "MeshCentral";
3327 if (this.ws.httprequest.soptions != null)
3328 {
3329 if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; }
3330 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); }
3331 }
3332 try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (ex) { }
3333 }
3334 this.ws.resume();
3335 this.ws = null;
3336 }
3337 function files_consentpromise_rejected(e)
3338 {
3339 if (this.ws) {
3340 if(this.ws.httprequest){ // User Consent Denied
3341 if ((this.ws.httprequest.oldStyle === true) && (this.ws.httprequest.consentAutoAccept === true) && (e.toString() != "7")) {
3342 files_consentpromise_resolved.call(this); // oldStyle prompt timed out and User Consent is not required so connect anyway
3343 return;
3344 }
3345 MeshServerLogEx(41, null, "Failed to start remote files after local user rejected (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
3346 } else { } // Connection was closed server side, maybe log some messages somewhere?
3347 this.ws._consentpromise = null;
3348 this.ws.end(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
3349 this.ws = null;
3350 } else { } // no websocket, maybe log some messages somewhere?
3351 }
3352 function files_tunnel_endhandler()
3353 {
3354 if (this._consentpromise && this._consentpromise.close) { this._consentpromise.close(); }
3355 }
3356
3357 function onTunnelData(data)
3358 {
3359 //sendConsoleText('OnTunnelData, ' + data.length + ', ' + typeof data + ', ' + data);
3360
3361 // If this is upload data, save it to file
3362 if ((this.httprequest.uploadFile) && (typeof data == 'object') && (data[0] != 123)) {
3363 // Save the data to file being uploaded.
3364 if (data[0] == 0) {
3365 // If data starts with zero, skip the first byte. This is used to escape binary file data from JSON.
3366 this.httprequest.uploadFileSize += (data.length - 1);
3367 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.
3368 } else {
3369 // If data does not start with zero, save as-is.
3370 this.httprequest.uploadFileSize += data.length;
3371 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.
3372 }
3373 this.write(Buffer.from(JSON.stringify({ action: 'uploadack', reqid: this.httprequest.uploadFileid }))); // Ask for more data.
3374 return;
3375 }
3376
3377 if (this.httprequest.state == 0) {
3378 // Check if this is a relay connection
3379 if ((data == 'c') || (data == 'cr')) {
3380 this.httprequest.state = 1;
3381 //sendConsoleText("Tunnel #" + this.httprequest.index + " now active", this.httprequest.sessionid);
3382 }
3383 }
3384 else {
3385 // Handle tunnel data
3386 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
3387 // Take a look at the protocol
3388 if ((data.length > 3) && (data[0] == '{')) { onTunnelControlData(data, this); return; }
3389 this.httprequest.protocol = parseInt(data);
3390 if (typeof this.httprequest.protocol != 'number') { this.httprequest.protocol = 0; }
3391
3392 // See if this protocol request is allowed.
3393 if ((this.httprequest.soptions != null) && (this.httprequest.soptions.usages != null) && (this.httprequest.soptions.usages.indexOf(this.httprequest.protocol) == -1)) { this.httprequest.protocol = 0; }
3394
3395 if (this.httprequest.protocol == 10) {
3396 //
3397 // Basic file transfer
3398 //
3399 var stats = null;
3400 if ((process.platform != 'win32') && (this.httprequest.xoptions.file.startsWith('/') == false)) { this.httprequest.xoptions.file = '/' + this.httprequest.xoptions.file; }
3401 try { stats = require('fs').statSync(this.httprequest.xoptions.file) } catch (ex) { }
3402 try { if (stats) { this.httprequest.downloadFile = fs.createReadStream(this.httprequest.xoptions.file, { flags: 'rbN' }); } } catch (ex) { }
3403 if (this.httprequest.downloadFile) {
3404 MeshServerLogEx(106, [this.httprequest.xoptions.file, stats.size], 'Download: \"' + this.httprequest.xoptions.file + '\", Size: ' + stats.size, this.httprequest);
3405 //sendConsoleText('BasicFileTransfer, ok, ' + this.httprequest.xoptions.file + ', ' + JSON.stringify(stats));
3406 this.write(JSON.stringify({ op: 'ok', size: stats.size }));
3407 this.httprequest.downloadFile.pipe(this);
3408 this.httprequest.downloadFile.end = function () { }
3409 } else {
3410 //sendConsoleText('BasicFileTransfer, cancel, ' + this.httprequest.xoptions.file);
3411 this.write(JSON.stringify({ op: 'cancel' }));
3412 }
3413 }
3414 else if ((this.httprequest.protocol == 1) || (this.httprequest.protocol == 6) || (this.httprequest.protocol == 8) || (this.httprequest.protocol == 9)) {
3415 //
3416 // Remote Terminal
3417 //
3418
3419 // Check user access rights for terminal
3420 if (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) || ((this.httprequest.rights != 0xFFFFFFFF) && ((this.httprequest.rights & MESHRIGHT_NOTERMINAL) != 0)))
3421 {
3422 // Disengage this tunnel, user does not have the rights to do this!!
3423 this.httprequest.protocol = 999999;
3424 this.httprequest.s.end();
3425 sendConsoleText("Error: No Terminal Control Rights.");
3426 return;
3427 }
3428
3429 this.descriptorMetadata = "Remote Terminal";
3430
3431 // Look for a TSID
3432 var tsid = null;
3433 if ((this.httprequest.xoptions != null) && (typeof this.httprequest.xoptions.tsid == 'number')) { tsid = this.httprequest.xoptions.tsid; }
3434 require('MeshAgent')._tsid = tsid;
3435 this.tsid = tsid;
3436
3437 if (process.platform == 'win32')
3438 {
3439 if (!require('win-terminal').PowerShellCapable() && (this.httprequest.protocol == 6 || this.httprequest.protocol == 9)) {
3440 this.httprequest.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: 'PowerShell is not supported on this version of windows', msgid: 1 }));
3441 this.httprequest.s.end();
3442 return;
3443 }
3444 }
3445
3446 var prom = require('promise');
3447 this.httprequest.tpromise = new prom(promise_init);
3448 this.httprequest.tpromise.that = this;
3449 this.httprequest.tpromise.httprequest = this.httprequest;
3450 this.end = terminal_end;
3451
3452 // Perform User-Consent if needed.
3453 if (this.httprequest.consent && (this.httprequest.consent & 16)) {
3454 // User asked for consent so now we check if we can auto accept if no user is present/loggedin
3455 if (this.httprequest.consentAutoAcceptIfNoUser || this.httprequest.consentAutoAcceptIfTerminalNoUser || this.httprequest.consentAutoAcceptIfLocked || this.httprequest.consentAutoAcceptIfTerminalLocked) {
3456 var p = require('user-sessions').enumerateUsers();
3457 p.sessionid = this.httprequest.sessionid;
3458 p.ws = this;
3459 p.then(function (u) {
3460 var v = [];
3461 for (var i in u) {
3462 if (u[i].State == 'Active') { v.push({ tsid: i, type: u[i].StationName, user: u[i].Username, domain: u[i].Domain }); }
3463 }
3464 var autoAccept = false;
3465
3466 // Check if we should auto-accept because no user is present
3467 if ((this.ws.httprequest.consentAutoAcceptIfNoUser || this.ws.httprequest.consentAutoAcceptIfTerminalNoUser) && (v.length == 0)) {
3468 autoAccept = true;
3469 }
3470
3471 // Check if we should auto-accept because all users are locked
3472 if ((this.ws.httprequest.consentAutoAcceptIfLocked || this.ws.httprequest.consentAutoAcceptIfTerminalLocked) && (v.length > 0)) {
3473 var allUsersLocked = true;
3474 if (!meshCoreObj.lusers || meshCoreObj.lusers.length == 0) {
3475 // No locked users list available, assume users are not locked
3476 allUsersLocked = false;
3477 } else {
3478 for (var i in v) {
3479 var username = v[i].domain ? (v[i].domain + '\\' + v[i].user) : v[i].user;
3480 if (meshCoreObj.lusers.indexOf(username) == -1) {
3481 allUsersLocked = false;
3482 break;
3483 }
3484 }
3485 }
3486 if (allUsersLocked) { autoAccept = true; }
3487 }
3488
3489 if (autoAccept) {
3490 this.ws.httprequest.tpromise._res();
3491 } else {
3492 // User is present and not all locked, so we still need consent
3493 terminal_consent_ask(this.ws);
3494 }
3495 });
3496 } else {
3497 terminal_consent_ask(this);
3498 }
3499 } else {
3500 // User-Consent is not required, so just resolve this promise
3501 this.httprequest.tpromise._res();
3502 }
3503 this.httprequest.tpromise.then(terminal_promise_consent_resolved, terminal_promise_consent_rejected);
3504 }
3505 else if (this.httprequest.protocol == 2)
3506 {
3507 //
3508 // Remote Desktop
3509 //
3510
3511 // Check user access rights for desktop
3512 if ((((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) && ((this.httprequest.rights & MESHRIGHT_REMOTEVIEW) == 0)) || ((this.httprequest.rights != 0xFFFFFFFF) && ((this.httprequest.rights & MESHRIGHT_NODESKTOP) != 0))) {
3513 // Disengage this tunnel, user does not have the rights to do this!!
3514 this.httprequest.protocol = 999999;
3515 this.httprequest.s.end();
3516 sendConsoleText("Error: No Desktop Control Rights.");
3517 return;
3518 }
3519
3520 this.descriptorMetadata = "Remote KVM";
3521
3522 // Look for a TSID
3523 var tsid = null;
3524 if ((this.httprequest.xoptions != null) && (typeof this.httprequest.xoptions.tsid == 'number')) { tsid = this.httprequest.xoptions.tsid; }
3525 require('MeshAgent')._tsid = tsid;
3526 this.tsid = tsid;
3527
3528 // If MacOS, Wake up device with caffeinate
3529 if(process.platform == 'darwin'){
3530 try {
3531 var options = {};
3532 try { options.uid = require('user-sessions').consoleUid(); } catch (ex) { }
3533 options.type = require('child_process').SpawnTypes.TERM;
3534 var replydata = "";
3535 var cmdchild = require('child_process').execFile('/usr/bin/caffeinate', ['caffeinate', '-u', '-t', '10'], options);
3536 cmdchild.descriptorMetadata = 'UserCommandsShell';
3537 cmdchild.stdout.on('data', function (c) { replydata += c.toString(); });
3538 cmdchild.stderr.on('data', function (c) { replydata + c.toString(); });
3539 cmdchild.on('exit', function () { delete cmdchild; });
3540 } catch(err) { }
3541 }
3542 // Remote desktop using native pipes
3543 this.httprequest.desktop = { state: 0, kvm: mesh.getRemoteDesktopStream(tsid), tunnel: this };
3544 this.httprequest.desktop.kvm.parent = this.httprequest.desktop;
3545 this.desktop = this.httprequest.desktop;
3546
3547 // Add ourself to the list of remote desktop sessions
3548 if (this.httprequest.desktop.kvm.tunnels == null) { this.httprequest.desktop.kvm.tunnels = []; }
3549 this.httprequest.desktop.kvm.tunnels.push(this);
3550
3551 // Send a metadata update to all desktop sessions
3552 var users = {};
3553 if (this.httprequest.desktop.kvm.tunnels != null)
3554 {
3555 for (var i in this.httprequest.desktop.kvm.tunnels)
3556 {
3557 try {
3558 var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest.desktop.kvm.tunnels[i].httprequest);
3559 if (users[userid] == null) { users[userid] = 1; } else { users[userid]++; }
3560 } catch (ex) { sendConsoleText(ex); }
3561 }
3562 for (var i in this.httprequest.desktop.kvm.tunnels)
3563 {
3564 try { this.httprequest.desktop.kvm.tunnels[i].write(JSON.stringify({ ctrlChannel: '102938', type: 'metadata', users: users })); } catch (ex) { }
3565 }
3566 tunnelUserCount.desktop = users;
3567 try { mesh.SendCommand({ action: 'sessions', type: 'kvm', value: users }); } catch (ex) { }
3568 broadcastSessionsToRegisteredApps();
3569 }
3570
3571 this.end = tunnel_kvm_end;
3572
3573 if (this.httprequest.desktop.kvm.hasOwnProperty('connectionCount')) {
3574 this.httprequest.desktop.kvm.connectionCount++;
3575 this.httprequest.desktop.kvm.rusers.push(this.httprequest.realname);
3576 this.httprequest.desktop.kvm.users.push(this.httprequest.username);
3577 this.httprequest.desktop.kvm.rusers.sort();
3578 this.httprequest.desktop.kvm.users.sort();
3579 } else {
3580 this.httprequest.desktop.kvm.connectionCount = 1;
3581 this.httprequest.desktop.kvm.rusers = [this.httprequest.realname];
3582 this.httprequest.desktop.kvm.users = [this.httprequest.username];
3583 }
3584
3585 if ((this.httprequest.desktopviewonly != true) && ((this.httprequest.rights == 0xFFFFFFFF) || (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) != 0) && ((this.httprequest.rights & MESHRIGHT_REMOTEVIEW) == 0))))
3586 {
3587 // If we have remote control rights, pipe the KVM input
3588 this.pipe(this.httprequest.desktop.kvm, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. Pipe the Browser --> KVM input.
3589 }
3590 else
3591 {
3592 // We need to only pipe non-mouse & non-keyboard inputs.
3593 // sendConsoleText('Warning: No Remote Desktop Input Rights.');
3594 // TODO!!!
3595 }
3596
3597 // Perform notification if needed. Toast messages may not be supported on all platforms.
3598 if (this.httprequest.consent && (this.httprequest.consent & 8)) {
3599
3600 // User asked for consent but now we check if can auto accept if no user is present
3601 if (this.httprequest.consentAutoAcceptIfNoUser || this.httprequest.consentAutoAcceptIfDesktopNoUser || this.httprequest.consentAutoAcceptIfLocked || this.httprequest.consentAutoAcceptIfDesktopLocked) {
3602 // Get list of users to check if we any actual users logged in, and if users logged in, we still need consent
3603 var p = require('user-sessions').enumerateUsers();
3604 p.sessionid = this.httprequest.sessionid;
3605 p.ws = this;
3606 p.then(function (u) {
3607 var v = [];
3608 for (var i in u) {
3609 if (u[i].State == 'Active') { v.push({ tsid: i, type: u[i].StationName, user: u[i].Username, domain: u[i].Domain }); }
3610 }
3611 var autoAccept = false;
3612
3613 // Check if we can auto-accept because no user is present
3614 if ((this.ws.httprequest.consentAutoAcceptIfNoUser || this.ws.httprequest.consentAutoAcceptIfDesktopNoUser) && (v.length == 0)) {
3615 // No user is present, auto accept
3616 autoAccept = true;
3617 }
3618
3619 // Check if we can auto-accept because all users are locked
3620 if ((this.ws.httprequest.consentAutoAcceptIfLocked || this.ws.httprequest.consentAutoAcceptIfDesktopLocked) && (v.length > 0)) {
3621 var allUsersLocked = true;
3622 if (!meshCoreObj.lusers || meshCoreObj.lusers.length == 0) {
3623 // No locked users list available, assume users are not locked
3624 allUsersLocked = false;
3625 } else {
3626 for (var i in v) {
3627 var username = v[i].domain ? (v[i].domain + '\\' + v[i].user) : v[i].user;
3628 if (meshCoreObj.lusers.indexOf(username) == -1) {
3629 allUsersLocked = false;
3630 break;
3631 }
3632 }
3633 }
3634 if (allUsersLocked) { autoAccept = true; }
3635 }
3636
3637 if (autoAccept) {
3638 kvm_consent_ok(this.ws);
3639 } else {
3640 // User is present and not all locked, so we still need consent
3641 kvm_consent_ask(this.ws);
3642 }
3643 });
3644 } else {
3645 // User Consent Prompt is required
3646 kvm_consent_ask(this);
3647 }
3648 } else {
3649 // User Consent Prompt is not required
3650 kvm_consent_ok(this);
3651 }
3652
3653 this.removeAllListeners('data');
3654 this.on('data', onTunnelControlData);
3655 //this.write('MeshCore KVM Hello!1');
3656 } else if (this.httprequest.protocol == 5) {
3657 //
3658 // Remote Files
3659 //
3660
3661 // Check user access rights for files
3662 if (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) || ((this.httprequest.rights != 0xFFFFFFFF) && ((this.httprequest.rights & MESHRIGHT_NOFILES) != 0))) {
3663 // Disengage this tunnel, user does not have the rights to do this!!
3664 this.httprequest.protocol = 999999;
3665 this.httprequest.s.end();
3666 sendConsoleText("Error: No files control rights.");
3667 return;
3668 }
3669
3670 this.descriptorMetadata = "Remote Files";
3671
3672 // Look for a TSID
3673 var tsid = null;
3674 if ((this.httprequest.xoptions != null) && (typeof this.httprequest.xoptions.tsid == 'number')) { tsid = this.httprequest.xoptions.tsid; }
3675 require('MeshAgent')._tsid = tsid;
3676 this.tsid = tsid;
3677
3678 // Add the files session to the count to update the server
3679 if (this.httprequest.userid != null) {
3680 var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest);
3681 if (tunnelUserCount.files[userid] == null) { tunnelUserCount.files[userid] = 1; } else { tunnelUserCount.files[userid]++; }
3682 try { mesh.SendCommand({ action: 'sessions', type: 'files', value: tunnelUserCount.files }); } catch (ex) { }
3683 broadcastSessionsToRegisteredApps();
3684 }
3685
3686 this.end = function ()
3687 {
3688 // Remove the files session from the count to update the server
3689 if (this.httprequest.userid != null) {
3690 var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest);
3691 if (tunnelUserCount.files[userid] != null) { tunnelUserCount.files[userid]--; if (tunnelUserCount.files[userid] <= 0) { delete tunnelUserCount.files[userid]; } }
3692 try { mesh.SendCommand({ action: 'sessions', type: 'files', value: tunnelUserCount.files }); } catch (ex) { }
3693 broadcastSessionsToRegisteredApps();
3694 }
3695 };
3696
3697 // Perform notification if needed. Toast messages may not be supported on all platforms.
3698 if (this.httprequest.consent && (this.httprequest.consent & 32)) {
3699 // User asked for consent so now we check if we can auto accept if no user is present/loggedin
3700 if (this.httprequest.consentAutoAcceptIfNoUser || this.httprequest.consentAutoAcceptIfFileNoUser || this.httprequest.consentAutoAcceptIfLocked || this.httprequest.consentAutoAcceptIfFileLocked) {
3701 var p = require('user-sessions').enumerateUsers();
3702 p.sessionid = this.httprequest.sessionid;
3703 p.ws = this;
3704 p.then(function (u) {
3705 var v = [];
3706 for (var i in u) {
3707 if (u[i].State == 'Active') { v.push({ tsid: i, type: u[i].StationName, user: u[i].Username, domain: u[i].Domain }); }
3708 }
3709 var autoAccept = false;
3710
3711 // Check if we should auto-accept because no user is present
3712 if ((this.ws.httprequest.consentAutoAcceptIfNoUser || this.ws.httprequest.consentAutoAcceptIfFileNoUser) && (v.length == 0)) {
3713 autoAccept = true;
3714 }
3715
3716 // Check if we should auto-accept because all users are locked
3717 if ((this.ws.httprequest.consentAutoAcceptIfLocked || this.ws.httprequest.consentAutoAcceptIfFileLocked) && (v.length > 0)) {
3718 var allUsersLocked = true;
3719 if (!meshCoreObj.lusers || meshCoreObj.lusers.length == 0) {
3720 // No locked users list available, assume users are not locked
3721 allUsersLocked = false;
3722 } else {
3723 for (var i in v) {
3724 var username = v[i].domain ? (v[i].domain + '\\' + v[i].user) : v[i].user;
3725 if (meshCoreObj.lusers.indexOf(username) == -1) {
3726 allUsersLocked = false;
3727 break;
3728 }
3729 }
3730 }
3731 if (allUsersLocked) { autoAccept = true; }
3732 }
3733
3734 if (autoAccept) {
3735 // User Consent Prompt is not required
3736 files_consent_ok(this.ws);
3737 } else {
3738 // User is present and not all locked, so we still need consent
3739 files_consent_ask(this.ws);
3740 }
3741 });
3742 } else {
3743 // User Consent Prompt is required
3744 files_consent_ask(this);
3745 }
3746 } else {
3747 // User Consent Prompt is not required
3748 files_consent_ok(this);
3749 }
3750
3751 // Setup files
3752 // NOP
3753 }
3754 } else if (this.httprequest.protocol == 1) {
3755 // Send data into terminal stdin
3756 //this.write(data); // Echo back the keys (Does not seem to be a good idea)
3757 } else if (this.httprequest.protocol == 2) {
3758 // Send data into remote desktop
3759 if (this.httprequest.desktop.state == 0) {
3760 this.write(Buffer.from(String.fromCharCode(0x11, 0xFE, 0x00, 0x00, 0x4D, 0x45, 0x53, 0x48, 0x00, 0x00, 0x00, 0x00, 0x02)));
3761 this.httprequest.desktop.state = 1;
3762 } else {
3763 this.httprequest.desktop.write(data);
3764 }
3765 } else if (this.httprequest.protocol == 5) {
3766 // Process files commands
3767 var cmd = null;
3768 try { cmd = JSON.parse(data); } catch (ex) { };
3769 if (cmd == null) { return; }
3770 if ((cmd.ctrlChannel == '102938') || ((cmd.type == 'offer') && (cmd.sdp != null))) { onTunnelControlData(cmd, this); return; } // If this is control data, handle it now.
3771 if (cmd.action == undefined) { return; }
3772 //sendConsoleText('CMD: ' + JSON.stringify(cmd));
3773
3774 if ((cmd.path != null) && (process.platform != 'win32') && (cmd.path[0] != '/')) { cmd.path = '/' + cmd.path; } // Add '/' to paths on non-windows
3775 //console.log(objToString(cmd, 0, ' '));
3776 switch (cmd.action) {
3777 case 'ls': {
3778 /*
3779 // Close the watcher if required
3780 var samepath = ((this.httprequest.watcher != undefined) && (cmd.path == this.httprequest.watcher.path));
3781 if ((this.httprequest.watcher != undefined) && (samepath == false)) {
3782 //console.log('Closing watcher: ' + this.httprequest.watcher.path);
3783 //this.httprequest.watcher.close(); // TODO: This line causes the agent to crash!!!!
3784 delete this.httprequest.watcher;
3785 }
3786 */
3787
3788 // Send the folder content to the browser
3789 var response = getDirectoryInfo(cmd.path);
3790 response.reqid = cmd.reqid;
3791 this.write(Buffer.from(JSON.stringify(response)));
3792
3793 /*
3794 // Start the directory watcher
3795 if ((cmd.path != '') && (samepath == false)) {
3796 var watcher = fs.watch(cmd.path, onFileWatcher);
3797 watcher.tunnel = this.httprequest;
3798 watcher.path = cmd.path;
3799 this.httprequest.watcher = watcher;
3800 //console.log('Starting watcher: ' + this.httprequest.watcher.path);
3801 }
3802 */
3803 break;
3804 }
3805 case 'mkdir': {
3806 // Create a new empty folder
3807 fs.mkdirSync(cmd.path);
3808 MeshServerLogEx(44, [cmd.path], "Create folder: \"" + cmd.path + "\"", this.httprequest);
3809 break;
3810 }
3811 case 'mkfile': {
3812 // Create a new empty file
3813 fs.closeSync(fs.openSync(cmd.path, 'w'));
3814 MeshServerLogEx(164, [cmd.path], "Create file: \"" + cmd.path + "\"", this.httprequest);
3815 break;
3816 }
3817 case 'rm': {
3818 // Delete, possibly recursive delete
3819 for (var i in cmd.delfiles) {
3820 var p = obj.path.join(cmd.path, cmd.delfiles[i]), delcount = 0;
3821 try { delcount = deleteFolderRecursive(p, cmd.rec); } catch (ex) { }
3822 if ((delcount == 1) && !cmd.rec) {
3823 MeshServerLogEx(45, [p], "Delete: \"" + p + "\"", this.httprequest);
3824 } else {
3825 if (cmd.rec) {
3826 MeshServerLogEx(46, [p, delcount], "Delete recursive: \"" + p + "\", " + delcount + " element(s) removed", this.httprequest);
3827 } else {
3828 MeshServerLogEx(47, [p, delcount], "Delete: \"" + p + "\", " + delcount + " element(s) removed", this.httprequest);
3829 }
3830 }
3831 }
3832 break;
3833 }
3834 case 'open': {
3835 // Open the local file/folder on the users desktop
3836 if (cmd.path) {
3837 MeshServerLogEx(20, [cmd.path], "Opening: " + cmd.path, cmd);
3838 openFileOnDesktop(cmd.path);
3839 }
3840 }
3841 case 'markcoredump': {
3842 // If we are asking for the coredump file, set the right path.
3843 var coreDumpPath = null;
3844 if (process.platform == 'win32') {
3845 if (fs.existsSync(process.coreDumpLocation)) { coreDumpPath = process.coreDumpLocation; }
3846 } else {
3847 if ((process.cwd() != '//') && fs.existsSync(process.cwd() + 'core')) { coreDumpPath = process.cwd() + 'core'; }
3848 }
3849 if (coreDumpPath != null) { db.Put('CoreDumpTime', require('fs').statSync(coreDumpPath).mtime); }
3850 break;
3851 }
3852 case 'rename':
3853 {
3854 // Rename a file or folder
3855 var oldfullpath = obj.path.join(cmd.path, cmd.oldname);
3856 var newfullpath = obj.path.join(cmd.path, cmd.newname);
3857 MeshServerLogEx(48, [oldfullpath, cmd.newname], 'Rename: \"' + oldfullpath + '\" to \"' + cmd.newname + '\"', this.httprequest);
3858 try { fs.renameSync(oldfullpath, newfullpath); } catch (ex) { console.log(ex); }
3859 break;
3860 }
3861 case 'findfile':
3862 {
3863 // Search for files
3864 var r = require('file-search').find('"' + cmd.path + '"', cmd.filter);
3865 if (!r.cancel) { r.cancel = function cancel() { this.child.kill(); }; }
3866 this._search = r;
3867 r.socket = this;
3868 r.socket.reqid = cmd.reqid; // Search request id. This is used to send responses and cancel the request.
3869 r.socket.path = cmd.path; // Search path
3870 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) { } });
3871 r.then(function () { try { this.socket.write(Buffer.from(JSON.stringify({ action: 'findfile', r: null, reqid: this.socket.reqid }))); } catch (ex) { } });
3872 break;
3873 }
3874 case 'cancelfindfile':
3875 {
3876 if (this._search) { this._search.cancel(); this._search = null; }
3877 break;
3878 }
3879 case 'download':
3880 {
3881 // Download a file
3882 var sendNextBlock = 0;
3883 if (cmd.sub == 'start') { // Setup the download
3884 if ((cmd.path == null) && (cmd.ask == 'coredump')) { // If we are asking for the coredump file, set the right path.
3885 if (process.platform == 'win32') {
3886 if (fs.existsSync(process.coreDumpLocation)) { cmd.path = process.coreDumpLocation; }
3887 } else {
3888 if ((process.cwd() != '//') && fs.existsSync(process.cwd() + 'core')) { cmd.path = process.cwd() + 'core'; }
3889 }
3890 }
3891 MeshServerLogEx((cmd.ask == 'coredump') ? 104 : 49, [cmd.path], 'Download: \"' + cmd.path + '\"', this.httprequest);
3892 if ((cmd.path == null) || (this.filedownload != null)) { this.write({ action: 'download', sub: 'cancel', id: this.filedownload.id }); delete this.filedownload; }
3893 this.filedownload = { id: cmd.id, path: cmd.path, ptr: 0 }
3894 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; }
3895 if (this.filedownload) { this.write({ action: 'download', sub: 'start', id: cmd.id }); }
3896 } else if ((this.filedownload != null) && (cmd.id == this.filedownload.id)) { // Download commands
3897 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; }
3898 }
3899 // Send the next download block(s)
3900 if (sendNextBlock > 0) {
3901 sendNextBlock--;
3902 var buf = Buffer.alloc(16384);
3903 var len = fs.readSync(this.filedownload.f, buf, 4, 16380, null);
3904 this.filedownload.ptr += len;
3905 if (len < 16380) { buf.writeInt32BE(0x01000001, 0); fs.closeSync(this.filedownload.f); delete this.filedownload; sendNextBlock = 0; } else { buf.writeInt32BE(0x01000000, 0); }
3906 this.write(buf.slice(0, len + 4)); // Write as binary
3907 }
3908 break;
3909 }
3910 case 'upload':
3911 {
3912 // Upload a file, browser to agent
3913 if (this.httprequest.uploadFile != null) { fs.closeSync(this.httprequest.uploadFile); delete this.httprequest.uploadFile; }
3914 if (cmd.path == undefined) break;
3915 var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path;
3916 this.httprequest.uploadFilePath = filepath;
3917 this.httprequest.uploadFileSize = 0;
3918 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; }
3919 this.httprequest.uploadFileid = cmd.reqid;
3920 if (this.httprequest.uploadFile) { this.write(Buffer.from(JSON.stringify({ action: 'uploadstart', reqid: this.httprequest.uploadFileid }))); }
3921 break;
3922 }
3923 case 'uploaddone':
3924 {
3925 // Indicates that an upload is done
3926 if (this.httprequest.uploadFile) {
3927 MeshServerLogEx(105, [this.httprequest.uploadFilePath, this.httprequest.uploadFileSize], 'Upload: \"' + this.httprequest.uploadFilePath + '\", Size: ' + this.httprequest.uploadFileSize, this.httprequest);
3928 fs.closeSync(this.httprequest.uploadFile);
3929 this.write(Buffer.from(JSON.stringify({ action: 'uploaddone', reqid: this.httprequest.uploadFileid }))); // Indicate that we closed the file.
3930 delete this.httprequest.uploadFile;
3931 delete this.httprequest.uploadFileid;
3932 delete this.httprequest.uploadFilePath;
3933 delete this.httprequest.uploadFileSize;
3934 }
3935 break;
3936 }
3937 case 'uploadcancel':
3938 {
3939 // Indicates that an upload is canceled
3940 if (this.httprequest.uploadFile) {
3941 fs.closeSync(this.httprequest.uploadFile);
3942 fs.unlinkSync(this.httprequest.uploadFilePath);
3943 this.write(Buffer.from(JSON.stringify({ action: 'uploadcancel', reqid: this.httprequest.uploadFileid }))); // Indicate that we closed the file.
3944 delete this.httprequest.uploadFile;
3945 delete this.httprequest.uploadFileid;
3946 delete this.httprequest.uploadFilePath;
3947 delete this.httprequest.uploadFileSize;
3948 }
3949 break;
3950 }
3951 case 'uploadhash':
3952 {
3953 // Hash a file
3954 var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path;
3955 var h = null;
3956 try { h = getSHA384FileHash(filepath); } catch (ex) { sendConsoleText(ex); }
3957 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) })));
3958 break
3959 }
3960 case 'copy':
3961 {
3962 // Copy a bunch of files from scpath to dspath
3963 for (var i in cmd.names) {
3964 var sc = obj.path.join(cmd.scpath, cmd.names[i]), ds = obj.path.join(cmd.dspath, cmd.names[i]);
3965 MeshServerLogEx(51, [sc, ds], 'Copy: \"' + sc + '\" to \"' + ds + '\"', this.httprequest);
3966 if (sc != ds) { try { fs.copyFileSync(sc, ds); } catch (ex) { } }
3967 }
3968 break;
3969 }
3970 case 'move':
3971 {
3972 // Move a bunch of files from scpath to dspath
3973 for (var i in cmd.names) {
3974 var sc = obj.path.join(cmd.scpath, cmd.names[i]), ds = obj.path.join(cmd.dspath, cmd.names[i]);
3975 MeshServerLogEx(52, [sc, ds], 'Move: \"' + sc + '\" to \"' + ds + '\"', this.httprequest);
3976 if (sc != ds) { try { fs.copyFileSync(sc, ds); fs.unlinkSync(sc); } catch (ex) { } }
3977 }
3978 break;
3979 }
3980 case 'zip':
3981 // Zip a bunch of files
3982 if (this.zip != null) return; // Zip operating is currently running, exit now.
3983
3984 // Check that the specified files exist & build full paths
3985 var fp, stat, p = [];
3986 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); } }
3987 if (p.length == 0) return; // No files, quit now.
3988
3989 // Setup file compression
3990 var ofile = cmd.path + '/' + cmd.output;
3991 this.write(Buffer.from(JSON.stringify({ action: 'dialogmessage', msg: 'zipping' })));
3992 this.zipfile = ofile;
3993 delete this.zipcancel;
3994 var out = require('fs').createWriteStream(ofile, { flags: 'wb' });
3995 out.xws = this;
3996 out.on('close', function () {
3997 this.xws.write(Buffer.from(JSON.stringify({ action: 'dialogmessage', msg: null })));
3998 this.xws.write(Buffer.from(JSON.stringify({ action: 'refresh' })));
3999 if (this.xws.zipcancel === true) { fs.unlinkSync(this.xws.zipfile); } // Delete the complete file.
4000 delete this.xws.zipcancel;
4001 delete this.xws.zipfile;
4002 delete this.xws.zip;
4003 });
4004 this.zip = require('zip-writer').write({ files: p, basePath: cmd.path });
4005 this.zip.xws = this;
4006 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));
4007 this.zip.pipe(out);
4008 break;
4009 case 'unzip':
4010 if (this.unzip != null) return; // Unzip operating is currently running, exit now.
4011 this.unzip = require('zip-reader').read(cmd.input);
4012 this.unzip._dest = cmd.dest;
4013 this.unzip.xws = this;
4014 this.unzip.then(function (zipped) {
4015 this.xws.write(Buffer.from(JSON.stringify({ action: 'dialogmessage', msg: 'unzipping' })));
4016 zipped.xws = this.xws;
4017 zipped.extractAll(this._dest).then(function () { // finished extracting
4018 zipped.xws.write(Buffer.from(JSON.stringify({ action: 'dialogmessage', msg: null })));
4019 zipped.xws.write(Buffer.from(JSON.stringify({ action: 'refresh' })));
4020 delete zipped.xws.unzip;
4021 }, function (e) { // error extracting
4022 zipped.xws.write(Buffer.from(JSON.stringify({ action: 'dialogmessage', msg: 'unziperror', error: e })));
4023 delete zipped.xws.unzip;
4024 });
4025 }, function (e) { this.xws.write(Buffer.from(JSON.stringify({ action: 'dialogmessage', msg: 'unziperror', error: e }))); delete this.xws.unzip });
4026 break;
4027 case 'cancel':
4028 // Cancel zip operation if present
4029 try { this.zipcancel = true; this.zip.cancel(function () { }); } catch (ex) { }
4030 this.zip = null;
4031 break;
4032 default:
4033 // Unknown action, ignore it.
4034 break;
4035 }
4036 } else if (this.httprequest.protocol == 7) { // Plugin data exchange
4037 var cmd = null;
4038 try { cmd = JSON.parse(data); } catch (ex) { };
4039 if (cmd == null) { return; }
4040 if ((cmd.ctrlChannel == '102938') || ((cmd.type == 'offer') && (cmd.sdp != null))) { onTunnelControlData(cmd, this); return; } // If this is control data, handle it now.
4041 if (cmd.action == undefined) return;
4042
4043 switch (cmd.action) {
4044 case 'plugin': {
4045 try { require(cmd.plugin).consoleaction(cmd, null, null, this); } catch (ex) { throw ex; }
4046 break;
4047 }
4048 default: {
4049 // probably shouldn't happen, but just in case this feature is expanded
4050 }
4051 }
4052
4053 }
4054 //sendConsoleText("Got tunnel #" + this.httprequest.index + " data: " + data, this.httprequest.sessionid);
4055 }
4056 }
4057
4058 // Delete a directory with a files and directories within it
4059 function deleteFolderRecursive(path, rec) {
4060 var count = 0;
4061 if (fs.existsSync(path)) {
4062 if (rec == true) {
4063 fs.readdirSync(obj.path.join(path, '*')).forEach(function (file, index) {
4064 var curPath = obj.path.join(path, file);
4065 if (fs.statSync(curPath).isDirectory()) { // recurse
4066 count += deleteFolderRecursive(curPath, true);
4067 } else { // delete file
4068 fs.unlinkSync(curPath);
4069 count++;
4070 }
4071 });
4072 }
4073 fs.unlinkSync(path);
4074 count++;
4075 }
4076 return count;
4077 }
4078
4079 // Called when receiving control data on WebRTC
4080 function onTunnelWebRTCControlData(data) {
4081 if (typeof data != 'string') return;
4082 var obj;
4083 try { obj = JSON.parse(data); } catch (ex) { sendConsoleText('Invalid control JSON on WebRTC: ' + data); return; }
4084 if (obj.type == 'close') {
4085 //sendConsoleText('Tunnel #' + this.xrtc.websocket.tunnel.index + ' WebRTC control close');
4086 try { this.close(); } catch (ex) { }
4087 try { this.xrtc.close(); } catch (ex) { }
4088 }
4089 }
4090
4091 function tunnel_webrtc_onEnd()
4092 {
4093 // The WebRTC channel closed, unpipe the KVM now. This is also done when the web socket closes.
4094 //sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC data channel closed');
4095 if (this.websocket.desktop && this.websocket.desktop.kvm)
4096 {
4097 try
4098 {
4099 this.unpipe(this.websocket.desktop.kvm);
4100 this.websocket.httprequest.desktop.kvm.unpipe(this);
4101 } catch (ex) { }
4102 }
4103 this.httprequest = null;
4104 this.websocket = null;
4105 }
4106 function tunnel_webrtc_DataChannel_OnFinalized()
4107 {
4108 console.info1('WebRTC DataChannel Finalized');
4109 }
4110 function tunnel_webrtc_OnDataChannel(rtcchannel)
4111 {
4112 //sendConsoleText('WebRTC Datachannel open, protocol: ' + this.websocket.httprequest.protocol);
4113 //rtcchannel.maxFragmentSize = 32768;
4114 rtcchannel.xrtc = this;
4115 rtcchannel.websocket = this.websocket;
4116 this.rtcchannel = rtcchannel;
4117 this.rtcchannel.once('~', tunnel_webrtc_DataChannel_OnFinalized);
4118 this.websocket.rtcchannel = rtcchannel;
4119 this.websocket.rtcchannel.on('data', onTunnelWebRTCControlData);
4120 this.websocket.rtcchannel.on('end', tunnel_webrtc_onEnd);
4121 this.websocket.write('{\"ctrlChannel\":\"102938\",\"type\":\"webrtc0\"}'); // Indicate we are ready for WebRTC switch-over.
4122 }
4123
4124 function tunnel_webrtc_OnFinalized()
4125 {
4126 console.info1('WebRTC Connection Finalized');
4127 }
4128
4129 // Called when receiving control data on websocket
4130 function onTunnelControlData(data, ws) {
4131 var obj;
4132 if (ws == null) { ws = this; }
4133 if (typeof data == 'string') { try { obj = JSON.parse(data); } catch (ex) { sendConsoleText('Invalid control JSON: ' + data); return; } }
4134 else if (typeof data == 'object') { obj = data; } else { return; }
4135 //sendConsoleText('onTunnelControlData(' + ws.httprequest.protocol + '): ' + JSON.stringify(data));
4136 //console.log('onTunnelControlData: ' + JSON.stringify(data));
4137
4138 switch (obj.type) {
4139 case 'lock': {
4140 // Look for a TSID
4141 var tsid = null;
4142 if ((ws.httprequest.xoptions != null) && (typeof ws.httprequest.xoptions.tsid == 'number')) { tsid = ws.httprequest.xoptions.tsid; }
4143
4144 // Lock the current user out of the desktop
4145 MeshServerLogEx(53, null, "Locking remote user out of desktop", ws.httprequest);
4146 lockDesktop(tsid);
4147 break;
4148 }
4149 case 'autolock': {
4150 // Set the session to auto lock on disconnect
4151 if (obj.value === true) {
4152 ws.httprequest.autolock = true;
4153 if (ws.httprequest.unlockerHelper == null) {
4154 destopLockHelper_pipe(ws.httprequest);
4155 }
4156 }
4157 else {
4158 delete ws.httprequest.autolock;
4159 }
4160 break;
4161 }
4162 case 'options': {
4163 // These are additional connection options passed in the control channel.
4164 //sendConsoleText('options: ' + JSON.stringify(obj));
4165 delete obj.type;
4166 ws.httprequest.xoptions = obj;
4167
4168 // Set additional user consent options if present
4169 if ((obj != null) && (typeof obj.consent == 'number')) { ws.httprequest.consent |= obj.consent; }
4170
4171 // Set autolock
4172 if ((obj != null) && (obj.autolock === true)) {
4173 ws.httprequest.autolock = true;
4174 if (ws.httprequest.unlockerHelper == null) {
4175 destopLockHelper_pipe(ws.httprequest);
4176 }
4177 }
4178
4179 break;
4180 }
4181 case 'close': {
4182 // We received the close on the websocket
4183 //sendConsoleText('Tunnel #' + ws.tunnel.index + ' WebSocket control close');
4184 // Attempt to send EOF (Ctrl-D) multiple times to exit nested shells (screen, su, etc.) cleanly,
4185 // This allows the shell to write its history before the process is killed
4186 if (process.platform != 'win32' && ws.httprequest && ws.httprequest.process && ws.httprequest.process.stdin) {
4187 try { ws.httprequest.process.stdin.write('\x04\x04\x04'); } catch (ex) { }
4188 }
4189 try { ws.close(); } catch (ex) { }
4190 break;
4191 }
4192 case 'termsize': {
4193 // Indicates a change in terminal size
4194 if (process.platform == 'win32') {
4195 if (ws.httprequest._dispatcher == null) return;
4196 //sendConsoleText('Win32-TermSize: ' + obj.cols + 'x' + obj.rows);
4197 if (ws.httprequest._dispatcher.invoke) { ws.httprequest._dispatcher.invoke('resizeTerminal', [obj.cols, obj.rows]); }
4198 } else {
4199 if (ws.httprequest.process == null || ws.httprequest.process.pty == 0) return;
4200 //sendConsoleText('Linux Resize: ' + obj.cols + 'x' + obj.rows);
4201
4202 if (ws.httprequest.process.tcsetsize) { ws.httprequest.process.tcsetsize(obj.rows, obj.cols); }
4203 }
4204 break;
4205 }
4206 case 'webrtc0': { // Browser indicates we can start WebRTC switch-over.
4207 if (ws.httprequest.protocol == 1)
4208 { // Terminal
4209 // 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
4210 if (process.platform == 'win32') {
4211 ws.httprequest._term.unpipe(ws);
4212 } else {
4213 ws.httprequest.process.stdout.unpipe(ws);
4214 ws.httprequest.process.stderr.unpipe(ws);
4215 }
4216 } else if (ws.httprequest.protocol == 2) { // Desktop
4217 // 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
4218 ws.httprequest.desktop.kvm.unpipe(ws);
4219 } else
4220 {
4221 // Switch things around so all WebRTC data goes to onTunnelData().
4222 ws.rtcchannel.httprequest = ws.httprequest;
4223 ws.rtcchannel.removeAllListeners('data');
4224 ws.rtcchannel.on('data', onTunnelData);
4225 }
4226 ws.write("{\"ctrlChannel\":\"102938\",\"type\":\"webrtc1\"}"); // End of data marker
4227 break;
4228 }
4229 case 'webrtc1':
4230 {
4231 if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6))
4232 { // Terminal
4233 // Switch the user input from websocket to webrtc at this point.
4234 if (process.platform == 'win32') {
4235 ws.unpipe(ws.httprequest._term);
4236 ws.rtcchannel.pipe(ws.httprequest._term, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
4237 } else {
4238 ws.unpipe(ws.httprequest.process.stdin);
4239 ws.rtcchannel.pipe(ws.httprequest.process.stdin, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
4240 }
4241 ws.resume(); // Resume the websocket to keep receiving control data
4242 }
4243 else if (ws.httprequest.protocol == 2)
4244 { // Desktop
4245 // Switch the user input from websocket to webrtc at this point.
4246 ws.unpipe(ws.httprequest.desktop.kvm);
4247 if ((ws.httprequest.desktopviewonly != true) && ((ws.httprequest.rights == 0xFFFFFFFF) || (((ws.httprequest.rights & MESHRIGHT_REMOTECONTROL) != 0) && ((ws.httprequest.rights & MESHRIGHT_REMOTEVIEW) == 0)))) {
4248 // If we have remote control rights, pipe the KVM input
4249 try { ws.webrtc.rtcchannel.pipe(ws.httprequest.desktop.kvm, { dataTypeSkip: 1, end: false }); } catch (ex) { sendConsoleText('EX2'); } // 0 = Binary, 1 = Text.
4250 } else {
4251 // We need to only pipe non-mouse & non-keyboard inputs.
4252 // sendConsoleText('Warning: No Remote Desktop Input Rights.');
4253 // TODO!!!
4254 }
4255 ws.resume(); // Resume the websocket to keep receiving control data
4256 }
4257 ws.write('{\"ctrlChannel\":\"102938\",\"type\":\"webrtc2\"}'); // Indicates we will no longer get any data on websocket, switching to WebRTC at this point.
4258 break;
4259 }
4260 case 'webrtc2': {
4261 // Other side received websocket end of data marker, start sending data on WebRTC channel
4262 if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) { // Terminal
4263 if (process.platform == 'win32') {
4264 ws.httprequest._term.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
4265 } else {
4266 ws.httprequest.process.stdout.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
4267 ws.httprequest.process.stderr.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
4268 }
4269 } else if (ws.httprequest.protocol == 2) { // Desktop
4270 ws.httprequest.desktop.kvm.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
4271 }
4272 break;
4273 }
4274 case 'offer': {
4275 // This is a WebRTC offer.
4276 if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) return; // TODO: Terminal is currently broken with WebRTC. Reject WebRTC upgrade for now.
4277 ws.webrtc = rtc.createConnection();
4278 ws.webrtc.once('~', tunnel_webrtc_OnFinalized);
4279 ws.webrtc.websocket = ws;
4280 //ws.webrtc.on('connected', function () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC connected');*/ });
4281 //ws.webrtc.on('disconnected', function () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC disconnected');*/ });
4282 ws.webrtc.on('dataChannel', tunnel_webrtc_OnDataChannel);
4283
4284 var sdp = null;
4285 try { sdp = ws.webrtc.setOffer(obj.sdp); } catch (ex) { }
4286 if (sdp != null) { ws.write({ type: 'answer', ctrlChannel: '102938', sdp: sdp }); }
4287 break;
4288 }
4289 case 'ping': {
4290 ws.write("{\"ctrlChannel\":\"102938\",\"type\":\"pong\"}"); // Send pong response
4291 break;
4292 }
4293 case 'pong': { // NOP
4294 break;
4295 }
4296 case 'rtt': {
4297 ws.write({ type: 'rtt', ctrlChannel: '102938', time: obj.time });
4298 break;
4299 }
4300 }
4301 }
4302
4303 // Console state
4304 var consoleWebSockets = {};
4305 var consoleHttpRequest = null;
4306
4307 // Console HTTP response
4308 function consoleHttpResponse(response) {
4309 response.data = function (data) { sendConsoleText(rstr2hex(buf2rstr(data)), this.sessionid); consoleHttpRequest = null; }
4310 response.close = function () { sendConsoleText('httprequest.response.close', this.sessionid); consoleHttpRequest = null; }
4311 }
4312
4313 // Open a local file on current user's desktop
4314 function openFileOnDesktop(file) {
4315 var child = null;
4316 try {
4317 switch (process.platform) {
4318 case 'win32':
4319 var uid = require('user-sessions').consoleUid();
4320 var user = require('user-sessions').getUsername(uid);
4321 var domain = require('user-sessions').getDomain(uid);
4322 var task = { name: 'MeshChatTask', user: user, domain: domain, execPath: (require('fs').statSync(file).isDirectory() ? process.env['windir'] + '\\explorer.exe' : file) };
4323 if (require('fs').statSync(file).isDirectory()) task.arguments = [file];
4324 try {
4325 require('win-tasks').addTask(task);
4326 require('win-tasks').getTask({ name: 'MeshChatTask' }).run();
4327 require('win-tasks').deleteTask('MeshChatTask');
4328 return (true);
4329 }
4330 catch (ex) {
4331 var taskoptions = { env: { _target: (require('fs').statSync(file).isDirectory() ? process.env['windir'] + '\\explorer.exe' : file), _user: '"' + domain + '\\' + user + '"' }, _args: "" };
4332 if (require('fs').statSync(file).isDirectory()) taskoptions.env._args = file;
4333 for (var c1e in process.env) {
4334 taskoptions.env[c1e] = process.env[c1e];
4335 }
4336 var child = require('child_process').execFile(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', ['powershell', '-noprofile', '-nologo', '-command', '-'], taskoptions);
4337 child.stderr.on('data', function (c) { });
4338 child.stdout.on('data', function (c) { });
4339 child.stdin.write('SCHTASKS /CREATE /F /TN MeshChatTask /SC ONCE /ST 00:00 ');
4340 if (user) { child.stdin.write('/RU $env:_user '); }
4341 child.stdin.write('/TR "$env:_target $env:_args"\r\n');
4342 child.stdin.write('$ts = New-Object -ComObject Schedule.service\r\n');
4343 child.stdin.write('$ts.connect()\r\n');
4344 child.stdin.write('$tsfolder = $ts.getfolder("\\")\r\n');
4345 child.stdin.write('$task = $tsfolder.GetTask("MeshChatTask")\r\n');
4346 child.stdin.write('$taskdef = $task.Definition\r\n');
4347 child.stdin.write('$taskdef.Settings.StopIfGoingOnBatteries = $false\r\n');
4348 child.stdin.write('$taskdef.Settings.DisallowStartIfOnBatteries = $false\r\n');
4349 child.stdin.write('$taskdef.Actions.Item(1).Path = $env:_target\r\n');
4350 child.stdin.write('$taskdef.Actions.Item(1).Arguments = $env:_args\r\n');
4351 child.stdin.write('$tsfolder.RegisterTaskDefinition($task.Name, $taskdef, 4, $null, $null, $null)\r\n');
4352 child.stdin.write('SCHTASKS /RUN /TN MeshChatTask\r\n');
4353 child.stdin.write('SCHTASKS /DELETE /F /TN MeshChatTask\r\nexit\r\n');
4354 child.waitExit();
4355 }
4356 break;
4357 case 'linux':
4358 child = require('child_process').execFile('/usr/bin/xdg-open', ['xdg-open', file], { uid: require('user-sessions').consoleUid() });
4359 break;
4360 case 'darwin':
4361 child = require('child_process').execFile('/usr/bin/open', ['open', file]);
4362 break;
4363 default:
4364 // Unknown platform, ignore this command.
4365 break;
4366 }
4367 } catch (ex) { }
4368 return child;
4369 }
4370
4371 // Open a web browser to a specified URL on current user's desktop
4372 function openUserDesktopUrl(url) {
4373 if ((url.toLowerCase().startsWith('http://') == false) && (url.toLowerCase().startsWith('https://') == false)) { return null; }
4374 var child = null;
4375 try {
4376 switch (process.platform) {
4377 case 'win32':
4378 var uid = require('user-sessions').consoleUid();
4379 var user = require('user-sessions').getUsername(uid);
4380 var domain = require('user-sessions').getDomain(uid);
4381 var task = { name: 'MeshChatTask', user: user, domain: domain, execPath: process.env['windir'] + '\\system32\\cmd.exe', arguments: ['/C START ' + url.split('&').join('^&')] };
4382
4383 try {
4384 require('win-tasks').addTask(task);
4385 require('win-tasks').getTask({ name: 'MeshChatTask' }).run();
4386 require('win-tasks').deleteTask('MeshChatTask');
4387 return (true);
4388 }
4389 catch (ex) {
4390 var taskoptions = { env: { _target: process.env['windir'] + '\\system32\\cmd.exe', _args: '/C START ' + url.split('&').join('^&'), _user: '"' + domain + '\\' + user + '"' } };
4391 for (var c1e in process.env) {
4392 taskoptions.env[c1e] = process.env[c1e];
4393 }
4394 var child = require('child_process').execFile(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', ['powershell', '-noprofile', '-nologo', '-command', '-'], taskoptions);
4395 child.stderr.on('data', function (c) { });
4396 child.stdout.on('data', function (c) { });
4397 child.stdin.write('SCHTASKS /CREATE /F /TN MeshChatTask /SC ONCE /ST 00:00 ');
4398 if (user) { child.stdin.write('/RU $env:_user '); }
4399 child.stdin.write('/TR "$env:_target $env:_args"\r\n');
4400 child.stdin.write('$ts = New-Object -ComObject Schedule.service\r\n');
4401 child.stdin.write('$ts.connect()\r\n');
4402 child.stdin.write('$tsfolder = $ts.getfolder("\\")\r\n');
4403 child.stdin.write('$task = $tsfolder.GetTask("MeshChatTask")\r\n');
4404 child.stdin.write('$taskdef = $task.Definition\r\n');
4405 child.stdin.write('$taskdef.Settings.StopIfGoingOnBatteries = $false\r\n');
4406 child.stdin.write('$taskdef.Settings.DisallowStartIfOnBatteries = $false\r\n');
4407 child.stdin.write('$taskdef.Actions.Item(1).Path = $env:_target\r\n');
4408 child.stdin.write('$taskdef.Actions.Item(1).Arguments = $env:_args\r\n');
4409 child.stdin.write('$tsfolder.RegisterTaskDefinition($task.Name, $taskdef, 4, $null, $null, $null)\r\n');
4410
4411 child.stdin.write('SCHTASKS /RUN /TN MeshChatTask\r\n');
4412 child.stdin.write('SCHTASKS /DELETE /F /TN MeshChatTask\r\nexit\r\n');
4413 child.waitExit();
4414 }
4415 break;
4416 case 'linux':
4417 child = require('child_process').execFile('/usr/bin/xdg-open', ['xdg-open', url], { uid: require('user-sessions').consoleUid() });
4418 break;
4419 case 'darwin':
4420 child = require('child_process').execFile('/usr/bin/open', ['open', url], { uid: require('user-sessions').consoleUid() });
4421 break;
4422 default:
4423 // Unknown platform, ignore this command.
4424 break;
4425 }
4426 } catch (ex) { }
4427 return child;
4428 }
4429
4430 // Process a mesh agent console command
4431 function processConsoleCommand(cmd, args, rights, sessionid) {
4432 try {
4433 var response = null;
4434 switch (cmd) {
4435 case 'help': { // Displays available commands
4436 var fin = '', f = '', availcommands = 'domain,translations,agentupdate,errorlog,msh,timerinfo,coreinfo,coreinfoupdate,coredump,service,fdsnapshot,fdcount,startupoptions,';
4437 availcommands += 'alert,agentsize,versions,help,info,osinfo,args,print,type,dbkeys,dbget,dbset,dbdelete,dbcompact,eval,parseuri,httpget,wslist,plugin,wsconnect,wssend,wsclose,notify,';
4438 availcommands += 'ls,ps,kill,netinfo,location,power,wakeonlan,setdebug,smbios,rawsmbios,toast,lock,users,openurl,getscript,getclip,setclip,log,cpuinfo,sysinfo,';
4439 availcommands += 'apf,scanwifi,wallpaper,agentmsg,task,uninstallagent,display,openfile,installedapps';
4440 if (require('os').dns != null) { availcommands += ',dnsinfo'; }
4441 try { require('linux-dhcp'); availcommands += ',dhcp'; } catch (ex) { }
4442 if (process.platform == 'win32') {
4443 availcommands += ',bitlocker,cs,wpfhwacceleration,uac,volumes,rdpport,domaininfo,printers,wmi';
4444 if (bcdOK()) { availcommands += ',safemode'; }
4445 if (require('notifybar-desktop').DefaultPinned != null) { availcommands += ',privacybar'; }
4446 try { require('win-utils'); availcommands += ',taskbar'; } catch (ex) { }
4447 try { require('win-info'); availcommands += ',qfe,defender,av,installedstoreapps'; } catch (ex) { }
4448 try { require('win-deskutils'); availcommands += ',mousetrails,idletime,deskbackground'; } catch (ex) { }
4449 }
4450 if (amt != null) { availcommands += ',amt,amtconfig,amtevents'; }
4451 if (process.platform != 'freebsd') { availcommands += ',vm'; }
4452 if (require('MeshAgent').maxKvmTileSize != null) { availcommands += ',kvmmode'; }
4453 try { require('zip-reader'); availcommands += ',zip,unzip'; } catch (ex) { }
4454
4455 availcommands = availcommands.split(',').sort();
4456 while (availcommands.length > 0) {
4457 if (f.length > 90) { fin += (f + ',\r\n'); f = ''; }
4458 f += (((f != '') ? ', ' : ' ') + availcommands.shift());
4459 }
4460 if (f != '') { fin += f; }
4461 response = "Available commands: \r\n" + fin + ".";
4462 break;
4463 }
4464 case 'mousetrails':
4465 try { require('win-deskutils'); } catch (ex) { response = 'Unknown command "mousetrails", type "help" for list of available commands.'; break; }
4466 var id = require('user-sessions').getProcessOwnerName(process.pid).tsid == 0 ? 1 : null;
4467 switch (args['_'].length)
4468 {
4469 case 0:
4470 var trails = require('win-deskutils').mouse.getTrails(id);
4471 response = trails == 0 ? 'MouseTrails Disabled' : ('MouseTrails enabled (' + trails + ')');
4472 response += '\nTo change setting, specify a positive integer, where 0 is disable: mousetrails [n]';
4473 break;
4474 case 1:
4475 var trails = parseInt(args['_'][0]);
4476 require('win-deskutils').mouse.setTrails(trails, id);
4477 trails = require('win-deskutils').mouse.getTrails(id);
4478 response = trails == 0 ? 'MouseTrails Disabled' : ('MouseTrails enabled (' + trails + ')');
4479 break;
4480 default:
4481 response = 'Proper usage: mousetrails [n]';
4482 break;
4483 }
4484 break;
4485 case 'deskbackground':
4486 try { require('win-deskutils'); } catch (ex) { response = 'Unknown command "deskbackground", type "help" for list of available commands.'; break; }
4487 var id = require('user-sessions').getProcessOwnerName(process.pid).tsid == 0 ? 1 : null;
4488 switch (args['_'].length)
4489 {
4490 case 0:
4491 response = 'Desktop Background: ' + require('win-deskutils').background.get(id);
4492 break;
4493 case 1:
4494 require('win-deskutils').background.set(args['_'][0], id);
4495 response = 'Desktop Background: ' + require('win-deskutils').background.get(id);
4496 break;
4497 default:
4498 response = 'Proper usage: deskbackground [path]';
4499 break;
4500 }
4501 break;
4502 case 'idletime':
4503 try { require('win-deskutils'); } catch (ex) { response = 'Unknown command "idletime", type "help" for list of available commands.'; break; }
4504 require('win-deskutils').idle.getSecondsAllSessions().then(function (seconds) { sendConsoleText((seconds === -1 ? 'No active users' : 'Idle time for all sessions: ' + seconds + ' seconds'), sessionid); });
4505 break;
4506 case 'taskbar':
4507 try { require('win-utils'); } catch (ex) { response = 'Unknown command "taskbar", type "help" for list of available commands.'; break; }
4508 switch (args['_'].length) {
4509 case 1:
4510 case 2:
4511 {
4512 var tsid = parseInt(args['_'][1]);
4513 if (isNaN(tsid)) { tsid = require('user-sessions').consoleUid(); }
4514 sendConsoleText('Changing TaskBar AutoHide status. Please wait...', sessionid);
4515 try {
4516 var result = require('win-utils').taskBar.autoHide(tsid, args['_'][0].toLowerCase() == 'hide');
4517 response = 'Current Status of TaskBar AutoHide: ' + result;
4518 } catch (ex) { response = 'Unable to change TaskBar settings'; }
4519 }
4520 break;
4521 default:
4522 {
4523 response = 'Proper usage: taskbar HIDE|SHOW [TSID]';
4524 break;
4525 }
4526 }
4527 break;
4528 case 'printers':
4529 if (process.platform != 'win32') {
4530 response = 'Unknown command "printers", type "help" for list of available commands.';
4531 } else {
4532 var wmi = require('win-wmi-fixed');
4533 var printers = wmi.query('ROOT\\CIMV2', 'SELECT * FROM Win32_Printer');
4534 trimResults(printers);
4535 var tcpPorts = wmi.query('ROOT\\CIMV2', 'SELECT Name, HostAddress, PortNumber FROM Win32_TCPIPPrinterPort');
4536 trimResults(tcpPorts);
4537 var portMap = {};
4538 for (var j = 0; j < tcpPorts.length; ++j) { portMap[tcpPorts[j].Name] = tcpPorts[j].HostAddress + ':' + tcpPorts[j].PortNumber; }
4539 // For vendor ports not covered by Win32_TCPIPPrinterPort, walk the registry under Print\Monitors
4540 try {
4541 var reg = require('win-registry');
4542 var HKLM = reg.HKEY.LocalMachine;
4543 var monitorsKey = 'SYSTEM\\CurrentControlSet\\Control\\Print\\Monitors';
4544 var monitors = reg.QueryKey(HKLM, monitorsKey);
4545 if (monitors && monitors.keys) {
4546 for (var m = 0; m < monitors.keys.length; ++m) {
4547 var portsKey = monitorsKey + '\\' + monitors.keys[m] + '\\Ports';
4548 try {
4549 var portsNode = reg.QueryKey(HKLM, portsKey);
4550 if (portsNode && portsNode.keys) {
4551 for (var p = 0; p < portsNode.keys.length; ++p) {
4552 var portName = portsNode.keys[p];
4553 if (portMap[portName]) continue;
4554 var portKey = portsKey + '\\' + portName;
4555 var ip = null;
4556 try { ip = reg.QueryKey(HKLM, portKey, 'IPAddress'); } catch (e) {}
4557 if (!ip) { try { ip = reg.QueryKey(HKLM, portKey, 'HostName'); } catch (e) {} }
4558 if (ip) { portMap[portName] = ip; }
4559 }
4560 }
4561 } catch (e) {}
4562 }
4563 }
4564 } catch (e) {}
4565 // For Epson and other vendor ports still missing, query ROOT\StandardCimv2\MSFT_PrinterPort
4566 try {
4567 var msftPorts = wmi.query('ROOT\\StandardCimv2', 'SELECT Name, Description FROM MSFT_PrinterPort');
4568 trimResults(msftPorts);
4569 for (var j = 0; j < msftPorts.length; ++j) {
4570 if (!portMap[msftPorts[j].Name] && msftPorts[j].Description) {
4571 portMap[msftPorts[j].Name] = msftPorts[j].Description;
4572 }
4573 }
4574 } catch (e) {}
4575 var printJobs = wmi.query('ROOT\\CIMV2', 'SELECT Name FROM Win32_PrintJob');
4576 trimResults(printJobs);
4577 var jobCount = {};
4578 for (var j = 0; j < printJobs.length; ++j) {
4579 var jobPrinter = printJobs[j].Name.split(',')[0];
4580 jobCount[jobPrinter] = (jobCount[jobPrinter] || 0) + 1;
4581 }
4582 var printerStatusMap = { 1: 'Other', 2: 'Unknown', 3: 'Idle', 4: 'Printing', 5: 'Warmup', 6: 'Stopped', 7: 'Offline' };
4583 var errorStateMap = { 0: 'Unknown', 1: 'Other', 2: 'No Error', 3: 'Low Paper', 4: 'No Paper', 5: 'Low Toner', 6: 'No Toner', 7: 'Door Open', 8: 'Jammed', 9: 'Offline', 10: 'Service Requested', 11: 'Output Bin Full' };
4584 for (var i = 0; i < printers.length; ++i) {
4585 var portDesc = portMap[printers[i].PortName];
4586 var jobs = jobCount[printers[i].Name] || 0;
4587 var status = printerStatusMap[printers[i].PrinterStatus] || 'Unknown';
4588 var errors = [];
4589 var err = parseInt(printers[i].DetectedErrorState) || 0;
4590 if (err > 2) { errors.push(errorStateMap[err] || ('Error ' + err)); }
4591 var line = printers[i].Name +
4592 ' - ' + printers[i].PortName +
4593 (portDesc ? ' (' + portDesc + ')' : '') +
4594 ' [' + status + ']' +
4595 (errors.length > 0 ? ' [' + errors.join(', ') + ']' : '') +
4596 (jobs > 0 ? ' [' + jobs + ' job' + (jobs > 1 ? 's' : '') + ' queued]' : '');
4597 sendConsoleText(line, sessionid);
4598 }
4599 }
4600 break;
4601 case 'privacybar':
4602 if (process.platform != 'win32' || require('notifybar-desktop').DefaultPinned == null) {
4603 response = 'Unknown command "privacybar", type "help" for list of available commands.';
4604 }
4605 else {
4606 switch (args['_'].length) {
4607 default:
4608 // Show Help
4609 response = "Current Default Pinned State: " + (require('notifybar-desktop').DefaultPinned ? "PINNED" : "UNPINNED") + '\r\n';
4610 response += "To set default pinned state:\r\n privacybar [PINNED|UNPINNED]\r\n";
4611 break;
4612 case 1:
4613 switch (args['_'][0].toUpperCase()) {
4614 case 'PINNED':
4615 require('notifybar-desktop').DefaultPinned = true;
4616 response = "privacybar default pinned state is: PINNED";
4617 break;
4618 case 'UNPINNED':
4619 require('notifybar-desktop').DefaultPinned = false;
4620 response = "privacybar default pinned state is: UNPINNED";
4621 break;
4622 default:
4623 response = "INVALID parameter: " + args['_'][0].toUpperCase();
4624 break;
4625 }
4626 break;
4627 }
4628 }
4629 break;
4630 case 'domain':
4631 response = getDomainInfo();
4632 break;
4633 case 'domaininfo':
4634 {
4635 if (process.platform != 'win32') {
4636 response = 'Unknown command "domaininfo", type "help" for list of available commands.';
4637 break;
4638 }
4639 if (global._domainQuery != null) {
4640 response = "There is already an outstanding Domain Controller Query... Please try again later...";
4641 break;
4642 }
4643
4644 sendConsoleText('Querying Domain Controller... This can take up to 60 seconds. Please wait...', sessionid);
4645 global._domainQuery = require('win-wmi').queryAsync('ROOT\\CIMV2', 'SELECT * FROM Win32_NTDomain');
4646 global._domainQuery.session = sessionid;
4647 global._domainQuery.then(function (v) {
4648 var results = [];
4649 if (Array.isArray(v)) {
4650 var i;
4651 var r;
4652 for (i = 0; i < v.length; ++i) {
4653 r = {};
4654 if (v[i].DomainControllerAddress != null) { r.DomainControllerAddress = v[i].DomainControllerAddress.split('\\').pop(); }
4655 if (r.DomainControllerName != null) { r.DomainControllerName = v[i].DomainControllerName.split('\\').pop(); }
4656 r.DomainGuid = v[i].DomainGuid;
4657 r.DomainName = v[i].DomainName;
4658 if (r.DomainGuid != null) {
4659 results.push(r);
4660 }
4661 }
4662 }
4663 if (results.length > 0) {
4664 sendConsoleText('Domain Controller Results:', this.session);
4665 sendConsoleText(JSON.stringify(results, null, 1), this.session);
4666 sendConsoleText('End of results...', this.session);
4667 }
4668 else {
4669 sendConsoleText('Domain Controller: No results returned. Is the domain controller reachable?', this.session);
4670 }
4671 global._domainQuery = null;
4672 });
4673 break;
4674 }
4675 case 'wmi':
4676 if (process.platform != 'win32') {
4677 response = 'Unknown command "wmi", type "help" for list of available commands.';
4678 break;
4679 }
4680 if (args['_'].length < 2 || args['_'].length > 3) {
4681 response = 'Execute a WMI query.\r\nUsage: wmi namespace "query" [(a)sync][(p)retty]\r\n' +
4682 'Example: wmi [ROOT\\]CIMV2 "SELECT Name,ProcessId FROM Win32_Process WHERE Name=\'meshagent.exe\'" ap\r\n';
4683 break;
4684 }
4685 var opt = (args['_'][2]|| '').toLowerCase();
4686 var ns = args['_'][0].trim();
4687 if (!/^root\\\w/i.test(ns)) { ns = 'ROOT\\' + ns; }
4688 var q = (args['_'][1]).trim();
4689 var wmi = require('win-wmi-fixed');
4690 var output = function (res) { sendConsoleText(res && res[0] ? JSON.stringify(res, null, ((opt.indexOf('p') !== -1) ? 2 : 0)) : 'No results', sessionid); };
4691 var error = function (e) { var msg = (e && e.message) ? e.message : (typeof e === 'string' ? e : JSON.stringify(e)); sendConsoleText('Error: ' + msg, sessionid);};
4692 sendConsoleText('Performing query. Response can take a while (sometimes >60s)', sessionid);
4693 if (opt.indexOf('a') !== -1) {
4694 wmi.queryAsync(ns, q)
4695 .then( output )
4696 .catch( error );
4697 } else {
4698 try { output(wmi.query(ns, q)); } catch (e) { error(e); }
4699 }
4700 break;
4701 case 'translations': {
4702 response = JSON.stringify(coretranslations, null, 2);
4703 break;
4704 }
4705 case 'volumes':
4706 response = JSON.stringify(require('win-volumes').getVolumes(), null, 1);
4707 break;
4708 case 'bitlocker':
4709 if (process.platform == 'win32') {
4710 if (require('win-volumes').volumes_promise != null) {
4711 var p = require('win-volumes').volumes_promise();
4712 p.then(function (res) { sendConsoleText(JSON.stringify(cleanGetBitLockerVolumeInfo(res), null, 1), this.session); });
4713 }
4714 }
4715 break;
4716 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.
4717 {
4718 try { require('linux-dhcp'); } catch (ex) { response = 'Unknown command "dhcp", type "help" for list of available commands.'; break; }
4719 if (args['_'].length == 0) {
4720 var j = require('os').networkInterfaces();
4721 var ifcs = [];
4722 for (var i in j) {
4723 for (var z in j[i]) {
4724 if (j[i][z].status == 'up' && j[i][z].type != 'loopback' && j[i][z].address != null) {
4725 ifcs.push('"' + i + '"');
4726 break;
4727 }
4728 }
4729 }
4730 response = 'Proper usage: dhcp [' + ifcs.join(' | ') + ']';
4731 }
4732 else {
4733 require('linux-dhcp').client.info(args['_'][0]).
4734 then(function (d) {
4735 sendConsoleText(JSON.stringify(d, null, 1), sessionid);
4736 },
4737 function (e) {
4738 sendConsoleText(e, sessionid);
4739 });
4740 }
4741 break;
4742 }
4743 case 'cs':
4744 if (process.platform != 'win32') {
4745 response = 'Unknown command "cs", type "help" for list of available commands.';
4746 break;
4747 }
4748 switch (args['_'].length) {
4749 case 0:
4750 try {
4751 var cs = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'System\\CurrentControlSet\\Control\\Power', 'CsEnabled');
4752 response = "Connected Standby: " + (cs == 1 ? "ENABLED" : "DISABLED");
4753 } catch (ex) {
4754 response = "This machine does not support Connected Standby";
4755 }
4756 break;
4757 case 1:
4758 if ((args['_'][0].toUpperCase() != 'ENABLE' && args['_'][0].toUpperCase() != 'DISABLE')) {
4759 response = "Proper usage:\r\n cs [ENABLE|DISABLE]";
4760 }
4761 else {
4762 try {
4763 var cs = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'System\\CurrentControlSet\\Control\\Power', 'CsEnabled');
4764 require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'System\\CurrentControlSet\\Control\\Power', 'CsEnabled', args['_'][0].toUpperCase() == 'ENABLE' ? 1 : 0);
4765
4766 cs = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'System\\CurrentControlSet\\Control\\Power', 'CsEnabled');
4767 response = "Connected Standby: " + (cs == 1 ? "ENABLED" : "DISABLED");
4768 } catch (ex) {
4769 response = "This machine does not support Connected Standby";
4770 }
4771 }
4772 break;
4773 default:
4774 response = "Proper usage:\r\n cs [ENABLE|DISABLE]";
4775 break;
4776 }
4777 break;
4778 case 'assistant':
4779 if (process.platform == 'win32') {
4780 // Install MeshCentral Assistant on this device
4781 response = "Usage: Assistant [info|install|uninstall]";
4782 if (args['_'].length == 1) {
4783 if ((args['_'][0] == 'install') || (args['_'][0] == 'info')) { response = ''; require('MeshAgent').SendCommand({ action: 'meshToolInfo', sessionid: sessionid, name: 'MeshCentralAssistant', cookie: true, tag: args['_'][0] }); }
4784 // TODO: Uninstall
4785 }
4786 } else {
4787 response = "MeshCentral Assistant is not supported on this platform.";
4788 }
4789 break;
4790 case 'userimage':
4791 require('MeshAgent').SendCommand({ action: 'getUserImage', sessionid: sessionid, userid: args['_'][0], tag: 'info' });
4792 response = 'ok';
4793 break;
4794 case 'agentupdate':
4795 require('MeshAgent').SendCommand({ action: 'agentupdate', sessionid: sessionid });
4796 break;
4797 case 'agentupdateex':
4798 // Perform an direct agent update without requesting any information from the server, this should not typically be used.
4799 if (args['_'].length == 1) {
4800 if (args['_'][0].startsWith('https://')) { agentUpdate_Start(args['_'][0], { sessionid: sessionid }); } else { response = "Usage: agentupdateex https://server/path"; }
4801 } else {
4802 agentUpdate_Start(null, { sessionid: sessionid });
4803 }
4804 break;
4805 case 'errorlog':
4806 switch (args['_'].length) {
4807 case 0:
4808 // All Error Logs
4809 response = JSON.stringify(require('util-agentlog').read(), null, 1);
4810 break;
4811 case 1:
4812 // Error Logs, by either count or timestamp
4813 response = JSON.stringify(require('util-agentlog').read(parseInt(args['_'][0])), null, 1);
4814 break;
4815 default:
4816 response = "Proper usage:\r\n errorlog [lastCount|linuxEpoch]";
4817 break;
4818 }
4819 break;
4820 case 'msh':
4821 if (args['_'].length == 0) {
4822 response = JSON.stringify(_MSH(), null, 2);
4823 } else if (args['_'].length > 3) {
4824 response = 'Proper usage: msh [get|set|delete]\r\nmsh get MeshServer\r\nmsh set abc "xyz"\r\nmsh delete abc';
4825 } else {
4826 var mshFileName = process.execPath.replace('.exe','') + '.msh';
4827 switch (args['_'][0].toLocaleLowerCase()) {
4828 case 'get':
4829 if (typeof args['_'][1] != 'string' || args['_'].length > 2) {
4830 response = 'Proper usage: msh get MeshServer';
4831 } else if(_MSH()[args['_'][1]]) {
4832 response = _MSH()[args['_'][1]];
4833 } else {
4834 response = "Unknown Value: " + args['_'][1];
4835 }
4836 break;
4837 case 'set':
4838 if (typeof args['_'][1] != 'string' || typeof args['_'][2] != 'string') {
4839 response = 'Proper usage: msh set abc "xyz"';
4840 } else {
4841 var jsonToSave = _MSH();
4842 jsonToSave[args['_'][1]] = args['_'][2];
4843 var updatedContent = '';
4844 for (var key in jsonToSave) {
4845 if (jsonToSave.hasOwnProperty(key)) {
4846 updatedContent += key + '=' + jsonToSave[key] + '\n';
4847 }
4848 }
4849 try {
4850 require('fs').writeFileSync(mshFileName, updatedContent);
4851 response = "msh set " + args['_'][1] + " successful"
4852 } catch (ex) {
4853 response = "msh set " + args['_'][1] + " unsuccessful";
4854 }
4855 }
4856 break;
4857 case 'delete':
4858 if (typeof args['_'][1] != 'string') {
4859 response = 'Proper usage: msh delete abc';
4860 } else {
4861 var jsonToSave = _MSH();
4862 delete jsonToSave[args['_'][1]];
4863 var updatedContent = '';
4864 for (var key in jsonToSave) {
4865 if (jsonToSave.hasOwnProperty(key)) {
4866 updatedContent += key + '=' + jsonToSave[key] + '\n';
4867 }
4868 }
4869 try {
4870 require('fs').writeFileSync(mshFileName, updatedContent);
4871 response = "msh delete " + args['_'][1] + " successful"
4872 } catch (ex) {
4873 response = "msh delete " + args['_'][1] + " unsuccessful";
4874 }
4875 }
4876 break;
4877 default:
4878 response = 'Proper usage: msh [get|set|delete]\r\nmsh get MeshServer\r\nmsh set abc "xyz"\r\nmsh delete abc';
4879 break;
4880 }
4881 }
4882 break;
4883 case 'dnsinfo':
4884 if (require('os').dns == null) {
4885 response = "Unknown command \"" + cmd + "\", type \"help\" for list of available commands.";
4886 }
4887 else {
4888 response = 'DNS Servers: ';
4889 var dns = require('os').dns();
4890 for (var i = 0; i < dns.length; ++i) {
4891 if (i > 0) { response += ', '; }
4892 response += dns[i];
4893 }
4894 }
4895 break;
4896 case 'timerinfo':
4897 response = require('ChainViewer').getTimerInfo();
4898 break;
4899 case 'rdpport':
4900 if (process.platform != 'win32') {
4901 response = 'Unknown command "rdpport", type "help" for list of available commands.';
4902 return;
4903 }
4904 if (args['_'].length == 0) {
4905 response = 'Proper usage: rdpport [get|default|PORTNUMBER]';
4906 } else {
4907 switch (args['_'][0].toLocaleLowerCase()) {
4908 case 'get':
4909 var rdpport = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'System\\CurrentControlSet\\Control\\Terminal Server\\WinStations\\RDP-Tcp', 'PortNumber');
4910 response = "Current RDP Port Set To: " + rdpport + '\r\n';
4911 break;
4912 case 'default':
4913 try {
4914 require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'System\\CurrentControlSet\\Control\\Terminal Server\\WinStations\\RDP-Tcp', 'PortNumber', 3389);
4915 response = 'RDP Port Set To 3389, Please Dont Forget To Restart Your Computer To Fully Apply';
4916 } catch (ex) {
4917 response = 'Unable to Set RDP Port To: 3389';
4918 }
4919 break;
4920 default:
4921 if (isNaN(parseFloat(args['_'][0]))){
4922 response = 'Proper usage: rdpport [get|default|PORTNUMBER]';
4923 } else if(parseFloat(args['_'][0]) < 0 || args['_'][0] > 65535) {
4924 response = 'RDP Port Must Be More Than 0 And Less Than 65535';
4925 } else {
4926 try {
4927 require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'System\\CurrentControlSet\\Control\\Terminal Server\\WinStations\\RDP-Tcp', 'PortNumber', parseFloat(args['_'][0]));
4928 response = 'RDP Port Set To ' + args['_'][0] + ', Please Dont Forget To Restart Your Computer To Fully Apply';
4929 } catch (ex) {
4930 response = 'Unable to Set RDP Port To: '+args['_'][0];
4931 }
4932 }
4933 break;
4934 }
4935 }
4936 break;
4937 case 'find':
4938 if (args['_'].length <= 1) {
4939 response = "Proper usage:\r\n find root criteria [criteria2] [criteria n...]";
4940 }
4941 else {
4942 var root = args['_'][0];
4943 var p = args['_'].slice(1);
4944 var r = require('file-search').find(root, p);
4945 r.sid = sessionid;
4946 r.on('result', function (str) { sendConsoleText(str, this.sid); });
4947 r.then(function () { sendConsoleText('*** End Results ***', this.sid); });
4948 response = "Find: [" + root + "] " + JSON.stringify(p);
4949 }
4950 break;
4951 case 'coreinfo': {
4952 response = JSON.stringify(meshCoreObj, null, 2);
4953 break;
4954 }
4955 case 'coreinfoupdate': {
4956 sendPeriodicServerUpdate(null, true);
4957 response = "Core Info Update Requested"
4958 break;
4959 }
4960 case 'agentmsg': {
4961 if (args['_'].length == 0) {
4962 response = "Proper usage:\r\n agentmsg add \"[message]\" [iconIndex]\r\n agentmsg remove [id]\r\n agentmsg list"; // Display usage
4963 } else {
4964 if ((args['_'][0] == 'add') && (args['_'].length > 1)) {
4965 var msgID, iconIndex = 0;
4966 if (args['_'].length >= 3) { try { iconIndex = parseInt(args['_'][2]); } catch (ex) { } }
4967 if (typeof iconIndex != 'number') { iconIndex = 0; }
4968 msgID = sendAgentMessage(args['_'][1], iconIndex);
4969 response = 'Agent message: ' + msgID + ' added.';
4970 } else if ((args['_'][0] == 'remove') && (args['_'].length > 1)) {
4971 var r = removeAgentMessage(args['_'][1]);
4972 response = 'Message ' + (r ? 'removed' : 'NOT FOUND');
4973 } else if (args['_'][0] == 'list') {
4974 response = JSON.stringify(sendAgentMessage(), null, 2);
4975 }
4976 broadcastSessionsToRegisteredApps();
4977 }
4978 break;
4979 }
4980 case 'clearagentmsg': {
4981 removeAgentMessage();
4982 broadcastSessionsToRegisteredApps();
4983 break;
4984 }
4985 case 'coredump':
4986 if (args['_'].length != 1) {
4987 response = "Proper usage: coredump on|off|status|clear"; // Display usage
4988 } else {
4989 switch (args['_'][0].toLowerCase()) {
4990 case 'on':
4991 process.coreDumpLocation = (process.platform == 'win32') ? (process.execPath.replace('.exe', '.dmp')) : (process.execPath + '.dmp');
4992 response = 'coredump is now on';
4993 break;
4994 case 'off':
4995 process.coreDumpLocation = null;
4996 response = 'coredump is now off';
4997 break;
4998 case 'status':
4999 response = 'coredump is: ' + ((process.coreDumpLocation == null) ? 'off' : 'on');
5000 if (process.coreDumpLocation != null) {
Showing first 5,000 of 7,042 lines. View raw