meshcentral
Version:
Web based remote computer management server
1,020 lines (929 loc) • 308 kB
JavaScript
/*
Copyright 2018-2022 Intel Corporation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
process.on('uncaughtException', function (ex) {
require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: "uncaughtException1: " + ex });
});
if (process.platform == 'win32' && require('user-sessions').getDomain == null) {
require('user-sessions').getDomain = function getDomain(uid) {
return (this.getSessionAttribute(uid, this.InfoClass.WTSDomainName));
};
}
var promise = require('promise');
// Mesh Rights
var MNG_ERROR = 65;
var MESHRIGHT_EDITMESH = 1;
var MESHRIGHT_MANAGEUSERS = 2;
var MESHRIGHT_MANAGECOMPUTERS = 4;
var MESHRIGHT_REMOTECONTROL = 8;
var MESHRIGHT_AGENTCONSOLE = 16;
var MESHRIGHT_SERVERFILES = 32;
var MESHRIGHT_WAKEDEVICE = 64;
var MESHRIGHT_SETNOTES = 128;
var MESHRIGHT_REMOTEVIEW = 256; // Remote View Only
var MESHRIGHT_NOTERMINAL = 512;
var MESHRIGHT_NOFILES = 1024;
var MESHRIGHT_NOAMT = 2048;
var MESHRIGHT_LIMITEDINPUT = 4096;
var MESHRIGHT_LIMITEVENTS = 8192;
var MESHRIGHT_CHATNOTIFY = 16384;
var MESHRIGHT_UNINSTALL = 32768;
var MESHRIGHT_NODESKTOP = 65536;
var pendingSetClip = false; // This is a temporary hack to prevent multiple setclips at the same time to stop the agent from crashing.
//
// 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
// and will use this helper if it is not. This helper will inject 'sysnative' into the results when calling readdirSync() on %windir%.
//
function __readdirSync_fix(path)
{
var sysnative = false;
pathstr = require('fs')._fixwinpath(path);
if (pathstr.split('\\*').join('').toLowerCase() == process.env['windir'].toLowerCase()) { sysnative = true; }
var ret = require('fs').__readdirSync_old(path);
if (sysnative) { ret.push('sysnative'); }
return (ret);
}
if (process.platform == 'win32' && require('_GenericMarshal').PointerSize == 4 && require('os').arch() == 'x64')
{
if (require('fs').readdirSync.version == null)
{
//
// 32 Bit Windows Agent on 64 bit Windows has not been patched for sysnative issue, so lets use our own solution
//
require('fs').__readdirSync_old = require('fs').readdirSync;
require('fs').readdirSync = __readdirSync_fix;
}
}
function bcdOK() {
if (process.platform != 'win32') { return (false); }
if (require('os').arch() == 'x64') {
return (require('_GenericMarshal').PointerSize == 8);
}
return (true);
}
function getDomainInfo() {
var hostname = require('os').hostname();
var ret = { Name: hostname, Domain: "" };
switch (process.platform) {
case 'win32':
try {
ret = require('win-wmi').query('ROOT\\CIMV2', 'SELECT * FROM Win32_ComputerSystem', ['Name', 'Domain'])[0];
}
catch (x) {
}
break;
case 'linux':
var hasrealm = false;
try {
hasrealm = require('lib-finder').hasBinary('realm');
}
catch (x) {
}
if (hasrealm) {
var child = require('child_process').execFile('/bin/sh', ['sh']);
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
child.stdin.write("realm list | grep domain-name: | tr '\\n' '`' | ");
child.stdin.write("awk -F'`' '{ ");
child.stdin.write(' printf("[");');
child.stdin.write(' ST="";');
child.stdin.write(' for(i=1;i<NF;++i)');
child.stdin.write(' {');
child.stdin.write(' match($i,/domain-name: /);');
child.stdin.write(' printf("%s\\"%s\\"", ST, substr($i, RSTART+RLENGTH));');
child.stdin.write(' ST=",";');
child.stdin.write(' }');
child.stdin.write(' printf("]");');
child.stdin.write(" }'");
child.stdin.write('\nexit\n');
child.waitExit();
var names = [];
try {
names = JSON.parse(child.stdout.str);
}
catch (e) {
}
while (names.length > 0) {
if (hostname.endsWith('.' + names.peek())) {
ret = { Name: hostname.substring(0, hostname.length - names.peek().length - 1), Domain: names.peek() };
break;
}
names.pop();
}
}
break;
}
return (ret);
}
try {
Object.defineProperty(Array.prototype, 'findIndex', {
value: function (func) {
var i = 0;
for (i = 0; i < this.length; ++i) {
if (func(this[i], i, this)) {
return (i);
}
}
return (-1);
}
});
} catch (ex) { }
if (require('MeshAgent').ARCHID == null) {
var id = null;
switch (process.platform) {
case 'win32':
id = require('_GenericMarshal').PointerSize == 4 ? 3 : 4;
break;
case 'freebsd':
id = require('_GenericMarshal').PointerSize == 4 ? 31 : 30;
break;
case 'darwin':
try {
id = require('os').arch() == 'x64' ? 16 : 29;
} catch (ex) { id = 16; }
break;
}
if (id != null) { Object.defineProperty(require('MeshAgent'), 'ARCHID', { value: id }); }
}
function setDefaultCoreTranslation(obj, field, value) {
if (obj[field] == null || obj[field] == '') { obj[field] = value; }
}
function getCoreTranslation() {
var ret = {};
if (global.coretranslations != null) {
try {
var lang = require('util-language').current;
if (coretranslations[lang] == null) { lang = lang.split('-')[0]; }
if (coretranslations[lang] == null) { lang = 'en'; }
if (coretranslations[lang] != null) { ret = coretranslations[lang]; }
}
catch (ex) { }
}
setDefaultCoreTranslation(ret, 'allow', 'Allow');
setDefaultCoreTranslation(ret, 'deny', 'Deny');
setDefaultCoreTranslation(ret, 'autoAllowForFive', 'Auto accept all connections for next 5 minutes');
setDefaultCoreTranslation(ret, 'terminalConsent', '{0} requesting remote terminal access. Grant access?');
setDefaultCoreTranslation(ret, 'desktopConsent', '{0} requesting remote desktop access. Grant access?');
setDefaultCoreTranslation(ret, 'fileConsent', '{0} requesting remote file Access. Grant access?');
setDefaultCoreTranslation(ret, 'terminalNotify', '{0} started a remote terminal session.');
setDefaultCoreTranslation(ret, 'desktopNotify', '{0} started a remote desktop session.');
setDefaultCoreTranslation(ret, 'fileNotify', '{0} started a remote file session.');
setDefaultCoreTranslation(ret, 'privacyBar', 'Sharing desktop with: {0}');
return (ret);
}
var currentTranslation = getCoreTranslation();
try {
require('kvm-helper');
}
catch (e) {
var j =
{
users: function () {
var r = {};
require('user-sessions').Current(function (c) { r = c; });
if (process.platform != 'win32') {
for (var i in r) {
r[i].SessionId = r[i].uid;
}
}
return (r);
}
};
addModuleObject('kvm-helper', j);
}
function lockDesktop(uid) {
switch (process.platform) {
case 'linux':
if (uid != null) {
var name = require('user-sessions').getUsername(uid);
var child = require('child_process').execFile('/bin/sh', ['sh']);
child.stdout.str = ''; child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });
child.stderr.str = ''; child.stderr.on('data', function (chunk) { this.str += chunk.toString(); });
child.stdin.write('loginctl show-user -p Sessions ' + name + " | awk '{");
child.stdin.write('gsub(/^Sessions=/,"",$0);');
child.stdin.write('cmd = sprintf("loginctl lock-session %s",$0);');
child.stdin.write('system(cmd);');
child.stdin.write("}'\nexit\n");
child.waitExit();
}
else {
var child = require('child_process').execFile('/bin/sh', ['sh']);
child.stdout.str = ''; child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });
child.stderr.str = ''; child.stderr.on('data', function (chunk) { this.str += chunk.toString(); });
child.stdin.write('loginctl lock-sessions\nexit\n');
child.waitExit();
}
break;
case 'win32':
{
var options = { type: 1, uid: uid };
var child = require('child_process').execFile(process.env['windir'] + '\\system32\\cmd.exe', ['/c', 'RunDll32.exe user32.dll,LockWorkStation'], options);
child.waitExit();
}
break;
default:
break;
}
}
var writable = require('stream').Writable;
function destopLockHelper_pipe(httprequest) {
if (process.platform != 'linux' && process.platform != 'freebsd') { return; }
if (httprequest.unlockerHelper == null && httprequest.desktop != null && httprequest.desktop.kvm != null) {
httprequest.unlockerHelper = new writable(
{
'write': function (chunk, flush) {
if (chunk.readUInt16BE(0) == 65) {
delete this.request.autolock;
}
flush();
return (true);
},
'final': function (flush) {
flush();
}
});
httprequest.unlockerHelper.request = httprequest;
httprequest.desktop.kvm.pipe(httprequest.unlockerHelper);
}
}
var obj = { serverInfo: {} };
var agentFileHttpRequests = {}; // Currently active agent HTTPS GET requests from the server.
var agentFileHttpPendingRequests = []; // Pending HTTPS GET requests from the server.
var debugConsole = (global._MSH && (_MSH().debugConsole == 1));
var color_options =
{
background: (global._MSH != null) ? global._MSH().background : '0,54,105',
foreground: (global._MSH != null) ? global._MSH().foreground : '255,255,255'
};
if (process.platform == 'win32' && require('user-sessions').isRoot()) {
// Check the Agent Uninstall MetaData for correctness, as the installer may have written an incorrect value
try {
var writtenSize = 0, actualSize = Math.floor(require('fs').statSync(process.execPath).size / 1024);
try { writtenSize = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MeshCentralAgent', 'EstimatedSize'); } catch (ex) { }
if (writtenSize != actualSize) { try { require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MeshCentralAgent', 'EstimatedSize', actualSize); } catch (ex) { } }
} catch (ex) { }
// Check to see if we are the Installed Mesh Agent Service, if we are, make sure we can run in Safe Mode
var svcname = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent';
try {
svcname = require('MeshAgent').serviceName;
} catch (ex) { }
try {
var meshCheck = false;
try { meshCheck = require('service-manager').manager.getService(svcname).isMe(); } catch (ex) { }
if (meshCheck && require('win-bcd').isSafeModeService && !require('win-bcd').isSafeModeService(svcname)) { require('win-bcd').enableSafeModeService(svcname); }
} catch (ex) { }
}
if (process.platform != 'win32') {
var ch = require('child_process');
ch._execFile = ch.execFile;
ch.execFile = function execFile(path, args, options) {
if (options && options.type && options.type == ch.SpawnTypes.TERM && options.env) {
options.env['TERM'] = 'xterm-256color';
}
return (this._execFile(path, args, options));
};
}
if (process.platform == 'darwin' && !process.versions) {
// This is an older MacOS Agent, so we'll need to check the service definition so that Auto-Update will function correctly
var child = require('child_process').execFile('/bin/sh', ['sh']);
child.stdout.str = '';
child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });
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, \">\"); ");
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]; } }");
child.stdin.write(" else { split(c[1], ka, \"/\"); if(ka[1]==\"true\") {print \"ALWAYS\";} } }'\nexit\n");
child.waitExit();
if (child.stdout.str.trim() == 'Crashed') {
child = require('child_process').execFile('/bin/sh', ['sh']);
child.stdout.str = '';
child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });
child.stdin.write("launchctl list | grep 'meshagent' | awk '{ if($3==\"meshagent\"){print $1;}}'\nexit\n");
child.waitExit();
if (parseInt(child.stdout.str.trim()) == process.pid) {
// The currently running MeshAgent is us, so we can continue with the update
var plist = require('fs').readFileSync('/Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist').toString();
var tokens = plist.split('<key>KeepAlive</key>');
if (tokens[1].split('>')[0].split('<')[1] == 'dict') {
var tmp = tokens[1].split('</dict>');
tmp.shift();
tokens[1] = '\n <true/>' + tmp.join('</dict>');
tokens = tokens.join('<key>KeepAlive</key>');
require('fs').writeFileSync('/Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist', tokens);
var fix = '';
fix += ("function macosRepair()\n");
fix += ("{\n");
fix += (" var child = require('child_process').execFile('/bin/sh', ['sh']);\n");
fix += (" child.stdout.str = '';\n");
fix += (" child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });\n");
fix += (" child.stderr.on('data', function (chunk) { });\n");
fix += (" child.stdin.write('launchctl unload /Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist\\n');\n");
fix += (" child.stdin.write('launchctl load /Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist\\n');\n");
fix += (" child.stdin.write('rm /Library/LaunchDaemons/meshagentRepair.plist\\n');\n");
fix += (" child.stdin.write('rm " + process.cwd() + "/macosRepair.js\\n');\n");
fix += (" child.stdin.write('launchctl stop meshagentRepair\\nexit\\n');\n");
fix += (" child.waitExit();\n");
fix += ("}\n");
fix += ("macosRepair();\n");
fix += ("process.exit();\n");
require('fs').writeFileSync(process.cwd() + '/macosRepair.js', fix);
var plist = '<?xml version="1.0" encoding="UTF-8"?>\n';
plist += '<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n';
plist += '<plist version="1.0">\n';
plist += ' <dict>\n';
plist += ' <key>Label</key>\n';
plist += (' <string>meshagentRepair</string>\n');
plist += ' <key>ProgramArguments</key>\n';
plist += ' <array>\n';
plist += (' <string>' + process.execPath + '</string>\n');
plist += ' <string>macosRepair.js</string>\n';
plist += ' </array>\n';
plist += ' <key>WorkingDirectory</key>\n';
plist += (' <string>' + process.cwd() + '</string>\n');
plist += ' <key>RunAtLoad</key>\n';
plist += ' <true/>\n';
plist += ' </dict>\n';
plist += '</plist>';
require('fs').writeFileSync('/Library/LaunchDaemons/meshagentRepair.plist', plist);
child = require('child_process').execFile('/bin/sh', ['sh']);
child.stdout.str = '';
child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });
child.stdin.write("launchctl load /Library/LaunchDaemons/meshagentRepair.plist\nexit\n");
child.waitExit();
}
}
}
}
// Add an Intel AMT event to the log
function addAmtEvent(msg) {
if (obj.amtevents == null) { obj.amtevents = []; }
var d = new Date(), e = zeroPad(d.getHours(), 2) + ':' + zeroPad(d.getMinutes(), 2) + ':' + zeroPad(d.getSeconds(), 2) + ', ' + msg;
obj.amtevents.push(e);
if (obj.amtevents.length > 100) { obj.amtevents.splice(0, obj.amtevents.length - 100); }
if (obj.showamtevent) { require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: e }); }
}
function zeroPad(num, size) { var s = '000000000' + num; return s.substr(s.length - size); }
// Create Secure IPC for Diagnostic Agent Communications
obj.DAIPC = require('net').createServer();
if (process.platform != 'win32') { try { require('fs').unlinkSync(process.cwd() + '/DAIPC'); } catch (ex) { } }
obj.DAIPC.IPCPATH = process.platform == 'win32' ? ('\\\\.\\pipe\\' + require('_agentNodeId')() + '-DAIPC') : (process.cwd() + '/DAIPC');
try { obj.DAIPC.listen({ path: obj.DAIPC.IPCPATH, writableAll: true, maxConnections: 5 }); } catch (ex) { }
obj.DAIPC._daipc = [];
obj.DAIPC.on('connection', function (c) {
c._send = function (j) {
var data = JSON.stringify(j);
var packet = Buffer.alloc(data.length + 4);
packet.writeUInt32LE(data.length + 4, 0);
Buffer.from(data).copy(packet, 4);
this.write(packet);
};
this._daipc.push(c);
c.parent = this;
c.on('end', function () { removeRegisteredApp(this); });
c.on('data', function (chunk) {
if (chunk.length < 4) { this.unshift(chunk); return; }
var len = chunk.readUInt32LE(0);
if (len > 8192) { removeRegisteredApp(this); this.end(); return; }
if (chunk.length < len) { this.unshift(chunk); return; }
var data = chunk.slice(4, len);
try { data = JSON.parse(data.toString()); } catch (ex) { }
if ((data == null) || (typeof data.cmd != 'string')) return;
try {
switch (data.cmd) {
case 'requesthelp':
if (this._registered == null) return;
sendConsoleText('Request Help (' + this._registered + '): ' + data.value);
var help = {};
help[this._registered] = data.value;
try { mesh.SendCommand({ action: 'sessions', type: 'help', value: help }); } catch (ex) { }
MeshServerLogEx(98, [this._registered, data.value], "Help Requested, user: " + this._registered + ", details: " + data.value, null);
break;
case 'cancelhelp':
if (this._registered == null) return;
sendConsoleText('Cancel Help (' + this._registered + ')');
try { mesh.SendCommand({ action: 'sessions', type: 'help', value: {} }); } catch (ex) { }
break;
case 'register':
if (typeof data.value == 'string') {
this._registered = data.value;
var apps = {};
apps[data.value] = 1;
try { mesh.SendCommand({ action: 'sessions', type: 'app', value: apps }); } catch (ex) { }
this._send({ cmd: 'serverstate', value: meshServerConnectionState, url: require('MeshAgent').ConnectedServer, amt: (amt != null) });
}
break;
case 'query':
switch (data.value) {
case 'connection':
data.result = require('MeshAgent').ConnectedServer;
this._send(data);
break;
case 'descriptors':
require('ChainViewer').getSnapshot().then(function (f) {
this.tag.payload.result = f;
this.tag.ipc._send(this.tag.payload);
}).parentPromise.tag = { ipc: this, payload: data };
break;
case 'timerinfo':
data.result = require('ChainViewer').getTimerInfo();
this._send(data);
break;
}
break;
case 'amtstate':
if (amt == null) return;
var func = function amtStateFunc(state) { if (state != null) { amtStateFunc.pipe._send({ cmd: 'amtstate', value: state }); } }
func.pipe = this;
amt.getMeiState(11, func);
break;
case 'sessions':
this._send({ cmd: 'sessions', sessions: tunnelUserCount });
break;
case 'meshToolInfo':
try { mesh.SendCommand({ action: 'meshToolInfo', name: data.name, hash: data.hash, cookie: data.cookie ? true : false, pipe: true }); } catch (ex) { }
break;
case 'getUserImage':
try { mesh.SendCommand({ action: 'getUserImage', userid: data.userid, pipe: true }); } catch (ex) { }
break;
case 'console':
if (debugConsole) {
var args = splitArgs(data.value);
processConsoleCommand(args[0].toLowerCase(), parseArgs(args), 0, 'pipe');
}
break;
}
}
catch (ex) { removeRegisteredApp(this); this.end(); return; }
});
});
// Send current sessions to registered apps
function broadcastSessionsToRegisteredApps(x) {
var p = {}, i;
for (i = 0; sendAgentMessage.messages != null && i < sendAgentMessage.messages.length; ++i) {
p[i] = sendAgentMessage.messages[i];
}
tunnelUserCount.msg = p;
broadcastToRegisteredApps({ cmd: 'sessions', sessions: tunnelUserCount });
tunnelUserCount.msg = {};
}
// Send this object to all registered local applications
function broadcastToRegisteredApps(x) {
if ((obj.DAIPC == null) || (obj.DAIPC._daipc == null)) return;
for (var i in obj.DAIPC._daipc) {
if (obj.DAIPC._daipc[i]._registered != null) { obj.DAIPC._daipc[i]._send(x); }
}
}
// Send this object to a specific registered local applications
function sendToRegisteredApp(appid, x) {
if ((obj.DAIPC == null) || (obj.DAIPC._daipc == null)) return;
for (var i in obj.DAIPC._daipc) { if (obj.DAIPC._daipc[i]._registered == appid) { obj.DAIPC._daipc[i]._send(x); } }
}
// Send list of registered apps to the server
function updateRegisteredAppsToServer() {
if ((obj.DAIPC == null) || (obj.DAIPC._daipc == null)) return;
var apps = {};
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]++; } }
try { mesh.SendCommand({ action: 'sessions', type: 'app', value: apps }); } catch (ex) { }
}
// Remove a registered app
function removeRegisteredApp(pipe) {
for (var i = obj.DAIPC._daipc.length - 1; i >= 0; i--) { if (obj.DAIPC._daipc[i] === pipe) { obj.DAIPC._daipc.splice(i, 1); } }
if (pipe._registered != null) updateRegisteredAppsToServer();
}
function diagnosticAgent_uninstall() {
require('service-manager').manager.uninstallService('meshagentDiagnostic');
require('task-scheduler').delete('meshagentDiagnostic/periodicStart'); // TODO: Using "delete" here breaks the minifier since this is a reserved keyword
}
function diagnosticAgent_installCheck(install) {
try {
var diag = require('service-manager').manager.getService('meshagentDiagnostic');
return (diag);
} catch (ex) { }
if (!install) { return null; }
var svc = null;
try {
require('service-manager').manager.installService(
{
name: 'meshagentDiagnostic',
displayName: "Mesh Agent Diagnostic Service",
description: "Mesh Agent Diagnostic Service",
servicePath: process.execPath,
parameters: ['-recovery']
//files: [{ newName: 'diagnostic.js', _buffer: Buffer.from('LyoNCkNvcHlyaWdodCAyMDE5IEludGVsIENvcnBvcmF0aW9uDQoNCkxpY2Vuc2VkIHVuZGVyIHRoZSBBcGFjaGUgTGljZW5zZSwgVmVyc2lvbiAyLjAgKHRoZSAiTGljZW5zZSIpOw0KeW91IG1heSBub3QgdXNlIHRoaXMgZmlsZSBleGNlcHQgaW4gY29tcGxpYW5jZSB3aXRoIHRoZSBMaWNlbnNlLg0KWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0DQoNCiAgICBodHRwOi8vd3d3LmFwYWNoZS5vcmcvbGljZW5zZXMvTElDRU5TRS0yLjANCg0KVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQ0KZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywNCldJVEhPVVQgV0FSUkFOVElFUyBPUiBDT05ESVRJT05TIE9GIEFOWSBLSU5ELCBlaXRoZXIgZXhwcmVzcyBvciBpbXBsaWVkLg0KU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZA0KbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuDQoqLw0KDQp2YXIgaG9zdCA9IHJlcXVpcmUoJ3NlcnZpY2UtaG9zdCcpLmNyZWF0ZSgnbWVzaGFnZW50RGlhZ25vc3RpYycpOw0KdmFyIFJlY292ZXJ5QWdlbnQgPSByZXF1aXJlKCdNZXNoQWdlbnQnKTsNCg0KaG9zdC5vbignc2VydmljZVN0YXJ0JywgZnVuY3Rpb24gKCkNCnsNCiAgICBjb25zb2xlLnNldERlc3RpbmF0aW9uKGNvbnNvbGUuRGVzdGluYXRpb25zLkxPR0ZJTEUpOw0KICAgIGhvc3Quc3RvcCA9IGZ1bmN0aW9uKCkNCiAgICB7DQogICAgICAgIHJlcXVpcmUoJ3NlcnZpY2UtbWFuYWdlcicpLm1hbmFnZXIuZ2V0U2VydmljZSgnbWVzaGFnZW50RGlhZ25vc3RpYycpLnN0b3AoKTsNCiAgICB9DQogICAgUmVjb3ZlcnlBZ2VudC5vbignQ29ubmVjdGVkJywgZnVuY3Rpb24gKHN0YXR1cykNCiAgICB7DQogICAgICAgIGlmIChzdGF0dXMgPT0gMCkNCiAgICAgICAgew0KICAgICAgICAgICAgY29uc29sZS5sb2coJ0RpYWdub3N0aWMgQWdlbnQ6IFNlcnZlciBjb25uZWN0aW9uIGxvc3QuLi4nKTsNCiAgICAgICAgICAgIHJldHVybjsNCiAgICAgICAgfQ0KICAgICAgICBjb25zb2xlLmxvZygnRGlhZ25vc3RpYyBBZ2VudDogQ29ubmVjdGlvbiBFc3RhYmxpc2hlZCB3aXRoIFNlcnZlcicpOw0KICAgICAgICBzdGFydCgpOw0KICAgIH0pOw0KfSk7DQpob3N0Lm9uKCdub3JtYWxTdGFydCcsIGZ1bmN0aW9uICgpDQp7DQogICAgaG9zdC5zdG9wID0gZnVuY3Rpb24gKCkNCiAgICB7DQogICAgICAgIHByb2Nlc3MuZXhpdCgpOw0KICAgIH0NCiAgICBjb25zb2xlLmxvZygnTm9uIFNlcnZpY2UgTW9kZScpOw0KICAgIFJlY292ZXJ5QWdlbnQub24oJ0Nvbm5lY3RlZCcsIGZ1bmN0aW9uIChzdGF0dXMpDQogICAgew0KICAgICAgICBpZiAoc3RhdHVzID09IDApDQogICAgICAgIHsNCiAgICAgICAgICAgIGNvbnNvbGUubG9nKCdEaWFnbm9zdGljIEFnZW50OiBTZXJ2ZXIgY29ubmVjdGlvbiBsb3N0Li4uJyk7DQogICAgICAgICAgICByZXR1cm47DQogICAgICAgIH0NCiAgICAgICAgY29uc29sZS5sb2coJ0RpYWdub3N0aWMgQWdlbnQ6IENvbm5lY3Rpb24gRXN0YWJsaXNoZWQgd2l0aCBTZXJ2ZXInKTsNCiAgICAgICAgc3RhcnQoKTsNCiAgICB9KTsNCn0pOw0KaG9zdC5vbignc2VydmljZVN0b3AnLCBmdW5jdGlvbiAoKSB7IHByb2Nlc3MuZXhpdCgpOyB9KTsNCmhvc3QucnVuKCk7DQoNCg0KZnVuY3Rpb24gc3RhcnQoKQ0Kew0KDQp9Ow0K', 'base64') }]
});
svc = require('service-manager').manager.getService('meshagentDiagnostic');
}
catch (ex) { return null; }
var proxyConfig = require('global-tunnel').proxyConfig;
var cert = require('MeshAgent').GenerateAgentCertificate('CN=MeshNodeDiagnosticCertificate');
var nodeid = require('tls').loadCertificate(cert.root).getKeyHash().toString('base64');
ddb = require('SimpleDataStore').Create(svc.appWorkingDirectory().replace('\\', '/') + '/meshagentDiagnostic.db');
ddb.Put('disableUpdate', '1');
ddb.Put('MeshID', Buffer.from(require('MeshAgent').ServerInfo.MeshID, 'hex'));
ddb.Put('ServerID', require('MeshAgent').ServerInfo.ServerID);
ddb.Put('MeshServer', require('MeshAgent').ServerInfo.ServerUri);
if (cert.root.pfx) { ddb.Put('SelfNodeCert', cert.root.pfx); }
if (cert.tls) { ddb.Put('SelfNodeTlsCert', cert.tls.pfx); }
if (proxyConfig) {
ddb.Put('WebProxy', proxyConfig.host + ':' + proxyConfig.port);
} else {
ddb.Put('ignoreProxyFile', '1');
}
require('MeshAgent').SendCommand({ action: 'diagnostic', value: { command: 'register', value: nodeid } });
require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: "Diagnostic Agent Registered [" + nodeid.length + "/" + nodeid + "]" });
delete ddb;
// Set a recurrent task, to run the Diagnostic Agent every 2 days
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' });
//require('task-scheduler').create({ name: 'meshagentDiagnostic/periodicStart', daily: '1', time: '17:16', service: 'meshagentDiagnostic' });
return (svc);
}
// Monitor the file 'batterystate.txt' in the agent's folder and sends battery update when this file is changed.
if ((require('fs').existsSync(process.cwd() + 'batterystate.txt')) && (require('fs').watch != null)) {
// Setup manual battery monitoring
require('MeshAgent')._batteryFileWatcher = require('fs').watch(process.cwd(), function () {
if (require('MeshAgent')._batteryFileTimer != null) return;
require('MeshAgent')._batteryFileTimer = setTimeout(function () {
try {
require('MeshAgent')._batteryFileTimer = null;
var data = null;
try { data = require('fs').readFileSync(process.cwd() + 'batterystate.txt').toString(); } catch (ex) { }
if ((data != null) && (data.length < 10)) {
data = data.split(',');
if ((data.length == 2) && ((data[0] == 'ac') || (data[0] == 'dc'))) {
var level = parseInt(data[1]);
if ((level >= 0) && (level <= 100)) { require('MeshAgent').SendCommand({ action: 'battery', state: data[0], level: level }); }
}
}
} catch (ex) { }
}, 1000);
});
}
else {
try {
// Setup normal battery monitoring
if (require('identifiers').isBatteryPowered && require('identifiers').isBatteryPowered()) {
require('MeshAgent')._battLevelChanged = function _battLevelChanged(val) {
_battLevelChanged.self._currentBatteryLevel = val;
_battLevelChanged.self.SendCommand({ action: 'battery', state: _battLevelChanged.self._currentPowerState, level: val });
};
require('MeshAgent')._battLevelChanged.self = require('MeshAgent');
require('MeshAgent')._powerChanged = function _powerChanged(val) {
_powerChanged.self._currentPowerState = (val == 'AC' ? 'ac' : 'dc');
_powerChanged.self.SendCommand({ action: 'battery', state: (val == 'AC' ? 'ac' : 'dc'), level: _powerChanged.self._currentBatteryLevel });
};
require('MeshAgent')._powerChanged.self = require('MeshAgent');
require('MeshAgent').on('Connected', function (status) {
if (status == 0) {
require('power-monitor').removeListener('acdc', this._powerChanged);
require('power-monitor').removeListener('batteryLevel', this._battLevelChanged);
} else {
require('power-monitor').on('acdc', this._powerChanged);
require('power-monitor').on('batteryLevel', this._battLevelChanged);
}
});
}
}
catch (ex) { }
}
// MeshAgent JavaScript Core Module. This code is sent to and running on the mesh agent.
var meshCoreObj = { action: 'coreinfo', value: (require('MeshAgent').coreHash ? ((process.versions.compileTime ? process.versions.compileTime : '').split(', ')[1].replace(' ', ' ') + ', ' + crc32c(require('MeshAgent').coreHash)) : ('MeshCore v6')), caps: 14, root: require('user-sessions').isRoot() }; // Capability bitmask: 1 = Desktop, 2 = Terminal, 4 = Files, 8 = Console, 16 = JavaScript, 32 = Temporary Agent, 64 = Recovery Agent
// Get the operating system description string
try { require('os').name().then(function (v) { meshCoreObj.osdesc = v; meshCoreObjChanged(); }); } catch (ex) { }
// Setup logged in user monitoring (THIS IS BROKEN IN WIN7)
try {
var userSession = require('user-sessions');
userSession.on('changed', function onUserSessionChanged() {
userSession.enumerateUsers().then(function (users) {
if (process.platform == 'linux') {
if (userSession._startTime == null) {
userSession._startTime = Date.now();
userSession._count = users.length;
}
else if (Date.now() - userSession._startTime < 10000 && users.length == userSession._count) {
userSession.removeAllListeners('changed');
return;
}
}
var u = [], a = users.Active;
for (var i = 0; i < a.length; i++) {
var un = a[i].Domain ? (a[i].Domain + '\\' + a[i].Username) : (a[i].Username);
if (u.indexOf(un) == -1) { u.push(un); } // Only push users in the list once.
}
meshCoreObj.users = u;
meshCoreObjChanged();
});
});
userSession.emit('changed');
//userSession.on('locked', function (user) { sendConsoleText('[' + (user.Domain ? user.Domain + '\\' : '') + user.Username + '] has LOCKED the desktop'); });
//userSession.on('unlocked', function (user) { sendConsoleText('[' + (user.Domain ? user.Domain + '\\' : '') + user.Username + '] has UNLOCKED the desktop'); });
} catch (ex) { }
var meshServerConnectionState = 0;
var tunnels = {};
var lastNetworkInfo = null;
var lastPublicLocationInfo = null;
var selfInfoUpdateTimer = null;
var http = require('http');
var net = require('net');
var fs = require('fs');
var rtc = require('ILibWebRTC');
var amt = null;
var processManager = require('process-manager');
var wifiScannerLib = null;
var wifiScanner = null;
var networkMonitor = null;
var nextTunnelIndex = 1;
var apftunnel = null;
var tunnelUserCount = { terminal: {}, files: {}, tcp: {}, udp: {}, msg: {} }; // List of userid->count sessions for terminal, files and TCP/UDP routing
// Add to the server event log
function MeshServerLog(msg, state) {
if (typeof msg == 'string') { msg = { action: 'log', msg: msg }; } else { msg.action = 'log'; }
if (state) {
if (state.userid) { msg.userid = state.userid; }
if (state.username) { msg.username = state.username; }
if (state.sessionid) { msg.sessionid = state.sessionid; }
if (state.remoteaddr) { msg.remoteaddr = state.remoteaddr; }
if (state.guestname) { msg.guestname = state.guestname; }
}
mesh.SendCommand(msg);
}
// Add to the server event log, use internationalized events
function MeshServerLogEx(id, args, msg, state) {
var msg = { action: 'log', msgid: id, msgArgs: args, msg: msg };
if (state) {
if (state.userid) { msg.userid = state.userid; }
if (state.xuserid) { msg.xuserid = state.xuserid; }
if (state.username) { msg.username = state.username; }
if (state.sessionid) { msg.sessionid = state.sessionid; }
if (state.remoteaddr) { msg.remoteaddr = state.remoteaddr; }
if (state.guestname) { msg.guestname = state.guestname; }
}
mesh.SendCommand(msg);
}
// Import libraries
db = require('SimpleDataStore').Shared();
sha = require('SHA256Stream');
mesh = require('MeshAgent');
childProcess = require('child_process');
if (mesh.hasKVM == 1) { // if the agent is compiled with KVM support
// Check if this computer supports a desktop
try {
if ((process.platform == 'win32') || (process.platform == 'darwin') || (require('monitor-info').kvm_x11_support)) {
meshCoreObj.caps |= 1; meshCoreObjChanged();
} else if (process.platform == 'linux' || process.platform == 'freebsd') {
require('monitor-info').on('kvmSupportDetected', function (value) { meshCoreObj.caps |= 1; meshCoreObjChanged(); });
}
} catch (ex) { }
}
mesh.DAIPC = obj.DAIPC;
/*
// Try to load up the network monitor
try {
networkMonitor = require('NetworkMonitor');
networkMonitor.on('change', function () { sendNetworkUpdateNagle(); });
networkMonitor.on('add', function (addr) { sendNetworkUpdateNagle(); });
networkMonitor.on('remove', function (addr) { sendNetworkUpdateNagle(); });
} catch (ex) { networkMonitor = null; }
*/
// Fetch the SMBios Tables
var SMBiosTables = null;
var SMBiosTablesRaw = null;
try {
var SMBiosModule = null;
try { SMBiosModule = require('smbios'); } catch (ex) { }
if (SMBiosModule != null) {
SMBiosModule.get(function (data) {
if (data != null) {
SMBiosTablesRaw = data;
SMBiosTables = require('smbios').parse(data)
if (mesh.isControlChannelConnected) { mesh.SendCommand({ action: 'smbios', value: SMBiosTablesRaw }); }
// If SMBios tables say that Intel AMT is present, try to connect MEI
if (SMBiosTables.amtInfo && (SMBiosTables.amtInfo.AMT == true)) {
var amtmodule = require('amt-manage');
amt = new amtmodule(mesh, db, false);
amt.on('portBinding_LMS', function (map) { mesh.SendCommand({ action: 'lmsinfo', value: { ports: map.keys() } }); });
amt.on('stateChange_LMS', function (v) { if (!meshCoreObj.intelamt) { meshCoreObj.intelamt = {}; } meshCoreObj.intelamt.microlms = v; meshCoreObjChanged(); }); // 0 = Disabled, 1 = Connecting, 2 = Connected
amt.onStateChange = function (state) { if (state == 2) { sendPeriodicServerUpdate(1); } } // MEI State
amt.reset();
}
}
});
}
} catch (ex) { sendConsoleText("ex1: " + ex); }
// Try to load up the WIFI scanner
try {
var wifiScannerLib = require('wifi-scanner');
wifiScanner = new wifiScannerLib();
wifiScanner.on('accessPoint', function (data) { sendConsoleText("wifiScanner: " + data); });
} catch (ex) { wifiScannerLib = null; wifiScanner = null; }
// Get our location (lat/long) using our public IP address
var getIpLocationDataExInProgress = false;
var getIpLocationDataExCounts = [0, 0];
function getIpLocationDataEx(func) {
if (getIpLocationDataExInProgress == true) { return false; }
try {
getIpLocationDataExInProgress = true;
getIpLocationDataExCounts[0]++;
var options = http.parseUri("http://ipinfo.io/json");
options.method = 'GET';
http.request(options, function (resp) {
if (resp.statusCode == 200) {
var geoData = '';
resp.data = function (geoipdata) { geoData += geoipdata; };
resp.end = function () {
var location = null;
try {
if (typeof geoData == 'string') {
var result = JSON.parse(geoData);
if (result.ip && result.loc) { location = result; }
}
} catch (ex) { }
if (func) { getIpLocationDataExCounts[1]++; func(location); }
}
} else
{ func(null); }
getIpLocationDataExInProgress = false;
}).end();
return true;
}
catch (ex) { return false; }
}
// Remove all Gateway MAC addresses for interface list. This is useful because the gateway MAC is not always populated reliably.
function clearGatewayMac(str) {
if (typeof str != 'string') return null;
var x = JSON.parse(str);
for (var i in x.netif) { try { if (x.netif[i].gatewaymac) { delete x.netif[i].gatewaymac } } catch (ex) { } }
return JSON.stringify(x);
}
function getIpLocationData(func) {
// Get the location information for the cache if possible
var publicLocationInfo = db.Get('publicLocationInfo');
if (publicLocationInfo != null) { publicLocationInfo = JSON.parse(publicLocationInfo); }
if (publicLocationInfo == null) {
// Nothing in the cache, fetch the data
getIpLocationDataEx(function (locationData) {
if (locationData != null) {
publicLocationInfo = {};
publicLocationInfo.netInfoStr = lastNetworkInfo;
publicLocationInfo.locationData = locationData;
var x = db.Put('publicLocationInfo', JSON.stringify(publicLocationInfo)); // Save to database
if (func) func(locationData); // Report the new location
}
else {
if (func) func(null); // Report no location
}
});
}
else {
// Check the cache
if (clearGatewayMac(publicLocationInfo.netInfoStr) == clearGatewayMac(lastNetworkInfo)) {
// Cache match
if (func) func(publicLocationInfo.locationData);
}
else {
// Cache mismatch
getIpLocationDataEx(function (locationData) {
if (locationData != null) {
publicLocationInfo = {};
publicLocationInfo.netInfoStr = lastNetworkInfo;
publicLocationInfo.locationData = locationData;
var x = db.Put('publicLocationInfo', JSON.stringify(publicLocationInfo)); // Save to database
if (func) func(locationData); // Report the new location
}
else {
if (func) func(publicLocationInfo.locationData); // Can't get new location, report the old location
}
});
}
}
}
// Polyfill String.endsWith
if (!String.prototype.endsWith) {
String.prototype.endsWith = function (searchString, position) {
var subjectString = this.toString();
if (typeof position !== 'number' || !isFinite(position) || Math.floor(position) !== position || position > subjectString.length) { position = subjectString.length; }
position -= searchString.length;
var lastIndex = subjectString.lastIndexOf(searchString, position);
return lastIndex !== -1 && lastIndex === position;
};
}
// Polyfill path.join
obj.path =
{
join: function () {
var x = [];
for (var i in arguments) {
var w = arguments[i];
if (w != null) {
while (w.endsWith('/') || w.endsWith('\\')) { w = w.substring(0, w.length - 1); }
if (i != 0) {
while (w.startsWith('/') || w.startsWith('\\')) { w = w.substring(1); }
}
x.push(w);
}
}
if (x.length == 0) return '/';
return x.join('/');
}
};
// Replace a string with a number if the string is an exact number
function toNumberIfNumber(x) { if ((typeof x == 'string') && (+parseInt(x) === x)) { x = parseInt(x); } return x; }
// Convert decimal to hex
function char2hex(i) { return (i + 0x100).toString(16).substr(-2).toUpperCase(); }
// Convert a raw string to a hex string
function rstr2hex(input) { var r = '', i; for (i = 0; i < input.length; i++) { r += char2hex(input.charCodeAt(i)); } return r; }
// Convert a buffer into a string
function buf2rstr(buf) { var r = ''; for (var i = 0; i < buf.length; i++) { r += String.fromCharCode(buf[i]); } return r; }
// Convert a hex string to a raw string // TODO: Do this using Buffer(), will be MUCH faster
function hex2rstr(d) {
if (typeof d != "string" || d.length == 0) return '';
var r = '', m = ('' + d).match(/../g), t;
while (t = m.shift()) r += String.fromCharCode('0x' + t);
return r
}
// Convert an object to string with all functions
function objToString(x, p, pad, ret) {
if (ret == undefined) ret = '';
if (p == undefined) p = 0;
if (x == null) { return '[null]'; }
if (p > 8) { return '[...]'; }
if (x == undefined) { return '[undefined]'; }
if (typeof x == 'string') { if (p == 0) return x; return '"' + x + '"'; }
if (typeof x == 'buffer') { return '[buffer]'; }
if (typeof x != 'object') { return x; }
var r = '{' + (ret ? '\r\n' : ' ');
for (var i in x) { if (i != '_ObjectID') { r += (addPad(p + 2, pad) + i + ': ' + objToString(x[i], p + 2, pad, ret) + (ret ? '\r\n' : ' ')); } }
return r + addPad(p, pad) + '}';
}
// Return p number of spaces
function addPad(p, ret) { var r = ''; for (var i = 0; i < p; i++) { r += ret; } return r; }
// Split a string taking into account the quoats. Used for command line parsing
function splitArgs(str) {
var myArray = [], myRegexp = /[^\s"]+|"([^"]*)"/gi;
do { var match = myRegexp.exec(str); if (match != null) { myArray.push(match[1] ? match[1] : match[0]); } } while (match != null);
return myArray;
}
// Parse arguments string array into an object
function parseArgs(argv) {
var results = { '_': [] }, current = null;
for (var i = 1, len = argv.length; i < len; i++) {
var x = argv[i];
if (x.length > 2 && x[0] == '-' && x[1] == '-') {
if (current != null) { results[current] = true; }
current = x.substring(2);
} else {
if (current != null) { results[current] = toNumberIfNumber(x); current = null; } else { results['_'].push(toNumberIfNumber(x)); }
}
}
if (current != null) { results[current] = true; }
return results;
}
// Get server target url with a custom path
function getServerTargetUrl(path) {
var x = mesh.ServerUrl;
//sendConsoleText("mesh.ServerUrl: " + mesh.ServerUrl);
if (x == null) { return null; }
if (path == null) { path = ''; }
x = http.parseUri(x);
if (x == null) return null;
return x.protocol + '//' + x.host + ':' + x.port + '/' + path;
}
// Get server url. If the url starts with "*/..." change it, it not use the url as is.
function getServerTargetUrlEx(url) {
if (url.substring(0, 2) == '*/') { return getServerTargetUrl(url.substring(2)); }
return url;
}
function sendWakeOnLanEx_interval() {
var t = require('MeshAgent').wakesockets;
if (t.list.length == 0) {
clearInterval(t);
delete require('MeshAgent').wakesockets;
return;
}
var mac = t.list.shift().split(':').join('')
var magic = 'FFFFFFFFFFFF';
for (var x = 1; x <= 16; ++x) { magic += mac; }
var magicbin = Buffer.from(magic, 'hex');
for (var i in t.sockets) {
t.sockets[i].send(magicbin, 7, '255.255.255.255');
//sendConsoleText('Sending wake packet on ' + JSON.stringify(t.sockets[i].address()));
}
}
function sendWakeOnLanEx(hexMacList) {
var ret = 0;
if (require('MeshAgent').wakesockets == null) {
// Create a new interval timer
require('MeshAgent').wakesockets = setInterval(sendWakeOnLanEx_interval, 10);
require('MeshAgent').wakesockets.sockets = [];
require('MeshAgent').wakesockets.list = hexMacList;
var interfaces = require('os').networkInterfaces();
for (var adapter in interfaces) {
if (interfaces.hasOwnProperty(adapter)) {
for (var i = 0; i < interfaces[adapter].length; ++i) {
var addr = interfaces[adapter][i];
if ((addr.family == 'IPv4