UNPKG

meshcentral

Version:

Web based remote computer management and file server

876 lines (816 loc) • 201 kB
/* Copyright 2018-2020 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 }); }); // NOTE: This seems to cause big problems, don't enable the debugger in the server's meshcore. //attachDebugger({ webport: 9999, wait: 1 }).then(function (prt) { console.log('Point Browser for Debug to port: ' + prt); }); // 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; 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; function createMeshCore(agent) { var obj = {}; 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 (x) { } if (writtenSize != actualSize) { try { require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MeshCentralAgent', 'EstimatedSize', actualSize); } catch (x2) { } } } catch (ex) { } // Check to see if we are the Installed Mesh Agent Service, if we are, make sure we can run in Safe Mode try { var meshCheck = false; try { meshCheck = require('service-manager').manager.getService('Mesh Agent').isMe(); } catch (mce) { } if (meshCheck && require('win-bcd').isSafeModeService && !require('win-bcd').isSafeModeService('Mesh Agent')) { require('win-bcd').enableSafeModeService('Mesh Agent'); } } catch (ex) { } } 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(); } } } } // Create Secure IPC for Diagnostic Agent Communications obj.DAIPC = require('net').createServer(); if (process.platform != 'win32') { try { require('fs').unlinkSync(process.cwd() + '/DAIPC'); } catch (ee) { } } obj.DAIPC.IPCPATH = process.platform == 'win32' ? ('\\\\.\\pipe\\' + require('_agentNodeId')() + '-DAIPC') : (process.cwd() + '/DAIPC'); try { obj.DAIPC.listen({ path: obj.DAIPC.IPCPATH }); } catch (e) { } 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.end(packet); }; this._daipc = c; c.parent = this; c.on('end', function () { console.log("Connection Closed"); this.parent._daipc = null; }); c.on('data', function (chunk) { if (chunk.length < 4) { this.unshift(chunk); return; } var len = chunk.readUInt32LE(0); if (len > 8192) { this.parent._daipc = null; this.end(); return; } if (chunk.length < len) { this.unshift(chunk); return; } var data = chunk.slice(4, len); try { data = JSON.parse(data.toString()); } catch (de) { this.parent._daipc = null; this.end(); return; } if (!data.cmd) { this.parent._daipc = null; this.end(); return; } try { switch (data.cmd) { 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; } break; default: this.parent._daipc = null; this.end(); return; } } catch (xe) { this.parent._daipc = null; this.end(); return; } }); }); function diagnosticAgent_uninstall() { require('service-manager').manager.uninstallService('meshagentDiagnostic'); require('task-scheduler').delete('meshagentDiagnostic/periodicStart'); }; function diagnosticAgent_installCheck(install) { try { var diag = require('service-manager').manager.getService('meshagentDiagnostic'); return (diag); } catch (e) { } 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 (e) { 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); } 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); } }); } /* function borderController() { this.container = null; this.Start = function Start(user) { if (this.container == null) { if (process.platform == 'win32') { try { this.container = require('ScriptContainer').Create({ processIsolation: 1, sessionId: user.SessionId }); } catch (ex) { this.container = require('ScriptContainer').Create({ processIsolation: 1 }); } } else { this.container = require('ScriptContainer').Create({ processIsolation: 1, sessionId: user.uid }); } this.container.parent = this; this.container.addModule('monitor-info', getJSModule('monitor-info')); this.container.addModule('monitor-border', getJSModule('monitor-border')); this.container.addModule('promise', getJSModule('promise')); this.container.once('exit', function (code) { sendConsoleText('Border Process Exited with code: ' + code); this.parent.container = this.parent._container = null; }); this.container.ExecuteString("var border = require('monitor-border'); border.Start();"); } } this.Stop = function Stop() { if (this.container != null) { this._container = this.container; this._container.parent = this; this.container = null; this._container.exit(); } } } obj.borderManager = new borderController(); */ // MeshAgent JavaScript Core Module. This code is sent to and running on the mesh agent. var meshCoreObj = { action: 'coreinfo', value: (require('MeshAgent').coreHash ? ('MeshCore CRC-' + crc32c(require('MeshAgent').coreHash)) : ('MeshCore v6')), caps: 14 }; // Capability bitmask: 1 = Desktop, 2 = Terminal, 4 = Files, 8 = Console, 16 = JavaScript, 32 = Temporary Agent, 64 = Recovery Agent // Get the operating system description string try { require('os').name().then(function (v) { meshCoreObj.osdesc = v; }); } catch (ex) { } var meshServerConnectionState = 0; var tunnels = {}; var lastMeInfo = null; 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 amtscanner = null; var nextTunnelIndex = 1; var amtPolicy = null; var apftunnel = null; var tunnelUserCount = { terminal: {}, files: {} }; // List of userid->count sessions for terminal and files. // 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; } } mesh.SendCommand(msg); } // If we are running in Duktape, agent will be null if (agent == null) { // Running in native agent, 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; } else if(process.platform == 'linux' || process.platform == 'freebsd') { require('monitor-info').on('kvmSupportDetected', function (value) { meshCoreObj.caps |= 1; mesh.SendCommand(meshCoreObj); }); } } catch (ex) { } } } else { // Running in nodejs meshCoreObj.value += '-NodeJS'; meshCoreObj.caps = 8; mesh = agent.getMeshApi(); } mesh.DAIPC = obj.DAIPC; /* var AMTScanner = require("AMTScanner"); var scan = new AMTScanner(); scan.on("found", function (data) { if (typeof data === 'string') { console.log(data); } else { console.log(JSON.stringify(data, null, " ")); } }); scan.scan("10.2.55.140", 1000); scan.scan("10.2.55.139-10.2.55.145", 1000); scan.scan("10.2.55.128/25", 2000); */ /* // 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 (e) { networkMonitor = null; } */ // Try to load up the Intel AMT scanner try { var AMTScannerModule = require('amt-scanner'); amtscanner = new AMTScannerModule(); //amtscanner.on('found', function (data) { if (typeof data != 'string') { data = JSON.stringify(data, null, " "); } sendConsoleText(data); }); } catch (ex) { amtscanner = 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.onStateChange = function (state) { if (state == 2) { sendPeriodicServerUpdate(1); } } if (amtPolicy != null) { amt.setPolicy(amtPolicy); } amt.start(); } } }); } } 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 (e) { } if (func) { getIpLocationDataExCounts[1]++; func(location); } } } else { func(null); } getIpLocationDataExInProgress = false; }).end(); return true; } catch (e) { 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 (str == null) return null; var x = JSON.parse(str); for (var i in x.netif) { if (x.netif[i].gatewaymac) { delete x.netif[i].gatewaymac } } 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; } // Send a wake-on-lan packet function sendWakeOnLan(hexMac) { var count = 0; try { var interfaces = require('os').networkInterfaces(); var magic = 'FFFFFFFFFFFF'; for (var x = 1; x <= 16; ++x) { magic += hexMac; } var magicbin = Buffer.from(magic, 'hex'); 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') && (addr.mac != '00:00:00:00:00:00')) { var socket = require('dgram').createSocket({ type: 'udp4' }); socket.bind({ address: addr.address }); socket.setBroadcast(true); socket.send(magicbin, 7, '255.255.255.255'); count++; } } } } } catch (e) { } return count; } // Handle a mesh agent command function handleServerCommand(data) { if (typeof data == 'object') { // If this is a console command, parse it and call the console handler switch (data.action) { case 'msg': { switch (data.type) { case 'console': { // Process a console command if (data.value && data.sessionid) { MeshServerLog("Processing console command: " + data.value, data); var args = splitArgs(data.value); processConsoleCommand(args[0].toLowerCase(), parseArgs(args), data.rights, data.sessionid); } break; } case 'tunnel': { if (data.value != null) { // Process a new tunnel connection request // Create a new tunnel object var xurl = getServerTargetUrlEx(data.value); if (xurl != null) { var woptions = http.parseUri(xurl); woptions.rejectUnauthorized = 0; //sendConsoleText(JSON.stringify(woptions)); //sendConsoleText('TUNNEL: ' + JSON.stringify(data)); var tunnel = http.request(woptions); tunnel.upgrade = onTunnelUpgrade; tunnel.on('error', function (e) { sendConsoleText("ERROR: Unable to connect relay tunnel to: " + this.url + ", " + JSON.stringify(e)); }); tunnel.sessionid = data.sessionid; tunnel.rights = data.rights; tunnel.consent = data.consent; tunnel.privacybartext = data.privacybartext ? data.privacybartext : "Sharing desktop with: {0}"; tunnel.username = data.username; tunnel.userid = data.userid; tunnel.remoteaddr = data.remoteaddr; tunnel.state = 0; tunnel.url = xurl; tunnel.protocol = 0; tunnel.soptions = data.soptions; tunnel.tcpaddr = data.tcpaddr; tunnel.tcpport = data.tcpport; tunnel.udpaddr = data.udpaddr; tunnel.udpport = data.udpport; tunnel.end(); // Put the tunnel in the tunnels list var index = nextTunnelIndex++; tunnel.index = index; tunnels[index] = tunnel; //sendConsoleText('New tunnel connection #' + index + ': ' + tunnel.url + ', rights: ' + tunnel.rights, data.sessionid); } } break; } case 'messagebox': { // Display a message box if (data.title && data.msg) { MeshServerLog("Displaying message box, title=" + data.title + ", message=" + data.msg, data); data.msg = data.msg.split('\r').join('\\r').split('\n').join('\\n'); try { require('message-box').create(data.title, data.msg, 120); } catch (ex) { } } break; } case 'ps': { // Return the list of running processes if (data.sessionid) { processManager.getProcesses(function (plist) { mesh.SendCommand({ action: 'msg', type: 'ps', value: JSON.stringify(plist), sessionid: data.sessionid }); }); } break; } case 'pskill': { // Kill a process if (data.value) { MeshServerLog("Killing process " + data.value, data); try { process.kill(data.value); } catch (e) { sendConsoleText("pskill: " + JSON.stringify(e)); } } break; } case 'services': { // Return the list of installed services var services = null; try { services = require('service-manager').manager.enumerateService(); } catch (e) { } if (services != null) { mesh.SendCommand({ action: 'msg', type: 'services', value: JSON.stringify(services), sessionid: data.sessionid }); } break; } case 'serviceStop': { // Stop a service try { var service = require('service-manager').manager.getService(data.serviceName); if (service != null) { service.stop(); } } catch (e) { } break; } case 'serviceStart': { // Start a service try { var service = require('service-manager').manager.getService(data.serviceName); if (service != null) { service.start(); } } catch (e) { } break; } case 'serviceRestart': { // Restart a service try { var service = require('service-manager').manager.getService(data.serviceName); if (service != null) { service.restart(); } } catch (e) { } break; } case 'deskBackground': { // Toggle desktop background try { if (process.platform == 'win32') { var stype = require('user-sessions').getProcessOwnerName(process.pid).tsid == 0 ? 1 : 0; var sid = undefined; if (stype == 1) { if (require('MeshAgent')._tsid != null) { stype = 5; sid = require('MeshAgent')._tsid; } } var id = require('user-sessions').getProcessOwnerName(process.pid).tsid == 0 ? 1 : 0; var child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0='], { type: stype, uid: sid }); child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); child.stderr.on('data', function () { }); child.waitExit(); var current = child.stdout.str.trim(); if (current != '') { require('MeshAgent')._wallpaper = current; } child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0=', current != '' ? '""' : require('MeshAgent')._wallpaper], { type: stype, uid: sid }); child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); child.stderr.on('data', function () { }); child.waitExit(); } else { var id = require('user-sessions').consoleUid(); var current = require('linux-gnome-helpers').getDesktopWallpaper(id); if (current != '/dev/null') { require('MeshAgent')._wallpaper = current; } require('linux-gnome-helpers').setDesktopWallpaper(id, current != '/dev/null' ? undefined : require('MeshAgent')._wallpaper); } } catch (e) { sendConsoleText(e); } break; } case 'openUrl': { // Open a local web browser and return success/fail MeshServerLog("Opening: " + data.url, data); sendConsoleText("OpenURL: " + data.url); if (data.url) { mesh.SendCommand({ action: 'msg', type: 'openUrl', url: data.url, sessionid: data.sessionid, success: (openUserDesktopUrl(data.url) != null) }); } break; } case 'getclip': { // Send the load clipboard back to the user //sendConsoleText('getClip: ' + JSON.stringify(data)); if (require('MeshAgent').isService) { require('clipboard').dispatchRead().then(function (str) { if (str) { MeshServerLog("Getting clipboard content, " + str.length + " byte(s)", data); mesh.SendCommand({ action: 'msg', type: 'getclip', sessionid: data.sessionid, data: str }); } }); } else { require("clipboard").read().then(function (str) { if (str) { MeshServerLog("Getting clipboard content, " + str.length + " byte(s)", data); mesh.SendCommand({ action: 'msg', type: 'getclip', sessionid: data.sessionid, data: str }); } }); } break; } case 'setclip': { // Set the load clipboard to a user value //sendConsoleText('setClip: ' + JSON.stringify(data)); if (typeof data.data == 'string') { MeshServerLog("Setting clipboard content, " + data.data.length + " byte(s)", data); if (require('MeshAgent').isService) { require('clipboard').dispatchWrite(data.data); } else { require("clipboard")(data.data); } // Set the clipboard mesh.SendCommand({ action: 'msg', type: 'setclip', sessionid: data.sessionid, success: true }); } break; } case 'userSessions': { // Send back current user sessions list, this is Windows only. //sendConsoleText('userSessions: ' + JSON.stringify(data)); if (process.platform != 'win32') break; var p = require('user-sessions').enumerateUsers(); p.sessionid = data.sessionid; p.then(function (u) { mesh.SendCommand({ action: 'msg', type: 'userSessions', sessionid: data.sessionid, data: u, tag: data.tag }); }); break; } default: // Unknown action, ignore it. break; } break; } case 'acmactivate': { if (amt != null) { MeshServerLog("Attempting Intel AMT ACM mode activation", data); amt.setAcmResponse(data); } break; } case 'wakeonlan': { // Send wake-on-lan on all interfaces for all MAC addresses in da