2Copyright 2018-2022 Intel Corporation
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
8 http://www.apache.org/licenses/LICENSE-2.0
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
17process.on('uncaughtException', function (ex) {
18 require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: "uncaughtException1: " + ex });
20if (process.platform == 'win32' && require('user-sessions').getDomain == null) {
21 require('user-sessions').getDomain = function getDomain(uid) {
22 return (this.getSessionAttribute(uid, this.InfoClass.WTSDomainName));
26var promise = require('promise');
30var MESHRIGHT_EDITMESH = 1;
31var MESHRIGHT_MANAGEUSERS = 2;
32var MESHRIGHT_MANAGECOMPUTERS = 4;
33var MESHRIGHT_REMOTECONTROL = 8;
34var MESHRIGHT_AGENTCONSOLE = 16;
35var MESHRIGHT_SERVERFILES = 32;
36var MESHRIGHT_WAKEDEVICE = 64;
37var MESHRIGHT_SETNOTES = 128;
38var MESHRIGHT_REMOTEVIEW = 256; // Remote View Only
39var MESHRIGHT_NOTERMINAL = 512;
40var MESHRIGHT_NOFILES = 1024;
41var MESHRIGHT_NOAMT = 2048;
42var MESHRIGHT_LIMITEDINPUT = 4096;
43var MESHRIGHT_LIMITEVENTS = 8192;
44var MESHRIGHT_CHATNOTIFY = 16384;
45var MESHRIGHT_UNINSTALL = 32768;
46var MESHRIGHT_NODESKTOP = 65536;
48var pendingSetClip = false; // This is a temporary hack to prevent multiple setclips at the same time to stop the agent from crashing.
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%.
54function __readdirSync_fix(path)
56 var sysnative = false;
57 pathstr = require('fs')._fixwinpath(path);
58 if (pathstr.split('\\*').join('').toLowerCase() == process.env['windir'].toLowerCase()) { sysnative = true; }
60 var ret = require('fs').__readdirSync_old(path);
61 if (sysnative) { ret.push('sysnative'); }
65if (process.platform == 'win32' && require('_GenericMarshal').PointerSize == 4 && require('os').arch() == 'x64')
67 if (require('fs').readdirSync.version == null)
70 // 32 Bit Windows Agent on 64 bit Windows has not been patched for sysnative issue, so lets use our own solution
72 require('fs').__readdirSync_old = require('fs').readdirSync;
73 require('fs').readdirSync = __readdirSync_fix;
78 if (process.platform != 'win32') { return (false); }
79 if (require('os').arch() == 'x64') {
80 return (require('_GenericMarshal').PointerSize == 8);
84function getDomainInfo() {
85 var hostname = require('os').hostname();
86 var ret = { Name: hostname, Domain: "", PartOfDomain: false };
88 switch (process.platform) {
91 ret = require('win-wmi').query('ROOT\\CIMV2', 'SELECT * FROM Win32_ComputerSystem', ['Name', 'Domain', 'PartOfDomain'])[0];
100 hasrealm = require('lib-finder').hasBinary('realm');
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');
123 names = JSON.parse(child.stdout.str);
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 };
140function getLogonCacheKeys() {
141 var registry = require('win-registry');
142 var HKLM = registry.HKEY.LocalMachine;
146 function readSubKeys(path) {
147 var vals = registry.QueryKey(HKLM, path);
150 // Extract IdentityName, SAMName, SID if they exist
151 var identityName = null, samName = null, sid = null;
153 for (var i = 0; i in vals.values; i++) {
154 if (vals.values[i].toLowerCase() === 'identityname' && identityName === null) {
155 identityName = registry.QueryKey(HKLM, path, vals.values[i]);
157 if (vals.values[i].toLowerCase() === 'samname' && samName === null) {
158 samName = registry.QueryKey(HKLM, path, vals.values[i]);
160 if (vals.values[i].toLowerCase() === 'sid' && sid === null) {
161 sid = registry.QueryKey(HKLM, path, vals.values[i]);
165 // If IdentityName exists, add to userObj
174 // Recurse into subkeys if any
175 if (vals.subkeys && vals.subkeys.length > 0) {
176 for (var j = 0; j < vals.subkeys.length; j++) {
177 readSubKeys(path + '\\' + vals.subkeys[j]);
182 // Start recursion from the LogonCache root
183 readSubKeys('SOFTWARE\\Microsoft\\IdentityStore\\LogonCache');
187 function pushUnique(arr, val) {
188 if (val && arr.indexOf(val) === -1) arr.push(val);
191 // Group by UPN and merge values
192 for (var i = 0; i < userObj.length; i++) {
195 if (!grouped[u.UPN]) grouped[u.UPN] = {UPN: u.UPN, SID: [], SAM: []};
197 pushUnique(grouped[u.UPN].SID, u.SID);
198 pushUnique(grouped[u.UPN].SAM, u.SAM);
202 // Convert grouped object to array
203 for (var k in grouped) if (grouped.hasOwnProperty(k)) userObj.push(grouped[k]);
209function getJoinState() {
210 if (process.platform != 'win32') { return -1; }
211 var isAzureAD = false;
212 var isOnPrem = false;
213 var isHybrid = false;
214 var isMicrosoft = false;
215 // 1 Azure AD / Entra ID
217 const joinInfo = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'SYSTEM\\CurrentControlSet\\Control\\CloudDomainJoin\\JoinInfo');
218 isAzureAD = Array.isArray(joinInfo.subkeys) && joinInfo.subkeys.length > 0;
222 const tcpip = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters','Domain');
223 isOnPrem = !!(tcpip !== "" || null);
226 isHybrid = isAzureAD && isOnPrem;
227 // 4 Microsoft Account
229 const userAccounts = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'SOFTWARE\\Microsoft\\IdentityStore\\LogonCache\\D7F9888F-E3FC-49b0-9EA6-A85B5F392A4F');
230 isMicrosoft = Array.isArray(userAccounts.subkeys) && userAccounts.subkeys.length > 0;
232 if (isMicrosoft) return 4;
233 if (isHybrid) return 3;
234 if (isOnPrem) return 2;
235 if (isAzureAD) return 1;
242 Object.defineProperty(Array.prototype, 'findIndex', {
243 value: function (func) {
245 for (i = 0; i < this.length; ++i) {
246 if (func(this[i], i, this)) {
255if (require('MeshAgent').ARCHID == null) {
257 switch (process.platform) {
259 id = require('_GenericMarshal').PointerSize == 4 ? 3 : 4;
262 id = require('_GenericMarshal').PointerSize == 4 ? 31 : 30;
266 id = require('os').arch() == 'x64' ? 16 : 29;
267 } catch (ex) { id = 16; }
270 if (id != null) { Object.defineProperty(require('MeshAgent'), 'ARCHID', { value: id }); }
273function setDefaultCoreTranslation(obj, field, value) {
274 if (obj[field] == null || obj[field] == '') { obj[field] = value; }
277function getCoreTranslation() {
279 if (global.coretranslations != null) {
281 var lang = require('util-language').current;
282 if (coretranslations[lang] == null) { lang = lang.split('-')[0]; }
283 if (coretranslations[lang] == null) { lang = 'en'; }
284 if (coretranslations[lang] != null) { ret = coretranslations[lang]; }
289 setDefaultCoreTranslation(ret, 'allow', 'Allow');
290 setDefaultCoreTranslation(ret, 'deny', 'Deny');
291 setDefaultCoreTranslation(ret, 'autoAllowForFive', 'Auto accept all connections for next 5 minutes');
292 setDefaultCoreTranslation(ret, 'terminalConsent', '{0} requesting remote terminal access. Grant access?');
293 setDefaultCoreTranslation(ret, 'desktopConsent', '{0} requesting remote desktop access. Grant access?');
294 setDefaultCoreTranslation(ret, 'fileConsent', '{0} requesting remote file Access. Grant access?');
295 setDefaultCoreTranslation(ret, 'terminalNotify', '{0} started a remote terminal session.');
296 setDefaultCoreTranslation(ret, 'desktopNotify', '{0} started a remote desktop session.');
297 setDefaultCoreTranslation(ret, 'fileNotify', '{0} started a remote file session.');
298 setDefaultCoreTranslation(ret, 'privacyBar', 'Sharing desktop with: {0}');
302var currentTranslation = getCoreTranslation();
305 require('kvm-helper');
312 require('user-sessions').Current(function (c) { r = c; });
313 if (process.platform != 'win32') {
315 r[i].SessionId = r[i].uid;
321 addModuleObject('kvm-helper', j);
325function lockDesktop(uid) {
326 switch (process.platform) {
329 var name = require('user-sessions').getUsername(uid);
330 var child = require('child_process').execFile('/bin/sh', ['sh']);
331 child.stdout.str = ''; child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });
332 child.stderr.str = ''; child.stderr.on('data', function (chunk) { this.str += chunk.toString(); });
333 child.stdin.write('loginctl show-user -p Sessions ' + name + " | awk '{");
334 child.stdin.write('gsub(/^Sessions=/,"",$0);');
335 child.stdin.write('cmd = sprintf("loginctl lock-session %s",$0);');
336 child.stdin.write('system(cmd);');
337 child.stdin.write("}'\nexit\n");
341 var child = require('child_process').execFile('/bin/sh', ['sh']);
342 child.stdout.str = ''; child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });
343 child.stderr.str = ''; child.stderr.on('data', function (chunk) { this.str += chunk.toString(); });
344 child.stdin.write('loginctl lock-sessions\nexit\n');
350 var options = { type: 1, uid: uid };
351 var child = require('child_process').execFile(process.env['windir'] + '\\system32\\cmd.exe', ['/c', 'RunDll32.exe user32.dll,LockWorkStation'], options);
359var writable = require('stream').Writable;
360function destopLockHelper_pipe(httprequest) {
361 if (process.platform != 'linux' && process.platform != 'freebsd') { return; }
363 if (httprequest.unlockerHelper == null && httprequest.desktop != null && httprequest.desktop.kvm != null) {
364 httprequest.unlockerHelper = new writable(
366 'write': function (chunk, flush) {
367 if (chunk.readUInt16BE(0) == 65) {
368 delete this.request.autolock;
373 'final': function (flush) {
377 httprequest.unlockerHelper.request = httprequest;
378 httprequest.desktop.kvm.pipe(httprequest.unlockerHelper);
382var obj = { serverInfo: {} };
383var agentFileHttpRequests = {}; // Currently active agent HTTPS GET requests from the server.
384var agentFileHttpPendingRequests = []; // Pending HTTPS GET requests from the server.
385var debugConsole = (global._MSH && (_MSH().debugConsole == 1));
389 background: (global._MSH != null) ? global._MSH().background : '0,54,105',
390 foreground: (global._MSH != null) ? global._MSH().foreground : '255,255,255'
393if (process.platform == 'win32' && require('user-sessions').isRoot()) {
394 // Check the Agent Uninstall MetaData for correctness, as the installer may have written an incorrect value
396 var writtenSize = 0, actualSize = Math.floor(require('fs').statSync(process.execPath).size / 1024);
397 var serviceName = (_MSH().serviceName ? _MSH().serviceName : (require('_agentNodeId').serviceName() ? require('_agentNodeId').serviceName() : 'Mesh Agent'));
398 try { writtenSize = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + serviceName, 'EstimatedSize'); } catch (ex) { }
399 if (writtenSize != actualSize) { try { require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + serviceName, 'EstimatedSize', actualSize); } catch (ex) { } }
402 // Check to see if we are the Installed Mesh Agent Service, if we are, make sure we can run in Safe Mode
403 var svcname = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent';
405 svcname = require('MeshAgent').serviceName;
409 var meshCheck = false;
410 try { meshCheck = require('service-manager').manager.getService(svcname).isMe(); } catch (ex) { }
411 if (meshCheck && require('win-bcd').isSafeModeService && !require('win-bcd').isSafeModeService(svcname)) { require('win-bcd').enableSafeModeService(svcname); }
414 // Check the Agent Uninstall MetaData for DisplayVersion and update if not the same and only on windows
415 if (process.platform == 'win32') {
417 var writtenDisplayVersion = 0, actualDisplayVersion = process.versions.commitDate.toString();
418 var serviceName = (_MSH().serviceName ? _MSH().serviceName : (require('_agentNodeId').serviceName() ? require('_agentNodeId').serviceName() : 'Mesh Agent'));
419 try { writtenDisplayVersion = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + serviceName, 'DisplayVersion'); } catch (ex) { }
420 if (writtenDisplayVersion != actualDisplayVersion) { try { require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + serviceName, 'DisplayVersion', actualDisplayVersion); } catch (ex) { } }
425if (process.platform != 'win32') {
426 var ch = require('child_process');
427 ch._execFile = ch.execFile;
428 ch.execFile = function execFile(path, args, options) {
429 if (options && options.type && options.type == ch.SpawnTypes.TERM && options.env) {
430 options.env['TERM'] = 'xterm-256color';
432 return (this._execFile(path, args, options));
437if (process.platform == 'darwin' && !process.versions) {
438 // This is an older MacOS Agent, so we'll need to check the service definition so that Auto-Update will function correctly
439 var child = require('child_process').execFile('/bin/sh', ['sh']);
440 child.stdout.str = '';
441 child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });
442 child.stdin.write("cat /Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist | tr '\n' '\.' | awk '{split($0, a, \"<key>KeepAlive</key>\"); split(a[2], b, \"<\"); split(b[2], c, \">\"); ");
443 child.stdin.write(" if(c[1]==\"dict\"){ split(a[2], d, \"</dict>\"); if(split(d[1], truval, \"<true/>\")>1) { split(truval[1], kn1, \"<key>\"); split(kn1[2], kn2, \"</key>\"); print kn2[1]; } }");
444 child.stdin.write(" else { split(c[1], ka, \"/\"); if(ka[1]==\"true\") {print \"ALWAYS\";} } }'\nexit\n");
446 if (child.stdout.str.trim() == 'Crashed') {
447 child = require('child_process').execFile('/bin/sh', ['sh']);
448 child.stdout.str = '';
449 child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });
450 child.stdin.write("launchctl list | grep 'meshagent' | awk '{ if($3==\"meshagent\"){print $1;}}'\nexit\n");
453 if (parseInt(child.stdout.str.trim()) == process.pid) {
454 // The currently running MeshAgent is us, so we can continue with the update
455 var plist = require('fs').readFileSync('/Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist').toString();
456 var tokens = plist.split('<key>KeepAlive</key>');
457 if (tokens[1].split('>')[0].split('<')[1] == 'dict') {
458 var tmp = tokens[1].split('</dict>');
460 tokens[1] = '\n <true/>' + tmp.join('</dict>');
461 tokens = tokens.join('<key>KeepAlive</key>');
463 require('fs').writeFileSync('/Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist', tokens);
466 fix += ("function macosRepair()\n");
468 fix += (" var child = require('child_process').execFile('/bin/sh', ['sh']);\n");
469 fix += (" child.stdout.str = '';\n");
470 fix += (" child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });\n");
471 fix += (" child.stderr.on('data', function (chunk) { });\n");
472 fix += (" child.stdin.write('launchctl unload /Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist\\n');\n");
473 fix += (" child.stdin.write('launchctl load /Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist\\n');\n");
474 fix += (" child.stdin.write('rm /Library/LaunchDaemons/meshagentRepair.plist\\n');\n");
475 fix += (" child.stdin.write('rm " + process.cwd() + "/macosRepair.js\\n');\n");
476 fix += (" child.stdin.write('launchctl stop meshagentRepair\\nexit\\n');\n");
477 fix += (" child.waitExit();\n");
479 fix += ("macosRepair();\n");
480 fix += ("process.exit();\n");
481 require('fs').writeFileSync(process.cwd() + '/macosRepair.js', fix);
483 var plist = '<?xml version="1.0" encoding="UTF-8"?>\n';
484 plist += '<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n';
485 plist += '<plist version="1.0">\n';
486 plist += ' <dict>\n';
487 plist += ' <key>Label</key>\n';
488 plist += (' <string>meshagentRepair</string>\n');
489 plist += ' <key>ProgramArguments</key>\n';
490 plist += ' <array>\n';
491 plist += (' <string>' + process.execPath + '</string>\n');
492 plist += ' <string>macosRepair.js</string>\n';
493 plist += ' </array>\n';
494 plist += ' <key>WorkingDirectory</key>\n';
495 plist += (' <string>' + process.cwd() + '</string>\n');
496 plist += ' <key>RunAtLoad</key>\n';
497 plist += ' <true/>\n';
498 plist += ' </dict>\n';
500 require('fs').writeFileSync('/Library/LaunchDaemons/meshagentRepair.plist', plist);
502 child = require('child_process').execFile('/bin/sh', ['sh']);
503 child.stdout.str = '';
504 child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });
505 child.stdin.write("launchctl load /Library/LaunchDaemons/meshagentRepair.plist\nexit\n");
512// Add an Intel AMT event to the log
513function addAmtEvent(msg) {
514 if (obj.amtevents == null) { obj.amtevents = []; }
515 var d = new Date(), e = zeroPad(d.getHours(), 2) + ':' + zeroPad(d.getMinutes(), 2) + ':' + zeroPad(d.getSeconds(), 2) + ', ' + msg;
516 obj.amtevents.push(e);
517 if (obj.amtevents.length > 100) { obj.amtevents.splice(0, obj.amtevents.length - 100); }
518 if (obj.showamtevent) { require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: e }); }
520function zeroPad(num, size) { var s = '000000000' + num; return s.substr(s.length - size); }
523// Create Secure IPC for Diagnostic Agent Communications
524obj.DAIPC = require('net').createServer();
525if (process.platform != 'win32') { try { require('fs').unlinkSync(process.cwd() + '/DAIPC'); } catch (ex) { } }
526obj.DAIPC.IPCPATH = process.platform == 'win32' ? ('\\\\.\\pipe\\' + require('_agentNodeId')() + '-DAIPC') : (process.cwd() + '/DAIPC');
527try { obj.DAIPC.listen({ path: obj.DAIPC.IPCPATH, writableAll: true, maxConnections: 5 }); } catch (ex) { }
528obj.DAIPC._daipc = [];
529obj.DAIPC.on('connection', function (c) {
530 c._send = function (j) {
531 var data = JSON.stringify(j);
532 var packet = Buffer.alloc(data.length + 4);
533 packet.writeUInt32LE(data.length + 4, 0);
534 Buffer.from(data).copy(packet, 4);
539 c.on('end', function () { removeRegisteredApp(this); });
540 c.on('data', function (chunk) {
541 if (chunk.length < 4) { this.unshift(chunk); return; }
542 var len = chunk.readUInt32LE(0);
543 if (len > 8192) { removeRegisteredApp(this); this.end(); return; }
544 if (chunk.length < len) { this.unshift(chunk); return; }
546 var data = chunk.slice(4, len);
547 try { data = JSON.parse(data.toString()); } catch (ex) { }
548 if ((data == null) || (typeof data.cmd != 'string')) return;
553 if (this._registered == null) return;
554 sendConsoleText('Request Help (' + this._registered + '): ' + data.value);
556 help[this._registered] = data.value;
557 try { mesh.SendCommand({ action: 'sessions', type: 'help', value: help }); } catch (ex) { }
558 MeshServerLogEx(98, [this._registered, data.value], "Help Requested, user: " + this._registered + ", details: " + data.value, null);
561 if (this._registered == null) return;
562 sendConsoleText('Cancel Help (' + this._registered + ')');
563 try { mesh.SendCommand({ action: 'sessions', type: 'help', value: {} }); } catch (ex) { }
566 if (typeof data.value == 'string') {
567 this._registered = data.value;
569 apps[data.value] = 1;
570 try { mesh.SendCommand({ action: 'sessions', type: 'app', value: apps }); } catch (ex) { }
571 this._send({ cmd: 'serverstate', value: meshServerConnectionState, url: require('MeshAgent').ConnectedServer, amt: (amt != null) });
575 switch (data.value) {
577 data.result = require('MeshAgent').ConnectedServer;
581 require('ChainViewer').getSnapshot().then(function (f) {
582 this.tag.payload.result = f;
583 this.tag.ipc._send(this.tag.payload);
584 }).parentPromise.tag = { ipc: this, payload: data };
587 data.result = require('ChainViewer').getTimerInfo();
593 if (amt == null) return;
594 var func = function amtStateFunc(state) { if (state != null) { amtStateFunc.pipe._send({ cmd: 'amtstate', value: state }); } }
596 amt.getMeiState(11, func);
599 this._send({ cmd: 'sessions', sessions: tunnelUserCount });
602 try { mesh.SendCommand({ action: 'meshToolInfo', name: data.name, hash: data.hash, cookie: data.cookie ? true : false, pipe: true }); } catch (ex) { }
605 try { mesh.SendCommand({ action: 'getUserImage', userid: data.userid, pipe: true }); } catch (ex) { }
609 var args = splitArgs(data.value);
610 processConsoleCommand(args[0].toLowerCase(), parseArgs(args), 0, 'pipe');
615 catch (ex) { removeRegisteredApp(this); this.end(); return; }
619// Send current sessions to registered apps
620function broadcastSessionsToRegisteredApps(x) {
622 for (i = 0; sendAgentMessage.messages != null && i < sendAgentMessage.messages.length; ++i) {
623 p[i] = sendAgentMessage.messages[i];
625 tunnelUserCount.msg = p;
626 broadcastToRegisteredApps({ cmd: 'sessions', sessions: tunnelUserCount });
627 tunnelUserCount.msg = {};
630// Send this object to all registered local applications
631function broadcastToRegisteredApps(x) {
632 if ((obj.DAIPC == null) || (obj.DAIPC._daipc == null)) return;
633 for (var i in obj.DAIPC._daipc) {
634 if (obj.DAIPC._daipc[i]._registered != null) { obj.DAIPC._daipc[i]._send(x); }
638// Send this object to a specific registered local applications
639function sendToRegisteredApp(appid, x) {
640 if ((obj.DAIPC == null) || (obj.DAIPC._daipc == null)) return;
641 for (var i in obj.DAIPC._daipc) { if (obj.DAIPC._daipc[i]._registered == appid) { obj.DAIPC._daipc[i]._send(x); } }
644// Send list of registered apps to the server
645function updateRegisteredAppsToServer() {
646 if ((obj.DAIPC == null) || (obj.DAIPC._daipc == null)) return;
648 for (var i in obj.DAIPC._daipc) { if (apps[obj.DAIPC._daipc[i]._registered] == null) { apps[obj.DAIPC._daipc[i]._registered] = 1; } else { apps[obj.DAIPC._daipc[i]._registered]++; } }
649 try { mesh.SendCommand({ action: 'sessions', type: 'app', value: apps }); } catch (ex) { }
652// Remove a registered app
653function removeRegisteredApp(pipe) {
654 for (var i = obj.DAIPC._daipc.length - 1; i >= 0; i--) { if (obj.DAIPC._daipc[i] === pipe) { obj.DAIPC._daipc.splice(i, 1); } }
655 if (pipe._registered != null) updateRegisteredAppsToServer();
658function diagnosticAgent_uninstall() {
659 require('service-manager').manager.uninstallService('meshagentDiagnostic');
660 require('task-scheduler').delete('meshagentDiagnostic/periodicStart'); // TODO: Using "delete" here breaks the minifier since this is a reserved keyword
662function diagnosticAgent_installCheck(install) {
664 var diag = require('service-manager').manager.getService('meshagentDiagnostic');
667 if (!install) { return null; }
671 require('service-manager').manager.installService(
673 name: 'meshagentDiagnostic',
674 displayName: "Mesh Agent Diagnostic Service",
675 description: "Mesh Agent Diagnostic Service",
676 servicePath: process.execPath,
677 parameters: ['-recovery']
678 //files: [{ newName: 'diagnostic.js', _buffer: Buffer.from('LyoNCkNvcHlyaWdodCAyMDE5IEludGVsIENvcnBvcmF0aW9uDQoNCkxpY2Vuc2VkIHVuZGVyIHRoZSBBcGFjaGUgTGljZW5zZSwgVmVyc2lvbiAyLjAgKHRoZSAiTGljZW5zZSIpOw0KeW91IG1heSBub3QgdXNlIHRoaXMgZmlsZSBleGNlcHQgaW4gY29tcGxpYW5jZSB3aXRoIHRoZSBMaWNlbnNlLg0KWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0DQoNCiAgICBodHRwOi8vd3d3LmFwYWNoZS5vcmcvbGljZW5zZXMvTElDRU5TRS0yLjANCg0KVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQ0KZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywNCldJVEhPVVQgV0FSUkFOVElFUyBPUiBDT05ESVRJT05TIE9GIEFOWSBLSU5ELCBlaXRoZXIgZXhwcmVzcyBvciBpbXBsaWVkLg0KU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZA0KbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuDQoqLw0KDQp2YXIgaG9zdCA9IHJlcXVpcmUoJ3NlcnZpY2UtaG9zdCcpLmNyZWF0ZSgnbWVzaGFnZW50RGlhZ25vc3RpYycpOw0KdmFyIFJlY292ZXJ5QWdlbnQgPSByZXF1aXJlKCdNZXNoQWdlbnQnKTsNCg0KaG9zdC5vbignc2VydmljZVN0YXJ0JywgZnVuY3Rpb24gKCkNCnsNCiAgICBjb25zb2xlLnNldERlc3RpbmF0aW9uKGNvbnNvbGUuRGVzdGluYXRpb25zLkxPR0ZJTEUpOw0KICAgIGhvc3Quc3RvcCA9IGZ1bmN0aW9uKCkNCiAgICB7DQogICAgICAgIHJlcXVpcmUoJ3NlcnZpY2UtbWFuYWdlcicpLm1hbmFnZXIuZ2V0U2VydmljZSgnbWVzaGFnZW50RGlhZ25vc3RpYycpLnN0b3AoKTsNCiAgICB9DQogICAgUmVjb3ZlcnlBZ2VudC5vbignQ29ubmVjdGVkJywgZnVuY3Rpb24gKHN0YXR1cykNCiAgICB7DQogICAgICAgIGlmIChzdGF0dXMgPT0gMCkNCiAgICAgICAgew0KICAgICAgICAgICAgY29uc29sZS5sb2coJ0RpYWdub3N0aWMgQWdlbnQ6IFNlcnZlciBjb25uZWN0aW9uIGxvc3QuLi4nKTsNCiAgICAgICAgICAgIHJldHVybjsNCiAgICAgICAgfQ0KICAgICAgICBjb25zb2xlLmxvZygnRGlhZ25vc3RpYyBBZ2VudDogQ29ubmVjdGlvbiBFc3RhYmxpc2hlZCB3aXRoIFNlcnZlcicpOw0KICAgICAgICBzdGFydCgpOw0KICAgIH0pOw0KfSk7DQpob3N0Lm9uKCdub3JtYWxTdGFydCcsIGZ1bmN0aW9uICgpDQp7DQogICAgaG9zdC5zdG9wID0gZnVuY3Rpb24gKCkNCiAgICB7DQogICAgICAgIHByb2Nlc3MuZXhpdCgpOw0KICAgIH0NCiAgICBjb25zb2xlLmxvZygnTm9uIFNlcnZpY2UgTW9kZScpOw0KICAgIFJlY292ZXJ5QWdlbnQub24oJ0Nvbm5lY3RlZCcsIGZ1bmN0aW9uIChzdGF0dXMpDQogICAgew0KICAgICAgICBpZiAoc3RhdHVzID09IDApDQogICAgICAgIHsNCiAgICAgICAgICAgIGNvbnNvbGUubG9nKCdEaWFnbm9zdGljIEFnZW50OiBTZXJ2ZXIgY29ubmVjdGlvbiBsb3N0Li4uJyk7DQogICAgICAgICAgICByZXR1cm47DQogICAgICAgIH0NCiAgICAgICAgY29uc29sZS5sb2coJ0RpYWdub3N0aWMgQWdlbnQ6IENvbm5lY3Rpb24gRXN0YWJsaXNoZWQgd2l0aCBTZXJ2ZXInKTsNCiAgICAgICAgc3RhcnQoKTsNCiAgICB9KTsNCn0pOw0KaG9zdC5vbignc2VydmljZVN0b3AnLCBmdW5jdGlvbiAoKSB7IHByb2Nlc3MuZXhpdCgpOyB9KTsNCmhvc3QucnVuKCk7DQoNCg0KZnVuY3Rpb24gc3RhcnQoKQ0Kew0KDQp9Ow0K', 'base64') }]
680 svc = require('service-manager').manager.getService('meshagentDiagnostic');
682 catch (ex) { return null; }
683 var proxyConfig = require('global-tunnel').proxyConfig;
684 var cert = require('MeshAgent').GenerateAgentCertificate('CN=MeshNodeDiagnosticCertificate');
685 var nodeid = require('tls').loadCertificate(cert.root).getKeyHash().toString('base64');
686 ddb = require('SimpleDataStore').Create(svc.appWorkingDirectory().replace('\\', '/') + '/meshagentDiagnostic.db');
687 ddb.Put('disableUpdate', '1');
688 ddb.Put('MeshID', Buffer.from(require('MeshAgent').ServerInfo.MeshID, 'hex'));
689 ddb.Put('ServerID', require('MeshAgent').ServerInfo.ServerID);
690 ddb.Put('MeshServer', require('MeshAgent').ServerInfo.ServerUri);
691 if (cert.root.pfx) { ddb.Put('SelfNodeCert', cert.root.pfx); }
692 if (cert.tls) { ddb.Put('SelfNodeTlsCert', cert.tls.pfx); }
694 ddb.Put('WebProxy', proxyConfig.host + ':' + proxyConfig.port);
696 ddb.Put('ignoreProxyFile', '1');
699 require('MeshAgent').SendCommand({ action: 'diagnostic', value: { command: 'register', value: nodeid } });
700 require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: "Diagnostic Agent Registered [" + nodeid.length + "/" + nodeid + "]" });
704 // Set a recurrent task, to run the Diagnostic Agent every 2 days
705 require('task-scheduler').create({ name: 'meshagentDiagnostic/periodicStart', daily: 2, time: require('tls').generateRandomInteger('0', '23') + ':' + require('tls').generateRandomInteger('0', '59').padStart(2, '0'), service: 'meshagentDiagnostic' });
706 //require('task-scheduler').create({ name: 'meshagentDiagnostic/periodicStart', daily: '1', time: '17:16', service: 'meshagentDiagnostic' });
711// Monitor the file 'batterystate.txt' in the agent's folder and sends battery update when this file is changed.
712if ((require('fs').existsSync(process.cwd() + 'batterystate.txt')) && (require('fs').watch != null)) {
713 // Setup manual battery monitoring
714 require('MeshAgent')._batteryFileWatcher = require('fs').watch(process.cwd(), function () {
715 if (require('MeshAgent')._batteryFileTimer != null) return;
716 require('MeshAgent')._batteryFileTimer = setTimeout(function () {
718 require('MeshAgent')._batteryFileTimer = null;
720 try { data = require('fs').readFileSync(process.cwd() + 'batterystate.txt').toString(); } catch (ex) { }
721 if ((data != null) && (data.length < 10)) {
722 data = data.split(',');
723 if ((data.length == 2) && ((data[0] == 'ac') || (data[0] == 'dc'))) {
724 var level = parseInt(data[1]);
725 if ((level >= 0) && (level <= 100)) { require('MeshAgent').SendCommand({ action: 'battery', state: data[0], level: level }); }
734 // Setup normal battery monitoring
735 if (require('computer-identifiers').isBatteryPowered && require('computer-identifiers').isBatteryPowered()) {
736 require('MeshAgent')._battLevelChanged = function _battLevelChanged(val) {
737 _battLevelChanged.self._currentBatteryLevel = val;
738 _battLevelChanged.self.SendCommand({ action: 'battery', state: _battLevelChanged.self._currentPowerState, level: val });
740 require('MeshAgent')._battLevelChanged.self = require('MeshAgent');
741 require('MeshAgent')._powerChanged = function _powerChanged(val) {
742 _powerChanged.self._currentPowerState = (val == 'AC' ? 'ac' : 'dc');
743 _powerChanged.self.SendCommand({ action: 'battery', state: (val == 'AC' ? 'ac' : 'dc'), level: _powerChanged.self._currentBatteryLevel });
745 require('MeshAgent')._powerChanged.self = require('MeshAgent');
746 require('MeshAgent').on('Connected', function (status) {
748 require('power-monitor').removeListener('acdc', this._powerChanged);
749 require('power-monitor').removeListener('batteryLevel', this._battLevelChanged);
751 require('power-monitor').on('acdc', this._powerChanged);
752 require('power-monitor').on('batteryLevel', this._battLevelChanged);
761// MeshAgent JavaScript Core Module. This code is sent to and running on the mesh agent.
762var meshCoreObj = { action: 'coreinfo', value: (require('MeshAgent').coreHash ? ((process.versions.compileTime ? process.versions.compileTime : '').split(', ')[1].replace(' ', ' ') + ', ' + crc32c(require('MeshAgent').coreHash)) : ('MeshCore v6')), caps: 14, root: require('user-sessions').isRoot() }; // Capability bitmask: 1 = Desktop, 2 = Terminal, 4 = Files, 8 = Console, 16 = JavaScript, 32 = Temporary Agent, 64 = Recovery Agent
764// Get the operating system description string
765try { require('os').name().then(function (v) { meshCoreObj.osdesc = v; meshCoreObjChanged(); }); } catch (ex) { }
767// Setup logged in user monitoring (THIS IS BROKEN IN WIN7)
768function onUserSessionChanged(user, locked) {
769 userSession.enumerateUsers().then(function (users) {
770 if (process.platform == 'linux') {
771 if (userSession._startTime == null) {
772 userSession._startTime = Date.now();
773 userSession._count = users.length;
775 else if (Date.now() - userSession._startTime < 10000 && users.length == userSession._count) {
776 userSession.removeAllListeners('changed');
781 var u = [], a = users.Active;
782 if(meshCoreObj.lusers == null) { meshCoreObj.lusers = []; }
783 if(meshCoreObj.upnusers == null) { meshCoreObj.upnusers = []; }
784 var ret = getDomainInfo();
785 for (var i = 0; i < a.length; i++) {
786 var un = a[i].Domain ? (a[i].Domain + '\\' + a[i].Username) : (a[i].Username);
787 if (user && locked && (JSON.stringify(a[i]) === JSON.stringify(user))) { if (meshCoreObj.lusers.indexOf(un) == -1) { meshCoreObj.lusers.push(un); } }
788 else if (user && !locked && (JSON.stringify(a[i]) === JSON.stringify(user))) { meshCoreObj.lusers.splice(meshCoreObj.lusers.indexOf(un), 1); }
789 if (u.indexOf(un) == -1) { u.push(un); } // Only push users in the list once.
790 if ((a[i].Domain != null && a[i].Domain == 'AzureAD') || getJoinState() == 1 ){
791 var userobj = getLogonCacheKeys();
792 if(userobj && userobj.length > 0){
793 for (var j = 0; j < userobj.length; j++) {
794 if (userobj[j] && userobj[j].SAM && userobj[j].SAM[0].trim() === a[i].Username) {
795 meshCoreObj.upnusers.push(userobj[j].UPN);
800 } else if (a[i].Domain != null) {
801 if (ret != null && ret.PartOfDomain === true) {
802 meshCoreObj.upnusers.push(a[i].Username + '@' + ret.Domain);
803 } else if (getJoinState() == 4) { // One account with Microsoft Account
804 var userobj = getLogonCacheKeys();
805 if(userobj && userobj.length > 0){
806 for (var j = 0; j < userobj.length; j++) {
807 if (userobj[j] && userobj[j].SAM && userobj[j].SAM.length == 0 && userobj[j].UPN && userobj[j].UPN != '') {
808 meshCoreObj.upnusers.push(userobj[j].UPN);
816 meshCoreObj.lusers = meshCoreObj.lusers;
817 meshCoreObj.users = u;
818 meshCoreObjChanged();
823 var userSession = require('user-sessions');
824 userSession.on('changed', function () { onUserSessionChanged(null, false); });
825 userSession.emit('changed');
826 userSession.on('locked', function (user) { if(user != undefined && user != null) { onUserSessionChanged(user, true); } });
827 userSession.on('unlocked', function (user) { if(user != undefined && user != null) { onUserSessionChanged(user, false); } });
830var meshServerConnectionState = 0;
832var lastNetworkInfo = null;
833var lastPublicLocationInfo = null;
834var selfInfoUpdateTimer = null;
835var http = require('http');
836var net = require('net');
837var fs = require('fs');
838var rtc = require('ILibWebRTC');
840var processManager = require('process-manager');
841var wifiScannerLib = null;
842var wifiScanner = null;
843var networkMonitor = null;
844var nextTunnelIndex = 1;
846var tunnelUserCount = { terminal: {}, files: {}, tcp: {}, udp: {}, msg: {} }; // List of userid->count sessions for terminal, files and TCP/UDP routing
848// Add to the server event log
849function MeshServerLog(msg, state) {
850 if (typeof msg == 'string') { msg = { action: 'log', msg: msg }; } else { msg.action = 'log'; }
852 if (state.userid) { msg.userid = state.userid; }
853 if (state.username) { msg.username = state.username; }
854 if (state.sessionid) { msg.sessionid = state.sessionid; }
855 if (state.remoteaddr) { msg.remoteaddr = state.remoteaddr; }
856 if (state.guestname) { msg.guestname = state.guestname; }
858 mesh.SendCommand(msg);
861// Add to the server event log, use internationalized events
862function MeshServerLogEx(id, args, msg, state) {
863 var msg = { action: 'log', msgid: id, msgArgs: args, msg: msg };
865 if (state.userid) { msg.userid = state.userid; }
866 if (state.xuserid) { msg.xuserid = state.xuserid; }
867 if (state.username) { msg.username = state.username; }
868 if (state.sessionid) { msg.sessionid = state.sessionid; }
869 if (state.remoteaddr) { msg.remoteaddr = state.remoteaddr; }
870 if (state.guestname) { msg.guestname = state.guestname; }
872 mesh.SendCommand(msg);
876db = require('SimpleDataStore').Shared();
877sha = require('SHA256Stream');
878mesh = require('MeshAgent');
879childProcess = require('child_process');
881if (mesh.hasKVM == 1) { // if the agent is compiled with KVM support
882 // Check if this computer supports a desktop
884 if ((process.platform == 'win32') || (process.platform == 'darwin') || (require('monitor-info').kvm_x11_support)) {
885 meshCoreObj.caps |= 1; meshCoreObjChanged();
886 } else if (process.platform == 'linux' || process.platform == 'freebsd') {
887 require('monitor-info').on('kvmSupportDetected', function (value) { meshCoreObj.caps |= 1; meshCoreObjChanged(); });
891mesh.DAIPC = obj.DAIPC;
894// Try to load up the network monitor
896 networkMonitor = require('NetworkMonitor');
897 networkMonitor.on('change', function () { sendNetworkUpdateNagle(); });
898 networkMonitor.on('add', function (addr) { sendNetworkUpdateNagle(); });
899 networkMonitor.on('remove', function (addr) { sendNetworkUpdateNagle(); });
900} catch (ex) { networkMonitor = null; }
903// Fetch the SMBios Tables
904var SMBiosTables = null;
905var SMBiosTablesRaw = null;
907 var SMBiosModule = null;
908 try { SMBiosModule = require('smbios'); } catch (ex) { }
909 if (SMBiosModule != null) {
910 SMBiosModule.get(function (data) {
912 SMBiosTablesRaw = data;
913 SMBiosTables = require('smbios').parse(data)
914 if (mesh.isControlChannelConnected) { mesh.SendCommand({ action: 'smbios', value: SMBiosTablesRaw }); }
916 // If SMBios tables say that Intel AMT is present, try to connect MEI
917 if (SMBiosTables.amtInfo && (SMBiosTables.amtInfo.AMT == true)) {
918 var amtmodule = require('amt-manage');
919 amt = new amtmodule(mesh, db, false);
920 amt.on('portBinding_LMS', function (map) { mesh.SendCommand({ action: 'lmsinfo', value: { ports: map.keys() } }); });
921 amt.on('stateChange_LMS', function (v) { if (!meshCoreObj.intelamt) { meshCoreObj.intelamt = {}; } meshCoreObj.intelamt.microlms = v; meshCoreObjChanged(); }); // 0 = Disabled, 1 = Connecting, 2 = Connected
922 amt.onStateChange = function (state) { if (state == 2) { sendPeriodicServerUpdate(1); } } // MEI State
928} catch (ex) { sendConsoleText("ex1: " + ex); }
930// Try to load up the WIFI scanner
932 var wifiScannerLib = require('wifi-scanner');
933 wifiScanner = new wifiScannerLib();
934 wifiScanner.on('accessPoint', function (data) { sendConsoleText("wifiScanner: " + data); });
935} catch (ex) { wifiScannerLib = null; wifiScanner = null; }
937// Get our location (lat/long) using our public IP address
938var getIpLocationDataExInProgress = false;
939var getIpLocationDataExCounts = [0, 0];
940function getIpLocationDataEx(func) {
941 if (getIpLocationDataExInProgress == true) { return false; }
943 getIpLocationDataExInProgress = true;
944 getIpLocationDataExCounts[0]++;
945 var options = http.parseUri("http://ipinfo.io/json");
946 options.method = 'GET';
947 http.request(options, function (resp) {
948 if (resp.statusCode == 200) {
950 resp.data = function (geoipdata) { geoData += geoipdata; };
951 resp.end = function () {
954 if (typeof geoData == 'string') {
955 var result = JSON.parse(geoData);
956 if (result.ip && result.loc) { location = result; }
959 if (func) { getIpLocationDataExCounts[1]++; func(location); }
963 getIpLocationDataExInProgress = false;
967 catch (ex) { return false; }
970// Remove all Gateway MAC addresses for interface list. This is useful because the gateway MAC is not always populated reliably.
971function clearGatewayMac(str) {
972 if (typeof str != 'string') return null;
973 var x = JSON.parse(str);
974 for (var i in x.netif) { try { if (x.netif[i].gatewaymac) { delete x.netif[i].gatewaymac } } catch (ex) { } }
975 return JSON.stringify(x);
978function getIpLocationData(func) {
979 // Get the location information for the cache if possible
980 var publicLocationInfo = db.Get('publicLocationInfo');
981 if (publicLocationInfo != null) { publicLocationInfo = JSON.parse(publicLocationInfo); }
982 if (publicLocationInfo == null) {
983 // Nothing in the cache, fetch the data
984 getIpLocationDataEx(function (locationData) {
985 if (locationData != null) {
986 publicLocationInfo = {};
987 publicLocationInfo.netInfoStr = lastNetworkInfo;
988 publicLocationInfo.locationData = locationData;
989 var x = db.Put('publicLocationInfo', JSON.stringify(publicLocationInfo)); // Save to database
990 if (func) func(locationData); // Report the new location
993 if (func) func(null); // Report no location
999 if (clearGatewayMac(publicLocationInfo.netInfoStr) == clearGatewayMac(lastNetworkInfo)) {
1001 if (func) func(publicLocationInfo.locationData);
1005 getIpLocationDataEx(function (locationData) {
1006 if (locationData != null) {
1007 publicLocationInfo = {};
1008 publicLocationInfo.netInfoStr = lastNetworkInfo;
1009 publicLocationInfo.locationData = locationData;
1010 var x = db.Put('publicLocationInfo', JSON.stringify(publicLocationInfo)); // Save to database
1011 if (func) func(locationData); // Report the new location
1014 if (func) func(publicLocationInfo.locationData); // Can't get new location, report the old location
1021// Polyfill String.endsWith
1022if (!String.prototype.endsWith) {
1023 String.prototype.endsWith = function (searchString, position) {
1024 var subjectString = this.toString();
1025 if (typeof position !== 'number' || !isFinite(position) || Math.floor(position) !== position || position > subjectString.length) { position = subjectString.length; }
1026 position -= searchString.length;
1027 var lastIndex = subjectString.lastIndexOf(searchString, position);
1028 return lastIndex !== -1 && lastIndex === position;
1032// Polyfill path.join
1037 for (var i in arguments) {
1038 var w = arguments[i];
1040 while (w.endsWith('/') || w.endsWith('\\')) { w = w.substring(0, w.length - 1); }
1042 while (w.startsWith('/') || w.startsWith('\\')) { w = w.substring(1); }
1047 if (x.length == 0) return '/';
1052// Replace a string with a number if the string is an exact number
1053function toNumberIfNumber(x) { if ((typeof x == 'string') && (+parseInt(x) === x)) { x = parseInt(x); } return x; }
1055// Convert decimal to hex
1056function char2hex(i) { return (i + 0x100).toString(16).substr(-2).toUpperCase(); }
1058// Convert a raw string to a hex string
1059function rstr2hex(input) { var r = '', i; for (i = 0; i < input.length; i++) { r += char2hex(input.charCodeAt(i)); } return r; }
1061// Convert a buffer into a string
1062function buf2rstr(buf) { var r = ''; for (var i = 0; i < buf.length; i++) { r += String.fromCharCode(buf[i]); } return r; }
1064// Convert a hex string to a raw string // TODO: Do this using Buffer(), will be MUCH faster
1065function hex2rstr(d) {
1066 if (typeof d != "string" || d.length == 0) return '';
1067 var r = '', m = ('' + d).match(/../g), t;
1068 while (t = m.shift()) r += String.fromCharCode('0x' + t);
1072// Convert an object to string with all functions
1073function objToString(x, p, pad, ret) {
1074 if (ret == undefined) ret = '';
1075 if (p == undefined) p = 0;
1076 if (x == null) { return '[null]'; }
1077 if (p > 8) { return '[...]'; }
1078 if (x == undefined) { return '[undefined]'; }
1079 if (typeof x == 'string') { if (p == 0) return x; return '"' + x + '"'; }
1080 if (typeof x == 'buffer') { return '[buffer]'; }
1081 if (typeof x != 'object') { return x; }
1082 var r = '{' + (ret ? '\r\n' : ' ');
1083 for (var i in x) { if (i != '_ObjectID') { r += (addPad(p + 2, pad) + i + ': ' + objToString(x[i], p + 2, pad, ret) + (ret ? '\r\n' : ' ')); } }
1084 return r + addPad(p, pad) + '}';
1087// Return p number of spaces
1088function addPad(p, ret) { var r = ''; for (var i = 0; i < p; i++) { r += ret; } return r; }
1090// Split a string taking into account the quoats. Used for command line parsing
1091function splitArgs(str) {
1092 var myArray = [], myRegexp = /[^\s"]+|"([^"]*)"/gi;
1093 do { var match = myRegexp.exec(str); if (match != null) { myArray.push(match[1] ? match[1] : match[0]); } } while (match != null);
1097// Parse arguments string array into an object
1098function parseArgs(argv) {
1099 var results = { '_': [] }, current = null;
1100 for (var i = 1, len = argv.length; i < len; i++) {
1102 if (x.length > 2 && x[0] == '-' && x[1] == '-') {
1103 if (current != null) { results[current] = true; }
1104 current = x.substring(2);
1106 if (current != null) { results[current] = toNumberIfNumber(x); current = null; } else { results['_'].push(toNumberIfNumber(x)); }
1109 if (current != null) { results[current] = true; }
1113// Get server target url with a custom path
1114function getServerTargetUrl(path) {
1115 var x = mesh.ServerUrl;
1116 //sendConsoleText("mesh.ServerUrl: " + mesh.ServerUrl);
1117 if (x == null) { return null; }
1118 if (path == null) { path = ''; }
1119 x = http.parseUri(x);
1120 if (x == null) return null;
1121 return x.protocol + '//' + x.host + ':' + x.port + '/' + path;
1124// Get server url. If the url starts with "*/..." change it, it not use the url as is.
1125function getServerTargetUrlEx(url) {
1126 if (url.substring(0, 2) == '*/') { return getServerTargetUrl(url.substring(2)); }
1130function sendWakeOnLanEx_interval() {
1131 var t = require('MeshAgent').wakesockets;
1132 if (t.list.length == 0) {
1134 delete require('MeshAgent').wakesockets;
1138 var mac = t.list.shift().split(':').join('')
1139 var magic = 'FFFFFFFFFFFF';
1140 for (var x = 1; x <= 16; ++x) { magic += mac; }
1141 var magicbin = Buffer.from(magic, 'hex');
1143 for (var i in t.sockets) {
1144 t.sockets[i].send(magicbin, 7, '255.255.255.255');
1145 //sendConsoleText('Sending wake packet on ' + JSON.stringify(t.sockets[i].address()));
1148function sendWakeOnLanEx(hexMacList) {
1151 if (require('MeshAgent').wakesockets == null) {
1152 // Create a new interval timer
1153 require('MeshAgent').wakesockets = setInterval(sendWakeOnLanEx_interval, 10);
1154 require('MeshAgent').wakesockets.sockets = [];
1155 require('MeshAgent').wakesockets.list = hexMacList;
1157 var interfaces = require('os').networkInterfaces();
1158 for (var adapter in interfaces) {
1159 if (interfaces.hasOwnProperty(adapter)) {
1160 for (var i = 0; i < interfaces[adapter].length; ++i) {
1161 var addr = interfaces[adapter][i];
1162 if ((addr.family == 'IPv4') && (addr.mac != '00:00:00:00:00:00')) {
1164 var socket = require('dgram').createSocket({ type: 'udp4' });
1165 socket.bind({ address: addr.address });
1166 socket.setBroadcast(true);
1167 socket.setMulticastInterface(addr.address);
1168 socket.setMulticastTTL(1);
1169 socket.descriptorMetadata = 'WoL (' + addr.address + ')';
1170 require('MeshAgent').wakesockets.sockets.push(socket);
1180 // Append to an existing interval timer
1181 for (var i in hexMacList) {
1182 require('MeshAgent').wakesockets.list.push(hexMacList[i]);
1184 ret = require('MeshAgent').wakesockets.sockets.length;
1190function server_promise_default(res, rej) {
1194function server_getUserImage(userid) {
1195 var xpromise = require('promise');
1196 var ret = new xpromise(server_promise_default);
1198 if (require('MeshAgent')._promises == null) { require('MeshAgent')._promises = {}; }
1199 require('MeshAgent')._promises[ret._hashCode()] = ret;
1200 require('MeshAgent').SendCommand({ action: 'getUserImage', userid: userid, promise: ret._hashCode(), sentDefault: true });
1203require('MeshAgent')._consentTimers = {};
1204function server_set_consentTimer(id) {
1205 require('MeshAgent')._consentTimers[id] = new Date();
1207function server_check_consentTimer(id) {
1208 if (require('MeshAgent')._consentTimers[id] != null) {
1209 if ((new Date()) - require('MeshAgent')._consentTimers[id] < (60000 * 5)) return true;
1210 require('MeshAgent')._consentTimers[id] = null;
1215function tunnel_finalized()
1217 console.info1('Tunnel Request Finalized');
1219function tunnel_checkServerIdentity(certs)
1222 try { sendConsoleText("certs[0].digest: " + certs[0].digest); } catch (ex) { sendConsoleText(ex); }
1223 try { sendConsoleText("certs[0].fingerprint: " + certs[0].fingerprint); } catch (ex) { sendConsoleText(ex); }
1224 try { sendConsoleText("control-digest: " + require('MeshAgent').ServerInfo.ControlChannelCertificate.digest); } catch (ex) { sendConsoleText(ex); }
1225 try { sendConsoleText("control-fingerprint: " + require('MeshAgent').ServerInfo.ControlChannelCertificate.fingerprint); } catch (ex) { sendConsoleText(ex); }
1228 // Check if this is an old agent, no certificate checks are possible in this situation. Display a warning.
1229 if ((require('MeshAgent').ServerInfo == null) || (require('MeshAgent').ServerInfo.ControlChannelCertificate == null) || (certs[0].digest == null)) { sendAgentMessage("This agent is using insecure tunnels, consider updating.", 3, 119, true); return; }
1231 // If the tunnel certificate matches the control channel certificate, accept the connection
1232 if (require('MeshAgent').ServerInfo.ControlChannelCertificate.digest == certs[0].digest) return; // Control channel certificate matches using full cert hash
1233 if ((certs[0].fingerprint != null) && (require('MeshAgent').ServerInfo.ControlChannelCertificate.fingerprint == certs[0].fingerprint)) return; // Control channel certificate matches using public key hash
1235 // Check that the certificate is the one expected by the server, fail if not.
1236 if ((tunnel_checkServerIdentity.servertlshash != null) && (tunnel_checkServerIdentity.servertlshash.toLowerCase() != certs[0].digest.split(':').join('').toLowerCase())) { throw new Error('BadCert') }
1239function tunnel_onError()
1241 sendConsoleText("ERROR: Unable to connect relay tunnel to: " + this.url + ", " + JSON.stringify(e));
1244// Handle a mesh agent command
1245function handleServerCommand(data) {
1246 if (typeof data == 'object') {
1247 // If this is a console command, parse it and call the console handler
1248 switch (data.action) {
1250 agentUpdate_Start(data.url, { hash: data.hash, tlshash: data.servertlshash, sessionid: data.sessionid });
1253 switch (data.type) {
1254 case 'console': { // Process a console command
1255 if ((typeof data.rights != 'number') || ((data.rights & 8) == 0) || ((data.rights & 16) == 0)) break; // Check console rights (Remote Control and Console)
1256 if (data.value && data.sessionid) {
1257 MeshServerLogEx(17, [data.value], "Processing console command: " + data.value, data);
1258 var args = splitArgs(data.value);
1259 processConsoleCommand(args[0].toLowerCase(), parseArgs(args), data.rights, data.sessionid);
1265 if (data.value != null) { // Process a new tunnel connection request
1266 // Create a new tunnel object
1267 var xurl = getServerTargetUrlEx(data.value);
1269 xurl = xurl.split('$').join('%24').split('@').join('%40'); // Escape the $ and @ characters
1270 var woptions = http.parseUri(xurl);
1271 woptions.perMessageDeflate = false;
1272 if (typeof data.perMessageDeflate == 'boolean') { woptions.perMessageDeflate = data.perMessageDeflate; }
1274 // Perform manual server TLS certificate checking based on the certificate hash given by the server.
1275 woptions.rejectUnauthorized = 0;
1276 woptions.checkServerIdentity = tunnel_checkServerIdentity;
1277 woptions.checkServerIdentity.servertlshash = data.servertlshash;
1279 //sendConsoleText(JSON.stringify(woptions));
1280 //sendConsoleText('TUNNEL: ' + JSON.stringify(data, null, 2));
1282 var tunnel = http.request(woptions);
1283 tunnel.upgrade = onTunnelUpgrade;
1284 tunnel.on('error', tunnel_onError);
1285 tunnel.sessionid = data.sessionid;
1286 tunnel.rights = data.rights;
1287 tunnel.consent = data.consent;
1288 if (global._MSH && _MSH().LocalConsent != null) { tunnel.consent |= parseInt(_MSH().LocalConsent); }
1289 tunnel.privacybartext = data.privacybartext ? data.privacybartext : currentTranslation['privacyBar'];
1290 tunnel.username = data.username + (data.guestname ? (' - ' + data.guestname) : '');
1291 tunnel.realname = (data.realname ? data.realname : data.username) + (data.guestname ? (' - ' + data.guestname) : '');
1292 tunnel.guestuserid = data.guestuserid;
1293 tunnel.guestname = data.guestname;
1294 tunnel.userid = data.userid;
1295 if (server_check_consentTimer(tunnel.userid)) { tunnel.consent = (tunnel.consent & -57); } // Deleting Consent Requirement
1296 tunnel.desktopviewonly = data.desktopviewonly;
1297 tunnel.remoteaddr = data.remoteaddr;
1300 tunnel.protocol = 0;
1301 tunnel.soptions = data.soptions;
1302 tunnel.consentTimeout = (tunnel.soptions && tunnel.soptions.consentTimeout) ? tunnel.soptions.consentTimeout : 30;
1303 tunnel.consentAutoAccept = (tunnel.soptions && (tunnel.soptions.consentAutoAccept === true));
1304 tunnel.consentAutoAcceptIfNoUser = (tunnel.soptions && (tunnel.soptions.consentAutoAcceptIfNoUser === true));
1305 tunnel.consentAutoAcceptIfDesktopNoUser = (tunnel.soptions && (tunnel.soptions.consentAutoAcceptIfDesktopNoUser === true));
1306 tunnel.consentAutoAcceptIfTerminalNoUser = (tunnel.soptions && (tunnel.soptions.consentAutoAcceptIfTerminalNoUser === true));
1307 tunnel.consentAutoAcceptIfFileNoUser = (tunnel.soptions && (tunnel.soptions.consentAutoAcceptIfFileNoUser === true));
1308 tunnel.consentAutoAcceptIfLocked = (tunnel.soptions && (tunnel.soptions.consentAutoAcceptIfLocked === true));
1309 tunnel.consentAutoAcceptIfDesktopLocked = (tunnel.soptions && (tunnel.soptions.consentAutoAcceptIfDesktopLocked === true));
1310 tunnel.consentAutoAcceptIfTerminalLocked = (tunnel.soptions && (tunnel.soptions.consentAutoAcceptIfTerminalLocked === true));
1311 tunnel.consentAutoAcceptIfFileLocked = (tunnel.soptions && (tunnel.soptions.consentAutoAcceptIfFileLocked === true));
1312 tunnel.oldStyle = (tunnel.soptions && tunnel.soptions.oldStyle) ? tunnel.soptions.oldStyle : false;
1313 tunnel.tcpaddr = data.tcpaddr;
1314 tunnel.tcpport = data.tcpport;
1315 tunnel.udpaddr = data.udpaddr;
1316 tunnel.udpport = data.udpport;
1318 // Put the tunnel in the tunnels list
1319 var index = nextTunnelIndex++;
1320 tunnel.index = index;
1321 tunnels[index] = tunnel;
1322 tunnel.once('~', tunnel_finalized);
1325 //sendConsoleText('New tunnel connection #' + index + ': ' + tunnel.url + ', rights: ' + tunnel.rights, data.sessionid);
1331 // Terminate one or more tunnels
1332 if ((data.rights != 4294967295) && (data.xuserid != data.userid)) return; // This command requires full admin rights on the device or user self-closes it's own sessions
1333 for (var i in tunnels) {
1334 if ((tunnels[i].userid == data.xuserid) && (tunnels[i].guestname == data.guestname)) {
1335 var disconnect = false, msgid = 0;
1336 if ((data.protocol == 'kvm') && (tunnels[i].protocol == 2)) { msgid = 134; disconnect = true; }
1337 else if ((data.protocol == 'terminal') && (tunnels[i].protocol == 1)) { msgid = 135; disconnect = true; }
1338 else if ((data.protocol == 'files') && (tunnels[i].protocol == 5)) { msgid = 136; disconnect = true; }
1339 else if ((data.protocol == 'tcp') && (tunnels[i].tcpport != null)) { msgid = 137; disconnect = true; }
1340 else if ((data.protocol == 'udp') && (tunnels[i].udpport != null)) { msgid = 137; disconnect = true; }
1342 if (tunnels[i].s != null) { tunnels[i].s.end(); } else { tunnels[i].end(); }
1344 // Log tunnel disconnection
1345 var xusername = data.xuserid.split('/')[2];
1346 if (data.guestname != null) { xusername += '/' + guestname; }
1347 MeshServerLogEx(msgid, [xusername], "Forcibly disconnected session of user: " + xusername, data);
1353 case 'messagebox': {
1354 // Display a message box
1355 if (data.title && data.msg) {
1356 MeshServerLogEx(18, [data.title, data.msg], "Displaying message box, title=" + data.title + ", message=" + data.msg, data);
1357 if (process.platform == 'win32') {
1358 if (global._clientmessage) {
1359 global._clientmessage.addMessage(data.msg);
1363 require('win-dialog');
1364 var ipr = server_getUserImage(data.userid);
1365 ipr.title = data.title;
1366 ipr.message = data.msg;
1367 ipr.username = data.username;
1368 if (data.realname && (data.realname != '')) { ipr.username = data.realname; }
1369 ipr.timeout = (typeof data.timeout === 'number' ? data.timeout : 120000);
1370 global._clientmessage = ipr.then(function (img) {
1371 var options = { b64Image: img.split(',').pop(), background: color_options.background, foreground: color_options.foreground }
1372 if (this.timeout != 0) { options.timeout = this.timeout; }
1373 this.messagebox = require('win-dialog').create(this.title, this.message, this.username, options);
1374 this.__childPromise.addMessage = this.messagebox.addMessage.bind(this.messagebox);
1375 return (this.messagebox);
1378 global._clientmessage.then(function () { global._clientmessage = null; });
1381 try { require('message-box').create(data.title, data.msg, 120).then(function () { }).catch(function () { }); } catch (ex) { }
1386 try { require('message-box').create(data.title, data.msg, 120).then(function () { }).catch(function () { }); } catch (ex) { }
1392 // Return the list of running processes
1393 if (data.sessionid) {
1394 processManager.getProcesses(function (plist) {
1395 mesh.SendCommand({ action: 'msg', type: 'ps', value: JSON.stringify(plist), sessionid: data.sessionid });
1401 // Requestion details information about a process
1403 var info = {}; // TODO: Replace with real data. Feel free not to give all values if not available.
1405 info = processManager.getProcessInfo(data.pid);
1408 info.processUser = "User"; // String
1409 info.processDomain = "Domain"; // String
1410 info.cmd = "abc"; // String
1411 info.processName = "dummydata";
1412 info.privateMemorySize = 123; // Bytes
1413 info.virtualMemorySize = 123; // Bytes
1414 info.workingSet = 123; // Bytes
1415 info.totalProcessorTime = 123; // Seconds
1416 info.userProcessorTime = 123; // Seconds
1417 info.startTime = "2012-12-30T23:59:59.000Z"; // Time in UTC ISO format
1418 info.sessionId = 123; // Number
1419 info.privilegedProcessorTime = 123; // Seconds
1420 info.PriorityBoostEnabled = true; // Boolean
1421 info.peakWorkingSet = 123; // Bytes
1422 info.peakVirtualMemorySize = 123; // Bytes
1423 info.peakPagedMemorySize = 123; // Bytes
1424 info.pagedSystemMemorySize = 123; // Bytes
1425 info.pagedMemorySize = 123; // Bytes
1426 info.nonpagedSystemMemorySize = 123; // Bytes
1427 info.mainWindowTitle = "dummydata"; // String
1428 info.machineName = "dummydata"; // Only set this if machine name is not "."
1429 info.handleCount = 123; // Number
1431 mesh.SendCommand({ action: 'msg', type: 'psinfo', pid: data.pid, sessionid: data.sessionid, value: info });
1438 var info = data.value.split('|'), pid = data.value, msg = " (Unknown)";
1439 if (info.length > 1) { pid = info[0]; msg = " (" + info[1] + ")"; } else { info[1] = "Unknown"; }
1440 MeshServerLogEx(19, info, "Killing process " + pid + msg, data);
1441 try { process.kill(parseInt(pid)); } catch (ex) { sendConsoleText("pskill: " + JSON.stringify(ex)); }
1446 // return information about the service
1448 var service = require('service-manager').manager.getService(data.serviceName);
1449 if (service != null) {
1451 name: (service.name ? service.name : ''),
1452 status: (service.status ? service.status : ''),
1453 startType: (service.startType ? service.startType : ''),
1454 failureActions: (service.failureActions ? service.failureActions : ''),
1455 installedDate: (service.installedDate ? service.installedDate : ''),
1456 installedBy: (service.installedBy ? service.installedBy : '') ,
1457 user: (service.user ? service.user : '')
1459 if(reply.installedBy.indexOf('S-1-5') != -1) {
1460 var cmd = "(Get-WmiObject -Class win32_userAccount -Filter \"SID='"+service.installedBy+"'\").Caption";
1462 var pws = require('child_process').execFile(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', ['powershell', '-noprofile', '-nologo', '-command', '-'], {});
1463 pws.descriptorMetadata = 'UserSIDPowerShell';
1464 pws.stdout.on('data', function (c) { replydata += c.toString(); });
1465 pws.stderr.on('data', function (c) { replydata += c.toString(); });
1466 pws.stdin.write(cmd + '\r\nexit\r\n');
1467 pws.on('exit', function () {
1468 if (replydata != "") reply.installedBy = replydata;
1469 mesh.SendCommand({ action: 'msg', type: 'service', value: JSON.stringify(reply), sessionid: data.sessionid });
1473 mesh.SendCommand({ action: 'msg', type: 'service', value: JSON.stringify(reply), sessionid: data.sessionid });
1477 mesh.SendCommand({ action: 'msg', type: 'service', error: ex, sessionid: data.sessionid })
1481 // Return the list of installed services
1482 var services = null;
1483 try { services = require('service-manager').manager.enumerateService(); } catch (ex) { }
1484 if (services != null) { mesh.SendCommand({ action: 'msg', type: 'services', value: JSON.stringify(services), sessionid: data.sessionid }); }
1487 case 'serviceStop': {
1490 var service = require('service-manager').manager.getService(data.serviceName);
1491 if (service != null) { service.stop(); }
1495 case 'serviceStart': {
1498 var service = require('service-manager').manager.getService(data.serviceName);
1499 if (service != null) { service.start(); }
1503 case 'serviceRestart': {
1504 // Restart a service
1506 var service = require('service-manager').manager.getService(data.serviceName);
1507 if (service != null) { service.restart(); }
1511 case 'deskBackground':
1513 // Toggle desktop background
1515 if (process.platform == 'win32') {
1516 var stype = require('user-sessions').getProcessOwnerName(process.pid).tsid == 0 ? 1 : 0;
1517 var sid = undefined;
1519 if (require('MeshAgent')._tsid != null) {
1521 sid = require('MeshAgent')._tsid;
1524 var id = require('user-sessions').getProcessOwnerName(process.pid).tsid == 0 ? 1 : 0;
1525 var child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0='], { type: stype, uid: sid });
1526 child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
1527 child.stderr.on('data', function () { });
1529 var current = child.stdout.str.trim();
1530 if (current != '') { require('MeshAgent')._wallpaper = current; }
1531 child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0=', current != '' ? '""' : require('MeshAgent')._wallpaper], { type: stype, uid: sid });
1532 child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
1533 child.stderr.on('data', function () { });
1535 mesh.SendCommand({ action: 'msg', type: 'deskBackground', sessionid: data.sessionid, data: (current != '' ? "" : require('MeshAgent')._wallpaper), });
1537 var id = require('user-sessions').consoleUid();
1538 var current = require('linux-gnome-helpers').getDesktopWallpaper(id);
1539 if (current != '/dev/null') { require('MeshAgent')._wallpaper = current; }
1540 require('linux-gnome-helpers').setDesktopWallpaper(id, current != '/dev/null' ? undefined : require('MeshAgent')._wallpaper);
1541 mesh.SendCommand({ action: 'msg', type: 'deskBackground', sessionid: data.sessionid, data: (current != '/dev/null' ? "" : require('MeshAgent')._wallpaper), });
1544 sendConsoleText(ex);
1549 // Open a local web browser and return success/fail
1550 MeshServerLogEx(20, [data.url], "Opening: " + data.url, data);
1551 sendConsoleText("OpenURL: " + data.url);
1552 if (data.url) { mesh.SendCommand({ action: 'msg', type: 'openUrl', url: data.url, sessionid: data.sessionid, success: (openUserDesktopUrl(data.url) != null) }); }
1556 // Send the load clipboard back to the user
1557 //sendConsoleText('getClip: ' + JSON.stringify(data));
1558 if (require('MeshAgent').isService) {
1559 require('clipboard').dispatchRead().then(function (str) {
1561 MeshServerLogEx(21, [str.length], "Getting clipboard content, " + str.length + " byte(s)", data);
1562 mesh.SendCommand({ action: 'msg', type: 'getclip', sessionid: data.sessionid, data: str, tag: data.tag });
1566 require('clipboard').read().then(function (str) {
1568 MeshServerLogEx(21, [str.length], "Getting clipboard content, " + str.length + " byte(s)", data);
1569 mesh.SendCommand({ action: 'msg', type: 'getclip', sessionid: data.sessionid, data: str, tag: data.tag });
1576 if (pendingSetClip) return;
1577 // Set the load clipboard to a user value
1578 if (typeof data.data == 'string') {
1579 MeshServerLogEx(22, [data.data.length], "Setting clipboard content, " + data.data.length + " byte(s)", data);
1580 if (require('MeshAgent').isService) {
1581 if (process.platform != 'win32') {
1582 require('clipboard').dispatchWrite(data.data);
1583 mesh.SendCommand({ action: 'msg', type: 'setclip', sessionid: data.sessionid, success: true });
1586 var clipargs = data.data;
1587 var uid = require('user-sessions').consoleUid();
1588 var user = require('user-sessions').getUsername(uid);
1589 var domain = require('user-sessions').getDomain(uid);
1590 user = (domain + '\\' + user);
1592 if (this._dispatcher) { this._dispatcher.close(); }
1593 this._dispatcher = require('win-dispatcher').dispatch({ user: user, modules: [{ name: 'clip-dispatch', script: "module.exports = { dispatch: function dispatch(val) { require('clipboard')(val); process.exit(); } };" }], launch: { module: 'clip-dispatch', method: 'dispatch', args: [clipargs] } });
1594 this._dispatcher.parent = this;
1595 //require('events').setFinalizerMetadata.call(this._dispatcher, 'clip-dispatch');
1596 pendingSetClip = true;
1597 this._dispatcher.on('connection', function (c) {
1599 this._c.root = this.parent;
1600 this._c.on('end', function ()
1602 pendingSetClip = false;
1603 try { this.root._dispatcher.close(); } catch (ex) { }
1604 this.root._dispatcher = null;
1606 mesh.SendCommand({ action: 'msg', type: 'setclip', sessionid: data.sessionid, success: true });
1612 require('clipboard')(data.data);
1613 mesh.SendCommand({ action: 'msg', type: 'setclip', sessionid: data.sessionid, success: true });
1614 } // Set the clipboard
1618 case 'userSessions': {
1619 mesh.SendCommand({ action: 'msg', type: 'userSessions', sessionid: data.sessionid, data: require('kvm-helper').users(), tag: data.tag });
1623 // CPU & memory utilization
1624 var cpuuse = require('sysinfo').cpuUtilization();
1625 cpuuse.sessionid = data.sessionid;
1626 cpuuse.tag = data.tag;
1627 cpuuse.then(function (data) {
1628 mesh.SendCommand(JSON.stringify(
1633 memory: require('sysinfo').memUtilization(),
1634 thermals: require('sysinfo').thermals == null ? [] : require('sysinfo').thermals(),
1635 sessionid: this.sessionid,
1638 }, function (ex) { });
1641 // Send a message to a local application
1642 sendConsoleText('localappMsg: ' + data.appid + ', ' + JSON.stringify(data.value));
1643 if (data.appid != null) { sendToRegisteredApp(data.appid, data.value); } else { broadcastToRegisteredApps(data.value); }
1646 // Display an old style alert box
1647 if (data.title && data.msg) {
1648 MeshServerLogEx(158, [data.title, data.msg], "Displaying alert box, title=" + data.title + ", message=" + data.msg, data);
1649 try { require('message-box').create(data.title, data.msg, 9999, 1).then(function () { }).catch(function () { }); } catch (ex) { }
1654 // Send system information
1655 getSystemInformation(function (results) {
1656 if ((results != null) && (data.hash != results.hash)) { mesh.SendCommand({ action: 'sysinfo', sessionid: this.sessionid, data: results }); }
1661 // Unknown action, ignore it.
1666 case 'acmactivate': {
1668 MeshServerLogEx(23, null, "Attempting Intel AMT ACM mode activation", data);
1669 amt.setAcmResponse(data);
1674 // Send wake-on-lan on all interfaces for all MAC addresses in data.macs array. The array is a list of HEX MAC addresses.
1675 //sendConsoleText("Server requesting wake-on-lan for: " + data.macs.join(', '));
1676 sendWakeOnLanEx(data.macs);
1677 sendWakeOnLanEx(data.macs);
1678 sendWakeOnLanEx(data.macs);
1681 case 'runcommands': {
1682 if (mesh.cmdchild != null) { sendConsoleText("Run commands can't execute, already busy."); break; }
1683 if (!data.reply) sendConsoleText("Run commands (" + data.runAsUser + "): " + data.cmds);
1685 // data.runAsUser: 0=Agent,1=UserOrAgent,2=UserOnly
1687 if (data.runAsUser > 0) {
1688 try { options.uid = require('user-sessions').consoleUid(); } catch (ex) { }
1689 options.type = require('child_process').SpawnTypes.TERM;
1691 if (data.runAsUser == 2) {
1692 if (options.uid == null) break;
1693 if (((require('user-sessions').minUid != null) && (options.uid < require('user-sessions').minUid()))) break; // This command can only run as user.
1696 if (process.platform == 'win32') {
1697 if (data.type == 1) {
1698 // Windows command shell
1699 mesh.cmdchild = require('child_process').execFile(process.env['windir'] + '\\system32\\cmd.exe', ['cmd'], options);
1700 mesh.cmdchild.descriptorMetadata = 'UserCommandsShell';
1701 mesh.cmdchild.stdout.on('data', function (c) { replydata += c.toString(); sendConsoleText(c.toString()); });
1702 mesh.cmdchild.stderr.on('data', function (c) { replydata += c.toString(); sendConsoleText(c.toString()); });
1703 mesh.cmdchild.stdin.write(data.cmds + '\r\nexit\r\n');
1704 mesh.cmdchild.on('exit', function () {
1706 mesh.SendCommand({ action: 'msg', type: 'runcommands', result: replydata, sessionid: data.sessionid, responseid: data.responseid });
1708 sendConsoleText("Run commands completed.");
1710 delete mesh.cmdchild;
1712 } else if (data.type == 2) {
1713 // Windows Powershell
1714 mesh.cmdchild = require('child_process').execFile(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', ['powershell', '-noprofile', '-nologo', '-command', '-'], options);
1715 mesh.cmdchild.descriptorMetadata = 'UserCommandsPowerShell';
1716 mesh.cmdchild.stdout.on('data', function (c) { replydata += c.toString(); sendConsoleText(c.toString()); });
1717 mesh.cmdchild.stderr.on('data', function (c) { replydata += c.toString(); sendConsoleText(c.toString()); });
1718 mesh.cmdchild.stdin.write(data.cmds + '\r\nexit\r\n');
1719 mesh.cmdchild.on('exit', function () {
1721 mesh.SendCommand({ action: 'msg', type: 'runcommands', result: replydata, sessionid: data.sessionid, responseid: data.responseid });
1723 sendConsoleText("Run commands completed.");
1725 delete mesh.cmdchild;
1728 } else if (data.type == 3) {
1730 mesh.cmdchild = require('child_process').execFile('/bin/sh', ['sh'], options);
1731 mesh.cmdchild.descriptorMetadata = 'UserCommandsShell';
1732 mesh.cmdchild.stdout.on('data', function (c) { replydata += c.toString(); sendConsoleText(c.toString()); });
1733 mesh.cmdchild.stderr.on('data', function (c) { replydata += c.toString(); sendConsoleText(c.toString()); });
1734 mesh.cmdchild.stdin.write(data.cmds.split('\r').join('') + '\nexit\n');
1735 mesh.cmdchild.on('exit', function () {
1737 mesh.SendCommand({ action: 'msg', type: 'runcommands', result: replydata, sessionid: data.sessionid, responseid: data.responseid });
1739 sendConsoleText("Run commands completed.");
1741 delete mesh.cmdchild;
1746 case 'uninstallagent':
1747 // Uninstall this agent
1748 var agentName = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent';
1750 agentName = require('MeshAgent').serviceName;
1753 if (require('service-manager').manager.getService(agentName).isMe()) {
1754 try { diagnosticAgent_uninstall(); } catch (ex) { }
1755 var js = "require('service-manager').manager.getService('" + agentName + "').stop(); require('service-manager').manager.uninstallService('" + agentName + "'); process.exit();";
1756 this.child = require('child_process').execFile(process.execPath, [process.platform == 'win32' ? (process.execPath.split('\\').pop()) : (process.execPath.split('/').pop()), '-b64exec', Buffer.from(js).toString('base64')], { type: 4, detached: true });
1759 case 'poweraction': {
1760 // Server telling us to execute a power action
1761 if ((mesh.ExecPowerState != undefined) && (data.actiontype)) {
1763 if (data.forced == 1) { forced = 1; }
1764 data.actiontype = parseInt(data.actiontype);
1765 MeshServerLogEx(25, [data.actiontype, forced], "Performing power action=" + data.actiontype + ", forced=" + forced, data);
1766 sendConsoleText("Performing power action=" + data.actiontype + ", forced=" + forced + '.');
1767 var r = mesh.ExecPowerState(data.actiontype, forced);
1768 sendConsoleText("ExecPowerState returned code: " + r);
1772 case 'iplocation': {
1773 // Update the IP location information of this node. Only do this when requested by the server since we have a limited amount of time we can call this per day
1774 getIpLocationData(function (location) { mesh.SendCommand({ action: 'iplocation', type: 'publicip', value: location }); });
1778 // Display a toast message
1779 if (data.title && data.msg) {
1780 MeshServerLogEx(26, [data.title, data.msg], "Displaying toast message, title=" + data.title + ", message=" + data.msg, data);
1781 data.msg = data.msg.split('\r').join('\\r').split('\n').join('\\n');
1782 try { require('toaster').Toast(data.title, data.msg); } catch (ex) { }
1787 // Open a local web browser and return success/fail
1788 //sendConsoleText('OpenURL: ' + data.url);
1789 MeshServerLogEx(20, [data.url], "Opening: " + data.url, data);
1790 if (data.url) { mesh.SendCommand({ action: 'openUrl', url: data.url, sessionid: data.sessionid, success: (openUserDesktopUrl(data.url) != null) }); }
1794 // Perform Intel AMT activation and/or configuration
1795 if ((apftunnel != null) || (amt == null) || (typeof data.user != 'string') || (typeof data.pass != 'string')) break;
1796 amt.getMeiState(15, function (state) {
1797 if ((apftunnel != null) || (amt == null)) return;
1798 if ((state == null) || (state.ProvisioningState == null)) return;
1799 if ((state.UUID == null) || (state.UUID.length != 36)) return; // Bad UUID
1800 getAmtOsDnsSuffix(state, function () {
1802 mpsurl: mesh.ServerUrl.replace('/agent.ashx', '/apf.ashx'),
1803 mpsuser: data.user, // Agent user name
1804 mpspass: data.pass, // Encrypted login cookie
1805 mpskeepalive: 60000,
1806 clientname: state.OsHostname,
1807 clientaddress: '127.0.0.1',
1808 clientuuid: state.UUID,
1809 conntype: 2, // 0 = CIRA, 1 = Relay, 2 = LMS. The correct value is 2 since we are performing an LMS relay, other values for testing.
1810 meiState: state // MEI state will be passed to MPS server
1812 addAmtEvent('LMS tunnel start.');
1813 apftunnel = require('amt-apfclient')({ debug: false }, apfarg);
1814 apftunnel.onJsonControl = handleApfJsonControl;
1815 apftunnel.onChannelClosed = function () { addAmtEvent('LMS tunnel closed.'); apftunnel = null; }
1816 try { apftunnel.connect(); } catch (ex) { }
1822 // Received a configuration script from the server
1823 sendConsoleText('getScript: ' + JSON.stringify(data));
1827 // Fetch system information
1828 getSystemInformation(function (results) {
1829 if ((results != null) && (data.hash != results.hash)) { mesh.SendCommand({ action: 'sysinfo', sessionid: this.sessionid, data: results }); }
1833 case 'ping': { mesh.SendCommand('{"action":"pong"}'); break; }
1834 case 'pong': { break; }
1836 try { require(data.plugin).consoleaction(data, data.rights, data.sessionid, this); } catch (ex) { throw ex; }
1840 // Set the current agent coredump situation.s
1841 if (data.value === true) {
1842 if (process.platform == 'win32') {
1843 // TODO: This replace() below is not ideal, would be better to remove the .exe at the end instead of replace.
1844 process.coreDumpLocation = process.execPath.replace('.exe', '.dmp');
1846 process.coreDumpLocation = (process.cwd() != '//') ? (process.cwd() + 'core') : null;
1848 } else if (data.value === false) {
1849 process.coreDumpLocation = null;
1853 // Ask the agent if a core dump is currently available, if yes, also return the hash of the agent.
1854 var r = { action: 'getcoredump', value: (process.coreDumpLocation != null) };
1855 var coreDumpPath = null;
1856 if (process.platform == 'win32') { coreDumpPath = process.coreDumpLocation; } else { coreDumpPath = (process.cwd() != '//') ? fs.existsSync(process.cwd() + 'core') : null; }
1857 if ((coreDumpPath != null) && (fs.existsSync(coreDumpPath))) {
1859 var coredate = fs.statSync(coreDumpPath).mtime;
1860 var coretime = new Date(coredate).getTime();
1861 var agenttime = new Date(fs.statSync(process.execPath).mtime).getTime();
1862 if (coretime > agenttime) { r.exists = (db.Get('CoreDumpTime') != coredate); }
1865 if (r.exists == true) {
1866 r.agenthashhex = getSHA384FileHash(process.execPath).toString('hex'); // Hash of current agent
1867 r.corehashhex = getSHA384FileHash(coreDumpPath).toString('hex'); // Hash of core dump file
1869 mesh.SendCommand(JSON.stringify(r));
1871 case 'meshToolInfo':
1872 if (data.pipe == true) { delete data.pipe; delete data.action; data.cmd = 'meshToolInfo'; broadcastToRegisteredApps(data); }
1873 if (data.tag == 'info') { sendConsoleText(JSON.stringify(data, null, 2)); }
1874 if (data.tag == 'install') {
1875 data.func = function (options, success) {
1876 sendConsoleText('Download of MeshCentral Assistant ' + (success ? 'succeed' : 'failed'));
1878 // TODO: Install & Run
1881 data.filename = 'MeshAssistant.exe';
1885 case 'getUserImage':
1886 if (data.pipe == true) { delete data.pipe; delete data.action; data.cmd = 'getUserImage'; broadcastToRegisteredApps(data); }
1887 if (data.tag == 'info') { sendConsoleText(JSON.stringify(data, null, 2)); }
1888 if (data.promise != null && require('MeshAgent')._promises[data.promise] != null) {
1889 var p = require('MeshAgent')._promises[data.promise];
1890 delete require('MeshAgent')._promises[data.promise];
1891 p.resolve(data.image);
1894 case 'wget': // Server uses this command to tell the agent to download a file using HTTPS/GET and place it in a given path. This is used for one-to-many file uploads.
1895 agentFileHttpPendingRequests.push(data);
1898 case 'serverInfo': // Server information
1899 obj.serverInfo = data;
1900 delete obj.serverInfo.action;
1902 case 'errorlog': // Return agent error log
1903 try { mesh.SendCommand(JSON.stringify({ action: 'errorlog', log: require('util-agentlog').read(data.startTime) })); } catch (ex) { }
1906 // Unknown action, ignore it.
1912// On non-Windows platforms, we need to query the DHCP server for the DNS suffix
1913function getAmtOsDnsSuffix(mestate, func) {
1914 if ((process.platform == 'win32') || (mestate.net0 == null) || (mestate.net0.mac == null)) { func(mestate); return; }
1915 try { require('linux-dhcp') } catch (ex) { func(mestate); return; }
1916 require('linux-dhcp').client.info(mestate.net0.mac).then(function (d) {
1917 if ((typeof d.options == 'object') && (typeof d.options.domainname == 'string')) { mestate.OsDnsSuffix = d.options.domainname; }
1920 console.log('DHCP error', e);
1925// Download a file from the server and check the hash.
1926// This download is similar to the one used for meshcore self-update.
1927var trustedDownloads = {};
1928function downloadFile(downloadoptions) {
1929 var options = require('http').parseUri(downloadoptions.url);
1930 options.rejectUnauthorized = false;
1931 options.checkServerIdentity = function checkServerIdentity(certs) {
1932 // If the tunnel certificate matches the control channel certificate, accept the connection
1933 try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.digest == certs[0].digest) return; } catch (ex) { }
1934 try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.fingerprint == certs[0].fingerprint) return; } catch (ex) { }
1935 // Check that the certificate is the one expected by the server, fail if not.
1936 if (checkServerIdentity.servertlshash == null) { if (require('MeshAgent').ServerInfo == null || require('MeshAgent').ServerInfo.ControlChannelCertificate == null) return; throw new Error('BadCert'); }
1937 if (certs[0].digest == null) return;
1938 if ((checkServerIdentity.servertlshash != null) && (checkServerIdentity.servertlshash.toLowerCase() != certs[0].digest.split(':').join('').toLowerCase())) { throw new Error('BadCert') }
1940 //options.checkServerIdentity.servertlshash = downloadoptions.serverhash;
1941 trustedDownloads[downloadoptions.name] = downloadoptions;
1942 trustedDownloads[downloadoptions.name].dl = require('https').get(options);
1943 trustedDownloads[downloadoptions.name].dl.on('error', function (e) { downloadoptions.func(downloadoptions, false); delete trustedDownloads[downloadoptions.name]; });
1944 trustedDownloads[downloadoptions.name].dl.on('response', function (img) {
1945 this._file = require('fs').createWriteStream(trustedDownloads[downloadoptions.name].filename, { flags: 'wb' });
1946 this._filehash = require('SHA384Stream').create();
1947 this._filehash.on('hash', function (h) { if ((downloadoptions.hash != null) && (downloadoptions.hash.toLowerCase() != h.toString('hex').toLowerCase())) { downloadoptions.func(downloadoptions, false); delete trustedDownloads[downloadoptions.name]; return; } downloadoptions.func(downloadoptions, true); });
1948 img.pipe(this._file);
1949 img.pipe(this._filehash);
1953// Handle APF JSON control commands
1954function handleApfJsonControl(data) {
1955 if (data.action == 'console') { addAmtEvent(data.msg); } // Add console message to AMT event log
1956 if (data.action == 'mestate') { amt.getMeiState(15, function (state) { apftunnel.updateMeiState(state); }); } // Update the MEI state
1957 if (data.action == 'close') { try { apftunnel.disconnect(); } catch (ex) { } apftunnel = null; } // Close the CIRA-LMS connection
1958 if (amt.amtMei != null) {
1959 if (data.action == 'deactivate') { // Request CCM deactivation
1960 amt.amtMei.unprovision(1, function (status) { if (apftunnel) apftunnel.sendMeiDeactivationState(status); }); // 0 = Success
1962 if (data.action == 'startTlsHostConfig') { // Request start of host based TLS ACM activation
1963 amt.amtMei.startConfigurationHBased(Buffer.from(data.hash, 'hex'), data.hostVpn, data.dnsSuffixList, function (response) { apftunnel.sendStartTlsHostConfigResponse(response); });
1965 if (data.action == 'stopConfiguration') { // Request Intel AMT stop configuration.
1966 amt.amtMei.stopConfiguration(function (status) { apftunnel.sendStopConfigurationResponse(status); });
1971// Agent just get a file from the server and save it locally.
1972function serverFetchFile() {
1973 if ((Object.keys(agentFileHttpRequests).length > 4) || (agentFileHttpPendingRequests.length == 0)) return; // No more than 4 active HTTPS requests to the server.
1974 var data = agentFileHttpPendingRequests.shift();
1975 if ((data.overwrite !== true) && fs.existsSync(data.path)) return; // Don't overwrite an existing file.
1976 if (data.createFolder) { try { fs.mkdirSync(data.folder); } catch (ex) { } } // If requested, create the local folder.
1977 data.url = 'http' + getServerTargetUrlEx('*/').substring(2);
1978 var agentFileHttpOptions = http.parseUri(data.url);
1979 agentFileHttpOptions.path = data.urlpath;
1981 // Perform manual server TLS certificate checking based on the certificate hash given by the server.
1982 agentFileHttpOptions.rejectUnauthorized = 0;
1983 agentFileHttpOptions.checkServerIdentity = function checkServerIdentity(certs) {
1984 // If the tunnel certificate matches the control channel certificate, accept the connection
1985 try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.digest == certs[0].digest) return; } catch (ex) { }
1986 try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.fingerprint == certs[0].fingerprint) return; } catch (ex) { }
1987 // Check that the certificate is the one expected by the server, fail if not.
1988 if ((checkServerIdentity.servertlshash != null) && (checkServerIdentity.servertlshash.toLowerCase() != certs[0].digest.split(':').join('').toLowerCase())) { throw new Error('BadCert') }
1990 agentFileHttpOptions.checkServerIdentity.servertlshash = data.servertlshash;
1992 if (agentFileHttpOptions == null) return;
1993 var agentFileHttpRequest = http.request(agentFileHttpOptions,
1994 function (response) {
1995 response.xparent = this;
1997 response.xfile = fs.createWriteStream(this.xpath, { flags: 'wbN' })
1998 response.pipe(response.xfile);
1999 response.end = function () { delete agentFileHttpRequests[this.xparent.xurlpath]; delete this.xparent; serverFetchFile(); }
2000 } catch (ex) { delete agentFileHttpRequests[this.xurlpath]; delete response.xparent; serverFetchFile(); return; }
2003 agentFileHttpRequest.on('error', function (ex) { sendConsoleText(ex); delete agentFileHttpRequests[this.xurlpath]; serverFetchFile(); });
2004 agentFileHttpRequest.end();
2005 agentFileHttpRequest.xurlpath = data.urlpath;
2006 agentFileHttpRequest.xpath = data.path;
2007 agentFileHttpRequests[data.urlpath] = agentFileHttpRequest;
2010// Called when a file changed in the file system
2012function onFileWatcher(a, b) {
2013 console.log('onFileWatcher', a, b, this.path);
2014 var response = getDirectoryInfo(this.path);
2015 if ((response != undefined) && (response != null)) { this.tunnel.s.write(JSON.stringify(response)); }
2019// Replace all key name spaces with _ in an object recursively.
2020// This is a workaround since require('computer-identifiers').get() returns key names with spaces in them on Linux.
2021function replaceSpacesWithUnderscoresRec(o) {
2022 if (typeof o != 'object') return;
2023 for (var i in o) { if (i.indexOf(' ') >= 0) { o[i.split(' ').join('_')] = o[i]; delete o[i]; } replaceSpacesWithUnderscoresRec(o[i]); }
2026function getSystemInformation(func) {
2028 var results = { hardware: require('computer-identifiers').get() }; // Hardware info
2029 if (results.hardware && results.hardware.windows) {
2030 // Remove extra entries and things that change quickly
2031 var x = results.hardware.windows.osinfo;
2032 try { delete x.FreePhysicalMemory; } catch (ex) { }
2033 try { delete x.FreeSpaceInPagingFiles; } catch (ex) { }
2034 try { delete x.FreeVirtualMemory; } catch (ex) { }
2035 try { delete x.LocalDateTime; } catch (ex) { }
2036 try { delete x.MaxProcessMemorySize; } catch (ex) { }
2037 try { delete x.TotalVirtualMemorySize; } catch (ex) { }
2038 try { delete x.TotalVisibleMemorySize; } catch (ex) { }
2040 if (results.hardware.windows.memory) { for (var i in results.hardware.windows.memory) { delete results.hardware.windows.memory[i].Node; } }
2041 if (results.hardware.windows.osinfo) {
2042 delete results.hardware.windows.osinfo.Node;
2043 results.hardware.windows.osinfo.Domain = getDomainInfo().Domain;
2044 results.hardware.windows.osinfo.PartOfDomain = getDomainInfo().PartOfDomain;
2045 results.hardware.windows.osinfo.DomainState = getJoinState();
2047 if (results.hardware.windows.partitions) { for (var i in results.hardware.windows.partitions) { delete results.hardware.windows.partitions[i].Node; } }
2049 if (x.LastBootUpTime) { // detect windows uptime
2051 year: parseInt(x.LastBootUpTime.substring(0, 4)),
2052 month: parseInt(x.LastBootUpTime.substring(4, 6)) - 1, // Months are 0-based in JavaScript (0 - January, 11 - December)
2053 day: parseInt(x.LastBootUpTime.substring(6, 8)),
2054 hours: parseInt(x.LastBootUpTime.substring(8, 10)),
2055 minutes: parseInt(x.LastBootUpTime.substring(10, 12)),
2056 seconds: parseInt(x.LastBootUpTime.substring(12, 14)),
2058 var thelastbootuptime = new Date(thedate.year, thedate.month, thedate.day, thedate.hours, thedate.minutes, thedate.seconds);
2059 meshCoreObj.lastbootuptime = thelastbootuptime.getTime(); // store the last boot up time in coreinfo for columns
2060 meshCoreObjChanged();
2061 var nowtime = new Date();
2062 var differenceInMilliseconds = Math.abs(thelastbootuptime - nowtime);
2063 if (differenceInMilliseconds < 300000) { // computer uptime less than 5 minutes
2064 MeshServerLogEx(159, [thelastbootuptime.toString()], "Device Powered On", null);
2068 if(results.hardware && results.hardware.linux) {
2069 if(results.hardware.linux.LastBootUpTime) {
2070 var thelastbootuptime = new Date(results.hardware.linux.LastBootUpTime);
2071 meshCoreObj.lastbootuptime = thelastbootuptime.getTime(); // store the last boot up time in coreinfo for columns
2072 meshCoreObjChanged();
2073 var nowtime = new Date();
2074 var differenceInMilliseconds = Math.abs(thelastbootuptime - nowtime);
2075 if (differenceInMilliseconds < 300000) { // computer uptime less than 5 minutes
2076 MeshServerLogEx(159, [thelastbootuptime.toString()], "Device Powered On", null);
2080 if(results.hardware && results.hardware.darwin){
2081 if(results.hardware.darwin.LastBootUpTime) {
2082 var thelastbootuptime = new Date(results.hardware.darwin.LastBootUpTime * 1000); // must times by 1000 even tho timestamp is correct?
2083 meshCoreObj.lastbootuptime = thelastbootuptime.getTime(); // store the last boot up time in coreinfo for columns
2084 meshCoreObjChanged();
2085 var nowtime = new Date();
2086 var differenceInMilliseconds = Math.abs(thelastbootuptime - nowtime);
2087 if (differenceInMilliseconds < 300000) { // computer uptime less than 5 minutes
2088 MeshServerLogEx(159, [thelastbootuptime.toString()], "Device Powered On", null);
2092 results.hardware.agentvers = process.versions;
2093 results.hardware.network = { dns: require('os').dns() };
2094 replaceSpacesWithUnderscoresRec(results);
2095 var hasher = require('SHA384Stream').create();
2097 // On Windows platforms, get volume information - Needs more testing.
2098 if (process.platform == 'win32')
2100 results.pendingReboot = require('win-info').pendingReboot(); // Pending reboot
2101 if (require('win-volumes').volumes_promise != null)
2103 var p = require('win-volumes').volumes_promise();
2104 p.then(function (res)
2106 results.hardware.windows.volumes = cleanGetBitLockerVolumeInfo(res);
2107 results.hash = hasher.syncHash(JSON.stringify(results)).toString('hex');
2113 results.hash = hasher.syncHash(JSON.stringify(results)).toString('hex');
2119 results.hash = hasher.syncHash(JSON.stringify(results)).toString('hex');
2123 } catch (ex) { func(null, ex); }
2126// Get a formated response for a given directory path
2127function getDirectoryInfo(reqpath) {
2128 var response = { path: reqpath, dir: [] };
2129 if (((reqpath == undefined) || (reqpath == '')) && (process.platform == 'win32')) {
2130 // List all the drives in the root, or the root itself
2132 try { results = fs.readDrivesSync(); } catch (ex) { }
2133 if (results != null) {
2134 for (var i = 0; i < results.length; ++i) {
2135 var drive = { n: results[i].name, t: 1, dt: results[i].type, s: (results[i].size ? results[i].size : 0), f: (results[i].free ? results[i].free : 0) };
2136 response.dir.push(drive);
2140 // List all the files and folders in this path
2141 if (reqpath == '') { reqpath = '/'; }
2142 var results = null, xpath = obj.path.join(reqpath, '*');
2143 //if (process.platform == "win32") { xpath = xpath.split('/').join('\\'); }
2144 try { results = fs.readdirSync(xpath); } catch (ex) { }
2145 try { if ((results != null) && (results.length == 0) && (fs.existsSync(reqpath) == false)) { results = null; } } catch (ex) { }
2146 if (results != null) {
2147 for (var i = 0; i < results.length; ++i) {
2148 if ((results[i] != '.') && (results[i] != '..')) {
2149 var stat = null, p = obj.path.join(reqpath, results[i]);
2150 //if (process.platform == "win32") { p = p.split('/').join('\\'); }
2151 try { stat = fs.statSync(p); } catch (ex) { } // TODO: Get file size/date
2152 if ((stat != null) && (stat != undefined)) {
2153 if (stat.isDirectory() == true) {
2154 response.dir.push({ n: results[i], t: 2, d: stat.mtime });
2156 response.dir.push({ n: results[i], t: 3, s: stat.size, d: stat.mtime });
2162 response.dir = null;
2168function tunnel_s_finalized()
2170 console.info1('Tunnel Socket Finalized');
2174function tunnel_onIdleTimeout()
2177 this.setTimeout(require('MeshAgent').idleTimeout * 1000);
2180// Tunnel callback operations
2181function onTunnelUpgrade(response, s, head)
2185 s.once('~', tunnel_s_finalized);
2186 s.httprequest = this;
2187 s.end = onTunnelClosed;
2189 s.descriptorMetadata = "MeshAgent_relayTunnel";
2192 if (require('MeshAgent').idleTimeout != null)
2194 s.setTimeout(require('MeshAgent').idleTimeout * 1000);
2195 s.on('timeout', tunnel_onIdleTimeout);
2198 //sendConsoleText('onTunnelUpgrade - ' + this.tcpport + ' - ' + this.udpport);
2200 if (this.tcpport != null) {
2201 // This is a TCP relay connection, pause now and try to connect to the target.
2203 s.data = onTcpRelayServerTunnelData;
2204 var connectionOptions = { port: parseInt(this.tcpport) };
2205 if (this.tcpaddr != null) { connectionOptions.host = this.tcpaddr; } else { connectionOptions.host = '127.0.0.1'; }
2206 s.tcprelay = net.createConnection(connectionOptions, onTcpRelayTargetTunnelConnect);
2207 s.tcprelay.peerindex = this.index;
2209 // Add the TCP session to the count and update the server
2210 if (s.httprequest.userid != null) {
2211 var userid = getUserIdAndGuestNameFromHttpRequest(s.httprequest);
2212 if (tunnelUserCount.tcp[userid] == null) { tunnelUserCount.tcp[userid] = 1; } else { tunnelUserCount.tcp[userid]++; }
2213 try { mesh.SendCommand({ action: 'sessions', type: 'tcp', value: tunnelUserCount.tcp }); } catch (ex) { }
2214 broadcastSessionsToRegisteredApps();
2217 if (this.udpport != null) {
2218 // This is a UDP relay connection, get the UDP socket setup. // TODO: ***************
2219 s.data = onUdpRelayServerTunnelData;
2220 s.udprelay = require('dgram').createSocket({ type: 'udp4' });
2221 s.udprelay.bind({ port: 0 });
2222 s.udprelay.peerindex = this.index;
2223 s.udprelay.on('message', onUdpRelayTargetTunnelConnect);
2224 s.udprelay.udpport = this.udpport;
2225 s.udprelay.udpaddr = this.udpaddr;
2226 s.udprelay.first = true;
2228 // Add the UDP session to the count and update the server
2229 if (s.httprequest.userid != null) {
2230 var userid = getUserIdAndGuestNameFromHttpRequest(s.httprequest);
2231 if (tunnelUserCount.udp[userid] == null) { tunnelUserCount.udp[userid] = 1; } else { tunnelUserCount.udp[userid]++; }
2232 try { mesh.SendCommand({ action: 'sessions', type: 'udp', value: tunnelUserCount.tcp }); } catch (ex) { }
2233 broadcastSessionsToRegisteredApps();
2237 // This is a normal connect for KVM/Terminal/Files
2238 s.data = onTunnelData;
2242// If the HTTP Request has a guest name, we need to form a userid that includes the guest name in hex.
2243// This is so we can tell the server that a session is for a given userid/guest sharing pair.
2244function getUserIdAndGuestNameFromHttpRequest(request) {
2245 if (request.guestname == null) return request.userid; else return request.guestuserid + '/guest:' + Buffer.from(request.guestname).toString('base64');
2248// Called when UDP relay data is received // TODO****
2249function onUdpRelayTargetTunnelConnect(data) {
2250 var peerTunnel = tunnels[this.peerindex];
2251 peerTunnel.s.write(data);
2254// Called when we get data from the server for a TCP relay (We have to skip the first received 'c' and pipe the rest)
2255function onUdpRelayServerTunnelData(data) {
2256 if (this.udprelay.first === true) {
2257 delete this.udprelay.first; // Skip the first 'c' that is received.
2259 this.udprelay.send(data, parseInt(this.udprelay.udpport), this.udprelay.udpaddr ? this.udprelay.udpaddr : '127.0.0.1');
2263// Called when the TCP relay target is connected
2264function onTcpRelayTargetTunnelConnect() {
2265 var peerTunnel = tunnels[this.peerindex];
2266 this.pipe(peerTunnel.s); // Pipe Target --> Server
2267 peerTunnel.s.first = true;
2268 peerTunnel.s.resume();
2271// Called when we get data from the server for a TCP relay (We have to skip the first received 'c' and pipe the rest)
2272function onTcpRelayServerTunnelData(data) {
2273 if (this.first == true) {
2275 this.pipe(this.tcprelay, { dataTypeSkip: 1 }); // Pipe Server --> Target (don't pipe text type websocket frames)
2279function onTunnelClosed()
2281 if (this.httprequest._dispatcher != null && this.httprequest.term == null)
2283 // Windows Dispatcher was created to spawn a child connection, but the child didn't connect yet, so we have to shutdown the dispatcher, otherwise the child may end up hanging
2284 if (this.httprequest._dispatcher.close) { this.httprequest._dispatcher.close(); }
2285 this.httprequest._dispatcher = null;
2290 if (tunnels[this.httprequest.index] == null)
2292 this.tunnel.s = null;
2298 var tunnel = tunnels[this.httprequest.index];
2299 if (tunnel == null) return; // Stop duplicate calls.
2301 // Perform display locking on disconnect
2302 if ((this.httprequest.protocol == 2) && (this.httprequest.autolock === true)) {
2305 if ((this.httprequest.xoptions != null) && (typeof this.httprequest.xoptions.tsid == 'number')) { tsid = this.httprequest.xoptions.tsid; }
2307 // Lock the current user out of the desktop
2308 MeshServerLogEx(53, null, "Locking remote user out of desktop", this.httprequest);
2312 // If this is a routing session, clean up and send the new session counts.
2313 if (this.httprequest.userid != null) {
2314 if (this.httprequest.tcpport != null) {
2315 var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest);
2316 if (tunnelUserCount.tcp[userid] != null) { tunnelUserCount.tcp[userid]--; if (tunnelUserCount.tcp[userid] <= 0) { delete tunnelUserCount.tcp[userid]; } }
2317 try { mesh.SendCommand({ action: 'sessions', type: 'tcp', value: tunnelUserCount.tcp }); } catch (ex) { }
2318 broadcastSessionsToRegisteredApps();
2319 } else if (this.httprequest.udpport != null) {
2320 var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest);
2321 if (tunnelUserCount.udp[userid] != null) { tunnelUserCount.udp[userid]--; if (tunnelUserCount.udp[userid] <= 0) { delete tunnelUserCount.udp[userid]; } }
2322 try { mesh.SendCommand({ action: 'sessions', type: 'udp', value: tunnelUserCount.udp }); } catch (ex) { }
2323 broadcastSessionsToRegisteredApps();
2328 // Sent tunnel statistics to the server, only send this if compression was used.
2329 if ((this.bytesSent_uncompressed) && (this.bytesSent_uncompressed.toString() != this.bytesSent_actual.toString())) {
2331 action: 'tunnelCloseStats',
2333 userid: tunnel.userid,
2334 protocol: tunnel.protocol,
2335 sessionid: tunnel.sessionid,
2336 sent: this.bytesSent_uncompressed.toString(),
2337 sentActual: this.bytesSent_actual.toString(),
2338 sentRatio: this.bytesSent_ratio,
2339 received: this.bytesReceived_uncompressed.toString(),
2340 receivedActual: this.bytesReceived_actual.toString(),
2341 receivedRatio: this.bytesReceived_ratio
2346 //sendConsoleText("Tunnel #" + this.httprequest.index + " closed. Sent -> " + this.bytesSent_uncompressed + ' bytes (uncompressed), ' + this.bytesSent_actual + ' bytes (actual), ' + this.bytesSent_ratio + '% compression', this.httprequest.sessionid);
2350 // Close the watcher if required
2351 if (this.httprequest.watcher != undefined) {
2352 //console.log('Closing watcher: ' + this.httprequest.watcher.path);
2353 //this.httprequest.watcher.close(); // TODO: This line causes the agent to crash!!!!
2354 delete this.httprequest.watcher;
2358 // If there is a upload or download active on this connection, close the file
2359 if (this.httprequest.uploadFile) { fs.closeSync(this.httprequest.uploadFile); delete this.httprequest.uploadFile; delete this.httprequest.uploadFileid; delete this.httprequest.uploadFilePath; delete this.httprequest.uploadFileSize; }
2360 if (this.httprequest.downloadFile) { delete this.httprequest.downloadFile; }
2363 if (this.webrtc != null) {
2364 if (this.webrtc.rtcchannel) { try { this.webrtc.rtcchannel.close(); } catch (ex) { } this.webrtc.rtcchannel.removeAllListeners('data'); this.webrtc.rtcchannel.removeAllListeners('end'); delete this.webrtc.rtcchannel; }
2365 if (this.webrtc.websocket) { delete this.webrtc.websocket; }
2366 try { this.webrtc.close(); } catch (ex) { }
2367 this.webrtc.removeAllListeners('connected');
2368 this.webrtc.removeAllListeners('disconnected');
2369 this.webrtc.removeAllListeners('dataChannel');
2373 // Clean up WebSocket
2374 delete tunnels[this.httprequest.index];
2376 this.tunnel.s = null;
2378 this.removeAllListeners('data');
2380function onTunnelSendOk() { /*sendConsoleText("Tunnel #" + this.index + " SendOK.", this.sessionid);*/ }
2382function terminal_onconnection (c)
2384 if (this.httprequest.connectionPromise.completed)
2390 this.httprequest.connectionPromise._res(c);
2393function terminal_user_onconnection(c)
2395 console.info1('completed-2: ' + this.connectionPromise.completed);
2397 if (this.connectionPromise.completed)
2403 this.connectionPromise._res(c);
2406function terminal_stderr_ondata(c)
2408 this.stdout.write(c);
2410function terminal_onend()
2412 this.httprequest.process.kill();
2415function terminal_onexit()
2419function terminal_onfinalized()
2421 this.httprequest = null;
2422 console.info1('Dispatcher Finalized');
2424function terminal_end()
2426 if (this.httprequest == null) { return; }
2427 if (this.httprequest.tpromise._consent) { this.httprequest.tpromise._consent.close(); }
2428 if (this.httprequest.connectionPromise) { this.httprequest.connectionPromise._rej('Closed'); }
2430 // Remove the terminal session to the count to update the server
2431 if (this.httprequest.userid != null)
2433 var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest);
2434 if (tunnelUserCount.terminal[userid] != null) { tunnelUserCount.terminal[userid]--; if (tunnelUserCount.terminal[userid] <= 0) { delete tunnelUserCount.terminal[userid]; } }
2435 try { mesh.SendCommand({ action: 'sessions', type: 'terminal', value: tunnelUserCount.terminal }); } catch (ex) { }
2436 broadcastSessionsToRegisteredApps();
2439 if (process.platform == 'win32')
2441 // Unpipe the web socket
2442 this.unpipe(this.httprequest._term);
2443 if (this.httprequest._term) { this.httprequest._term.unpipe(this); }
2445 // Unpipe the WebRTC channel if needed (This will also be done when the WebRTC channel ends).
2446 if (this.rtcchannel)
2448 this.rtcchannel.unpipe(this.httprequest._term);
2449 if (this.httprequest._term) { this.httprequest._term.unpipe(this.rtcchannel); }
2453 if (this.httprequest._term) { this.httprequest._term.end(); }
2454 this.httprequest._term = null;
2455 this.httprequest._dispatcher = null;
2458 this.httprequest = null;
2462function terminal_consent_ask(ws) {
2463 ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 }));
2464 var consentMessage = currentTranslation['terminalConsent'].replace(/\{0\}/g, ws.httprequest.realname).replace(/\{1\}/g, ws.httprequest.username);
2465 var consentTitle = 'MeshCentral';
2466 if (ws.httprequest.soptions != null) {
2467 if (ws.httprequest.soptions.consentTitle != null) { consentTitle = ws.httprequest.soptions.consentTitle; }
2468 if (ws.httprequest.soptions.consentMsgTerminal != null) { consentMessage = ws.httprequest.soptions.consentMsgTerminal.replace(/\{0\}/g, ws.httprequest.realname).replace(/\{1\}/g, ws.httprequest.username); }
2470 if (process.platform == 'win32') {
2471 var enhanced = false;
2472 if (ws.httprequest.oldStyle === false) {
2473 try { require('win-userconsent'); enhanced = true; } catch (ex) { }
2476 var ipr = server_getUserImage(ws.httprequest.userid);
2477 ipr.consentTitle = consentTitle;
2478 ipr.consentMessage = consentMessage;
2479 ipr.consentTimeout = ws.httprequest.consentTimeout;
2480 ipr.consentAutoAccept = ws.httprequest.consentAutoAccept;
2481 ipr.username = ws.httprequest.realname;
2483 ipr.translations = { Allow: currentTranslation['allow'], Deny: currentTranslation['deny'], Auto: currentTranslation['autoAllowForFive'], Caption: consentMessage };
2484 ws.httprequest.tpromise._consent = ipr.then(function (img) {
2485 this.consent = require('win-userconsent').create(this.consentTitle, this.consentMessage, this.username, { b64Image: img.split(',').pop(), uid: this.tsid, timeout: this.consentTimeout * 1000, timeoutAutoAccept: this.consentAutoAccept, translations: this.translations, background: color_options.background, foreground: color_options.foreground });
2486 this.__childPromise.close = this.consent.close.bind(this.consent);
2487 return (this.consent);
2490 ws.httprequest.tpromise._consent = require('message-box').create(consentTitle, consentMessage, ws.httprequest.consentTimeout);
2493 ws.httprequest.tpromise._consent = require('message-box').create(consentTitle, consentMessage, ws.httprequest.consentTimeout);
2495 ws.httprequest.tpromise._consent.retPromise = ws.httprequest.tpromise;
2496 ws.httprequest.tpromise._consent.then(function (always) {
2497 if (always && process.platform == 'win32') { server_set_consentTimer(this.retPromise.httprequest.userid); }
2499 MeshServerLogEx(27, null, "Local user accepted remote terminal request (" + this.retPromise.httprequest.remoteaddr + ")", this.retPromise.that.httprequest);
2500 this.retPromise.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null, msgid: 0 }));
2501 this.retPromise._consent = null;
2502 this.retPromise._res();
2504 if (this.retPromise.that) {
2505 if(this.retPromise.that.httprequest){ // User Consent Denied
2506 MeshServerLogEx(28, null, "Local user rejected remote terminal request (" + this.retPromise.that.httprequest.remoteaddr + ")", this.retPromise.that.httprequest);
2507 } else { } // Connection was closed server side, maybe log some messages somewhere?
2508 this.retPromise._consent = null;
2509 this.retPromise.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
2510 } else { } // no websocket, maybe log some messages somewhere?
2511 this.retPromise._rej(e.toString());
2515function terminal_promise_connection_rejected(e)
2517 // FAILED to connect terminal
2518 this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
2522function terminal_promise_connection_resolved(term)
2524 this._internal.completedArgs = [];
2529 if (process.platform == 'win32')
2531 this.ws.httprequest._term = term;
2532 this.ws.httprequest._term.tunnel = this.ws;
2533 stdoutstream = stdinstream = term;
2537 term.descriptorMetadata = 'Remote Terminal';
2538 this.ws.httprequest.process = term;
2539 this.ws.httprequest.process.tunnel = this.ws;
2540 term.stderr.stdout = term.stdout;
2541 term.stderr.on('data', terminal_stderr_ondata);
2542 stdoutstream = term.stdout;
2543 stdinstream = term.stdin;
2544 this.ws.prependListener('end', terminal_onend);
2545 term.prependListener('exit', terminal_onexit);
2548 this.ws.removeAllListeners('data');
2549 this.ws.on('data', onTunnelControlData);
2551 stdoutstream.pipe(this.ws, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
2552 this.ws.pipe(stdinstream, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
2554 // Add the terminal session to the count to update the server
2555 if (this.ws.httprequest.userid != null)
2557 var userid = getUserIdAndGuestNameFromHttpRequest(this.ws.httprequest);
2558 if (tunnelUserCount.terminal[userid] == null) { tunnelUserCount.terminal[userid] = 1; } else { tunnelUserCount.terminal[userid]++; }
2559 try { mesh.SendCommand({ action: 'sessions', type: 'terminal', value: tunnelUserCount.terminal }); } catch (ex) { }
2560 broadcastSessionsToRegisteredApps();
2563 // Toast Notification, if required
2564 if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 2))
2566 // User Notifications is required
2567 var notifyMessage = currentTranslation['terminalNotify'].replace(/\{0\}/g, this.ws.httprequest.realname ? this.ws.httprequest.realname : this.ws.httprequest.username);
2568 var notifyTitle = "MeshCentral";
2569 if (this.ws.httprequest.soptions != null)
2571 if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; }
2572 if (this.ws.httprequest.soptions.notifyMsgTerminal != null) { notifyMessage = this.ws.httprequest.soptions.notifyMsgTerminal.replace(/\{0\}/g, this.ws.httprequest.realname).replace(/\{1\}/g, this.ws.httprequest.username); }
2574 try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (ex) { }
2578function terminal_promise_consent_rejected(e)
2580 // DO NOT start terminal
2582 if(this.that.httprequest){ // User Consent Denied
2583 if ((this.that.httprequest.oldStyle === true) && (this.that.httprequest.consentAutoAccept === true) && (e.toString() != "7")) {
2584 terminal_promise_consent_resolved.call(this); // oldStyle prompt timed out and User Consent is not required so connect anyway
2587 } else { } // Connection was closed server side, maybe log some messages somewhere?
2588 this.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
2592 this.httprequest = null;
2593 } else { } // no websocket, maybe log some messages somewhere?
2595function promise_init(res, rej) { this._res = res; this._rej = rej; }
2596function terminal_userpromise_resolved(u)
2599 var that = this.that;
2600 if (u.Active.length > 0)
2603 var username = '"' + u.Active[0].Domain + '\\' + u.Active[0].Username + '"';
2606 if (require('win-virtual-terminal').supported)
2608 // ConPTY PseudoTerminal
2609 tmp = require('win-dispatcher').dispatch({ user: username, modules: [{ name: 'win-virtual-terminal', script: getJSModule('win-virtual-terminal') }], launch: { module: 'win-virtual-terminal', method: (that.httprequest.protocol == 9 ? 'StartPowerShell' : 'Start'), args: [this.cols, this.rows] } });
2614 tmp = require('win-dispatcher').dispatch({ user: username, modules: [{ name: 'win-terminal', script: getJSModule('win-terminal') }], launch: { module: 'win-terminal', method: (that.httprequest.protocol == 9 ? 'StartPowerShell' : 'Start'), args: [this.cols, this.rows] } });
2616 that.httprequest._dispatcher = tmp;
2617 that.httprequest._dispatcher.connectionPromise = that.httprequest.connectionPromise;
2618 that.httprequest._dispatcher.on('connection', terminal_user_onconnection);
2619 that.httprequest._dispatcher.on('~', terminal_onfinalized);
2625function terminal_promise_consent_resolved()
2627 this.httprequest.connectionPromise = new promise(promise_init);
2628 this.httprequest.connectionPromise.ws = this.that;
2631 if (process.platform == 'win32')
2635 var cols = 80, rows = 25;
2636 if (this.httprequest.xoptions)
2638 if (this.httprequest.xoptions.rows) { rows = this.httprequest.xoptions.rows; }
2639 if (this.httprequest.xoptions.cols) { cols = this.httprequest.xoptions.cols; }
2642 if ((this.httprequest.protocol == 1) || (this.httprequest.protocol == 6))
2645 if (require('win-virtual-terminal').supported)
2647 // ConPTY PseudoTerminal
2648 // this.httprequest._term = require('win-virtual-terminal')[this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'](80, 25);
2650 // The above line is commented out, because there is a bug with ClosePseudoConsole() API, so this is the workaround
2651 this.httprequest._dispatcher = require('win-dispatcher').dispatch({ modules: [{ name: 'win-virtual-terminal', script: getJSModule('win-virtual-terminal') }], launch: { module: 'win-virtual-terminal', method: (this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'), args: [cols, rows] } });
2652 this.httprequest._dispatcher.httprequest = this.httprequest;
2653 this.httprequest._dispatcher.on('connection', terminal_onconnection);
2654 this.httprequest._dispatcher.on('~', terminal_onfinalized);
2659 this.httprequest.connectionPromise._res(require('win-terminal')[this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'](cols, rows));
2665 var userPromise = require('user-sessions').enumerateUsers();
2666 userPromise.that = this;
2667 userPromise.cols = cols;
2668 userPromise.rows = rows;
2669 userPromise.then(terminal_userpromise_resolved);
2673 this.httprequest.connectionPromise._rej('Failed to start remote terminal session, ' + ex.toString());
2680 var bash = fs.existsSync('/bin/bash') ? '/bin/bash' : false;
2681 var sh = fs.existsSync('/bin/sh') ? '/bin/sh' : false;
2682 var login = process.platform == 'linux' ? '/bin/login' : '/usr/bin/login';
2684 var env = { HISTCONTROL: 'ignoreboth' };
2685 if (process.env['LANG']) { env['LANG'] = process.env['LANG']; }
2686 if (process.env['PATH']) { env['PATH'] = process.env['PATH']; }
2687 if (this.httprequest.xoptions)
2689 if (this.httprequest.xoptions.rows) { env.LINES = ('' + this.httprequest.xoptions.rows); }
2690 if (this.httprequest.xoptions.cols) { env.COLUMNS = ('' + this.httprequest.xoptions.cols); }
2692 var options = { type: childProcess.SpawnTypes.TERM, uid: (this.httprequest.protocol == 8) ? require('user-sessions').consoleUid() : null, env: env };
2693 if (this.httprequest.xoptions && this.httprequest.xoptions.requireLogin)
2695 if (!require('fs').existsSync(login)) { throw ('Unable to spawn login process'); }
2696 this.httprequest.connectionPromise._res(childProcess.execFile(login, ['login'], options)); // Start login shell
2700 var p = childProcess.execFile(bash, ['bash'], options); // Start bash
2701 // Spaces at the beginning of lines are needed to hide commands from the command history
2702 if ((obj.serverInfo.termlaunchcommand != null) && (typeof obj.serverInfo.termlaunchcommand[process.platform] == 'string'))
2704 if (obj.serverInfo.termlaunchcommand[process.platform] != '') { p.stdin.write(obj.serverInfo.termlaunchcommand[process.platform]); }
2705 } else if (process.platform == 'linux') { p.stdin.write(' alias ls=\'ls --color=auto\';clear\n'); }
2706 this.httprequest.connectionPromise._res(p);
2710 var p = childProcess.execFile(sh, ['sh'], options); // Start sh
2711 // Spaces at the beginning of lines are needed to hide commands from the command history
2712 if ((obj.serverInfo.termlaunchcommand != null) && (typeof obj.serverInfo.termlaunchcommand[process.platform] == 'string'))
2714 if (obj.serverInfo.termlaunchcommand[process.platform] != '') { p.stdin.write(obj.serverInfo.termlaunchcommand[process.platform]); }
2715 } else if (process.platform == 'linux') { p.stdin.write(' alias ls=\'ls --color=auto\';clear\n'); }
2716 this.httprequest.connectionPromise._res(p);
2720 this.httprequest.connectionPromise._rej('Failed to start remote terminal session, no shell found');
2724 this.httprequest.connectionPromise._rej('Failed to start remote terminal session, ' + ex.toString());
2728 this.httprequest.connectionPromise.then(terminal_promise_connection_resolved, terminal_promise_connection_rejected);
2730 this.httprequest = null;
2732function tunnel_kvm_end()
2734 --this.desktop.kvm.connectionCount;
2736 // Remove ourself from the list of remote desktop session
2737 var i = this.desktop.kvm.tunnels.indexOf(this);
2738 if (i >= 0) { this.desktop.kvm.tunnels.splice(i, 1); }
2740 // Send a metadata update to all desktop sessions
2742 if (this.httprequest.desktop.kvm.tunnels != null)
2744 for (var i in this.httprequest.desktop.kvm.tunnels)
2748 var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest.desktop.kvm.tunnels[i].httprequest);
2749 if (users[userid] == null) { users[userid] = 1; } else { users[userid]++; }
2750 } catch (ex) { sendConsoleText(ex); }
2752 for (var i in this.httprequest.desktop.kvm.tunnels)
2754 try { this.httprequest.desktop.kvm.tunnels[i].write(JSON.stringify({ ctrlChannel: '102938', type: 'metadata', users: users })); } catch (ex) { }
2756 tunnelUserCount.desktop = users;
2757 try { mesh.SendCommand({ action: 'sessions', type: 'kvm', value: users }); } catch (ex) { }
2758 broadcastSessionsToRegisteredApps();
2761 // Unpipe the web socket
2764 this.unpipe(this.httprequest.desktop.kvm);
2765 this.httprequest.desktop.kvm.unpipe(this);
2768 // Unpipe the WebRTC channel if needed (This will also be done when the WebRTC channel ends).
2769 if (this.rtcchannel)
2773 this.rtcchannel.unpipe(this.httprequest.desktop.kvm);
2774 this.httprequest.desktop.kvm.unpipe(this.rtcchannel);
2779 // Place wallpaper back if needed
2782 if (this.desktop.kvm.connectionCount == 0)
2784 // Display a toast message. This may not be supported on all platforms.
2785 // try { require('toaster').Toast('MeshCentral', 'Remote Desktop Control Ended.'); } catch (ex) { }
2787 this.httprequest.desktop.kvm.end();
2788 if (this.httprequest.desktop.kvm.connectionBar)
2790 this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close');
2791 this.httprequest.desktop.kvm.connectionBar.close();
2792 this.httprequest.desktop.kvm.connectionBar = null;
2796 for (var i in this.httprequest.desktop.kvm.users)
2798 if ((this.httprequest.desktop.kvm.users[i] == this.httprequest.username) && this.httprequest.desktop.kvm.connectionBar)
2800 for (var j in this.httprequest.desktop.kvm.rusers) { if (this.httprequest.desktop.kvm.rusers[j] == this.httprequest.realname) { this.httprequest.desktop.kvm.rusers.splice(j, 1); break; } }
2801 this.httprequest.desktop.kvm.users.splice(i, 1);
2802 this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close');
2803 this.httprequest.desktop.kvm.connectionBar.close();
2804 this.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(this.httprequest.privacybartext.replace(/\{0\}/g, this.httprequest.desktop.kvm.rusers.join(', ')).replace(/\{1\}/g, this.httprequest.desktop.kvm.users.join(', ')).replace(/'/g, "\\'\\"), require('MeshAgent')._tsid, color_options);
2805 this.httprequest.desktop.kvm.connectionBar.httprequest = this.httprequest;
2806 this.httprequest.desktop.kvm.connectionBar.on('close', function ()
2808 MeshServerLogEx(29, null, "Remote Desktop Connection forcefully closed by local user (" + this.httprequest.remoteaddr + ")", this.httprequest);
2809 for (var i in this.httprequest.desktop.kvm._pipedStreams)
2811 this.httprequest.desktop.kvm._pipedStreams[i].end();
2813 this.httprequest.desktop.kvm.end();
2820 if(this.httprequest.desktop.kvm.connectionBar)
2822 console.info1('Setting ConnectionBar request to NULL');
2823 this.httprequest.desktop.kvm.connectionBar.httprequest = null;
2826 this.httprequest = null;
2827 this.desktop.tunnel = null;
2830function kvm_tunnel_consentpromise_closehandler()
2832 if (this._consentpromise && this._consentpromise.close) { this._consentpromise.close(); }
2835function kvm_consent_ok(ws) {
2836 // User Consent Prompt is not required because no user is present
2837 if (ws.httprequest.consent && (ws.httprequest.consent & 1)){
2838 // User Notifications is required
2839 MeshServerLogEx(35, null, "Started remote desktop with toast notification (" + ws.httprequest.remoteaddr + ")", ws.httprequest);
2840 var notifyMessage = currentTranslation['desktopNotify'].replace(/\{0\}/g, ws.httprequest.realname);
2841 var notifyTitle = "MeshCentral";
2842 if (ws.httprequest.soptions != null) {
2843 if (ws.httprequest.soptions.notifyTitle != null) { notifyTitle = ws.httprequest.soptions.notifyTitle; }
2844 if (ws.httprequest.soptions.notifyMsgDesktop != null) { notifyMessage = ws.httprequest.soptions.notifyMsgDesktop.replace(/\{0\}/g, ws.httprequest.realname).replace(/\{1\}/g, ws.httprequest.username); }
2846 try { require('toaster').Toast(notifyTitle, notifyMessage, ws.tsid); } catch (ex) { }
2848 MeshServerLogEx(36, null, "Started remote desktop without notification (" + ws.httprequest.remoteaddr + ")", ws.httprequest);
2850 if (ws.httprequest.consent && (ws.httprequest.consent & 0x40)) {
2851 // Connection Bar is required
2852 if (ws.httprequest.desktop.kvm.connectionBar) {
2853 ws.httprequest.desktop.kvm.connectionBar.removeAllListeners('close');
2854 ws.httprequest.desktop.kvm.connectionBar.close();
2857 ws.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(ws.httprequest.privacybartext.replace(/\{0\}/g, ws.httprequest.desktop.kvm.rusers.join(', ')).replace(/\{1\}/g, ws.httprequest.desktop.kvm.users.join(', ')).replace(/'/g, "\\'\\"), require('MeshAgent')._tsid, color_options);
2858 MeshServerLogEx(31, null, "Remote Desktop Connection Bar Activated/Updated (" + ws.httprequest.remoteaddr + ")", ws.httprequest);
2860 MeshServerLogEx(32, null, "Remote Desktop Connection Bar Failed or not Supported (" + ws.httprequest.remoteaddr + ")", ws.httprequest);
2862 if (ws.httprequest.desktop.kvm.connectionBar) {
2863 ws.httprequest.desktop.kvm.connectionBar.state = {
2864 userid: ws.httprequest.userid,
2865 xuserid: ws.httprequest.xuserid,
2866 username: ws.httprequest.username,
2867 sessionid: ws.httprequest.sessionid,
2868 remoteaddr: ws.httprequest.remoteaddr,
2869 guestname: ws.httprequest.guestname,
2870 desktop: ws.httprequest.desktop
2872 ws.httprequest.desktop.kvm.connectionBar.on('close', function () {
2873 console.info1('Connection Bar Forcefully closed');
2874 MeshServerLogEx(29, null, "Remote Desktop Connection forcefully closed by local user (" + this.state.remoteaddr + ")", this.state);
2875 for (var i in this.state.desktop.kvm._pipedStreams) {
2876 this.state.desktop.kvm._pipedStreams[i].end();
2878 this.state.desktop.kvm.end();
2882 ws.httprequest.desktop.kvm.pipe(ws, { dataTypeSkip: 1 });
2883 if (ws.httprequest.autolock) {
2884 destopLockHelper_pipe(ws.httprequest);
2888function kvm_consent_ask(ws){
2889 // Send a console message back using the console channel, "\n" is supported.
2890 ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 }));
2891 var consentMessage = currentTranslation['desktopConsent'].replace(/\{0\}/g, ws.httprequest.realname).replace(/\{1\}/g, ws.httprequest.username);
2892 var consentTitle = 'MeshCentral';
2893 if (ws.httprequest.soptions != null) {
2894 if (ws.httprequest.soptions.consentTitle != null) { consentTitle = ws.httprequest.soptions.consentTitle; }
2895 if (ws.httprequest.soptions.consentMsgDesktop != null) { consentMessage = ws.httprequest.soptions.consentMsgDesktop.replace(/\{0\}/g, ws.httprequest.realname).replace(/\{1\}/g, ws.httprequest.username); }
2898 if (process.platform == 'win32') {
2899 var enhanced = false;
2900 if (ws.httprequest.oldStyle === false) {
2901 try { require('win-userconsent'); enhanced = true; } catch (ex) { }
2904 var ipr = server_getUserImage(ws.httprequest.userid);
2905 ipr.consentTitle = consentTitle;
2906 ipr.consentMessage = consentMessage;
2907 ipr.consentTimeout = ws.httprequest.consentTimeout;
2908 ipr.consentAutoAccept = ws.httprequest.consentAutoAccept;
2910 ipr.username = ws.httprequest.realname;
2911 ipr.translation = { Allow: currentTranslation['allow'], Deny: currentTranslation['deny'], Auto: currentTranslation['autoAllowForFive'], Caption: consentMessage };
2912 pr = ipr.then(function (img) {
2913 this.consent = require('win-userconsent').create(this.consentTitle, this.consentMessage, this.username, { b64Image: img.split(',').pop(), uid: this.tsid, timeout: this.consentTimeout * 1000, timeoutAutoAccept: this.consentAutoAccept, translations: this.translation, background: color_options.background, foreground: color_options.foreground });
2914 this.__childPromise.close = this.consent.close.bind(this.consent);
2915 return (this.consent);
2918 pr = require('message-box').create(consentTitle, consentMessage, ws.httprequest.consentTimeout, null, ws.tsid);
2921 pr = require('message-box').create(consentTitle, consentMessage, ws.httprequest.consentTimeout, null, ws.tsid);
2925 ws._consentpromise = pr;
2926 ws.prependOnceListener('end', kvm_tunnel_consentpromise_closehandler);
2927 pr.then(kvm_consentpromise_resolved, kvm_consentpromise_rejected);
2930function kvm_consentpromise_rejected(e)
2933 if(this.ws.httprequest){ // User Consent Denied
2934 if ((this.ws.httprequest.oldStyle === true) && (this.ws.httprequest.consentAutoAccept === true) && (e.toString() != "7")) {
2935 kvm_consentpromise_resolved.call(this); // oldStyle prompt timed out and User Consent is not required so connect anyway
2938 MeshServerLogEx(34, null, "Failed to start remote desktop after local user rejected (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
2939 } else { } // Connection was closed server side, maybe log some messages somewhere?
2940 this.ws._consentpromise = null;
2941 this.ws.end(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
2943 } else { } // no websocket, maybe log some messages somewhere?
2945function kvm_consentpromise_resolved(always)
2947 if (always && process.platform=='win32') { server_set_consentTimer(this.ws.httprequest.userid); }
2950 this.ws._consentpromise = null;
2951 MeshServerLogEx(30, null, "Starting remote desktop after local user accepted (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
2952 this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null, msgid: 0 }));
2953 if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 1))
2955 // User Notifications is required
2956 var notifyMessage = currentTranslation['desktopNotify'].replace(/\{0\}/g, this.ws.httprequest.realname);
2957 var notifyTitle = "MeshCentral";
2958 if (this.ws.httprequest.soptions != null)
2960 if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; }
2961 if (this.ws.httprequest.soptions.notifyMsgDesktop != null) { notifyMessage = this.ws.httprequest.soptions.notifyMsgDesktop.replace(/\{0\}/g, this.ws.httprequest.realname).replace(/\{1\}/g, this.ws.httprequest.username); }
2963 try { require('toaster').Toast(notifyTitle, notifyMessage, tsid); } catch (ex) { }
2965 if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 0x40))
2967 // Connection Bar is required
2968 if (this.ws.httprequest.desktop.kvm.connectionBar)
2970 this.ws.httprequest.desktop.kvm.connectionBar.removeAllListeners('close');
2971 this.ws.httprequest.desktop.kvm.connectionBar.close();
2975 this.ws.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(this.ws.httprequest.privacybartext.replace(/\{0\}/g, this.ws.httprequest.desktop.kvm.rusers.join(', ')).replace(/\{1\}/g, this.ws.httprequest.desktop.kvm.users.join(', ')).replace(/'/g, "\\'\\"), require('MeshAgent')._tsid, color_options);
2976 MeshServerLogEx(31, null, "Remote Desktop Connection Bar Activated/Updated (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
2979 if (process.platform != 'darwin')
2981 MeshServerLogEx(32, null, "Remote Desktop Connection Bar Failed or Not Supported (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
2985 if (this.ws.httprequest.desktop.kvm.connectionBar) {
2986 this.ws.httprequest.desktop.kvm.connectionBar.httprequest = this.ws.httprequest;
2987 this.ws.httprequest.desktop.kvm.connectionBar.on('close', function () {
2988 MeshServerLogEx(29, null, "Remote Desktop Connection forcefully closed by local user (" + this.httprequest.remoteaddr + ")", this.httprequest);
2989 for (var i in this.httprequest.desktop.kvm._pipedStreams) {
2990 this.httprequest.desktop.kvm._pipedStreams[i].end();
2992 this.httprequest.desktop.kvm.end();
2998 if (process.platform != 'darwin')
3000 MeshServerLogEx(32, null, "Failed2(" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
3004 this.ws.httprequest.desktop.kvm.pipe(this.ws, { dataTypeSkip: 1 });
3005 if (this.ws.httprequest.autolock)
3007 destopLockHelper_pipe(this.ws.httprequest);
3013function files_consent_ok(ws){
3014 // User Consent Prompt is not required
3015 if (ws.httprequest.consent && (ws.httprequest.consent & 4)) {
3016 // User Notifications is required
3017 MeshServerLogEx(42, null, "Started remote files with toast notification (" + ws.httprequest.remoteaddr + ")", ws.httprequest);
3018 var notifyMessage = currentTranslation['fileNotify'].replace(/\{0\}/g, ws.httprequest.realname);
3019 var notifyTitle = "MeshCentral";
3020 if (ws.httprequest.soptions != null) {
3021 if (ws.httprequest.soptions.notifyTitle != null) { notifyTitle = ws.httprequest.soptions.notifyTitle; }
3022 if (ws.httprequest.soptions.notifyMsgFiles != null) { notifyMessage = ws.httprequest.soptions.notifyMsgFiles.replace(/\{0\}/g, ws.httprequest.realname).replace(/\{1\}/g, ws.httprequest.username); }
3024 try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (ex) { }
3026 MeshServerLogEx(43, null, "Started remote files without notification (" + ws.httprequest.remoteaddr + ")", ws.httprequest);
3031function files_consent_ask(ws){
3032 // Send a console message back using the console channel, "\n" is supported.
3033 ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 }));
3034 var consentMessage = currentTranslation['fileConsent'].replace(/\{0\}/g, ws.httprequest.realname).replace(/\{1\}/g, ws.httprequest.username);
3035 var consentTitle = 'MeshCentral';
3037 if (ws.httprequest.soptions != null) {
3038 if (ws.httprequest.soptions.consentTitle != null) { consentTitle = ws.httprequest.soptions.consentTitle; }
3039 if (ws.httprequest.soptions.consentMsgFiles != null) { consentMessage = ws.httprequest.soptions.consentMsgFiles.replace(/\{0\}/g, ws.httprequest.realname).replace(/\{1\}/g, ws.httprequest.username); }
3042 if (process.platform == 'win32') {
3043 var enhanced = false;
3044 if (ws.httprequest.oldStyle === false) {
3045 try { require('win-userconsent'); enhanced = true; } catch (ex) { }
3048 var ipr = server_getUserImage(ws.httprequest.userid);
3049 ipr.consentTitle = consentTitle;
3050 ipr.consentMessage = consentMessage;
3051 ipr.consentTimeout = ws.httprequest.consentTimeout;
3052 ipr.consentAutoAccept = ws.httprequest.consentAutoAccept;
3053 ipr.username = ws.httprequest.realname;
3055 ipr.translations = { Allow: currentTranslation['allow'], Deny: currentTranslation['deny'], Auto: currentTranslation['autoAllowForFive'], Caption: consentMessage };
3056 pr = ipr.then(function (img) {
3057 this.consent = require('win-userconsent').create(this.consentTitle, this.consentMessage, this.username, { b64Image: img.split(',').pop(), uid: this.tsid, timeout: this.consentTimeout * 1000, timeoutAutoAccept: this.consentAutoAccept, translations: this.translations, background: color_options.background, foreground: color_options.foreground });
3058 this.__childPromise.close = this.consent.close.bind(this.consent);
3059 return (this.consent);
3062 pr = require('message-box').create(consentTitle, consentMessage, ws.httprequest.consentTimeout, null);
3065 pr = require('message-box').create(consentTitle, consentMessage, ws.httprequest.consentTimeout, null);
3069 ws._consentpromise = pr;
3070 ws.prependOnceListener('end', files_tunnel_endhandler);
3071 pr.then(files_consentpromise_resolved, files_consentpromise_rejected);
3074function files_consentpromise_resolved(always)
3076 if (always && process.platform == 'win32') { server_set_consentTimer(this.ws.httprequest.userid); }
3079 this.ws._consentpromise = null;
3080 MeshServerLogEx(40, null, "Starting remote files after local user accepted (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
3081 this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null }));
3082 if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 4))
3084 // User Notifications is required
3085 var notifyMessage = currentTranslation['fileNotify'].replace(/\{0\}/g, this.ws.httprequest.realname);
3086 var notifyTitle = "MeshCentral";
3087 if (this.ws.httprequest.soptions != null)
3089 if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; }
3090 if (this.ws.httprequest.soptions.notifyMsgFiles != null) { notifyMessage = this.ws.httprequest.soptions.notifyMsgFiles.replace(/\{0\}/g, this.ws.httprequest.realname).replace(/\{1\}/g, this.ws.httprequest.username); }
3092 try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (ex) { }
3097function files_consentpromise_rejected(e)
3100 if(this.ws.httprequest){ // User Consent Denied
3101 if ((this.ws.httprequest.oldStyle === true) && (this.ws.httprequest.consentAutoAccept === true) && (e.toString() != "7")) {
3102 files_consentpromise_resolved.call(this); // oldStyle prompt timed out and User Consent is not required so connect anyway
3105 MeshServerLogEx(41, null, "Failed to start remote files after local user rejected (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
3106 } else { } // Connection was closed server side, maybe log some messages somewhere?
3107 this.ws._consentpromise = null;
3108 this.ws.end(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
3110 } else { } // no websocket, maybe log some messages somewhere?
3112function files_tunnel_endhandler()
3114 if (this._consentpromise && this._consentpromise.close) { this._consentpromise.close(); }
3117function onTunnelData(data)
3119 //sendConsoleText('OnTunnelData, ' + data.length + ', ' + typeof data + ', ' + data);
3121 // If this is upload data, save it to file
3122 if ((this.httprequest.uploadFile) && (typeof data == 'object') && (data[0] != 123)) {
3123 // Save the data to file being uploaded.
3125 // If data starts with zero, skip the first byte. This is used to escape binary file data from JSON.
3126 this.httprequest.uploadFileSize += (data.length - 1);
3127 try { fs.writeSync(this.httprequest.uploadFile, data, 1, data.length - 1); } catch (ex) { sendConsoleText('FileUpload Error'); this.write(Buffer.from(JSON.stringify({ action: 'uploaderror' }))); return; } // Write to the file, if there is a problem, error out.
3129 // If data does not start with zero, save as-is.
3130 this.httprequest.uploadFileSize += data.length;
3131 try { fs.writeSync(this.httprequest.uploadFile, data); } catch (ex) { sendConsoleText('FileUpload Error'); this.write(Buffer.from(JSON.stringify({ action: 'uploaderror' }))); return; } // Write to the file, if there is a problem, error out.
3133 this.write(Buffer.from(JSON.stringify({ action: 'uploadack', reqid: this.httprequest.uploadFileid }))); // Ask for more data.
3137 if (this.httprequest.state == 0) {
3138 // Check if this is a relay connection
3139 if ((data == 'c') || (data == 'cr')) {
3140 this.httprequest.state = 1;
3141 //sendConsoleText("Tunnel #" + this.httprequest.index + " now active", this.httprequest.sessionid);
3145 // Handle tunnel data
3146 if (this.httprequest.protocol == 0) { // 1 = Terminal (admin), 2 = Desktop, 5 = Files, 6 = PowerShell (admin), 7 = Plugin Data Exchange, 8 = Terminal (user), 9 = PowerShell (user), 10 = FileTransfer
3147 // Take a look at the protocol
3148 if ((data.length > 3) && (data[0] == '{')) { onTunnelControlData(data, this); return; }
3149 this.httprequest.protocol = parseInt(data);
3150 if (typeof this.httprequest.protocol != 'number') { this.httprequest.protocol = 0; }
3152 // See if this protocol request is allowed.
3153 if ((this.httprequest.soptions != null) && (this.httprequest.soptions.usages != null) && (this.httprequest.soptions.usages.indexOf(this.httprequest.protocol) == -1)) { this.httprequest.protocol = 0; }
3155 if (this.httprequest.protocol == 10) {
3157 // Basic file transfer
3160 if ((process.platform != 'win32') && (this.httprequest.xoptions.file.startsWith('/') == false)) { this.httprequest.xoptions.file = '/' + this.httprequest.xoptions.file; }
3161 try { stats = require('fs').statSync(this.httprequest.xoptions.file) } catch (ex) { }
3162 try { if (stats) { this.httprequest.downloadFile = fs.createReadStream(this.httprequest.xoptions.file, { flags: 'rbN' }); } } catch (ex) { }
3163 if (this.httprequest.downloadFile) {
3164 MeshServerLogEx(106, [this.httprequest.xoptions.file, stats.size], 'Download: \"' + this.httprequest.xoptions.file + '\", Size: ' + stats.size, this.httprequest);
3165 //sendConsoleText('BasicFileTransfer, ok, ' + this.httprequest.xoptions.file + ', ' + JSON.stringify(stats));
3166 this.write(JSON.stringify({ op: 'ok', size: stats.size }));
3167 this.httprequest.downloadFile.pipe(this);
3168 this.httprequest.downloadFile.end = function () { }
3170 //sendConsoleText('BasicFileTransfer, cancel, ' + this.httprequest.xoptions.file);
3171 this.write(JSON.stringify({ op: 'cancel' }));
3174 else if ((this.httprequest.protocol == 1) || (this.httprequest.protocol == 6) || (this.httprequest.protocol == 8) || (this.httprequest.protocol == 9)) {
3179 // Check user access rights for terminal
3180 if (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) || ((this.httprequest.rights != 0xFFFFFFFF) && ((this.httprequest.rights & MESHRIGHT_NOTERMINAL) != 0)))
3182 // Disengage this tunnel, user does not have the rights to do this!!
3183 this.httprequest.protocol = 999999;
3184 this.httprequest.s.end();
3185 sendConsoleText("Error: No Terminal Control Rights.");
3189 this.descriptorMetadata = "Remote Terminal";
3193 if ((this.httprequest.xoptions != null) && (typeof this.httprequest.xoptions.tsid == 'number')) { tsid = this.httprequest.xoptions.tsid; }
3194 require('MeshAgent')._tsid = tsid;
3197 if (process.platform == 'win32')
3199 if (!require('win-terminal').PowerShellCapable() && (this.httprequest.protocol == 6 || this.httprequest.protocol == 9)) {
3200 this.httprequest.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: 'PowerShell is not supported on this version of windows', msgid: 1 }));
3201 this.httprequest.s.end();
3206 var prom = require('promise');
3207 this.httprequest.tpromise = new prom(promise_init);
3208 this.httprequest.tpromise.that = this;
3209 this.httprequest.tpromise.httprequest = this.httprequest;
3210 this.end = terminal_end;
3212 // Perform User-Consent if needed.
3213 if (this.httprequest.consent && (this.httprequest.consent & 16)) {
3214 // User asked for consent so now we check if we can auto accept if no user is present/loggedin
3215 if (this.httprequest.consentAutoAcceptIfNoUser || this.httprequest.consentAutoAcceptIfTerminalNoUser || this.httprequest.consentAutoAcceptIfLocked || this.httprequest.consentAutoAcceptIfTerminalLocked) {
3216 var p = require('user-sessions').enumerateUsers();
3217 p.sessionid = this.httprequest.sessionid;
3219 p.then(function (u) {
3222 if (u[i].State == 'Active') { v.push({ tsid: i, type: u[i].StationName, user: u[i].Username, domain: u[i].Domain }); }
3224 var autoAccept = false;
3226 // Check if we should auto-accept because no user is present
3227 if ((this.ws.httprequest.consentAutoAcceptIfNoUser || this.ws.httprequest.consentAutoAcceptIfTerminalNoUser) && (v.length == 0)) {
3231 // Check if we should auto-accept because all users are locked
3232 if ((this.ws.httprequest.consentAutoAcceptIfLocked || this.ws.httprequest.consentAutoAcceptIfTerminalLocked) && (v.length > 0)) {
3233 var allUsersLocked = true;
3234 if (!meshCoreObj.lusers || meshCoreObj.lusers.length == 0) {
3235 // No locked users list available, assume users are not locked
3236 allUsersLocked = false;
3239 var username = v[i].domain ? (v[i].domain + '\\' + v[i].user) : v[i].user;
3240 if (meshCoreObj.lusers.indexOf(username) == -1) {
3241 allUsersLocked = false;
3246 if (allUsersLocked) { autoAccept = true; }
3250 this.ws.httprequest.tpromise._res();
3252 // User is present and not all locked, so we still need consent
3253 terminal_consent_ask(this.ws);
3257 terminal_consent_ask(this);
3260 // User-Consent is not required, so just resolve this promise
3261 this.httprequest.tpromise._res();
3263 this.httprequest.tpromise.then(terminal_promise_consent_resolved, terminal_promise_consent_rejected);
3265 else if (this.httprequest.protocol == 2)
3271 // Check user access rights for desktop
3272 if ((((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) && ((this.httprequest.rights & MESHRIGHT_REMOTEVIEW) == 0)) || ((this.httprequest.rights != 0xFFFFFFFF) && ((this.httprequest.rights & MESHRIGHT_NODESKTOP) != 0))) {
3273 // Disengage this tunnel, user does not have the rights to do this!!
3274 this.httprequest.protocol = 999999;
3275 this.httprequest.s.end();
3276 sendConsoleText("Error: No Desktop Control Rights.");
3280 this.descriptorMetadata = "Remote KVM";
3284 if ((this.httprequest.xoptions != null) && (typeof this.httprequest.xoptions.tsid == 'number')) { tsid = this.httprequest.xoptions.tsid; }
3285 require('MeshAgent')._tsid = tsid;
3288 // If MacOS, Wake up device with caffeinate
3289 if(process.platform == 'darwin'){
3292 try { options.uid = require('user-sessions').consoleUid(); } catch (ex) { }
3293 options.type = require('child_process').SpawnTypes.TERM;
3295 var cmdchild = require('child_process').execFile('/usr/bin/caffeinate', ['caffeinate', '-u', '-t', '10'], options);
3296 cmdchild.descriptorMetadata = 'UserCommandsShell';
3297 cmdchild.stdout.on('data', function (c) { replydata += c.toString(); });
3298 cmdchild.stderr.on('data', function (c) { replydata + c.toString(); });
3299 cmdchild.on('exit', function () { delete cmdchild; });
3302 // Remote desktop using native pipes
3303 this.httprequest.desktop = { state: 0, kvm: mesh.getRemoteDesktopStream(tsid), tunnel: this };
3304 this.httprequest.desktop.kvm.parent = this.httprequest.desktop;
3305 this.desktop = this.httprequest.desktop;
3307 // Add ourself to the list of remote desktop sessions
3308 if (this.httprequest.desktop.kvm.tunnels == null) { this.httprequest.desktop.kvm.tunnels = []; }
3309 this.httprequest.desktop.kvm.tunnels.push(this);
3311 // Send a metadata update to all desktop sessions
3313 if (this.httprequest.desktop.kvm.tunnels != null)
3315 for (var i in this.httprequest.desktop.kvm.tunnels)
3318 var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest.desktop.kvm.tunnels[i].httprequest);
3319 if (users[userid] == null) { users[userid] = 1; } else { users[userid]++; }
3320 } catch (ex) { sendConsoleText(ex); }
3322 for (var i in this.httprequest.desktop.kvm.tunnels)
3324 try { this.httprequest.desktop.kvm.tunnels[i].write(JSON.stringify({ ctrlChannel: '102938', type: 'metadata', users: users })); } catch (ex) { }
3326 tunnelUserCount.desktop = users;
3327 try { mesh.SendCommand({ action: 'sessions', type: 'kvm', value: users }); } catch (ex) { }
3328 broadcastSessionsToRegisteredApps();
3331 this.end = tunnel_kvm_end;
3333 if (this.httprequest.desktop.kvm.hasOwnProperty('connectionCount')) {
3334 this.httprequest.desktop.kvm.connectionCount++;
3335 this.httprequest.desktop.kvm.rusers.push(this.httprequest.realname);
3336 this.httprequest.desktop.kvm.users.push(this.httprequest.username);
3337 this.httprequest.desktop.kvm.rusers.sort();
3338 this.httprequest.desktop.kvm.users.sort();
3340 this.httprequest.desktop.kvm.connectionCount = 1;
3341 this.httprequest.desktop.kvm.rusers = [this.httprequest.realname];
3342 this.httprequest.desktop.kvm.users = [this.httprequest.username];
3345 if ((this.httprequest.desktopviewonly != true) && ((this.httprequest.rights == 0xFFFFFFFF) || (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) != 0) && ((this.httprequest.rights & MESHRIGHT_REMOTEVIEW) == 0))))
3347 // If we have remote control rights, pipe the KVM input
3348 this.pipe(this.httprequest.desktop.kvm, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. Pipe the Browser --> KVM input.
3352 // We need to only pipe non-mouse & non-keyboard inputs.
3353 // sendConsoleText('Warning: No Remote Desktop Input Rights.');
3357 // Perform notification if needed. Toast messages may not be supported on all platforms.
3358 if (this.httprequest.consent && (this.httprequest.consent & 8)) {
3360 // User asked for consent but now we check if can auto accept if no user is present
3361 if (this.httprequest.consentAutoAcceptIfNoUser || this.httprequest.consentAutoAcceptIfDesktopNoUser || this.httprequest.consentAutoAcceptIfLocked || this.httprequest.consentAutoAcceptIfDesktopLocked) {
3362 // Get list of users to check if we any actual users logged in, and if users logged in, we still need consent
3363 var p = require('user-sessions').enumerateUsers();
3364 p.sessionid = this.httprequest.sessionid;
3366 p.then(function (u) {
3369 if (u[i].State == 'Active') { v.push({ tsid: i, type: u[i].StationName, user: u[i].Username, domain: u[i].Domain }); }
3371 var autoAccept = false;
3373 // Check if we can auto-accept because no user is present
3374 if ((this.ws.httprequest.consentAutoAcceptIfNoUser || this.ws.httprequest.consentAutoAcceptIfDesktopNoUser) && (v.length == 0)) {
3375 // No user is present, auto accept
3379 // Check if we can auto-accept because all users are locked
3380 if ((this.ws.httprequest.consentAutoAcceptIfLocked || this.ws.httprequest.consentAutoAcceptIfDesktopLocked) && (v.length > 0)) {
3381 var allUsersLocked = true;
3382 if (!meshCoreObj.lusers || meshCoreObj.lusers.length == 0) {
3383 // No locked users list available, assume users are not locked
3384 allUsersLocked = false;
3387 var username = v[i].domain ? (v[i].domain + '\\' + v[i].user) : v[i].user;
3388 if (meshCoreObj.lusers.indexOf(username) == -1) {
3389 allUsersLocked = false;
3394 if (allUsersLocked) { autoAccept = true; }
3398 kvm_consent_ok(this.ws);
3400 // User is present and not all locked, so we still need consent
3401 kvm_consent_ask(this.ws);
3405 // User Consent Prompt is required
3406 kvm_consent_ask(this);
3409 // User Consent Prompt is not required
3410 kvm_consent_ok(this);
3413 this.removeAllListeners('data');
3414 this.on('data', onTunnelControlData);
3415 //this.write('MeshCore KVM Hello!1');
3416 } else if (this.httprequest.protocol == 5) {
3421 // Check user access rights for files
3422 if (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) || ((this.httprequest.rights != 0xFFFFFFFF) && ((this.httprequest.rights & MESHRIGHT_NOFILES) != 0))) {
3423 // Disengage this tunnel, user does not have the rights to do this!!
3424 this.httprequest.protocol = 999999;
3425 this.httprequest.s.end();
3426 sendConsoleText("Error: No files control rights.");
3430 this.descriptorMetadata = "Remote Files";
3434 if ((this.httprequest.xoptions != null) && (typeof this.httprequest.xoptions.tsid == 'number')) { tsid = this.httprequest.xoptions.tsid; }
3435 require('MeshAgent')._tsid = tsid;
3438 // Add the files session to the count to update the server
3439 if (this.httprequest.userid != null) {
3440 var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest);
3441 if (tunnelUserCount.files[userid] == null) { tunnelUserCount.files[userid] = 1; } else { tunnelUserCount.files[userid]++; }
3442 try { mesh.SendCommand({ action: 'sessions', type: 'files', value: tunnelUserCount.files }); } catch (ex) { }
3443 broadcastSessionsToRegisteredApps();
3446 this.end = function ()
3448 // Remove the files session from the count to update the server
3449 if (this.httprequest.userid != null) {
3450 var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest);
3451 if (tunnelUserCount.files[userid] != null) { tunnelUserCount.files[userid]--; if (tunnelUserCount.files[userid] <= 0) { delete tunnelUserCount.files[userid]; } }
3452 try { mesh.SendCommand({ action: 'sessions', type: 'files', value: tunnelUserCount.files }); } catch (ex) { }
3453 broadcastSessionsToRegisteredApps();
3457 // Perform notification if needed. Toast messages may not be supported on all platforms.
3458 if (this.httprequest.consent && (this.httprequest.consent & 32)) {
3459 // User asked for consent so now we check if we can auto accept if no user is present/loggedin
3460 if (this.httprequest.consentAutoAcceptIfNoUser || this.httprequest.consentAutoAcceptIfFileNoUser || this.httprequest.consentAutoAcceptIfLocked || this.httprequest.consentAutoAcceptIfFileLocked) {
3461 var p = require('user-sessions').enumerateUsers();
3462 p.sessionid = this.httprequest.sessionid;
3464 p.then(function (u) {
3467 if (u[i].State == 'Active') { v.push({ tsid: i, type: u[i].StationName, user: u[i].Username, domain: u[i].Domain }); }
3469 var autoAccept = false;
3471 // Check if we should auto-accept because no user is present
3472 if ((this.ws.httprequest.consentAutoAcceptIfNoUser || this.ws.httprequest.consentAutoAcceptIfFileNoUser) && (v.length == 0)) {
3476 // Check if we should auto-accept because all users are locked
3477 if ((this.ws.httprequest.consentAutoAcceptIfLocked || this.ws.httprequest.consentAutoAcceptIfFileLocked) && (v.length > 0)) {
3478 var allUsersLocked = true;
3479 if (!meshCoreObj.lusers || meshCoreObj.lusers.length == 0) {
3480 // No locked users list available, assume users are not locked
3481 allUsersLocked = false;
3484 var username = v[i].domain ? (v[i].domain + '\\' + v[i].user) : v[i].user;
3485 if (meshCoreObj.lusers.indexOf(username) == -1) {
3486 allUsersLocked = false;
3491 if (allUsersLocked) { autoAccept = true; }
3495 // User Consent Prompt is not required
3496 files_consent_ok(this.ws);
3498 // User is present and not all locked, so we still need consent
3499 files_consent_ask(this.ws);
3503 // User Consent Prompt is required
3504 files_consent_ask(this);
3507 // User Consent Prompt is not required
3508 files_consent_ok(this);
3514 } else if (this.httprequest.protocol == 1) {
3515 // Send data into terminal stdin
3516 //this.write(data); // Echo back the keys (Does not seem to be a good idea)
3517 } else if (this.httprequest.protocol == 2) {
3518 // Send data into remote desktop
3519 if (this.httprequest.desktop.state == 0) {
3520 this.write(Buffer.from(String.fromCharCode(0x11, 0xFE, 0x00, 0x00, 0x4D, 0x45, 0x53, 0x48, 0x00, 0x00, 0x00, 0x00, 0x02)));
3521 this.httprequest.desktop.state = 1;
3523 this.httprequest.desktop.write(data);
3525 } else if (this.httprequest.protocol == 5) {
3526 // Process files commands
3528 try { cmd = JSON.parse(data); } catch (ex) { };
3529 if (cmd == null) { return; }
3530 if ((cmd.ctrlChannel == '102938') || ((cmd.type == 'offer') && (cmd.sdp != null))) { onTunnelControlData(cmd, this); return; } // If this is control data, handle it now.
3531 if (cmd.action == undefined) { return; }
3532 //sendConsoleText('CMD: ' + JSON.stringify(cmd));
3534 if ((cmd.path != null) && (process.platform != 'win32') && (cmd.path[0] != '/')) { cmd.path = '/' + cmd.path; } // Add '/' to paths on non-windows
3535 //console.log(objToString(cmd, 0, ' '));
3536 switch (cmd.action) {
3539 // Close the watcher if required
3540 var samepath = ((this.httprequest.watcher != undefined) && (cmd.path == this.httprequest.watcher.path));
3541 if ((this.httprequest.watcher != undefined) && (samepath == false)) {
3542 //console.log('Closing watcher: ' + this.httprequest.watcher.path);
3543 //this.httprequest.watcher.close(); // TODO: This line causes the agent to crash!!!!
3544 delete this.httprequest.watcher;
3548 // Send the folder content to the browser
3549 var response = getDirectoryInfo(cmd.path);
3550 response.reqid = cmd.reqid;
3551 this.write(Buffer.from(JSON.stringify(response)));
3554 // Start the directory watcher
3555 if ((cmd.path != '') && (samepath == false)) {
3556 var watcher = fs.watch(cmd.path, onFileWatcher);
3557 watcher.tunnel = this.httprequest;
3558 watcher.path = cmd.path;
3559 this.httprequest.watcher = watcher;
3560 //console.log('Starting watcher: ' + this.httprequest.watcher.path);
3566 // Create a new empty folder
3567 fs.mkdirSync(cmd.path);
3568 MeshServerLogEx(44, [cmd.path], "Create folder: \"" + cmd.path + "\"", this.httprequest);
3572 // Create a new empty file
3573 fs.closeSync(fs.openSync(cmd.path, 'w'));
3574 MeshServerLogEx(164, [cmd.path], "Create file: \"" + cmd.path + "\"", this.httprequest);
3578 // Delete, possibly recursive delete
3579 for (var i in cmd.delfiles) {
3580 var p = obj.path.join(cmd.path, cmd.delfiles[i]), delcount = 0;
3581 try { delcount = deleteFolderRecursive(p, cmd.rec); } catch (ex) { }
3582 if ((delcount == 1) && !cmd.rec) {
3583 MeshServerLogEx(45, [p], "Delete: \"" + p + "\"", this.httprequest);
3586 MeshServerLogEx(46, [p, delcount], "Delete recursive: \"" + p + "\", " + delcount + " element(s) removed", this.httprequest);
3588 MeshServerLogEx(47, [p, delcount], "Delete: \"" + p + "\", " + delcount + " element(s) removed", this.httprequest);
3595 // Open the local file/folder on the users desktop
3597 MeshServerLogEx(20, [cmd.path], "Opening: " + cmd.path, cmd);
3598 openFileOnDesktop(cmd.path);
3601 case 'markcoredump': {
3602 // If we are asking for the coredump file, set the right path.
3603 var coreDumpPath = null;
3604 if (process.platform == 'win32') {
3605 if (fs.existsSync(process.coreDumpLocation)) { coreDumpPath = process.coreDumpLocation; }
3607 if ((process.cwd() != '//') && fs.existsSync(process.cwd() + 'core')) { coreDumpPath = process.cwd() + 'core'; }
3609 if (coreDumpPath != null) { db.Put('CoreDumpTime', require('fs').statSync(coreDumpPath).mtime); }
3614 // Rename a file or folder
3615 var oldfullpath = obj.path.join(cmd.path, cmd.oldname);
3616 var newfullpath = obj.path.join(cmd.path, cmd.newname);
3617 MeshServerLogEx(48, [oldfullpath, cmd.newname], 'Rename: \"' + oldfullpath + '\" to \"' + cmd.newname + '\"', this.httprequest);
3618 try { fs.renameSync(oldfullpath, newfullpath); } catch (ex) { console.log(ex); }
3624 var r = require('file-search').find('"' + cmd.path + '"', cmd.filter);
3625 if (!r.cancel) { r.cancel = function cancel() { this.child.kill(); }; }
3628 r.socket.reqid = cmd.reqid; // Search request id. This is used to send responses and cancel the request.
3629 r.socket.path = cmd.path; // Search path
3630 r.on('result', function (str) { try { this.socket.write(Buffer.from(JSON.stringify({ action: 'findfile', r: str.substring(this.socket.path.length), reqid: this.socket.reqid }))); } catch (ex) { } });
3631 r.then(function () { try { this.socket.write(Buffer.from(JSON.stringify({ action: 'findfile', r: null, reqid: this.socket.reqid }))); } catch (ex) { } });
3634 case 'cancelfindfile':
3636 if (this._search) { this._search.cancel(); this._search = null; }
3642 var sendNextBlock = 0;
3643 if (cmd.sub == 'start') { // Setup the download
3644 if ((cmd.path == null) && (cmd.ask == 'coredump')) { // If we are asking for the coredump file, set the right path.
3645 if (process.platform == 'win32') {
3646 if (fs.existsSync(process.coreDumpLocation)) { cmd.path = process.coreDumpLocation; }
3648 if ((process.cwd() != '//') && fs.existsSync(process.cwd() + 'core')) { cmd.path = process.cwd() + 'core'; }
3651 MeshServerLogEx((cmd.ask == 'coredump') ? 104 : 49, [cmd.path], 'Download: \"' + cmd.path + '\"', this.httprequest);
3652 if ((cmd.path == null) || (this.filedownload != null)) { this.write({ action: 'download', sub: 'cancel', id: this.filedownload.id }); delete this.filedownload; }
3653 this.filedownload = { id: cmd.id, path: cmd.path, ptr: 0 }
3654 try { this.filedownload.f = fs.openSync(this.filedownload.path, 'rbN'); } catch (ex) { this.write({ action: 'download', sub: 'cancel', id: this.filedownload.id }); delete this.filedownload; }
3655 if (this.filedownload) { this.write({ action: 'download', sub: 'start', id: cmd.id }); }
3656 } else if ((this.filedownload != null) && (cmd.id == this.filedownload.id)) { // Download commands
3657 if (cmd.sub == 'startack') { sendNextBlock = ((typeof cmd.ack == 'number') ? cmd.ack : 8); } else if (cmd.sub == 'stop') { delete this.filedownload; } else if (cmd.sub == 'ack') { sendNextBlock = 1; }
3659 // Send the next download block(s)
3660 if (sendNextBlock > 0) {
3662 var buf = Buffer.alloc(16384);
3663 var len = fs.readSync(this.filedownload.f, buf, 4, 16380, null);
3664 this.filedownload.ptr += len;
3665 if (len < 16380) { buf.writeInt32BE(0x01000001, 0); fs.closeSync(this.filedownload.f); delete this.filedownload; sendNextBlock = 0; } else { buf.writeInt32BE(0x01000000, 0); }
3666 this.write(buf.slice(0, len + 4)); // Write as binary
3672 // Upload a file, browser to agent
3673 if (this.httprequest.uploadFile != null) { fs.closeSync(this.httprequest.uploadFile); delete this.httprequest.uploadFile; }
3674 if (cmd.path == undefined) break;
3675 var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path;
3676 this.httprequest.uploadFilePath = filepath;
3677 this.httprequest.uploadFileSize = 0;
3678 try { this.httprequest.uploadFile = fs.openSync(filepath, cmd.append ? 'abN' : 'wbN'); } catch (ex) { this.write(Buffer.from(JSON.stringify({ action: 'uploaderror', reqid: cmd.reqid }))); break; }
3679 this.httprequest.uploadFileid = cmd.reqid;
3680 if (this.httprequest.uploadFile) { this.write(Buffer.from(JSON.stringify({ action: 'uploadstart', reqid: this.httprequest.uploadFileid }))); }
3685 // Indicates that an upload is done
3686 if (this.httprequest.uploadFile) {
3687 MeshServerLogEx(105, [this.httprequest.uploadFilePath, this.httprequest.uploadFileSize], 'Upload: \"' + this.httprequest.uploadFilePath + '\", Size: ' + this.httprequest.uploadFileSize, this.httprequest);
3688 fs.closeSync(this.httprequest.uploadFile);
3689 this.write(Buffer.from(JSON.stringify({ action: 'uploaddone', reqid: this.httprequest.uploadFileid }))); // Indicate that we closed the file.
3690 delete this.httprequest.uploadFile;
3691 delete this.httprequest.uploadFileid;
3692 delete this.httprequest.uploadFilePath;
3693 delete this.httprequest.uploadFileSize;
3697 case 'uploadcancel':
3699 // Indicates that an upload is canceled
3700 if (this.httprequest.uploadFile) {
3701 fs.closeSync(this.httprequest.uploadFile);
3702 fs.unlinkSync(this.httprequest.uploadFilePath);
3703 this.write(Buffer.from(JSON.stringify({ action: 'uploadcancel', reqid: this.httprequest.uploadFileid }))); // Indicate that we closed the file.
3704 delete this.httprequest.uploadFile;
3705 delete this.httprequest.uploadFileid;
3706 delete this.httprequest.uploadFilePath;
3707 delete this.httprequest.uploadFileSize;
3714 var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path;
3716 try { h = getSHA384FileHash(filepath); } catch (ex) { sendConsoleText(ex); }
3717 this.write(Buffer.from(JSON.stringify({ action: 'uploadhash', reqid: cmd.reqid, path: cmd.path, name: cmd.name, tag: cmd.tag, hash: (h ? h.toString('hex') : null) })));
3722 // Copy a bunch of files from scpath to dspath
3723 for (var i in cmd.names) {
3724 var sc = obj.path.join(cmd.scpath, cmd.names[i]), ds = obj.path.join(cmd.dspath, cmd.names[i]);
3725 MeshServerLogEx(51, [sc, ds], 'Copy: \"' + sc + '\" to \"' + ds + '\"', this.httprequest);
3726 if (sc != ds) { try { fs.copyFileSync(sc, ds); } catch (ex) { } }
3732 // Move a bunch of files from scpath to dspath
3733 for (var i in cmd.names) {
3734 var sc = obj.path.join(cmd.scpath, cmd.names[i]), ds = obj.path.join(cmd.dspath, cmd.names[i]);
3735 MeshServerLogEx(52, [sc, ds], 'Move: \"' + sc + '\" to \"' + ds + '\"', this.httprequest);
3736 if (sc != ds) { try { fs.copyFileSync(sc, ds); fs.unlinkSync(sc); } catch (ex) { } }
3741 // Zip a bunch of files
3742 if (this.zip != null) return; // Zip operating is currently running, exit now.
3744 // Check that the specified files exist & build full paths
3745 var fp, stat, p = [];
3746 for (var i in cmd.files) { fp = cmd.path + '/' + cmd.files[i]; stat = null; try { stat = fs.statSync(fp); } catch (ex) { } if (stat != null) { p.push(fp); } }
3747 if (p.length == 0) return; // No files, quit now.
3749 // Setup file compression
3750 var ofile = cmd.path + '/' + cmd.output;
3751 this.write(Buffer.from(JSON.stringify({ action: 'dialogmessage', msg: 'zipping' })));
3752 this.zipfile = ofile;
3753 delete this.zipcancel;
3754 var out = require('fs').createWriteStream(ofile, { flags: 'wb' });
3756 out.on('close', function () {
3757 this.xws.write(Buffer.from(JSON.stringify({ action: 'dialogmessage', msg: null })));
3758 this.xws.write(Buffer.from(JSON.stringify({ action: 'refresh' })));
3759 if (this.xws.zipcancel === true) { fs.unlinkSync(this.xws.zipfile); } // Delete the complete file.
3760 delete this.xws.zipcancel;
3761 delete this.xws.zipfile;
3762 delete this.xws.zip;
3764 this.zip = require('zip-writer').write({ files: p, basePath: cmd.path });
3765 this.zip.xws = this;
3766 this.zip.on('progress', require('events').moderated(function (name, p) { this.xws.write(Buffer.from(JSON.stringify({ action: 'dialogmessage', msg: 'zippingFile', file: ((process.platform == 'win32') ? (name.split('/').join('\\')) : name), progress: p }))); }, 1000));
3770 if (this.unzip != null) return; // Unzip operating is currently running, exit now.
3771 this.unzip = require('zip-reader').read(cmd.input);
3772 this.unzip._dest = cmd.dest;
3773 this.unzip.xws = this;
3774 this.unzip.then(function (zipped) {
3775 this.xws.write(Buffer.from(JSON.stringify({ action: 'dialogmessage', msg: 'unzipping' })));
3776 zipped.xws = this.xws;
3777 zipped.extractAll(this._dest).then(function () { // finished extracting
3778 zipped.xws.write(Buffer.from(JSON.stringify({ action: 'dialogmessage', msg: null })));
3779 zipped.xws.write(Buffer.from(JSON.stringify({ action: 'refresh' })));
3780 delete zipped.xws.unzip;
3781 }, function (e) { // error extracting
3782 zipped.xws.write(Buffer.from(JSON.stringify({ action: 'dialogmessage', msg: 'unziperror', error: e })));
3783 delete zipped.xws.unzip;
3785 }, function (e) { this.xws.write(Buffer.from(JSON.stringify({ action: 'dialogmessage', msg: 'unziperror', error: e }))); delete this.xws.unzip });
3788 // Cancel zip operation if present
3789 try { this.zipcancel = true; this.zip.cancel(function () { }); } catch (ex) { }
3793 // Unknown action, ignore it.
3796 } else if (this.httprequest.protocol == 7) { // Plugin data exchange
3798 try { cmd = JSON.parse(data); } catch (ex) { };
3799 if (cmd == null) { return; }
3800 if ((cmd.ctrlChannel == '102938') || ((cmd.type == 'offer') && (cmd.sdp != null))) { onTunnelControlData(cmd, this); return; } // If this is control data, handle it now.
3801 if (cmd.action == undefined) return;
3803 switch (cmd.action) {
3805 try { require(cmd.plugin).consoleaction(cmd, null, null, this); } catch (ex) { throw ex; }
3809 // probably shouldn't happen, but just in case this feature is expanded
3814 //sendConsoleText("Got tunnel #" + this.httprequest.index + " data: " + data, this.httprequest.sessionid);
3818// Delete a directory with a files and directories within it
3819function deleteFolderRecursive(path, rec) {
3821 if (fs.existsSync(path)) {
3823 fs.readdirSync(obj.path.join(path, '*')).forEach(function (file, index) {
3824 var curPath = obj.path.join(path, file);
3825 if (fs.statSync(curPath).isDirectory()) { // recurse
3826 count += deleteFolderRecursive(curPath, true);
3827 } else { // delete file
3828 fs.unlinkSync(curPath);
3833 fs.unlinkSync(path);
3839// Called when receiving control data on WebRTC
3840function onTunnelWebRTCControlData(data) {
3841 if (typeof data != 'string') return;
3843 try { obj = JSON.parse(data); } catch (ex) { sendConsoleText('Invalid control JSON on WebRTC: ' + data); return; }
3844 if (obj.type == 'close') {
3845 //sendConsoleText('Tunnel #' + this.xrtc.websocket.tunnel.index + ' WebRTC control close');
3846 try { this.close(); } catch (ex) { }
3847 try { this.xrtc.close(); } catch (ex) { }
3851function tunnel_webrtc_onEnd()
3853 // The WebRTC channel closed, unpipe the KVM now. This is also done when the web socket closes.
3854 //sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC data channel closed');
3855 if (this.websocket.desktop && this.websocket.desktop.kvm)
3859 this.unpipe(this.websocket.desktop.kvm);
3860 this.websocket.httprequest.desktop.kvm.unpipe(this);
3863 this.httprequest = null;
3864 this.websocket = null;
3866function tunnel_webrtc_DataChannel_OnFinalized()
3868 console.info1('WebRTC DataChannel Finalized');
3870function tunnel_webrtc_OnDataChannel(rtcchannel)
3872 //sendConsoleText('WebRTC Datachannel open, protocol: ' + this.websocket.httprequest.protocol);
3873 //rtcchannel.maxFragmentSize = 32768;
3874 rtcchannel.xrtc = this;
3875 rtcchannel.websocket = this.websocket;
3876 this.rtcchannel = rtcchannel;
3877 this.rtcchannel.once('~', tunnel_webrtc_DataChannel_OnFinalized);
3878 this.websocket.rtcchannel = rtcchannel;
3879 this.websocket.rtcchannel.on('data', onTunnelWebRTCControlData);
3880 this.websocket.rtcchannel.on('end', tunnel_webrtc_onEnd);
3881 this.websocket.write('{\"ctrlChannel\":\"102938\",\"type\":\"webrtc0\"}'); // Indicate we are ready for WebRTC switch-over.
3884function tunnel_webrtc_OnFinalized()
3886 console.info1('WebRTC Connection Finalized');
3889// Called when receiving control data on websocket
3890function onTunnelControlData(data, ws) {
3892 if (ws == null) { ws = this; }
3893 if (typeof data == 'string') { try { obj = JSON.parse(data); } catch (ex) { sendConsoleText('Invalid control JSON: ' + data); return; } }
3894 else if (typeof data == 'object') { obj = data; } else { return; }
3895 //sendConsoleText('onTunnelControlData(' + ws.httprequest.protocol + '): ' + JSON.stringify(data));
3896 //console.log('onTunnelControlData: ' + JSON.stringify(data));
3902 if ((ws.httprequest.xoptions != null) && (typeof ws.httprequest.xoptions.tsid == 'number')) { tsid = ws.httprequest.xoptions.tsid; }
3904 // Lock the current user out of the desktop
3905 MeshServerLogEx(53, null, "Locking remote user out of desktop", ws.httprequest);
3910 // Set the session to auto lock on disconnect
3911 if (obj.value === true) {
3912 ws.httprequest.autolock = true;
3913 if (ws.httprequest.unlockerHelper == null) {
3914 destopLockHelper_pipe(ws.httprequest);
3918 delete ws.httprequest.autolock;
3923 // These are additional connection options passed in the control channel.
3924 //sendConsoleText('options: ' + JSON.stringify(obj));
3926 ws.httprequest.xoptions = obj;
3928 // Set additional user consent options if present
3929 if ((obj != null) && (typeof obj.consent == 'number')) { ws.httprequest.consent |= obj.consent; }
3932 if ((obj != null) && (obj.autolock === true)) {
3933 ws.httprequest.autolock = true;
3934 if (ws.httprequest.unlockerHelper == null) {
3935 destopLockHelper_pipe(ws.httprequest);
3942 // We received the close on the websocket
3943 //sendConsoleText('Tunnel #' + ws.tunnel.index + ' WebSocket control close');
3944 try { ws.close(); } catch (ex) { }
3948 // Indicates a change in terminal size
3949 if (process.platform == 'win32') {
3950 if (ws.httprequest._dispatcher == null) return;
3951 //sendConsoleText('Win32-TermSize: ' + obj.cols + 'x' + obj.rows);
3952 if (ws.httprequest._dispatcher.invoke) { ws.httprequest._dispatcher.invoke('resizeTerminal', [obj.cols, obj.rows]); }
3954 if (ws.httprequest.process == null || ws.httprequest.process.pty == 0) return;
3955 //sendConsoleText('Linux Resize: ' + obj.cols + 'x' + obj.rows);
3957 if (ws.httprequest.process.tcsetsize) { ws.httprequest.process.tcsetsize(obj.rows, obj.cols); }
3961 case 'webrtc0': { // Browser indicates we can start WebRTC switch-over.
3962 if (ws.httprequest.protocol == 1)
3964 // This is a terminal data stream, unpipe the terminal now and indicate to the other side that terminal data will no longer be received over WebSocket
3965 if (process.platform == 'win32') {
3966 ws.httprequest._term.unpipe(ws);
3968 ws.httprequest.process.stdout.unpipe(ws);
3969 ws.httprequest.process.stderr.unpipe(ws);
3971 } else if (ws.httprequest.protocol == 2) { // Desktop
3972 // This is a KVM data stream, unpipe the KVM now and indicate to the other side that KVM data will no longer be received over WebSocket
3973 ws.httprequest.desktop.kvm.unpipe(ws);
3976 // Switch things around so all WebRTC data goes to onTunnelData().
3977 ws.rtcchannel.httprequest = ws.httprequest;
3978 ws.rtcchannel.removeAllListeners('data');
3979 ws.rtcchannel.on('data', onTunnelData);
3981 ws.write("{\"ctrlChannel\":\"102938\",\"type\":\"webrtc1\"}"); // End of data marker
3986 if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6))
3988 // Switch the user input from websocket to webrtc at this point.
3989 if (process.platform == 'win32') {
3990 ws.unpipe(ws.httprequest._term);
3991 ws.rtcchannel.pipe(ws.httprequest._term, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
3993 ws.unpipe(ws.httprequest.process.stdin);
3994 ws.rtcchannel.pipe(ws.httprequest.process.stdin, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
3996 ws.resume(); // Resume the websocket to keep receiving control data
3998 else if (ws.httprequest.protocol == 2)
4000 // Switch the user input from websocket to webrtc at this point.
4001 ws.unpipe(ws.httprequest.desktop.kvm);
4002 if ((ws.httprequest.desktopviewonly != true) && ((ws.httprequest.rights == 0xFFFFFFFF) || (((ws.httprequest.rights & MESHRIGHT_REMOTECONTROL) != 0) && ((ws.httprequest.rights & MESHRIGHT_REMOTEVIEW) == 0)))) {
4003 // If we have remote control rights, pipe the KVM input
4004 try { ws.webrtc.rtcchannel.pipe(ws.httprequest.desktop.kvm, { dataTypeSkip: 1, end: false }); } catch (ex) { sendConsoleText('EX2'); } // 0 = Binary, 1 = Text.
4006 // We need to only pipe non-mouse & non-keyboard inputs.
4007 // sendConsoleText('Warning: No Remote Desktop Input Rights.');
4010 ws.resume(); // Resume the websocket to keep receiving control data
4012 ws.write('{\"ctrlChannel\":\"102938\",\"type\":\"webrtc2\"}'); // Indicates we will no longer get any data on websocket, switching to WebRTC at this point.
4016 // Other side received websocket end of data marker, start sending data on WebRTC channel
4017 if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) { // Terminal
4018 if (process.platform == 'win32') {
4019 ws.httprequest._term.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
4021 ws.httprequest.process.stdout.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
4022 ws.httprequest.process.stderr.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
4024 } else if (ws.httprequest.protocol == 2) { // Desktop
4025 ws.httprequest.desktop.kvm.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
4030 // This is a WebRTC offer.
4031 if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) return; // TODO: Terminal is currently broken with WebRTC. Reject WebRTC upgrade for now.
4032 ws.webrtc = rtc.createConnection();
4033 ws.webrtc.once('~', tunnel_webrtc_OnFinalized);
4034 ws.webrtc.websocket = ws;
4035 //ws.webrtc.on('connected', function () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC connected');*/ });
4036 //ws.webrtc.on('disconnected', function () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC disconnected');*/ });
4037 ws.webrtc.on('dataChannel', tunnel_webrtc_OnDataChannel);
4040 try { sdp = ws.webrtc.setOffer(obj.sdp); } catch (ex) { }
4041 if (sdp != null) { ws.write({ type: 'answer', ctrlChannel: '102938', sdp: sdp }); }
4045 ws.write("{\"ctrlChannel\":\"102938\",\"type\":\"pong\"}"); // Send pong response
4048 case 'pong': { // NOP
4052 ws.write({ type: 'rtt', ctrlChannel: '102938', time: obj.time });
4059var consoleWebSockets = {};
4060var consoleHttpRequest = null;
4062// Console HTTP response
4063function consoleHttpResponse(response) {
4064 response.data = function (data) { sendConsoleText(rstr2hex(buf2rstr(data)), this.sessionid); consoleHttpRequest = null; }
4065 response.close = function () { sendConsoleText('httprequest.response.close', this.sessionid); consoleHttpRequest = null; }
4068// Open a local file on current user's desktop
4069function openFileOnDesktop(file) {
4072 switch (process.platform) {
4074 var uid = require('user-sessions').consoleUid();
4075 var user = require('user-sessions').getUsername(uid);
4076 var domain = require('user-sessions').getDomain(uid);
4077 var task = { name: 'MeshChatTask', user: user, domain: domain, execPath: (require('fs').statSync(file).isDirectory() ? process.env['windir'] + '\\explorer.exe' : file) };
4078 if (require('fs').statSync(file).isDirectory()) task.arguments = [file];
4080 require('win-tasks').addTask(task);
4081 require('win-tasks').getTask({ name: 'MeshChatTask' }).run();
4082 require('win-tasks').deleteTask('MeshChatTask');
4086 var taskoptions = { env: { _target: (require('fs').statSync(file).isDirectory() ? process.env['windir'] + '\\explorer.exe' : file), _user: '"' + domain + '\\' + user + '"' }, _args: "" };
4087 if (require('fs').statSync(file).isDirectory()) taskoptions.env._args = file;
4088 for (var c1e in process.env) {
4089 taskoptions.env[c1e] = process.env[c1e];
4091 var child = require('child_process').execFile(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', ['powershell', '-noprofile', '-nologo', '-command', '-'], taskoptions);
4092 child.stderr.on('data', function (c) { });
4093 child.stdout.on('data', function (c) { });
4094 child.stdin.write('SCHTASKS /CREATE /F /TN MeshChatTask /SC ONCE /ST 00:00 ');
4095 if (user) { child.stdin.write('/RU $env:_user '); }
4096 child.stdin.write('/TR "$env:_target $env:_args"\r\n');
4097 child.stdin.write('$ts = New-Object -ComObject Schedule.service\r\n');
4098 child.stdin.write('$ts.connect()\r\n');
4099 child.stdin.write('$tsfolder = $ts.getfolder("\\")\r\n');
4100 child.stdin.write('$task = $tsfolder.GetTask("MeshChatTask")\r\n');
4101 child.stdin.write('$taskdef = $task.Definition\r\n');
4102 child.stdin.write('$taskdef.Settings.StopIfGoingOnBatteries = $false\r\n');
4103 child.stdin.write('$taskdef.Settings.DisallowStartIfOnBatteries = $false\r\n');
4104 child.stdin.write('$taskdef.Actions.Item(1).Path = $env:_target\r\n');
4105 child.stdin.write('$taskdef.Actions.Item(1).Arguments = $env:_args\r\n');
4106 child.stdin.write('$tsfolder.RegisterTaskDefinition($task.Name, $taskdef, 4, $null, $null, $null)\r\n');
4107 child.stdin.write('SCHTASKS /RUN /TN MeshChatTask\r\n');
4108 child.stdin.write('SCHTASKS /DELETE /F /TN MeshChatTask\r\nexit\r\n');
4113 child = require('child_process').execFile('/usr/bin/xdg-open', ['xdg-open', file], { uid: require('user-sessions').consoleUid() });
4116 child = require('child_process').execFile('/usr/bin/open', ['open', file]);
4119 // Unknown platform, ignore this command.
4126// Open a web browser to a specified URL on current user's desktop
4127function openUserDesktopUrl(url) {
4128 if ((url.toLowerCase().startsWith('http://') == false) && (url.toLowerCase().startsWith('https://') == false)) { return null; }
4131 switch (process.platform) {
4133 var uid = require('user-sessions').consoleUid();
4134 var user = require('user-sessions').getUsername(uid);
4135 var domain = require('user-sessions').getDomain(uid);
4136 var task = { name: 'MeshChatTask', user: user, domain: domain, execPath: process.env['windir'] + '\\system32\\cmd.exe', arguments: ['/C START ' + url.split('&').join('^&')] };
4139 require('win-tasks').addTask(task);
4140 require('win-tasks').getTask({ name: 'MeshChatTask' }).run();
4141 require('win-tasks').deleteTask('MeshChatTask');
4145 var taskoptions = { env: { _target: process.env['windir'] + '\\system32\\cmd.exe', _args: '/C START ' + url.split('&').join('^&'), _user: '"' + domain + '\\' + user + '"' } };
4146 for (var c1e in process.env) {
4147 taskoptions.env[c1e] = process.env[c1e];
4149 var child = require('child_process').execFile(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', ['powershell', '-noprofile', '-nologo', '-command', '-'], taskoptions);
4150 child.stderr.on('data', function (c) { });
4151 child.stdout.on('data', function (c) { });
4152 child.stdin.write('SCHTASKS /CREATE /F /TN MeshChatTask /SC ONCE /ST 00:00 ');
4153 if (user) { child.stdin.write('/RU $env:_user '); }
4154 child.stdin.write('/TR "$env:_target $env:_args"\r\n');
4155 child.stdin.write('$ts = New-Object -ComObject Schedule.service\r\n');
4156 child.stdin.write('$ts.connect()\r\n');
4157 child.stdin.write('$tsfolder = $ts.getfolder("\\")\r\n');
4158 child.stdin.write('$task = $tsfolder.GetTask("MeshChatTask")\r\n');
4159 child.stdin.write('$taskdef = $task.Definition\r\n');
4160 child.stdin.write('$taskdef.Settings.StopIfGoingOnBatteries = $false\r\n');
4161 child.stdin.write('$taskdef.Settings.DisallowStartIfOnBatteries = $false\r\n');
4162 child.stdin.write('$taskdef.Actions.Item(1).Path = $env:_target\r\n');
4163 child.stdin.write('$taskdef.Actions.Item(1).Arguments = $env:_args\r\n');
4164 child.stdin.write('$tsfolder.RegisterTaskDefinition($task.Name, $taskdef, 4, $null, $null, $null)\r\n');
4166 child.stdin.write('SCHTASKS /RUN /TN MeshChatTask\r\n');
4167 child.stdin.write('SCHTASKS /DELETE /F /TN MeshChatTask\r\nexit\r\n');
4172 child = require('child_process').execFile('/usr/bin/xdg-open', ['xdg-open', url], { uid: require('user-sessions').consoleUid() });
4175 child = require('child_process').execFile('/usr/bin/open', ['open', url], { uid: require('user-sessions').consoleUid() });
4178 // Unknown platform, ignore this command.
4185// Process a mesh agent console command
4186function processConsoleCommand(cmd, args, rights, sessionid) {
4188 var response = null;
4190 case 'help': { // Displays available commands
4191 var fin = '', f = '', availcommands = 'domain,translations,agentupdate,errorlog,msh,timerinfo,coreinfo,coreinfoupdate,coredump,service,fdsnapshot,fdcount,startupoptions,';
4192 availcommands += 'alert,agentsize,versions,help,info,osinfo,args,print,type,dbkeys,dbget,dbset,dbcompact,eval,parseuri,httpget,wslist,plugin,wsconnect,wssend,wsclose,notify,';
4193 availcommands += 'ls,ps,kill,netinfo,location,power,wakeonlan,setdebug,smbios,rawsmbios,toast,lock,users,openurl,getscript,getclip,setclip,log,cpuinfo,sysinfo';
4194 availcommands += 'apf,scanwifi,wallpaper,agentmsg,task,uninstallagent,display,openfile';
4195 if (require('os').dns != null) { availcommands += ',dnsinfo'; }
4196 try { require('linux-dhcp'); availcommands += ',dhcp'; } catch (ex) { }
4197 if (process.platform == 'win32') {
4198 availcommands += ',bitlocker,cs,wpfhwacceleration,uac,volumes,rdpport,deskbackground,domaininfo';
4199 if (bcdOK()) { availcommands += ',safemode'; }
4200 if (require('notifybar-desktop').DefaultPinned != null) { availcommands += ',privacybar'; }
4201 try { require('win-utils'); availcommands += ',taskbar'; } catch (ex) { }
4202 try { require('win-info'); availcommands += ',installedapps,qfe,defender,av,installedstoreapps'; } catch (ex) { }
4204 if (amt != null) { availcommands += ',amt,amtconfig,amtevents'; }
4205 if (process.platform != 'freebsd') { availcommands += ',vm'; }
4206 if (require('MeshAgent').maxKvmTileSize != null) { availcommands += ',kvmmode'; }
4207 try { require('zip-reader'); availcommands += ',zip,unzip'; } catch (ex) { }
4209 availcommands = availcommands.split(',').sort();
4210 while (availcommands.length > 0) {
4211 if (f.length > 90) { fin += (f + ',\r\n'); f = ''; }
4212 f += (((f != '') ? ', ' : ' ') + availcommands.shift());
4214 if (f != '') { fin += f; }
4215 response = "Available commands: \r\n" + fin + ".";
4219 try { require('win-deskutils'); } catch (ex) { response = 'Unknown command "mousetrails", type "help" for list of available commands.'; break; }
4220 var id = require('user-sessions').getProcessOwnerName(process.pid).tsid == 0 ? 1 : null;
4221 switch (args['_'].length)
4224 var trails = require('win-deskutils').mouse.getTrails(id);
4225 response = trails == 0 ? 'MouseTrails Disabled' : ('MouseTrails enabled (' + trails + ')');
4226 response += '\nTo change setting, specify a positive integer, where 0 is disable: mousetrails [n]';
4229 var trails = parseInt(args['_'][0]);
4230 require('win-deskutils').mouse.setTrails(trails, id);
4231 trails = require('win-deskutils').mouse.getTrails(id);
4232 response = trails == 0 ? 'MouseTrails Disabled' : ('MouseTrails enabled (' + trails + ')');
4235 response = 'Proper usage: mousetrails [n]';
4239 case 'deskbackground':
4240 try { require('win-deskutils'); } catch (ex) { response = 'Unknown command "deskbackground", type "help" for list of available commands.'; break; }
4241 var id = require('user-sessions').getProcessOwnerName(process.pid).tsid == 0 ? 1 : null;
4242 switch (args['_'].length)
4245 response = 'Desktop Background: ' + require('win-deskutils').background.get(id);
4248 require('win-deskutils').background.set(args['_'][0], id);
4249 response = 'Desktop Background: ' + require('win-deskutils').background.get(id);
4252 response = 'Proper usage: deskbackground [path]';
4257 try { require('win-utils'); } catch (ex) { response = 'Unknown command "taskbar", type "help" for list of available commands.'; break; }
4258 switch (args['_'].length) {
4262 var tsid = parseInt(args['_'][1]);
4263 if (isNaN(tsid)) { tsid = require('user-sessions').consoleUid(); }
4264 sendConsoleText('Changing TaskBar AutoHide status. Please wait...', sessionid);
4266 var result = require('win-utils').taskBar.autoHide(tsid, args['_'][0].toLowerCase() == 'hide');
4267 response = 'Current Status of TaskBar AutoHide: ' + result;
4268 } catch (ex) { response = 'Unable to change TaskBar settings'; }
4273 response = 'Proper usage: taskbar HIDE|SHOW [TSID]';
4279 if (process.platform != 'win32' || require('notifybar-desktop').DefaultPinned == null) {
4280 response = 'Unknown command "privacybar", type "help" for list of available commands.';
4283 switch (args['_'].length) {
4286 response = "Current Default Pinned State: " + (require('notifybar-desktop').DefaultPinned ? "PINNED" : "UNPINNED") + '\r\n';
4287 response += "To set default pinned state:\r\n privacybar [PINNED|UNPINNED]\r\n";
4290 switch (args['_'][0].toUpperCase()) {
4292 require('notifybar-desktop').DefaultPinned = true;
4293 response = "privacybar default pinned state is: PINNED";
4296 require('notifybar-desktop').DefaultPinned = false;
4297 response = "privacybar default pinned state is: UNPINNED";
4300 response = "INVALID parameter: " + args['_'][0].toUpperCase();
4308 response = getDomainInfo();
4312 if (process.platform != 'win32') {
4313 response = 'Unknown command "domaininfo", type "help" for list of available commands.';
4316 if (global._domainQuery != null) {
4317 response = "There is already an outstanding Domain Controller Query... Please try again later...";
4321 sendConsoleText('Querying Domain Controller... This can take up to 60 seconds. Please wait...', sessionid);
4322 global._domainQuery = require('win-wmi').queryAsync('ROOT\\CIMV2', 'SELECT * FROM Win32_NTDomain');
4323 global._domainQuery.session = sessionid;
4324 global._domainQuery.then(function (v) {
4326 if (Array.isArray(v)) {
4329 for (i = 0; i < v.length; ++i) {
4331 if (v[i].DomainControllerAddress != null) { r.DomainControllerAddress = v[i].DomainControllerAddress.split('\\').pop(); }
4332 if (r.DomainControllerName != null) { r.DomainControllerName = v[i].DomainControllerName.split('\\').pop(); }
4333 r.DomainGuid = v[i].DomainGuid;
4334 r.DomainName = v[i].DomainName;
4335 if (r.DomainGuid != null) {
4340 if (results.length > 0) {
4341 sendConsoleText('Domain Controller Results:', this.session);
4342 sendConsoleText(JSON.stringify(results, null, 1), this.session);
4343 sendConsoleText('End of results...', this.session);
4346 sendConsoleText('Domain Controller: No results returned. Is the domain controller reachable?', this.session);
4348 global._domainQuery = null;
4352 case 'translations': {
4353 response = JSON.stringify(coretranslations, null, 2);
4357 response = JSON.stringify(require('win-volumes').getVolumes(), null, 1);
4360 if (process.platform == 'win32') {
4361 if (require('win-volumes').volumes_promise != null) {
4362 var p = require('win-volumes').volumes_promise();
4363 p.then(function (res) { sendConsoleText(JSON.stringify(cleanGetBitLockerVolumeInfo(res), null, 1), this.session); });
4367 case 'dhcp': // This command is only supported on Linux, this is because Linux does not give us the DNS suffix for each network adapter independently so we have to ask the DHCP server.
4369 try { require('linux-dhcp'); } catch (ex) { response = 'Unknown command "dhcp", type "help" for list of available commands.'; break; }
4370 if (args['_'].length == 0) {
4371 var j = require('os').networkInterfaces();
4374 for (var z in j[i]) {
4375 if (j[i][z].status == 'up' && j[i][z].type != 'loopback' && j[i][z].address != null) {
4376 ifcs.push('"' + i + '"');
4381 response = 'Proper usage: dhcp [' + ifcs.join(' | ') + ']';
4384 require('linux-dhcp').client.info(args['_'][0]).
4386 sendConsoleText(JSON.stringify(d, null, 1), sessionid);
4389 sendConsoleText(e, sessionid);
4395 if (process.platform != 'win32') {
4396 response = 'Unknown command "cs", type "help" for list of available commands.';
4399 switch (args['_'].length) {
4402 var cs = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'System\\CurrentControlSet\\Control\\Power', 'CsEnabled');
4403 response = "Connected Standby: " + (cs == 1 ? "ENABLED" : "DISABLED");
4405 response = "This machine does not support Connected Standby";
4409 if ((args['_'][0].toUpperCase() != 'ENABLE' && args['_'][0].toUpperCase() != 'DISABLE')) {
4410 response = "Proper usage:\r\n cs [ENABLE|DISABLE]";
4414 var cs = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'System\\CurrentControlSet\\Control\\Power', 'CsEnabled');
4415 require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'System\\CurrentControlSet\\Control\\Power', 'CsEnabled', args['_'][0].toUpperCase() == 'ENABLE' ? 1 : 0);
4417 cs = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'System\\CurrentControlSet\\Control\\Power', 'CsEnabled');
4418 response = "Connected Standby: " + (cs == 1 ? "ENABLED" : "DISABLED");
4420 response = "This machine does not support Connected Standby";
4425 response = "Proper usage:\r\n cs [ENABLE|DISABLE]";
4430 if (process.platform == 'win32') {
4431 // Install MeshCentral Assistant on this device
4432 response = "Usage: Assistant [info|install|uninstall]";
4433 if (args['_'].length == 1) {
4434 if ((args['_'][0] == 'install') || (args['_'][0] == 'info')) { response = ''; require('MeshAgent').SendCommand({ action: 'meshToolInfo', sessionid: sessionid, name: 'MeshCentralAssistant', cookie: true, tag: args['_'][0] }); }
4438 response = "MeshCentral Assistant is not supported on this platform.";
4442 require('MeshAgent').SendCommand({ action: 'getUserImage', sessionid: sessionid, userid: args['_'][0], tag: 'info' });
4446 require('MeshAgent').SendCommand({ action: 'agentupdate', sessionid: sessionid });
4448 case 'agentupdateex':
4449 // Perform an direct agent update without requesting any information from the server, this should not typically be used.
4450 if (args['_'].length == 1) {
4451 if (args['_'][0].startsWith('https://')) { agentUpdate_Start(args['_'][0], { sessionid: sessionid }); } else { response = "Usage: agentupdateex https://server/path"; }
4453 agentUpdate_Start(null, { sessionid: sessionid });
4457 switch (args['_'].length) {
4460 response = JSON.stringify(require('util-agentlog').read(), null, 1);
4463 // Error Logs, by either count or timestamp
4464 response = JSON.stringify(require('util-agentlog').read(parseInt(args['_'][0])), null, 1);
4467 response = "Proper usage:\r\n errorlog [lastCount|linuxEpoch]";
4472 if (args['_'].length == 0) {
4473 response = JSON.stringify(_MSH(), null, 2);
4474 } else if (args['_'].length > 3) {
4475 response = 'Proper usage: msh [get|set|delete]\r\nmsh get MeshServer\r\nmsh set abc "xyz"\r\nmsh delete abc';
4477 var mshFileName = process.execPath.replace('.exe','') + '.msh';
4478 switch (args['_'][0].toLocaleLowerCase()) {
4480 if (typeof args['_'][1] != 'string' || args['_'].length > 2) {
4481 response = 'Proper usage: msh get MeshServer';
4482 } else if(_MSH()[args['_'][1]]) {
4483 response = _MSH()[args['_'][1]];
4485 response = "Unknown Value: " + args['_'][1];
4489 if (typeof args['_'][1] != 'string' || typeof args['_'][2] != 'string') {
4490 response = 'Proper usage: msh set abc "xyz"';
4492 var jsonToSave = _MSH();
4493 jsonToSave[args['_'][1]] = args['_'][2];
4494 var updatedContent = '';
4495 for (var key in jsonToSave) {
4496 if (jsonToSave.hasOwnProperty(key)) {
4497 updatedContent += key + '=' + jsonToSave[key] + '\n';
4501 require('fs').writeFileSync(mshFileName, updatedContent);
4502 response = "msh set " + args['_'][1] + " successful"
4504 response = "msh set " + args['_'][1] + " unsuccessful";
4509 if (typeof args['_'][1] != 'string') {
4510 response = 'Proper usage: msh delete abc';
4512 var jsonToSave = _MSH();
4513 delete jsonToSave[args['_'][1]];
4514 var updatedContent = '';
4515 for (var key in jsonToSave) {
4516 if (jsonToSave.hasOwnProperty(key)) {
4517 updatedContent += key + '=' + jsonToSave[key] + '\n';
4521 require('fs').writeFileSync(mshFileName, updatedContent);
4522 response = "msh delete " + args['_'][1] + " successful"
4524 response = "msh delete " + args['_'][1] + " unsuccessful";
4529 response = 'Proper usage: msh [get|set|delete]\r\nmsh get MeshServer\r\nmsh set abc "xyz"\r\nmsh delete abc';
4535 if (require('os').dns == null) {
4536 response = "Unknown command \"" + cmd + "\", type \"help\" for list of available commands.";
4539 response = 'DNS Servers: ';
4540 var dns = require('os').dns();
4541 for (var i = 0; i < dns.length; ++i) {
4542 if (i > 0) { response += ', '; }
4548 response = require('ChainViewer').getTimerInfo();
4551 if (process.platform != 'win32') {
4552 response = 'Unknown command "rdpport", type "help" for list of available commands.';
4555 if (args['_'].length == 0) {
4556 response = 'Proper usage: rdpport [get|default|PORTNUMBER]';
4558 switch (args['_'][0].toLocaleLowerCase()) {
4560 var rdpport = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'System\\CurrentControlSet\\Control\\Terminal Server\\WinStations\\RDP-Tcp', 'PortNumber');
4561 response = "Current RDP Port Set To: " + rdpport + '\r\n';
4565 require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'System\\CurrentControlSet\\Control\\Terminal Server\\WinStations\\RDP-Tcp', 'PortNumber', 3389);
4566 response = 'RDP Port Set To 3389, Please Dont Forget To Restart Your Computer To Fully Apply';
4568 response = 'Unable to Set RDP Port To: 3389';
4572 if (isNaN(parseFloat(args['_'][0]))){
4573 response = 'Proper usage: rdpport [get|default|PORTNUMBER]';
4574 } else if(parseFloat(args['_'][0]) < 0 || args['_'][0] > 65535) {
4575 response = 'RDP Port Must Be More Than 0 And Less Than 65535';
4578 require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'System\\CurrentControlSet\\Control\\Terminal Server\\WinStations\\RDP-Tcp', 'PortNumber', parseFloat(args['_'][0]));
4579 response = 'RDP Port Set To ' + args['_'][0] + ', Please Dont Forget To Restart Your Computer To Fully Apply';
4581 response = 'Unable to Set RDP Port To: '+args['_'][0];
4589 if (args['_'].length <= 1) {
4590 response = "Proper usage:\r\n find root criteria [criteria2] [criteria n...]";
4593 var root = args['_'][0];
4594 var p = args['_'].slice(1);
4595 var r = require('file-search').find(root, p);
4597 r.on('result', function (str) { sendConsoleText(str, this.sid); });
4598 r.then(function () { sendConsoleText('*** End Results ***', this.sid); });
4599 response = "Find: [" + root + "] " + JSON.stringify(p);
4603 response = JSON.stringify(meshCoreObj, null, 2);
4606 case 'coreinfoupdate': {
4607 sendPeriodicServerUpdate(null, true);
4608 response = "Core Info Update Requested"
4612 if (args['_'].length == 0) {
4613 response = "Proper usage:\r\n agentmsg add \"[message]\" [iconIndex]\r\n agentmsg remove [id]\r\n agentmsg list"; // Display usage
4615 if ((args['_'][0] == 'add') && (args['_'].length > 1)) {
4616 var msgID, iconIndex = 0;
4617 if (args['_'].length >= 3) { try { iconIndex = parseInt(args['_'][2]); } catch (ex) { } }
4618 if (typeof iconIndex != 'number') { iconIndex = 0; }
4619 msgID = sendAgentMessage(args['_'][1], iconIndex);
4620 response = 'Agent message: ' + msgID + ' added.';
4621 } else if ((args['_'][0] == 'remove') && (args['_'].length > 1)) {
4622 var r = removeAgentMessage(args['_'][1]);
4623 response = 'Message ' + (r ? 'removed' : 'NOT FOUND');
4624 } else if (args['_'][0] == 'list') {
4625 response = JSON.stringify(sendAgentMessage(), null, 2);
4627 broadcastSessionsToRegisteredApps();
4631 case 'clearagentmsg': {
4632 removeAgentMessage();
4633 broadcastSessionsToRegisteredApps();
4637 if (args['_'].length != 1) {
4638 response = "Proper usage: coredump on|off|status|clear"; // Display usage
4640 switch (args['_'][0].toLowerCase()) {
4642 process.coreDumpLocation = (process.platform == 'win32') ? (process.execPath.replace('.exe', '.dmp')) : (process.execPath + '.dmp');
4643 response = 'coredump is now on';
4646 process.coreDumpLocation = null;
4647 response = 'coredump is now off';
4650 response = 'coredump is: ' + ((process.coreDumpLocation == null) ? 'off' : 'on');
4651 if (process.coreDumpLocation != null) {
4652 if (process.platform == 'win32') {
4653 if (fs.existsSync(process.coreDumpLocation)) {
4654 response += '\r\n CoreDump present at: ' + process.coreDumpLocation;
4655 response += '\r\n CoreDump Time: ' + new Date(fs.statSync(process.coreDumpLocation).mtime).getTime();
4656 response += '\r\n Agent Time : ' + new Date(fs.statSync(process.execPath).mtime).getTime();
4659 if ((process.cwd() != '//') && fs.existsSync(process.cwd() + 'core')) {
4660 response += '\r\n CoreDump present at: ' + process.cwd() + 'core';
4661 response += '\r\n CoreDump Time: ' + new Date(fs.statSync(process.cwd() + 'core').mtime).getTime();
4662 response += '\r\n Agent Time : ' + new Date(fs.statSync(process.execPath).mtime).getTime();
4668 db.Put('CoreDumpTime', null);
4669 response = 'coredump db cleared';
4672 response = "Proper usage: coredump on|off|status"; // Display usage
4678 if (args['_'].length != 1) {
4679 response = "Proper usage: service status|restart"; // Display usage
4681 var svcname = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent';
4683 svcname = require('MeshAgent').serviceName;
4685 var s = require('service-manager').manager.getService(svcname);
4686 switch (args['_'][0].toLowerCase()) {
4688 response = 'Service ' + (s.isRunning() ? (s.isMe() ? '[SELF]' : '[RUNNING]') : ('[NOT RUNNING]'));
4694 response = 'Restarting another agent instance is not allowed';
4698 response = "Proper usage: service status|restart"; // Display usage
4701 if (process.platform == 'win32') { s.close(); }
4705 if (args['_'].length == 0) {
4706 response = "Proper usage: zip (output file name), input1 [, input n]"; // Display usage
4708 var p = args['_'].join(' ').split(',');
4709 var ofile = p.shift();
4710 sendConsoleText('Writing ' + ofile + '...');
4711 var out = require('fs').createWriteStream(ofile, { flags: 'wb' });
4713 out.sessionid = sessionid;
4714 out.on('close', function () { sendConsoleText('DONE writing ' + this.fname, this.sessionid); });
4715 var zip = require('zip-writer').write({ files: p });
4720 if (args['_'].length == 0) {
4721 response = "Proper usage: unzip input,destination"; // Display usage
4723 var p = args['_'].join(' ').split(',');
4724 if (p.length != 2) { response = "Proper usage: unzip input,destination"; break; } // Display usage
4725 var prom = require('zip-reader').read(p[0].trim());
4726 prom._dest = p[1].trim();
4728 prom.sessionid = sessionid;
4729 prom.then(function (zipped) {
4730 sendConsoleText('Extracting to ' + this._dest + '...', this.sessionid);
4731 zipped.extractAll(this._dest).then(function () { sendConsoleText('finished unzipping', this.sessionid); }, function (e) { sendConsoleText('Error unzipping: ' + e, this.sessionid); }).parentPromise.sessionid = this.sessionid;
4732 }, function (e) { sendConsoleText('Error unzipping: ' + e, this.sessionid); });
4736 // require('MeshAgent').SendCommand({ action: 'battery', state: 'dc', level: 55 });
4737 if ((args['_'].length > 0) && ((args['_'][0] == 'ac') || (args['_'][0] == 'dc'))) {
4738 var b = { action: 'battery', state: args['_'][0] };
4739 if (args['_'].length == 2) { b.level = parseInt(args['_'][1]); }
4740 require('MeshAgent').SendCommand(b);
4742 require('MeshAgent').SendCommand({ action: 'battery' });
4746 require('ChainViewer').getSnapshot().then(function (c) { sendConsoleText(c, this.sessionid); }).parentPromise.sessionid = sessionid;
4749 require('DescriptorEvents').getDescriptorCount().then(
4751 sendConsoleText('Descriptor Count: ' + c, this.sessionid);
4753 sendConsoleText('Error fetching descriptor count: ' + e, this.sessionid);
4754 }).parentPromise.sessionid = sessionid;
4757 if (process.platform != 'win32') {
4758 response = 'Unknown command "uac", type "help" for list of available commands.';
4761 if (args['_'].length != 1) {
4762 response = 'Proper usage: uac [get|interactive|secure]';
4765 switch (args['_'][0].toUpperCase()) {
4767 var secd = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System', 'PromptOnSecureDesktop');
4768 response = "UAC mode: " + (secd == 0 ? "Interactive Desktop" : "Secure Desktop");
4772 require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System', 'PromptOnSecureDesktop', 0);
4773 response = 'UAC mode changed to: Interactive Desktop';
4775 response = "Unable to change UAC Mode";
4780 require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System', 'PromptOnSecureDesktop', 1);
4781 response = 'UAC mode changed to: Secure Desktop';
4783 response = "Unable to change UAC Mode";
4787 response = 'Proper usage: uac [get|interactive|secure]';
4793 response = 'Virtual Machine = ' + require('computer-identifiers').isVM();
4795 case 'startupoptions':
4796 response = JSON.stringify(require('MeshAgent').getStartupOptions());
4799 if (require('MeshAgent').maxKvmTileSize == null) {
4800 response = "Unknown command \"kvmmode\", type \"help\" for list of available commands.";
4803 if (require('MeshAgent').maxKvmTileSize == 0) {
4804 response = 'KVM Mode: Full JUMBO';
4807 response = 'KVM Mode: ' + (require('MeshAgent').maxKvmTileSize <= 65500 ? 'NO JUMBO' : 'Partial JUMBO');
4808 response += (', TileLimit: ' + (require('MeshAgent').maxKvmTileSize < 1024 ? (require('MeshAgent').maxKvmTileSize + ' bytes') : (Math.round(require('MeshAgent').maxKvmTileSize / 1024) + ' Kbytes')));
4813 if (args['_'].length == 0) {
4814 response = "Proper usage: alert TITLE, CAPTION [, TIMEOUT]"; // Display usage
4817 var p = args['_'].join(' ').split(',');
4819 response = "Proper usage: alert TITLE, CAPTION [, TIMEOUT]"; // Display usage
4822 this._alert = require('message-box').create(p[0], p[1], p.length == 3 ? parseInt(p[2]) : 9999, 1);
4827 var actualSize = Math.floor(require('fs').statSync(process.execPath).size / 1024);
4828 if (process.platform == 'win32') {
4829 // Check the Agent Uninstall MetaData for correctness, as the installer may have written an incorrect value
4830 var writtenSize = 0;
4831 var serviceName = (_MSH().serviceName ? _MSH().serviceName : (require('_agentNodeId').serviceName() ? require('_agentNodeId').serviceName() : 'Mesh Agent'));
4832 try { writtenSize = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + serviceName, 'EstimatedSize'); } catch (ex) { response = ex; }
4833 if (writtenSize != actualSize) {
4834 response = "Size updated from: " + writtenSize + " to: " + actualSize;
4835 try { require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + serviceName, 'EstimatedSize', actualSize); } catch (ex) { response = ex; }
4837 { response = "Agent Size: " + actualSize + " kb"; }
4839 { response = "Agent Size: " + actualSize + " kb"; }
4842 response = JSON.stringify(process.versions, null, ' ');
4844 case 'wpfhwacceleration':
4845 if (process.platform != 'win32') { throw ("wpfhwacceleration setting is only supported on Windows"); }
4846 if (args['_'].length != 1) {
4847 response = "Proper usage: wpfhwacceleration (ON|OFF|STATUS)"; // Display usage
4850 var reg = require('win-registry');
4851 var uname = require('user-sessions').getUsername(require('user-sessions').consoleUid());
4852 var key = reg.usernameToUserKey(uname);
4854 switch (args['_'][0].toUpperCase()) {
4856 response = "Proper usage: wpfhwacceleration (ON|OFF|STATUS|DEFAULT)"; // Display usage
4860 reg.WriteKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration', 0);
4862 } catch (ex) { response = "FAILED"; }
4866 reg.WriteKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration', 1);
4868 } catch (ex) { response = 'FAILED'; }
4872 try { s = reg.QueryKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration') == 1 ? 'DISABLED' : 'ENABLED'; } catch (ex) { s = 'DEFAULT'; }
4873 response = "WPF Hardware Acceleration: " + s;
4876 try { reg.DeleteKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration'); } catch (ex) { }
4883 if (process.platform == 'win32') {
4884 if (args['_'].length != 1) {
4885 response = "TSID: " + (require('MeshAgent')._tsid == null ? "console" : require('MeshAgent')._tsid);
4887 var i = parseInt(args['_'][0]);
4888 require('MeshAgent')._tsid = (isNaN(i) ? null : i);
4889 response = "TSID set to: " + (require('MeshAgent')._tsid == null ? "console" : require('MeshAgent')._tsid);
4892 { response = "TSID command only supported on Windows"; }
4895 if (process.platform == 'win32') {
4896 var p = require('user-sessions').enumerateUsers();
4897 p.sessionid = sessionid;
4898 p.then(function (u) {
4900 var ret = getDomainInfo();
4902 if (u[i].State == 'Active') {
4903 if ((u[i].Domain != null && u[i].Domain == 'AzureAD') || getJoinState() == 1){
4904 var userobj = getLogonCacheKeys();
4905 if(userobj && userobj.length > 0){
4906 for (var j = 0; j < userobj.length; j++) {
4907 if (userobj[j] && userobj[j].SAM && userobj[j].SAM[0].trim() === u[i].Username) {
4908 u[i].UPN = userobj[j].UPN
4913 } else if (u[i].Domain != null) {
4914 if (ret != null && ret.PartOfDomain === true) {
4915 u[i].UPN = u[i].Username + '@' + ret.Domain;
4916 } else if (getJoinState() == 4) { // One account with Microsoft Account
4917 var userobj = getLogonCacheKeys();
4918 if(userobj && userobj.length > 0){
4919 for (var j = 0; j < userobj.length; j++) {
4920 if (userobj[j] && userobj[j].SAM && userobj[j].SAM.length == 0 && userobj[j].UPN && userobj[j].UPN != '') {
4921 u[i].UPN = userobj[j].UPN;
4928 if (u[i].UPN == null) { u[i].UPN = ''; }
4929 v.push({ tsid: i, type: u[i].StationName, user: u[i].Username, domain: u[i].Domain, upn: u[i].UPN });
4932 sendConsoleText(JSON.stringify(v, null, 1), this.sessionid);
4935 { response = "activeusers command only supported on Windows"; }
4938 if (process.platform != 'win32' && !(process.platform == 'linux' && require('linux-gnome-helpers').available)) {
4939 response = "wallpaper command not supported on this platform";
4942 if (args['_'].length != 1) {
4943 response = 'Proper usage: wallpaper (GET|TOGGLE)'; // Display usage
4946 switch (args['_'][0].toUpperCase()) {
4948 response = 'Proper usage: wallpaper (GET|TOGGLE)'; // Display usage
4952 if (process.platform == 'win32') {
4953 var id = require('user-sessions').getProcessOwnerName(process.pid).tsid == 0 ? 1 : 0;
4954 var child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0='], { type: id });
4955 child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
4956 child.stderr.on('data', function () { });
4958 var current = child.stdout.str.trim();
4959 if (args['_'][0].toUpperCase() == 'GET') {
4963 if (current != '') {
4964 require('MeshAgent')._wallpaper = current;
4965 response = 'Wallpaper cleared';
4967 response = 'Wallpaper restored';
4969 child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0=', current != '' ? '""' : require('MeshAgent')._wallpaper], { type: id });
4970 child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
4971 child.stderr.on('data', function () { });
4975 var id = require('user-sessions').consoleUid();
4976 var current = require('linux-gnome-helpers').getDesktopWallpaper(id);
4977 if (args['_'][0].toUpperCase() == 'GET') {
4981 if (current != '/dev/null') {
4982 require('MeshAgent')._wallpaper = current;
4983 response = 'Wallpaper cleared';
4985 response = 'Wallpaper restored';
4987 require('linux-gnome-helpers').setDesktopWallpaper(id, current != '/dev/null' ? undefined : require('MeshAgent')._wallpaper);
4995 if (process.platform != 'win32') {
4996 response = 'safemode only supported on Windows Platforms'
5000 response = 'safemode not supported on 64 bit Windows from a 32 bit process'
5003 if (args['_'].length != 1) {
5004 response = 'Proper usage: safemode (ON|OFF|STATUS)'; // Display usage
5007 var svcname = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent';
5009 svcname = require('MeshAgent').serviceName;
5012 switch (args['_'][0].toUpperCase()) {
5014 response = 'Proper usage: safemode (ON|OFF|STATUS)'; // Display usage
5017 require('win-bcd').setKey('safeboot', 'Network');
5018 require('win-bcd').enableSafeModeService(svcname);
5021 require('win-bcd').deleteKey('safeboot');
5024 var nextboot = require('win-bcd').getKey('safeboot');
5029 nextboot = 'SAFE_MODE_NETWORK';
5032 nextboot = 'SAFE_MODE';
5036 response = 'Current: ' + require('win-bcd').bootMode + ', NextBoot: ' + (nextboot ? nextboot : 'NORMAL');
5045 if ((args['_'].length == 1) && (args['_'][0] == 'on')) {
5046 if (meshCoreObj.users.length > 0) {
5047 obj.borderManager.Start(meshCoreObj.users[0]);
5048 response = 'Border blinking is on.';
5050 response = 'Cannot turn on border blinking, no logged in users.';
5052 } else if ((args['_'].length == 1) && (args['_'][0] == 'off')) {
5053 obj.borderManager.Stop();
5054 response = 'Border blinking is off.';
5056 response = 'Proper usage: border "on|off"'; // Display correct command usage
5062 if (process.platform == 'win32') {
5063 // Windows Command: "wmic /Namespace:\\root\SecurityCenter2 Path AntiVirusProduct get /FORMAT:CSV"
5064 response = JSON.stringify(require('win-info').av(), null, 1);
5066 response = 'Not supported on the platform';
5070 if (process.platform == 'win32') {
5071 // Windows Command: "wmic /Namespace:\\root\SecurityCenter2 Path AntiVirusProduct get /FORMAT:CSV"
5072 response = JSON.stringify(require('win-info').defender(), null, 1);
5074 response = 'Not supported on the platform';
5078 if (args['_'].length != 1) { response = 'Proper usage: log "sample text"'; } else { MeshServerLog(args['_'][0]); response = 'ok'; }
5081 if (require('MeshAgent').isService) {
5082 require('clipboard').dispatchRead().then(function (str) { sendConsoleText(str, sessionid); });
5084 require('clipboard').read().then(function (str) { sendConsoleText(str, sessionid); });
5088 if (pendingSetClip) {
5090 } else if (args['_'].length != 1) {
5091 response = 'Proper usage: setclip "sample text"';
5093 if (require('MeshAgent').isService) {
5094 if (process.platform != 'win32') {
5095 require('clipboard').dispatchWrite(args['_'][0]);
5098 var clipargs = args['_'][0];
5099 var uid = require('user-sessions').consoleUid();
5100 var user = require('user-sessions').getUsername(uid);
5101 var domain = require('user-sessions').getDomain(uid);
5102 user = (domain + '\\' + user);
5104 if (this._dispatcher) { this._dispatcher.close(); }
5105 this._dispatcher = require('win-dispatcher').dispatch({ user: user, modules: [{ name: 'clip-dispatch', script: "module.exports = { dispatch: function dispatch(val) { require('clipboard')(val); process.exit(); } };" }], launch: { module: 'clip-dispatch', method: 'dispatch', args: [clipargs] } });
5106 this._dispatcher.parent = this;
5107 //require('events').setFinalizerMetadata.call(this._dispatcher, 'clip-dispatch');
5108 pendingSetClip = true;
5109 this._dispatcher.on('connection', function (c) {
5111 this._c.root = this.parent;
5112 this._c.on('end', function ()
5114 pendingSetClip = false;
5115 try { this.root._dispatcher.close(); } catch (ex) { }
5116 this.root._dispatcher = null;
5121 response = 'Setting clipboard to: "' + args['_'][0] + '"';
5124 require('clipboard')(args['_'][0]); response = 'Setting clipboard to: "' + args['_'][0] + '"';
5130 if (args['_'].length != 1) { response = 'Proper usage: openurl (url)'; } // Display usage
5131 else { if (openUserDesktopUrl(args['_'][0]) == null) { response = 'Failed.'; } else { response = 'Success.'; } }
5135 if (args['_'].length != 1) { response = 'Proper usage: openfile (filepath)'; } // Display usage
5136 else { if (openFileOnDesktop(args['_'][0]) == null) { response = 'Failed.'; } else { response = 'Success.'; } }
5140 if (meshCoreObj.users == null) { response = 'Active users are unknown.'; } else { response = 'Active Users: ' + meshCoreObj.users.join(', ') + '.'; }
5141 require('user-sessions').enumerateUsers().then(function (u) {
5142 var ret = getDomainInfo();
5144 if ((u[i].Domain != null && u[i].Domain == 'AzureAD') || getJoinState() == 1){
5145 var userobj = getLogonCacheKeys();
5146 if(userobj && userobj.length > 0){
5147 for (var j = 0; j < userobj.length; j++) {
5149 if (a && a.SAM && a.SAM[0].trim() === u[i].Username) {
5155 } else if (u[i].Domain != null) {
5156 if (ret != null && ret.PartOfDomain === true) {
5157 u[i].UPN = u[i].Username + '@' + ret.Domain;
5158 } else if (getJoinState() == 4) { // One account with Microsoft Account
5159 var userobj = getLogonCacheKeys();
5160 if(userobj && userobj.length > 0){
5161 for (var j = 0; j < userobj.length; j++) {
5163 if (a && a.SAM && a.SAM.length == 0 && a.UPN && a.UPN != '') {
5171 if (u[i].UPN == null) { u[i].UPN = ''; }
5172 sendConsoleText(u[i]);
5178 response = JSON.stringify(require('kvm-helper').users(), null, 1);
5181 if (args['_'].length < 1) { response = 'Proper usage: toast "message"'; } else {
5182 if (require('MeshAgent')._tsid == null) {
5183 require('toaster').Toast('MeshCentral', args['_'][0]).then(sendConsoleText, sendConsoleText);
5186 require('toaster').Toast('MeshCentral', args['_'][0], require('MeshAgent')._tsid).then(sendConsoleText, sendConsoleText);
5192 if (args['_'].length < 1) { response = 'Proper usage: setdebug (target), 0 = Disabled, 1 = StdOut, 2 = This Console, * = All Consoles, 4 = WebLog, 8 = Logfile'; } // Display usage
5193 else { if (args['_'][0] == '*') { console.setDestination(2); } else { console.setDestination(parseInt(args['_'][0]), sessionid); } }
5197 processManager.getProcesses(function (plist) {
5199 for (var i in plist) { x += i + ((plist[i].user) ? (', ' + plist[i].user) : '') + ', ' + plist[i].cmd + '\r\n'; }
5200 sendConsoleText(x, sessionid);
5205 if ((args['_'].length < 1)) {
5206 response = 'Proper usage: kill [pid]'; // Display correct command usage
5208 process.kill(parseInt(args['_'][0]));
5209 response = 'Killed process ' + args['_'][0] + '.';
5214 if (SMBiosTables == null) { response = 'SMBios tables not available.'; } else { response = objToString(SMBiosTables, 0, ' ', true); }
5218 if (SMBiosTablesRaw == null) { response = 'SMBios tables not available.'; } else {
5220 for (var i in SMBiosTablesRaw) {
5222 for (var j in SMBiosTablesRaw[i]) {
5223 if (SMBiosTablesRaw[i][j].length > 0) {
5224 if (header == false) { response += ('Table type #' + i + ((require('smbios').smTableTypes[i] == null) ? '' : (', ' + require('smbios').smTableTypes[i]))) + '\r\n'; header = true; }
5225 response += (' ' + SMBiosTablesRaw[i][j].toString('hex')) + '\r\n';
5232 case 'eval': { // Eval JavaScript
5233 if (args['_'].length < 1) {
5234 response = 'Proper usage: eval "JavaScript code"'; // Display correct command usage
5236 response = JSON.stringify(mesh.eval(args['_'][0])); // This can only be run by trusted administrator.
5240 case 'uninstallagent': // Uninstall this agent
5241 var agentName = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent';
5243 agentName = require('MeshAgent').serviceName;
5246 if (!require('service-manager').manager.getService(agentName).isMe()) {
5247 response = 'Uininstall failed, this instance is not the service instance';
5249 try { diagnosticAgent_uninstall(); } catch (ex) { }
5250 var js = "require('service-manager').manager.getService('" + agentName + "').stop(); require('service-manager').manager.uninstallService('" + agentName + "'); process.exit();";
5251 this.child = require('child_process').execFile(process.execPath, [process.platform == 'win32' ? (process.execPath.split('\\').pop()) : (process.execPath.split('/').pop()), '-b64exec', Buffer.from(js).toString('base64')], { type: 4, detached: true });
5254 case 'notify': { // Send a notification message to the mesh
5255 if (args['_'].length != 1) {
5256 response = 'Proper usage: notify "message" [--session]'; // Display correct command usage
5258 var notification = { action: 'msg', type: 'notify', value: args['_'][0], tag: 'console' };
5259 if (args.session) { notification.sessionid = sessionid; } // If "--session" is specified, notify only this session, if not, the server will notify the mesh
5260 mesh.SendCommand(notification); // no sessionid or userid specified, notification will go to the entire mesh
5265 case 'cpuinfo': { // Return system information
5266 // CPU & memory utilization
5267 pr = require('sysinfo').cpuUtilization();
5268 pr.sessionid = sessionid;
5269 pr.then(function (data) {
5270 sendConsoleText(JSON.stringify(
5273 memory: require('sysinfo').memUtilization(),
5274 thermals: require('sysinfo').thermals == null ? [] : require('sysinfo').thermals()
5275 }, null, 1), this.sessionid);
5281 case 'sysinfo': { // Return system information
5282 getSystemInformation(function (results, err) {
5283 if (results == null) {
5284 sendConsoleText(err, this.sessionid);
5286 sendConsoleText(JSON.stringify(results, null, 1), this.sessionid);
5287 mesh.SendCommand({ action: 'sysinfo', sessionid: this.sessionid, data: results });
5292 case 'info': { // Return information about the agent and agent core module
5293 response = 'Current Core: ' + meshCoreObj.value + '\r\nAgent Time: ' + Date() + '.\r\nUser Rights: 0x' + rights.toString(16) + '.\r\nPlatform: ' + process.platform + '.\r\nCapabilities: ' + meshCoreObj.caps + '.\r\nServer URL: ' + mesh.ServerUrl + '.';
5294 if (amt != null) { response += '\r\nBuilt-in LMS: ' + ['Disabled', 'Connecting..', 'Connected'][amt.lmsstate] + '.'; }
5295 if (meshCoreObj.osdesc) { response += '\r\nOS: ' + meshCoreObj.osdesc + '.'; }
5296 response += '\r\nModules: ' + addedModules.join(', ') + '.';
5297 response += '\r\nServer Connection: ' + mesh.isControlChannelConnected + ', State: ' + meshServerConnectionState + '.';
5298 var oldNodeId = db.Get('OldNodeId');
5299 if (oldNodeId != null) { response += '\r\nOldNodeID: ' + oldNodeId + '.'; }
5300 response += '\r\nNode ID: ' + Buffer.from(require('_agentNodeId')(), 'hex').toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
5301 if (process.platform == 'linux' || process.platform == 'freebsd') { response += '\r\nX11 support: ' + require('monitor-info').kvm_x11_support + '.'; }
5302 response += '\r\nApplication Location: ' + process.cwd();
5303 //response += '\r\Debug Console: ' + debugConsole + '.';
5306 case 'osinfo': { // Return the operating system information
5308 if (args['_'].length > 0) { i = parseInt(args['_'][0]); if (i > 8) { i = 8; } response = 'Calling ' + i + ' times.'; }
5309 for (var j = 0; j < i; j++) {
5310 var pr = require('os').name();
5311 pr.sessionid = sessionid;
5312 pr.then(function (v) {
5313 sendConsoleText("OS: " + v + (process.platform == 'win32' ? (require('win-virtual-terminal').supported ? ' [ConPTY: YES]' : ' [ConPTY: NO]') : ''), this.sessionid);
5318 case 'args': { // Displays parsed command arguments
5319 response = 'args ' + objToString(args, 0, ' ', true);
5322 case 'print': { // Print a message on the mesh agent console, does nothing when running in the background
5324 for (var i in args['_']) { r.push(args['_'][i]); }
5325 console.log(r.join(' '));
5326 response = 'Message printed on agent console.';
5329 case 'type': { // Returns the content of a file
5330 if (args['_'].length == 0) {
5331 response = 'Proper usage: type (filepath) [maxlength]'; // Display correct command usage
5334 if ((args['_'].length > 1) && (typeof args['_'][1] == 'number')) { max = args['_'][1]; }
5335 if (max > 4096) max = 4096;
5336 var buf = Buffer.alloc(max), fd = fs.openSync(args['_'][0], "r"), r = fs.readSync(fd, buf, 0, max); // Read the file content
5337 response = buf.toString();
5338 var i = response.indexOf('\n');
5339 if ((i > 0) && (response[i - 1] != '\r')) { response = response.split('\n').join('\r\n'); }
5340 if (r == max) response += '...';
5345 case 'dbkeys': { // Return all data store keys
5346 response = JSON.stringify(db.Keys);
5349 case 'dbget': { // Return the data store value for a given key
5350 if (db == null) { response = 'Database not accessible.'; break; }
5351 if (args['_'].length != 1) {
5352 response = 'Proper usage: dbget (key)'; // Display the value for a given database key
5354 response = db.Get(args['_'][0]);
5358 case 'dbset': { // Set a data store key and value pair
5359 if (db == null) { response = 'Database not accessible.'; break; }
5360 if (args['_'].length != 2) {
5361 response = 'Proper usage: dbset (key) (value)'; // Set a database key
5363 var r = db.Put(args['_'][0], args['_'][1]);
5364 response = 'Key set: ' + r;
5368 case 'dbcompact': { // Compact the data store
5369 if (db == null) { response = 'Database not accessible.'; break; }
5370 var r = db.Compact();
5371 response = 'Database compacted: ' + r;
5375 if (consoleHttpRequest != null) {
5376 response = 'HTTP operation already in progress.';
5378 if (args['_'].length != 1) {
5379 response = 'Proper usage: httpget (url)';
5381 var options = http.parseUri(args['_'][0]);
5382 options.method = 'GET';
5383 if (options == null) {
5384 response = 'Invalid url.';
5386 try { consoleHttpRequest = http.request(options, consoleHttpResponse); } catch (ex) { response = 'Invalid HTTP GET request'; }
5387 consoleHttpRequest.sessionid = sessionid;
5388 if (consoleHttpRequest != null) {
5389 consoleHttpRequest.end();
5390 response = 'HTTPGET ' + options.protocol + '//' + options.host + ':' + options.port + options.path;
5397 case 'wslist': { // List all web sockets
5399 for (var i in consoleWebSockets) {
5400 var httprequest = consoleWebSockets[i];
5401 response += 'Websocket #' + i + ', ' + httprequest.url + '\r\n';
5403 if (response == '') { response = 'no websocket sessions.'; }
5406 case 'wsconnect': { // Setup a web socket
5407 if (args['_'].length == 0) {
5408 response = 'Proper usage: wsconnect (url)\r\nFor example: wsconnect wss://localhost:443/meshrelay.ashx?id=abc'; // Display correct command usage
5410 var httprequest = null;
5412 var options = http.parseUri(args['_'][0].split('$').join('%24').split('@').join('%40')); // Escape the $ and @ characters in the URL
5413 options.rejectUnauthorized = 0;
5414 httprequest = http.request(options);
5415 } catch (ex) { response = 'Invalid HTTP websocket request'; }
5416 if (httprequest != null) {
5417 httprequest.upgrade = onWebSocketUpgrade;
5418 httprequest.on('error', function (e) { sendConsoleText("ERROR: Unable to connect to: " + this.url + ", " + JSON.stringify(e)); });
5421 while (consoleWebSockets[index]) { index++; }
5422 httprequest.sessionid = sessionid;
5423 httprequest.index = index;
5424 httprequest.url = args['_'][0];
5425 consoleWebSockets[index] = httprequest;
5426 response = 'New websocket session #' + index;
5431 case 'wssend': { // Send data on a web socket
5432 if (args['_'].length == 0) {
5433 response = 'Proper usage: wssend (socketnumber)\r\n'; // Display correct command usage
5434 for (var i in consoleWebSockets) {
5435 var httprequest = consoleWebSockets[i];
5436 response += 'Websocket #' + i + ', ' + httprequest.url + '\r\n';
5439 var i = parseInt(args['_'][0]);
5440 var httprequest = consoleWebSockets[i];
5441 if (httprequest != undefined) {
5442 httprequest.s.write(args['_'][1]);
5445 response = 'Invalid web socket number';
5450 case 'wsclose': { // Close a websocket
5451 if (args['_'].length == 0) {
5452 response = 'Proper usage: wsclose (socketnumber)'; // Display correct command usage
5454 var i = parseInt(args['_'][0]);
5455 var httprequest = consoleWebSockets[i];
5456 if (httprequest != undefined) {
5457 if (httprequest.s != null) { httprequest.s.end(); } else { httprequest.end(); }
5460 response = 'Invalid web socket number';
5465 case 'tunnels': { // Show the list of current tunnels
5467 for (var i in tunnels) {
5468 response += 'Tunnel #' + i + ', ' + tunnels[i].protocol; //tunnels[i].url
5469 if (tunnels[i].userid) { response += ', ' + tunnels[i].userid; }
5470 if (tunnels[i].guestname) { response += '/' + tunnels[i].guestname; }
5473 if (response == '') { response = 'No websocket sessions.'; }
5476 case 'ls': { // Show list of files and folders
5479 if (args['_'].length > 0) { xpath = obj.path.join(args['_'][0], '*'); }
5480 response = 'List of ' + xpath + '\r\n';
5481 var results = fs.readdirSync(xpath);
5482 for (var i = 0; i < results.length; ++i) {
5483 var stat = null, p = obj.path.join(args['_'][0], results[i]);
5484 try { stat = fs.statSync(p); } catch (ex) { }
5485 if ((stat == null) || (stat == undefined)) {
5486 response += (results[i] + "\r\n");
5488 response += (results[i] + " " + ((stat.isDirectory()) ? "(Folder)" : "(File)") + "\r\n");
5493 case 'lsx': { // Show list of files and folders
5494 response = objToString(getDirectoryInfo(args['_'][0]), 0, ' ', true);
5497 case 'lock': { // Lock the current user out of the desktop
5501 case 'amt': { // Show Intel AMT status
5503 amt.getMeiState(9, function (state) {
5504 var resp = "Intel AMT not detected.";
5505 if (state != null) { resp = objToString(state, 0, ' ', true); }
5506 sendConsoleText(resp, sessionid);
5509 response = "Intel AMT not detected.";
5513 case 'netinfo': { // Show network interface information
5514 var interfaces = require('os').networkInterfaces();
5515 if (process.platform == 'win32') {
5517 var ret = require('win-wmi').query('ROOT\\CIMV2', 'SELECT InterfaceIndex,NetConnectionID,Speed FROM Win32_NetworkAdapter', ['InterfaceIndex','NetConnectionID','Speed']);
5520 for (var i = 0; i < ret.length; i++) speedMap[ret[i].InterfaceIndex] = ret[i].Speed;
5521 var adapterNames = Object.keys(interfaces);
5522 for (var j = 0; j < adapterNames.length; j++) {
5523 var theinterfaces = interfaces[adapterNames[j]];
5524 for (var k = 0; k < theinterfaces.length; k++) {
5525 var iface = theinterfaces[k], speed = speedMap[iface.index] || 0;
5526 iface.speed = parseInt(speed); // bits per seconds
5531 } else if (process.platform == 'linux') {
5532 var adapterNames = Object.keys(interfaces);
5533 for (var i = 0; i < adapterNames.length; i++) {
5534 var ifaceName = adapterNames[i];
5536 var speedStr = require('fs').readFileSync('/sys/class/net/' + ifaceName + '/speed').toString();
5537 if ((speedStr.trim() != "") && (speedStr.trim() != "-1")) {
5538 var theinterfaces = interfaces[ifaceName];
5539 for (var k = 0; k < theinterfaces.length; k++) {
5540 var iface = theinterfaces[k];
5541 iface.speed = parseInt(speedStr) * 1000000; // bits per seconds
5547 response = objToString(interfaces, 0, ' ', true);
5550 case 'wakeonlan': { // Send wake-on-lan
5551 if ((args['_'].length != 1) || (args['_'][0].length != 12)) {
5552 response = 'Proper usage: wakeonlan [mac], for example "wakeonlan 010203040506".';
5554 var count = sendWakeOnLanEx([args['_'][0]]);
5555 sendWakeOnLanEx([args['_'][0]]);
5556 sendWakeOnLanEx([args['_'][0]]);
5557 response = 'Sending wake-on-lan on ' + count + ' interface(s).';
5562 if (args['_'].length != 1) {
5563 response = 'Proper usage: display (sleep | awake)';
5565 var sleepawake = [args['_'][0]];
5566 if(sleepawake=='sleep'){
5567 require('power-monitor').sleepDisplay()
5568 }else if(sleepawake=='awake'){
5569 require('power-monitor').wakeDisplay()
5572 response = 'Setting Display To ' + sleepawake;
5576 case 'sendall': { // Send a message to all consoles on this mesh
5577 sendConsoleText(args['_'].join(' '));
5580 case 'power': { // Execute a power action on this computer
5581 if (mesh.ExecPowerState == undefined) {
5582 response = 'Power command not supported on this agent.';
5584 if ((args['_'].length == 0) || isNaN(Number(args['_'][0]))) {
5585 response = 'Proper usage: power (actionNumber), where actionNumber is:\r\n LOGOFF = 1\r\n SHUTDOWN = 2\r\n REBOOT = 3\r\n SLEEP = 4\r\n HIBERNATE = 5\r\n DISPLAYON = 6\r\n KEEPAWAKE = 7\r\n BEEP = 8\r\n CTRLALTDEL = 9\r\n VIBRATE = 13\r\n FLASH = 14'; // Display correct command usage
5587 var r = mesh.ExecPowerState(Number(args['_'][0]), Number(args['_'][1]));
5588 response = 'Power action executed with return code: ' + r + '.';
5594 getIpLocationData(function (location) {
5595 sendConsoleText(objToString({ action: 'iplocation', type: 'publicip', value: location }, 0, ' '));
5600 response = JSON.stringify(http.parseUri(args['_'][0]));
5604 if (wifiScanner != null) {
5605 var wifiPresent = wifiScanner.hasWireless;
5606 if (wifiPresent) { response = "Perfoming Wifi scan..."; wifiScanner.Scan(); } else { response = "Wifi absent."; }
5608 { response = "Wifi module not present."; }
5612 response = JSON.stringify(addedModules);
5615 case 'listservices': {
5616 var services = require('service-manager').manager.enumerateService();
5617 response = JSON.stringify(services, null, 1);
5621 if (args['_'].length != 1) {
5622 response = "Proper usage: getscript [scriptNumber].";
5624 mesh.SendCommand({ action: 'getScript', type: args['_'][0] });
5630 if (!mesh.DAIPC.listening) {
5631 response = 'Unable to bind to Diagnostic IPC, most likely because the path (' + process.cwd() + ') is not on a local file system';
5634 var diag = diagnosticAgent_installCheck();
5636 if (args['_'].length == 1 && args['_'][0] == 'uninstall') {
5637 diagnosticAgent_uninstall();
5638 response = 'Diagnostic Agent uninstalled';
5641 response = 'Diagnostic Agent installed at: ' + diag.appLocation();
5645 if (args['_'].length == 1 && args['_'][0] == 'install') {
5646 diag = diagnosticAgent_installCheck(true);
5648 response = 'Diagnostic agent was installed at: ' + diag.appLocation();
5651 response = 'Diagnostic agent installation failed';
5655 response = 'Diagnostic Agent Not installed. To install: diagnostic install';
5658 if (diag) { diag.close(); diag = null; }
5662 if ((args['_'].length == 1) && (args['_'][0] == 'on')) { obj.showamtevent = true; response = 'Intel AMT configuration events live view enabled.'; }
5663 else if ((args['_'].length == 1) && (args['_'][0] == 'off')) { delete obj.showamtevent; response = 'Intel AMT configuration events live view disabled.'; }
5664 else if (obj.amtevents == null) { response = 'No events.'; } else { response = obj.amtevents.join('\r\n'); }
5668 if (amt == null) { response = 'Intel AMT not detected.'; break; }
5669 if (!obj.showamtevent) { obj.showamtevent = true; require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: 'Enabled live view of Intel AMT configuration events, \"amtevents off\" to disable.' }); }
5670 if (apftunnel != null) { response = 'Intel AMT server tunnel already active'; break; }
5671 require('MeshAgent').SendCommand({ action: 'amtconfig' }); // Request that the server give us a server authentication cookie to start the APF session.
5675 if (meshCoreObj.intelamt !== null) {
5676 if (args['_'].length == 1) {
5677 var connType = -1, connTypeStr = args['_'][0].toLowerCase();
5678 if (connTypeStr == 'lms') { connType = 2; }
5679 if (connTypeStr == 'relay') { connType = 1; }
5680 if (connTypeStr == 'cira') { connType = 0; }
5681 if (connTypeStr == 'off') { connType = -2; }
5682 if (connType >= 0) { // Connect
5684 mpsurl: mesh.ServerUrl.replace('agent.ashx', 'apf.ashx'),
5685 mpsuser: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16).replace(/\+/g, '@').replace(/\//g, '$'),
5686 mpspass: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16).replace(/\+/g, '@').replace(/\//g, '$'),
5687 mpskeepalive: 60000,
5688 clientname: require('os').hostname(),
5689 clientaddress: '127.0.0.1',
5690 clientuuid: meshCoreObj.intelamt.UUID,
5691 conntype: connType // 0 = CIRA, 1 = Relay, 2 = LMS. The correct value is 2 since we are performing an LMS relay, other values for testing.
5693 if ((apfarg.clientuuid == null) || (apfarg.clientuuid.length != 36)) {
5694 response = "Unable to get Intel AMT UUID: " + apfarg.clientuuid;
5696 apftunnel = require('amt-apfclient')({ debug: false }, apfarg);
5697 apftunnel.onJsonControl = handleApfJsonControl;
5698 apftunnel.onChannelClosed = function () { apftunnel = null; }
5700 apftunnel.connect();
5701 response = "Started APF tunnel";
5703 response = JSON.stringify(ex);
5706 } else if (connType == -2) { // Disconnect
5708 apftunnel.disconnect();
5709 response = "Stopped APF tunnel";
5711 response = JSON.stringify(ex);
5715 response = "Invalid command.\r\nUse: apf lms|relay|cira|off";
5718 response = "APF tunnel is " + (apftunnel == null ? "off" : "on") + "\r\nUse: apf lms|relay|cira|off";
5721 response = "APF tunnel requires Intel AMT";
5726 if (typeof args['_'][0] == 'string') {
5728 // Pass off the action to the plugin
5729 // for plugin creators, you'll want to have a plugindir/modules_meshcore/plugin.js
5730 // to control the output / actions here.
5731 response = require(args['_'][0]).consoleaction(args, rights, sessionid, mesh);
5733 response = "There was an error in the plugin (" + ex + ")";
5736 response = "Proper usage: plugin [pluginName] [args].";
5740 case 'installedapps': {
5741 if(process.platform == 'win32'){
5742 require('win-info').installedApps().then(function (apps){ sendConsoleText(JSON.stringify(apps,null,1)); });
5746 case 'installedstoreapps': {
5747 if(process.platform == 'win32'){
5748 var apps = require('win-info').installedStoreApps();
5750 sendConsoleText(JSON.stringify(apps,null,1));
5756 if(process.platform == 'win32'){
5757 var qfe = require('win-info').qfe();
5758 sendConsoleText(JSON.stringify(qfe,null,1));
5762 default: { // This is an unknown command, return an error message
5763 response = "Unknown command \"" + cmd + "\", type \"help\" for list of available commands.";
5767 } catch (ex) { response = "Command returned an exception error: " + ex; console.log(ex); }
5768 if (response != null) { sendConsoleText(response, sessionid); }
5771// Send a mesh agent console command
5772function sendConsoleText(text, sessionid) {
5773 if (typeof text == 'object') { text = JSON.stringify(text); }
5774 if (debugConsole && ((sessionid == null) || (sessionid == 'pipe'))) { broadcastToRegisteredApps({ cmd: 'console', value: text }); }
5775 if (sessionid != 'pipe') { require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: text, sessionid: sessionid }); }
5778function removeAgentMessage(msgid) {
5780 if (msgid == null) {
5781 // Delete all messages
5782 sendAgentMessage.messages = [];
5786 var i = sendAgentMessage.messages.findIndex(function (v) { return (v.id == msgid); });
5788 sendAgentMessage.messages.splice(i, 1);
5792 if (ret) { sendAgentMessage(); }
5796// Send a mesh agent message to server, placing a bubble/badge on the agent device
5797function sendAgentMessage(msg, icon, serverid, first) {
5798 if (sendAgentMessage.messages == null) {
5799 sendAgentMessage.messages = [];
5802 if (arguments.length > 0) {
5803 if (first == null || (serverid && first && sendAgentMessage.messages.findIndex(function (v) { return (v.msgid == serverid); }) < 0)) {
5804 sendAgentMessage.messages.push({ msg: msg, icon: icon, msgid: serverid });
5805 sendAgentMessage.messages.peek().id = sendAgentMessage.messages.peek()._hashCode();
5810 for (i = 0; i < sendAgentMessage.messages.length; ++i) {
5811 p[i] = sendAgentMessage.messages[i];
5814 require('MeshAgent').SendCommand({ action: 'sessions', type: 'msg', value: p });
5816 return (arguments.length > 0 ? sendAgentMessage.messages.peek().id : sendAgentMessage.messages);
5818function getOpenDescriptors() {
5820 switch (process.platform) {
5822 var child = require('child_process').execFile('/bin/sh', ['sh']);
5823 child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
5824 child.stderr.on('data', function (c) { });
5826 child.stdin.write("procstat -f " + process.pid + " | tr '\\n' '`' | awk -F'`' '");
5827 child.stdin.write('{');
5828 child.stdin.write(' DEL="";');
5829 child.stdin.write(' printf "[";');
5830 child.stdin.write(' for(i=1;i<NF;++i)');
5831 child.stdin.write(' {');
5832 child.stdin.write(' A=split($i,B," ");');
5833 child.stdin.write(' if(B[3] ~ /^[0-9]/)');
5834 child.stdin.write(' {');
5835 child.stdin.write(' printf "%s%s", DEL, B[3];');
5836 child.stdin.write(' DEL=",";');
5837 child.stdin.write(' }');
5838 child.stdin.write(' }');
5839 child.stdin.write(' printf "]";');
5840 child.stdin.write("}'");
5842 child.stdin.write('\nexit\n');
5845 try { r = JSON.parse(child.stdout.str.trim()); } catch (ex) { }
5849 var child = require('child_process').execFile('/bin/sh', ['sh']);
5850 child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
5851 child.stderr.on('data', function (c) { });
5853 child.stdin.write("ls /proc/" + process.pid + "/fd | tr '\\n' '`' | awk -F'`' '");
5854 child.stdin.write('{');
5855 child.stdin.write(' printf "[";');
5856 child.stdin.write(' DEL="";');
5857 child.stdin.write(' for(i=1;i<NF;++i)');
5858 child.stdin.write(' {');
5859 child.stdin.write(' printf "%s%s",DEL,$i;');
5860 child.stdin.write(' DEL=",";');
5861 child.stdin.write(' }');
5862 child.stdin.write(' printf "]";');
5863 child.stdin.write("}'");
5864 child.stdin.write('\nexit\n');
5867 try { r = JSON.parse(child.stdout.str.trim()); } catch (ex) { }
5873function closeDescriptors(libc, descriptors) {
5875 while (descriptors.length > 0) {
5876 fd = descriptors.pop();
5882function linux_execv(name, agentfilename, sessionid) {
5883 var libs = require('monitor-info').getLibInfo('libc');
5886 if ((libs.length == 0 || libs.length == null) && require('MeshAgent').ARCHID == 33) {
5887 var child = require('child_process').execFile('/bin/sh', ['sh']);
5888 child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
5889 child.stderr.str = ''; child.stderr.on('data', function (c) { this.str += c.toString(); });
5890 child.stdin.write("ls /lib/libc.* | tr '\\n' '`' | awk -F'`' '{ " + ' printf "["; DEL=""; for(i=1;i<NF;++i) { printf "%s{\\"path\\":\\"%s\\"}",DEL,$i; DEL=""; } printf "]"; }\'\nexit\n');
5894 libs = JSON.parse(child.stdout.str.trim());
5898 while (libs.length > 0) {
5900 libc = require('_GenericMarshal').CreateNativeProxy(libs.pop().path);
5909 libc.CreateMethod('execv');
5910 libc.CreateMethod('close');
5917 // Couldn't find libc.so, fallback to using service manager to restart agent
5918 if (sessionid != null) { sendConsoleText('Restarting service via service-manager...', sessionid) }
5921 var s = require('service-manager').manager.getService(name);
5924 sendConsoleText('Self Update encountered an error trying to restart service', sessionid);
5925 sendAgentMessage('Self Update encountered an error trying to restart service', 3);
5930 if (sessionid != null) { sendConsoleText('Restarting service via execv()...', sessionid) }
5934 var argarr = [process.execPath];
5936 var path = require('_GenericMarshal').CreateVariable(process.execPath);
5938 if (require('MeshAgent').getStartupOptions != null) {
5939 var options = require('MeshAgent').getStartupOptions();
5940 for (i in options) {
5941 argarr.push('--' + i + '="' + options[i] + '"');
5945 args = require('_GenericMarshal').CreateVariable((1 + argarr.length) * require('_GenericMarshal').PointerSize);
5946 for (i = 0; i < argarr.length; ++i) {
5947 var arg = require('_GenericMarshal').CreateVariable(argarr[i]);
5949 arg.pointerBuffer().copy(args.toBuffer(), i * require('_GenericMarshal').PointerSize);
5952 var descriptors = getOpenDescriptors();
5953 closeDescriptors(libc, descriptors);
5955 libc.execv(path, args);
5956 if (sessionid != null) { sendConsoleText('Self Update failed because execv() failed', sessionid) }
5957 sendAgentMessage('Self Update failed because execv() failed', 3);
5960function bsd_execv(name, agentfilename, sessionid) {
5961 var child = require('child_process').execFile('/bin/sh', ['sh']);
5962 child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
5963 child.stderr.str = ''; child.stderr.on('data', function (c) { this.str += c.toString(); });
5964 child.stdin.write("cat /usr/lib/libc.so | awk '");
5965 child.stdin.write('{');
5966 child.stdin.write(' a=split($0, tok, "(");');
5967 child.stdin.write(' if(a>1)');
5968 child.stdin.write(' {');
5969 child.stdin.write(' split(tok[2], b, ")");');
5970 child.stdin.write(' split(b[1], c, " ");');
5971 child.stdin.write(' print c[1];');
5972 child.stdin.write(' }');
5973 child.stdin.write("}'\nexit\n");
5975 if (child.stdout.str.trim() == '') {
5976 if (sessionid != null) { sendConsoleText('Self Update failed because cannot find libc.so', sessionid) }
5977 sendAgentMessage('Self Update failed because cannot find libc.so', 3);
5983 libc = require('_GenericMarshal').CreateNativeProxy(child.stdout.str.trim());
5984 libc.CreateMethod('execv');
5985 libc.CreateMethod('close');
5987 if (sessionid != null) { sendConsoleText('Self Update failed: ' + ex.toString(), sessionid) }
5988 sendAgentMessage('Self Update failed: ' + ex.toString(), 3);
5992 var path = require('_GenericMarshal').CreateVariable(process.execPath);
5993 var argarr = [process.execPath];
5994 var args, i, argtmp = [];
5995 var options = require('MeshAgent').getStartupOptions();
5996 for (i in options) {
5997 argarr.push('--' + i + '="' + options[i] + '"');
5999 args = require('_GenericMarshal').CreateVariable((1 + argarr.length) * require('_GenericMarshal').PointerSize);
6000 for (i = 0; i < argarr.length; ++i) {
6001 var arg = require('_GenericMarshal').CreateVariable(argarr[i]);
6003 arg.pointerBuffer().copy(args.toBuffer(), i * require('_GenericMarshal').PointerSize);
6006 if (sessionid != null) { sendConsoleText('Restarting service via execv()', sessionid) }
6008 var descriptors = getOpenDescriptors();
6009 closeDescriptors(libc, descriptors);
6011 libc.execv(path, args);
6012 if (sessionid != null) { sendConsoleText('Self Update failed because execv() failed', sessionid) }
6013 sendAgentMessage('Self Update failed because execv() failed', 3);
6016function windows_execve(name, agentfilename, sessionid) {
6019 libc = require('_GenericMarshal').CreateNativeProxy('msvcrt.dll');
6020 libc.CreateMethod('_wexecve');
6022 sendConsoleText('Self Update failed because msvcrt.dll is missing', sessionid);
6023 sendAgentMessage('Self Update failed because msvcrt.dll is missing', 3);
6027 var cmd = require('_GenericMarshal').CreateVariable(process.env['windir'] + '\\system32\\cmd.exe', { wide: true });
6028 var args = require('_GenericMarshal').CreateVariable(3 * require('_GenericMarshal').PointerSize);
6029 var arg1 = require('_GenericMarshal').CreateVariable('cmd.exe', { wide: true });
6030 var arg2 = require('_GenericMarshal').CreateVariable('/C net stop "' + name + '" & "' + process.cwd() + agentfilename + '.update.exe" -b64exec ' + 'dHJ5CnsKICAgIHZhciBzZXJ2aWNlTG9jYXRpb24gPSBwcm9jZXNzLmFyZ3YucG9wKCkudG9Mb3dlckNhc2UoKTsKICAgIHJlcXVpcmUoJ3Byb2Nlc3MtbWFuYWdlcicpLmVudW1lcmF0ZVByb2Nlc3NlcygpLnRoZW4oZnVuY3Rpb24gKHByb2MpCiAgICB7CiAgICAgICAgZm9yICh2YXIgcCBpbiBwcm9jKQogICAgICAgIHsKICAgICAgICAgICAgaWYgKHByb2NbcF0ucGF0aCAmJiAocHJvY1twXS5wYXRoLnRvTG93ZXJDYXNlKCkgPT0gc2VydmljZUxvY2F0aW9uKSkKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgcHJvY2Vzcy5raWxsKHByb2NbcF0ucGlkKTsKICAgICAgICAgICAgfQogICAgICAgIH0KICAgICAgICBwcm9jZXNzLmV4aXQoKTsKICAgIH0pOwp9CmNhdGNoIChlKQp7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQ==' +
6031 ' "' + process.execPath + '" & copy "' + process.cwd() + agentfilename + '.update.exe" "' + process.execPath + '" & net start "' + name + '" & erase "' + process.cwd() + agentfilename + '.update.exe"', { wide: true });
6033 arg1.pointerBuffer().copy(args.toBuffer());
6034 arg2.pointerBuffer().copy(args.toBuffer(), require('_GenericMarshal').PointerSize);
6036 libc._wexecve(cmd, args, 0);
6039// Start a JavaScript based Agent Self-Update
6040function agentUpdate_Start(updateurl, updateoptions) {
6041 // If this value is null
6042 var sessionid = (updateoptions != null) ? updateoptions.sessionid : null; // If this is null, messages will be broadcast. Otherwise they will be unicasted
6044 // If the url starts with *, switch it to use the same protoco, host and port as the control channel.
6045 if (updateurl != null) {
6046 updateurl = getServerTargetUrlEx(updateurl);
6047 if (updateurl.startsWith("wss://")) { updateurl = "https://" + updateurl.substring(6); }
6050 if (agentUpdate_Start._selfupdate != null) {
6051 // We were already called, so we will ignore this duplicate request
6052 if (sessionid != null) { sendConsoleText('Self update already in progress...', sessionid); }
6055 if (agentUpdate_Start._retryCount == null) { agentUpdate_Start._retryCount = 0; }
6056 if (require('MeshAgent').ARCHID == null && updateurl == null) {
6057 // This agent doesn't have the ability to tell us which ARCHID it is, so we don't know which agent to pull
6058 sendConsoleText('Unable to initiate update, agent ARCHID is not defined', sessionid);
6061 var agentfilename = process.execPath.split(process.platform == 'win32' ? '\\' : '/').pop(); // Local File Name, ie: MeshAgent.exe
6062 var name = require('MeshAgent').serviceName;
6063 if (name == null) { name = (process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'); } // This is an older agent that doesn't expose the service name, so use the default
6065 var s = require('service-manager').manager.getService(name);
6067 if (process.platform == 'win32') { s.close(); }
6068 sendConsoleText('Self Update cannot continue, this agent is not an instance of (' + name + ')', sessionid);
6071 if (process.platform == 'win32') { s.close(); }
6074 sendConsoleText('Self Update Failed because this agent is not an instance of (' + name + ')', sessionid);
6075 sendAgentMessage('Self Update Failed because this agent is not an instance of (' + name + ')', 3);
6079 if ((sessionid != null) && (updateurl != null)) { sendConsoleText('Downloading update from: ' + updateurl, sessionid); }
6080 var options = require('http').parseUri(updateurl != null ? updateurl : require('MeshAgent').ServerUrl);
6081 options.protocol = 'https:';
6082 if (updateurl == null) { options.path = ('/meshagents?id=' + require('MeshAgent').ARCHID); sendConsoleText('Downloading update from: ' + options.path, sessionid); }
6083 options.rejectUnauthorized = false;
6084 options.checkServerIdentity = function checkServerIdentity(certs) {
6085 // If the tunnel certificate matches the control channel certificate, accept the connection
6086 try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.digest == certs[0].digest) return; } catch (ex) { }
6087 try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.fingerprint == certs[0].fingerprint) return; } catch (ex) { }
6089 // Check that the certificate is the one expected by the server, fail if not.
6090 if (checkServerIdentity.servertlshash == null) {
6091 if (require('MeshAgent').ServerInfo == null || require('MeshAgent').ServerInfo.ControlChannelCertificate == null) { return; }
6092 sendConsoleText('Self Update failed, because the url cannot be verified: ' + updateurl, sessionid);
6093 sendAgentMessage('Self Update failed, because the url cannot be verified: ' + updateurl, 3);
6094 throw new Error('BadCert');
6096 if (certs[0].digest == null) { return; }
6097 if ((checkServerIdentity.servertlshash != null) && (checkServerIdentity.servertlshash.toLowerCase() != certs[0].digest.split(':').join('').toLowerCase())) {
6098 sendConsoleText('Self Update failed, because the supplied certificate does not match', sessionid);
6099 sendAgentMessage('Self Update failed, because the supplied certificate does not match', 3);
6100 throw new Error('BadCert')
6103 options.checkServerIdentity.servertlshash = (updateoptions != null ? updateoptions.tlshash : null);
6104 agentUpdate_Start._selfupdate = require('https').get(options);
6105 agentUpdate_Start._selfupdate.on('error', function (e) {
6106 sendConsoleText('Self Update failed, because there was a problem trying to download the update from ' + updateurl, sessionid);
6107 sendAgentMessage('Self Update failed, because there was a problem trying to download the update from ' + updateurl, 3);
6108 agentUpdate_Start._selfupdate = null;
6110 agentUpdate_Start._selfupdate.on('response', function (img) {
6111 this._file = require('fs').createWriteStream(agentfilename + (process.platform == 'win32' ? '.update.exe' : '.update'), { flags: 'wb' });
6112 this._filehash = require('SHA384Stream').create();
6113 this._filehash.on('hash', function (h) {
6114 if (updateoptions != null && updateoptions.hash != null) {
6115 if (updateoptions.hash.toLowerCase() == h.toString('hex').toLowerCase()) {
6116 if (sessionid != null) { sendConsoleText('Download complete. HASH verified.', sessionid); }
6118 agentUpdate_Start._retryCount++;
6119 sendConsoleText('Self Update FAILED because the downloaded agent FAILED hash check (' + agentUpdate_Start._retryCount + '), URL: ' + updateurl, sessionid);
6120 sendConsoleText(updateoptions.hash + " != " + h.toString('hex'));
6121 sendAgentMessage('Self Update FAILED because the downloaded agent FAILED hash check (' + agentUpdate_Start._retryCount + '), URL: ' + updateurl, 3);
6122 agentUpdate_Start._selfupdate = null;
6124 if (agentUpdate_Start._retryCount < 4) {
6125 // Retry the download again
6126 sendConsoleText('Self Update will try again in 60 seconds...', sessionid);
6127 agentUpdate_Start._timeout = setTimeout(agentUpdate_Start, 60000, updateurl, updateoptions);
6130 sendConsoleText('Self Update giving up, too many failures...', sessionid);
6131 sendAgentMessage('Self Update giving up, too many failures...', 3);
6137 sendConsoleText('Download complete. HASH=' + h.toString('hex'), sessionid);
6140 // Send an indication to the server that we got the update download correctly.
6141 try { require('MeshAgent').SendCommand({ action: 'agentupdatedownloaded' }); } catch (ex) { }
6143 if (sessionid != null) { sendConsoleText('Updating and restarting agent...', sessionid); }
6144 if (process.platform == 'win32') {
6145 // Use _wexecve() equivalent to perform the update
6146 windows_execve(name, agentfilename, sessionid);
6149 var m = require('fs').statSync(process.execPath).mode;
6150 require('fs').chmodSync(process.cwd() + agentfilename + '.update', m);
6153 require('fs').unlinkSync(process.execPath);
6156 require('fs').copyFileSync(process.cwd() + agentfilename + '.update', process.execPath);
6157 require('fs').chmodSync(process.execPath, m);
6160 require('fs').unlinkSync(process.cwd() + agentfilename + '.update');
6162 switch (process.platform) {
6164 bsd_execv(name, agentfilename, sessionid);
6167 linux_execv(name, agentfilename, sessionid);
6172 var s = require('service-manager').manager.getService(name);
6176 sendConsoleText('Self Update encountered an error trying to restart service', sessionid);
6177 sendAgentMessage('Self Update encountered an error trying to restart service', 3);
6183 img.pipe(this._file);
6184 img.pipe(this._filehash);
6193// Called before the process exits
6194//process.exit = function (code) { console.log("Exit with code: " + code.toString()); }
6196// Called when the server connection state changes
6197function handleServerConnection(state) {
6198 meshServerConnectionState = state;
6199 if (meshServerConnectionState == 0) {
6200 // Server disconnected
6201 if (selfInfoUpdateTimer != null) { clearInterval(selfInfoUpdateTimer); selfInfoUpdateTimer = null; }
6202 lastSelfInfo = null;
6204 // Server connected, send mesh core information
6205 if (require('MeshAgent').ServerInfo == null || require('MeshAgent').ServerInfo.ControlChannelCertificate == null) {
6206 // Outdated Agent, will have insecure tunnels
6207 sendAgentMessage("This agent has an outdated certificate validation mechanism, consider updating.", 3, 118);
6209 else if (global._MSH == null) {
6210 sendAgentMessage("This is an old agent version, consider updating.", 3, 117);
6213 var oldNodeId = db.Get('OldNodeId');
6214 if (oldNodeId != null) { mesh.SendCommand({ action: 'mc1migration', oldnodeid: oldNodeId }); }
6216 // Send SMBios tables if present
6217 if (SMBiosTablesRaw != null) { mesh.SendCommand({ action: 'smbios', value: SMBiosTablesRaw }); }
6219 // Update the server on with basic info, logged in users and more advanced stuff, like Intel ME and Network Settings
6221 LastPeriodicServerUpdate = null;
6222 sendPeriodicServerUpdate(null, true);
6223 if (selfInfoUpdateTimer == null) {
6224 selfInfoUpdateTimer = setInterval(sendPeriodicServerUpdate, 1200000); // 20 minutes
6225 selfInfoUpdateTimer.metadata = 'meshcore (InfoUpdate Timer)';
6228 // Send any state messages
6229 if (Object.keys(tunnelUserCount.msg).length > 0) {
6231 broadcastSessionsToRegisteredApps();
6234 // Send update of registered applications to the server
6235 updateRegisteredAppsToServer();
6238 // Send server state update to registered applications
6239 broadcastToRegisteredApps({ cmd: 'serverstate', value: meshServerConnectionState, url: require('MeshAgent').ConnectedServer });
6242// Update the server with the latest network interface information
6243var sendNetworkUpdateNagleTimer = null;
6244function sendNetworkUpdateNagle() { if (sendNetworkUpdateNagleTimer != null) { clearTimeout(sendNetworkUpdateNagleTimer); sendNetworkUpdateNagleTimer = null; } sendNetworkUpdateNagleTimer = setTimeout(sendNetworkUpdate, 5000); }
6245function sendNetworkUpdate(force) {
6246 sendNetworkUpdateNagleTimer = null;
6249 // Update the network interfaces information data
6250 var netInfo = { netif2: require('os').networkInterfaces() };
6251 if (process.platform == 'win32') {
6253 var ret = require('win-wmi').query('ROOT\\CIMV2', 'SELECT InterfaceIndex,NetConnectionID,Speed FROM Win32_NetworkAdapter', ['InterfaceIndex','NetConnectionID','Speed']);
6256 for (var i = 0; i < ret.length; i++) speedMap[ret[i].InterfaceIndex] = ret[i].Speed;
6257 var adapterNames = Object.keys(netInfo.netif2);
6258 for (var j = 0; j < adapterNames.length; j++) {
6259 var interfaces = netInfo.netif2[adapterNames[j]];
6260 for (var k = 0; k < interfaces.length; k++) {
6261 var iface = interfaces[k], speed = speedMap[iface.index] || 0;
6262 iface.speed = parseInt(speed); // bits per seconds
6267 } else if (process.platform == 'linux') {
6268 var adapterNames = Object.keys(netInfo.netif2);
6269 for (var i = 0; i < adapterNames.length; i++) {
6270 var ifaceName = adapterNames[i];
6272 var speedStr = require('fs').readFileSync('/sys/class/net/' + ifaceName + '/speed').toString();
6273 if ((speedStr.trim() != "") && (speedStr.trim() != "-1")) {
6274 var theinterfaces = netInfo.netif2[ifaceName];
6275 for (var k = 0; k < theinterfaces.length; k++) {
6276 var iface = theinterfaces[k];
6277 iface.speed = parseInt(speedStr) * 1000000; // bits per seconds
6283 if (netInfo.netif2) {
6284 netInfo.action = 'netinfo';
6285 var netInfoStr = JSON.stringify(netInfo);
6286 if ((force == true) || (clearGatewayMac(netInfoStr) != clearGatewayMac(lastNetworkInfo))) { mesh.SendCommand(netInfo); lastNetworkInfo = netInfoStr; }
6291// Called periodically to check if we need to send updates to the server
6292function sendPeriodicServerUpdate(flags, force) {
6293 if (meshServerConnectionState == 0) return; // Not connected to server, do nothing.
6294 if (!flags) { flags = 0xFFFFFFFF; }
6295 if (!force) { force = false; }
6297 // If we have a connected MEI, get Intel ME information
6298 if ((flags & 1) && (amt != null) && (amt.state == 2)) {
6299 delete meshCoreObj.intelamt;
6300 amt.getMeiState(9, function (meinfo) {
6301 meshCoreObj.intelamt = meinfo;
6302 meshCoreObj.intelamt.microlms = amt.lmsstate;
6303 meshCoreObjChanged();
6307 // Update network information
6308 if (flags & 2) { sendNetworkUpdateNagle(false); }
6310 // Update anti-virus information
6311 if ((flags & 4) && (process.platform == 'win32')) {
6312 // Windows Command: "wmic /Namespace:\\root\SecurityCenter2 Path AntiVirusProduct get /FORMAT:CSV"
6313 try { meshCoreObj.av = require('win-info').av(); meshCoreObjChanged(); } catch (ex) { av = null; } // Antivirus
6314 //if (process.platform == 'win32') { try { meshCoreObj.pr = require('win-info').pendingReboot(); meshCoreObjChanged(); } catch (ex) { meshCoreObj.pr = null; } } // Pending reboot
6316 if (process.platform == 'win32') {
6317 if (require('MeshAgent')._securitycenter == null) {
6319 require('MeshAgent')._securitycenter = require('win-securitycenter').status();
6320 meshCoreObj['wsc'] = require('MeshAgent')._securitycenter; // Windows Security Central (WSC)
6321 require('win-securitycenter').on('changed', function () {
6322 require('MeshAgent')._securitycenter = require('win-securitycenter').status();
6323 meshCoreObj['wsc'] = require('MeshAgent')._securitycenter; // Windows Security Central (WSC)
6324 require('MeshAgent').SendCommand({ action: 'coreinfo', wsc: require('MeshAgent')._securitycenter });
6329 // Get Defender Information
6331 meshCoreObj.defender = require('win-info').defender();
6332 meshCoreObjChanged();
6336 // Send available data right now
6338 meshCoreObj = sortObjRec(meshCoreObj);
6339 var x = JSON.stringify(meshCoreObj);
6340 if (x != LastPeriodicServerUpdate) {
6341 LastPeriodicServerUpdate = x;
6342 mesh.SendCommand(meshCoreObj);
6347// Sort the names in an object
6348function sortObject(obj) { return Object.keys(obj).sort().reduce(function(a, v) { a[v] = obj[v]; return a; }, {}); }
6350// Fix the incoming data and cut down how much data we use
6351function cleanGetBitLockerVolumeInfo(volumes) {
6352 for (var i in volumes) {
6353 const v = volumes[i];
6354 if (typeof v.size == 'string') { v.size = parseInt(v.size); }
6355 if (typeof v.sizeremaining == 'string') { v.sizeremaining = parseInt(v.sizeremaining); }
6356 if (v.identifier == '') { delete v.identifier; }
6357 if (v.name == '') { delete v.name; }
6358 if (v.removable != true) { delete v.removable; }
6359 if (v.cdrom != true) { delete v.cdrom; }
6360 if (v.protectionStatus == 'On') { v.protectionStatus = true; } else { delete v.protectionStatus; }
6361 if (v.volumeStatus == 'FullyDecrypted') { delete v.volumeStatus; }
6362 if (v.recoveryPassword == '') { delete v.recoveryPassword; }
6364 return sortObject(volumes);
6367// Once we are done collecting all the data, send to server if needed
6368var LastPeriodicServerUpdate = null;
6369var PeriodicServerUpdateNagleTimer = null;
6370function meshCoreObjChanged() {
6371 if (PeriodicServerUpdateNagleTimer == null) {
6372 PeriodicServerUpdateNagleTimer = setTimeout(meshCoreObjChangedEx, 500);
6375function meshCoreObjChangedEx() {
6376 PeriodicServerUpdateNagleTimer = null;
6377 meshCoreObj = sortObjRec(meshCoreObj);
6378 var x = JSON.stringify(meshCoreObj);
6379 if (x != LastPeriodicServerUpdate) {
6380 try { LastPeriodicServerUpdate = x; mesh.SendCommand(meshCoreObj); } catch (ex) { }
6384function sortObjRec(o) { if ((typeof o != 'object') || (Array.isArray(o))) return o; for (var i in o) { if (typeof o[i] == 'object') { o[i] = sortObjRec(o[i]); } } return sortObj(o); }
6385function sortObj(o) { return Object.keys(o).sort().reduce(function (result, key) { result[key] = o[key]; return result; }, {}); }
6387function onWebSocketClosed() { sendConsoleText("WebSocket #" + this.httprequest.index + " closed.", this.httprequest.sessionid); delete consoleWebSockets[this.httprequest.index]; }
6388function onWebSocketData(data) { sendConsoleText("Got WebSocket #" + this.httprequest.index + " data: " + data, this.httprequest.sessionid); }
6389function onWebSocketSendOk() { sendConsoleText("WebSocket #" + this.index + " SendOK.", this.sessionid); }
6391function onWebSocketUpgrade(response, s, head) {
6392 sendConsoleText("WebSocket #" + this.index + " connected.", this.sessionid);
6394 s.httprequest = this;
6395 s.end = onWebSocketClosed;
6396 s.data = onWebSocketData;
6399mesh.AddCommandHandler(handleServerCommand);
6400mesh.AddConnectHandler(handleServerConnection);