/* 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, \"KeepAlive\"); split(a[2], b, \"<\"); split(b[2], c, \">\"); "); child.stdin.write(" if(c[1]==\"dict\"){ split(a[2], d, \"\"); if(split(d[1], truval, \"\")>1) { split(truval[1], kn1, \"\"); split(kn1[2], kn2, \"\"); 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('KeepAlive'); if (tokens[1].split('>')[0].split('<')[1] == 'dict') { var tmp = tokens[1].split(''); tmp.shift(); tokens[1] = '\n ' + tmp.join(''); tokens = tokens.join('KeepAlive'); 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 = '\n'; plist += '\n'; plist += '\n'; plist += ' \n'; plist += ' Label\n'; plist += (' meshagentRepair\n'); plist += ' ProgramArguments\n'; plist += ' \n'; plist += (' ' + process.execPath + '\n'); plist += ' macosRepair.js\n'; plist += ' \n'; plist += ' WorkingDirectory\n'; plist += (' ' + process.cwd() + '\n'); plist += ' RunAtLoad\n'; plist += ' \n'; plist += ' \n'; 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')) { try { var socket = require('dgram').createSocket({ type: 'udp4' }); socket.bind({ address: addr.address }); socket.setBroadcast(true); socket.send(magicbin, 7, '255.255.255.255'); socket.descriptorMetadata = 'WoL (' + addr.address + ' => ' + hexMac + ')'; count++; } catch(ee) { } } } } } } 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) { xurl = xurl.split('$').join('%24').split('@').join('%40'); // Escape the $ and @ characters 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 data.macs array. The array is a list of HEX MAC addresses. sendConsoleText("Server requesting wake-on-lan for: " + data.macs.join(', ')); for (var i in data.macs) { sendWakeOnLan(data.macs[i]); } break; } case 'uninstallagent': // Uninstall this agent var agentName = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'; if (require('service-manager').manager.getService(agentName).isMe()) { try { diagnosticAgent_uninstall(); } catch (x) { } var js = "require('service-manager').manager.getService('" + agentName + "').stop(); require('service-manager').manager.uninstallService('" + agentName + "'); process.exit();"; 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 }); } break; case 'poweraction': { // Server telling us to execute a power action if ((mesh.ExecPowerState != undefined) && (data.actiontype)) { var forced = 0; if (data.forced == 1) { forced = 1; } data.actiontype = parseInt(data.actiontype); MeshServerLog("Performing power action=" + data.actiontype + ", forced=" + forced, data); sendConsoleText("Performing power action=" + data.actiontype + ", forced=" + forced + '.'); var r = mesh.ExecPowerState(data.actiontype, forced); sendConsoleText("ExecPowerState returned code: " + r); } break; } case 'iplocation': { // 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 getIpLocationData(function (location) { mesh.SendCommand({ action: 'iplocation', type: 'publicip', value: location }); }); break; } case 'toast': { // Display a toast message if (data.title && data.msg) { MeshServerLog("Displaying toast message, title=" + data.title + ", message=" + data.msg, data); data.msg = data.msg.split('\r').join('\\r').split('\n').join('\\n'); try { require('toaster').Toast(data.title, data.msg); } catch (ex) { } } break; } case 'openUrl': { // Open a local web browser and return success/fail //sendConsoleText('OpenURL: ' + data.url); MeshServerLog("Opening: " + data.url, data); if (data.url) { mesh.SendCommand({ action: 'openUrl', url: data.url, sessionid: data.sessionid, success: (openUserDesktopUrl(data.url) != null) }); } break; } case 'amtPolicy': { // Store the latest Intel AMT policy amtPolicy = data.amtPolicy; if (data.amtPolicy != null) { db.Put('amtPolicy', JSON.stringify(data.amtPolicy)); } else { db.Put('amtPolicy', null); } if (amt != null) { amt.setPolicy(amtPolicy, true); } break; } case 'getScript': { // Received a configuration script from the server sendConsoleText('getScript: ' + JSON.stringify(data)); break; } case 'sysinfo': { // Fetch system information getSystemInformation(function (results) { if ((results != null) && (data.hash != results.hash)) { mesh.SendCommand({ action: 'sysinfo', sessionid: this.sessionid, data: results }); } }); break; } case 'ping': { mesh.SendCommand('{"action":"pong"}'); break; } case 'pong': { break; } case 'plugin': { try { require(data.plugin).consoleaction(data, data.rights, data.sessionid, this); } catch (e) { throw e; } break; } default: // Unknown action, ignore it. break; } } } // Called when a file changed in the file system /* function onFileWatcher(a, b) { console.log('onFileWatcher', a, b, this.path); var response = getDirectoryInfo(this.path); if ((response != undefined) && (response != null)) { this.tunnel.s.write(JSON.stringify(response)); } } */ function getSystemInformation(func) { try { var results = { hardware: require('identifiers').get() }; // Hardware info if (results.hardware && results.hardware.windows) { // Remove extra entries and things that change quickly var x = results.hardware.windows.osinfo; try { delete x.FreePhysicalMemory; } catch (ex) { } try { delete x.FreeSpaceInPagingFiles; } catch (ex) { } try { delete x.FreeVirtualMemory; } catch (ex) { } try { delete x.LocalDateTime; } catch (ex) { } try { delete x.MaxProcessMemorySize; } catch (ex) { } try { delete x.TotalVirtualMemorySize; } catch (ex) { } try { delete x.TotalVisibleMemorySize; } catch (ex) { } try { if (results.hardware.windows.memory) { for (var i in results.hardware.windows.memory) { delete results.hardware.windows.memory[i].Node; } } if (results.hardware.windows.osinfo) { delete results.hardware.windows.osinfo.Node; } if (results.hardware.windows.partitions) { for (var i in results.hardware.windows.partitions) { delete results.hardware.windows.partitions[i].Node; } } } catch (ex) { } } if (process.platform == 'win32') { results.pendingReboot = require('win-info').pendingReboot(); } // Pending reboot /* if (process.platform == 'win32') { var defragResult = function (r) { if (typeof r == 'object') { results[this.callname] = r; } if (this.callname == 'defrag') { var pr = require('win-info').installedApps(); // Installed apps pr.callname = 'installedApps'; pr.sessionid = data.sessionid; pr.then(defragResult, defragResult); } else { results.winpatches = require('win-info').qfe(); // Windows patches results.hash = require('SHA384Stream').create().syncHash(JSON.stringify(results)).toString('hex'); func(results); } } var pr = require('win-info').defrag({ volume: 'C:' }); // Defrag TODO pr.callname = 'defrag'; pr.sessionid = data.sessionid; pr.then(defragResult, defragResult); } else { */ results.hash = require('SHA384Stream').create().syncHash(JSON.stringify(results)).toString('hex'); func(results); //} } catch (ex) { func(null, ex); } } // Get a formated response for a given directory path function getDirectoryInfo(reqpath) { var response = { path: reqpath, dir: [] }; if (((reqpath == undefined) || (reqpath == '')) && (process.platform == 'win32')) { // List all the drives in the root, or the root itself var results = null; try { results = fs.readDrivesSync(); } catch (e) { } // TODO: Anyway to get drive total size and free space? Could draw a progress bar. if (results != null) { for (var i = 0; i < results.length; ++i) { var drive = { n: results[i].name, t: 1 }; if (results[i].type == 'REMOVABLE') { drive.dt = 'removable'; } // TODO: See if this is USB/CDROM or something else, we can draw icons. response.dir.push(drive); } } } else { // List all the files and folders in this path if (reqpath == '') { reqpath = '/'; } var results = null, xpath = obj.path.join(reqpath, '*'); //if (process.platform == "win32") { xpath = xpath.split('/').join('\\'); } try { results = fs.readdirSync(xpath); } catch (e) { } if (results != null) { for (var i = 0; i < results.length; ++i) { if ((results[i] != '.') && (results[i] != '..')) { var stat = null, p = obj.path.join(reqpath, results[i]); //if (process.platform == "win32") { p = p.split('/').join('\\'); } try { stat = fs.statSync(p); } catch (e) { } // TODO: Get file size/date if ((stat != null) && (stat != undefined)) { if (stat.isDirectory() == true) { response.dir.push({ n: results[i], t: 2, d: stat.mtime }); } else { response.dir.push({ n: results[i], t: 3, s: stat.size, d: stat.mtime }); } } } } } } return response; } // Tunnel callback operations function onTunnelUpgrade(response, s, head) { this.s = s; s.httprequest = this; s.end = onTunnelClosed; s.tunnel = this; s.descriptorMetadata = "MeshAgent_relayTunnel"; if (require('MeshAgent').idleTimeout != null) { s.setTimeout(require('MeshAgent').idleTimeout * 1000); s.on('timeout', function () { this.ping(); this.setTimeout(require('MeshAgent').idleTimeout * 1000); }); } //sendConsoleText('onTunnelUpgrade - ' + this.tcpport + ' - ' + this.udpport); if (this.tcpport != null) { // This is a TCP relay connection, pause now and try to connect to the target. s.pause(); s.data = onTcpRelayServerTunnelData; var connectionOptions = { port: parseInt(this.tcpport) }; if (this.tcpaddr != null) { connectionOptions.host = this.tcpaddr; } else { connectionOptions.host = '127.0.0.1'; } s.tcprelay = net.createConnection(connectionOptions, onTcpRelayTargetTunnelConnect); s.tcprelay.peerindex = this.index; } if (this.udpport != null) { // This is a UDP relay connection, get the UDP socket setup. // TODO: *************** s.data = onUdpRelayServerTunnelData; s.udprelay = require('dgram').createSocket({ type: 'udp4' }); s.udprelay.bind({ port: 0 }); s.udprelay.peerindex = this.index; s.udprelay.on('message', onUdpRelayTargetTunnelConnect); s.udprelay.udpport = this.udpport; s.udprelay.udpaddr = this.udpaddr; s.udprelay.first = true; } else { // This is a normal connect for KVM/Terminal/Files s.data = onTunnelData; } } // Called when UDP relay data is received // TODO**** function onUdpRelayTargetTunnelConnect(data) { var peerTunnel = tunnels[this.peerindex]; peerTunnel.s.write(data); } // Called when we get data from the server for a TCP relay (We have to skip the first received 'c' and pipe the rest) function onUdpRelayServerTunnelData(data) { if (this.udprelay.first === true) { delete this.udprelay.first; // Skip the first 'c' that is received. } else { this.udprelay.send(data, parseInt(this.udprelay.udpport), this.udprelay.udpaddr ? this.udprelay.udpaddr : '127.0.0.1'); } } // Called when the TCP relay target is connected function onTcpRelayTargetTunnelConnect() { var peerTunnel = tunnels[this.peerindex]; this.pipe(peerTunnel.s); // Pipe Target --> Server peerTunnel.s.first = true; peerTunnel.s.resume(); } // Called when we get data from the server for a TCP relay (We have to skip the first received 'c' and pipe the rest) function onTcpRelayServerTunnelData(data) { if (this.first == true) { this.first = false; this.pipe(this.tcprelay); } // Pipe Server --> Target } function onTunnelClosed() { if (tunnels[this.httprequest.index] == null) return; // Stop duplicate calls. //sendConsoleText("Tunnel #" + this.httprequest.index + " closed.", this.httprequest.sessionid); delete tunnels[this.httprequest.index]; /* // Close the watcher if required if (this.httprequest.watcher != undefined) { //console.log('Closing watcher: ' + this.httprequest.watcher.path); //this.httprequest.watcher.close(); // TODO: This line causes the agent to crash!!!! delete this.httprequest.watcher; } */ // If there is a upload or download active on this connection, close the file if (this.httprequest.uploadFile) { fs.closeSync(this.httprequest.uploadFile); this.httprequest.uploadFile = undefined; } if (this.httprequest.downloadFile) { fs.closeSync(this.httprequest.downloadFile); this.httprequest.downloadFile = undefined; } // Clean up WebRTC if (this.webrtc != null) { if (this.webrtc.rtcchannel) { try { this.webrtc.rtcchannel.close(); } catch (e) { } this.webrtc.rtcchannel.removeAllListeners('data'); this.webrtc.rtcchannel.removeAllListeners('end'); delete this.webrtc.rtcchannel; } if (this.webrtc.websocket) { delete this.webrtc.websocket; } try { this.webrtc.close(); } catch (e) { } this.webrtc.removeAllListeners('connected'); this.webrtc.removeAllListeners('disconnected'); this.webrtc.removeAllListeners('dataChannel'); delete this.webrtc; } // Clean up WebSocket this.removeAllListeners('data'); } function onTunnelSendOk() { /*sendConsoleText("Tunnel #" + this.index + " SendOK.", this.sessionid);*/ } function onTunnelData(data) { //console.log("OnTunnelData"); //sendConsoleText('OnTunnelData, ' + data.length + ', ' + typeof data + ', ' + data); // If this is upload data, save it to file if (this.httprequest.uploadFile) { if (typeof data == 'object') { try { fs.writeSync(this.httprequest.uploadFile, data); } catch (e) { this.write(new Buffer(JSON.stringify({ action: 'uploaderror' }))); return; } // Write to the file, if there is a problem, error out. this.write(new Buffer(JSON.stringify({ action: 'uploadack', reqid: this.httprequest.uploadFileid }))); // Ask for more data } return; } /* // If this is a download, send more of the file if (this.httprequest.downloadFile) { var buf = new Buffer(4096); var len = fs.readSync(this.httprequest.downloadFile, buf, 0, 4096, null); this.httprequest.downloadFilePtr += len; if (len > 0) { this.write(buf.slice(0, len)); } else { fs.closeSync(this.httprequest.downloadFile); this.httprequest.downloadFile = undefined; this.end(); } return; } */ if (this.httprequest.state == 0) { // Check if this is a relay connection if ((data == 'c') || (data == 'cr')) { this.httprequest.state = 1; /*sendConsoleText("Tunnel #" + this.httprequest.index + " now active", this.httprequest.sessionid);*/ } } else { // Handle tunnel data 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) // Take a look at the protocol if ((data.length > 3) && (data[0] == '{')) { onTunnelControlData(data, this); return; } this.httprequest.protocol = parseInt(data); if (typeof this.httprequest.protocol != 'number') { this.httprequest.protocol = 0; } if ((this.httprequest.protocol == 1) || (this.httprequest.protocol == 6) || (this.httprequest.protocol == 8) || (this.httprequest.protocol == 9)) { // // Remote Terminal // // Check user access rights for terminal if (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) || ((this.httprequest.rights != 0xFFFFFFFF) && ((this.httprequest.rights & MESHRIGHT_NOTERMINAL) != 0))) { // Disengage this tunnel, user does not have the rights to do this!! this.httprequest.protocol = 999999; this.httprequest.s.end(); sendConsoleText("Error: No Terminal Control Rights."); return; } this.descriptorMetadata = "Remote Terminal"; if (process.platform == 'win32') { if (!require('win-terminal').PowerShellCapable() && (this.httprequest.protocol == 6 || this.httprequest.protocol == 9)) { this.httprequest.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: 'PowerShell is not supported on this version of windows', msgid: 1 })); this.httprequest.s.end(); return; } } var prom = require('promise'); this.httprequest.tpromise = new prom(function (res, rej) { this._res = res; this._rej = rej; }); this.httprequest.tpromise.that = this; this.httprequest.tpromise.httprequest = this.httprequest; this.end = function () { if (this.httprequest.tpromise._consent) { this.httprequest.tpromise._consent.close(); } if (this.httprequest.connectionPromise) { this.httprequest.connectionPromise._rej('Closed'); } // Remove the terminal session to the count to update the server if (this.httprequest.userid != null) { if (tunnelUserCount.terminal[this.httprequest.userid] != null) { tunnelUserCount.terminal[this.httprequest.userid]--; if (tunnelUserCount.terminal[this.httprequest.userid] <= 0) { delete tunnelUserCount.terminal[this.httprequest.userid]; } } try { mesh.SendCommand({ action: 'sessions', type: 'terminal', value: tunnelUserCount.terminal }); } catch (ex) { } } if (process.platform == 'win32') { // Unpipe the web socket this.unpipe(this.httprequest._term); if (this.httprequest._term) { this.httprequest._term.unpipe(this); } // Unpipe the WebRTC channel if needed (This will also be done when the WebRTC channel ends). if (this.rtcchannel) { this.rtcchannel.unpipe(this.httprequest._term); if (this.httprequest._term) { this.httprequest._term.unpipe(this.rtcchannel); } } // Clean up if (this.httprequest._term) { this.httprequest._term.end(); } this.httprequest._term = null; } }; // Perform User-Consent if needed. if (this.httprequest.consent && (this.httprequest.consent & 16)) { this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 })); var consentMessage = this.httprequest.username + " requesting remote terminal access. Grant access?", consentTitle = 'MeshCentral'; if (this.httprequest.soptions != null) { if (this.httprequest.soptions.consentTitle != null) { consentTitle = this.httprequest.soptions.consentTitle; } if (this.httprequest.soptions.consentMsgTerminal != null) { consentMessage = this.httprequest.soptions.consentMsgTerminal.replace('{0}', this.httprequest.username); } } this.httprequest.tpromise._consent = require('message-box').create(consentTitle, consentMessage, 30); this.httprequest.tpromise._consent.retPromise = this.httprequest.tpromise; this.httprequest.tpromise._consent.then( function () { // Success MeshServerLog("Local user accepted remote terminal request (" + this.retPromise.httprequest.remoteaddr + ")", this.retPromise.that.httprequest); this.retPromise.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null, msgid: 0 })); this.retPromise._consent = null; this.retPromise._res(); }, function (e) { // Denied MeshServerLog("Local user rejected remote terminal request (" + this.retPromise.that.httprequest.remoteaddr + ")", this.retPromise.that.httprequest); this.retPromise.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 })); this.retPromise._rej(e.toString()); }); } else { // User-Consent is not required, so just resolve this promise this.httprequest.tpromise._res(); } this.httprequest.tpromise.then( function () { this.httprequest.connectionPromise = new prom(function (res, rej) { this._res = res; this._rej = rej; }); this.httprequest.connectionPromise.ws = this.that; // Start Terminal if(process.platform == 'win32') { try { var cols = 80, rows = 25; if (this.httprequest.xoptions) { if (this.httprequest.xoptions.rows) { rows = this.httprequest.xoptions.rows; } if (this.httprequest.xoptions.cols) { cols = this.httprequest.xoptions.cols; } } if ((this.httprequest.protocol == 1) || (this.httprequest.protocol == 6)) { // Admin Terminal if (require('win-virtual-terminal').supported) { // ConPTY PseudoTerminal // this.httprequest._term = require('win-virtual-terminal')[this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'](80, 25); // The above line is commented out, because there is a bug with ClosePseudoConsole() API, so this is the workaround 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] } }); this.httprequest._dispatcher.httprequest = this.httprequest; this.httprequest._dispatcher.on('connection', function (c) { if (this.httprequest.connectionPromise.completed) { c.end(); } else { this.httprequest.connectionPromise._res(c); } }); } else { // Legacy Terminal this.httprequest.connectionPromise._res(require('win-terminal')[this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'](cols, rows)); } } else { // Logged in user var userPromise = require('user-sessions').enumerateUsers(); userPromise.that = this; userPromise.then(function (u) { var that = this.that; if (u.Active.length > 0) { var username = u.Active[0].Username; if (require('win-virtual-terminal').supported) { // ConPTY PseudoTerminal that.httprequest._dispatcher = 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: [cols, rows] } }); } else { // Legacy Terminal that.httprequest._dispatcher = 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: [cols, rows] } }); } that.httprequest._dispatcher.ws = that; that.httprequest._dispatcher.on('connection', function (c) { if (this.ws.httprequest.connectionPromise.completed) { c.end(); } else { this.ws.httprequest.connectionPromise._res(c); } }); } }); } } catch (e) { this.httprequest.connectionPromise._rej('Failed to start remote terminal session, ' + e.toString()); } } else { try { var bash = fs.existsSync('/bin/bash') ? '/bin/bash' : false; var sh = fs.existsSync('/bin/sh') ? '/bin/sh' : false; var login = process.platform == 'linux' ? '/bin/login' : '/usr/bin/login'; var env = { HISTCONTROL: 'ignoreboth' }; if (this.httprequest.xoptions) { if (this.httprequest.xoptions.rows) { env.LINES = ('' + this.httprequest.xoptions.rows); } if (this.httprequest.xoptions.cols) { env.COLUMNS = ('' + this.httprequest.xoptions.cols); } } var options = { type: childProcess.SpawnTypes.TERM, uid: (this.httprequest.protocol == 8) ? require('user-sessions').consoleUid() : null, env: env }; if (this.httprequest.xoptions && this.httprequest.xoptions.requireLogin) { if (!require('fs').existsSync(login)) { throw ('Unable to spawn login process'); } this.httprequest.connectionPromise._res(childProcess.execFile(login, ['login'], options)); // Start login shell } else if (bash) { var p = childProcess.execFile(bash, ['bash'], options); // Start bash // Spaces at the beginning of lines are needed to hide commands from the command history if (process.platform == 'linux') { p.stdin.write(' alias ls=\'ls --color=auto\';clear\n'); } this.httprequest.connectionPromise._res(p); } else if (sh) { var p = childProcess.execFile(sh, ['sh'], options); // Start sh // Spaces at the beginning of lines are needed to hide commands from the command history if (process.platform == 'linux') { p.stdin.write(' alias ls=\'ls --color=auto\';clear\n'); } this.httprequest.connectionPromise._res(p); } else { this.httprequest.connectionPromise._rej('Failed to start remote terminal session, no shell found'); } } catch (e) { this.httprequest.connectionPromise._rej('Failed to start remote terminal session, ' + e.toString()); } } this.httprequest.connectionPromise.then( function (term) { // SUCCESS var stdoutstream; var stdinstream; if (process.platform == 'win32') { this.ws.httprequest._term = term; this.ws.httprequest._term.tunnel = this.ws; stdoutstream = stdinstream = term; } else { term.descriptorMetadata = 'Remote Terminal'; this.ws.httprequest.process = term; this.ws.httprequest.process.tunnel = this.ws; term.stderr.stdout = term.stdout; term.stderr.on('data', function (c) { this.stdout.write(c); }); stdoutstream = term.stdout; stdinstream = term.stdin; this.ws.prependListener('end', function () { this.httprequest.process.kill(); }); term.prependListener('exit', function () { this.tunnel.end(); }); } this.ws.removeAllListeners('data'); this.ws.on('data', onTunnelControlData); stdoutstream.pipe(this.ws, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text. this.ws.pipe(stdinstream, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. // Add the terminal session to the count to update the server if (this.ws.httprequest.userid != null) { if (tunnelUserCount.terminal[this.ws.httprequest.userid] == null) { tunnelUserCount.terminal[this.ws.httprequest.userid] = 1; } else { tunnelUserCount.terminal[this.ws.httprequest.userid]++; } try { mesh.SendCommand({ action: 'sessions', type: 'terminal', value: tunnelUserCount.terminal }); } catch (ex) { } } // Toast Notification, if required if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 2)) { // User Notifications is required var notifyMessage = this.ws.httprequest.username + " started a remote terminal session.", notifyTitle = "MeshCentral"; if (this.ws.httprequest.soptions != null) { if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; } if (this.ws.httprequest.soptions.notifyMsgTerminal != null) { notifyMessage = this.ws.httprequest.soptions.notifyMsgTerminal.replace('{0}', this.ws.httprequest.username); } } try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (ex) { } } }, function (e) { // FAILED to connect terminal this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 })); this.ws.end(); }); }, function (e) { // DO NOT start terminal this.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 })); this.that.end(); }); } else if (this.httprequest.protocol == 2) { // // Remote KVM // // Check user access rights for desktop if ((((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) && ((this.httprequest.rights & MESHRIGHT_REMOTEVIEW) == 0)) || ((this.httprequest.rights != 0xFFFFFFFF) && ((this.httprequest.rights & MESHRIGHT_NODESKTOP) != 0))) { // Disengage this tunnel, user does not have the rights to do this!! this.httprequest.protocol = 999999; this.httprequest.s.end(); sendConsoleText("Error: No Desktop Control Rights."); return; } this.descriptorMetadata = "Remote KVM"; // Look for a TSID var tsid = null; if ((this.httprequest.xoptions != null) && (typeof this.httprequest.xoptions.tsid == 'number')) { tsid = this.httprequest.xoptions.tsid; } require('MeshAgent')._tsid = tsid; // Remote desktop using native pipes this.httprequest.desktop = { state: 0, kvm: mesh.getRemoteDesktopStream(tsid), tunnel: this }; this.httprequest.desktop.kvm.parent = this.httprequest.desktop; this.desktop = this.httprequest.desktop; // Add ourself to the list of remote desktop sessions if (this.httprequest.desktop.kvm.tunnels == null) { this.httprequest.desktop.kvm.tunnels = []; } this.httprequest.desktop.kvm.tunnels.push(this); // Send a metadata update to all desktop sessions var users = {}; if (this.httprequest.desktop.kvm.tunnels != null) { for (var i in this.httprequest.desktop.kvm.tunnels) { try { var userid = this.httprequest.desktop.kvm.tunnels[i].httprequest.userid; if (users[userid] == null) { users[userid] = 1; } else { users[userid]++; } } catch (ex) { } } for (var i in this.httprequest.desktop.kvm.tunnels) { try { this.httprequest.desktop.kvm.tunnels[i].write(JSON.stringify({ ctrlChannel: '102938', type: 'metadata', users: users })); } catch (ex) { } } try { mesh.SendCommand({ action: 'sessions', type: 'kvm', value: users }); } catch (ex) { } } this.end = function () { --this.desktop.kvm.connectionCount; // Remove ourself from the list of remote desktop session var i = this.desktop.kvm.tunnels.indexOf(this); if (i >= 0) { this.desktop.kvm.tunnels.splice(i, 1); } // Send a metadata update to all desktop sessions var users = {}; if (this.httprequest.desktop.kvm.tunnels != null) { for (var i in this.httprequest.desktop.kvm.tunnels) { try { var userid = this.httprequest.desktop.kvm.tunnels[i].httprequest.userid; if (users[userid] == null) { users[userid] = 1; } else { users[userid]++; } } catch (ex) { } } for (var i in this.httprequest.desktop.kvm.tunnels) { try { this.httprequest.desktop.kvm.tunnels[i].write(JSON.stringify({ ctrlChannel: '102938', type: 'metadata', users: users })); } catch (ex) { } } try { mesh.SendCommand({ action: 'sessions', type: 'kvm', value: users }); } catch (ex) { } } // Unpipe the web socket try { this.unpipe(this.httprequest.desktop.kvm); this.httprequest.desktop.kvm.unpipe(this); } catch(ex) { } // Unpipe the WebRTC channel if needed (This will also be done when the WebRTC channel ends). if (this.rtcchannel) { try { this.rtcchannel.unpipe(this.httprequest.desktop.kvm); this.httprequest.desktop.kvm.unpipe(this.rtcchannel); } catch(ex) { } } // Place wallpaper back if needed // TODO if (this.desktop.kvm.connectionCount == 0) { // Display a toast message. This may not be supported on all platforms. // try { require('toaster').Toast('MeshCentral', 'Remote Desktop Control Ended.'); } catch (ex) { } this.httprequest.desktop.kvm.end(); if (this.httprequest.desktop.kvm.connectionBar) { this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close'); this.httprequest.desktop.kvm.connectionBar.close(); this.httprequest.desktop.kvm.connectionBar = null; } } else { for (var i in this.httprequest.desktop.kvm.users) { if (this.httprequest.desktop.kvm.users[i] == this.httprequest.username && this.httprequest.desktop.kvm.connectionBar) { this.httprequest.desktop.kvm.users.splice(i, 1); this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close'); this.httprequest.desktop.kvm.connectionBar.close(); this.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(this.httprequest.privacybartext.replace('{0}', this.httprequest.desktop.kvm.users.sort().join(', ')), require('MeshAgent')._tsid); this.httprequest.desktop.kvm.connectionBar.httprequest = this.httprequest; this.httprequest.desktop.kvm.connectionBar.on('close', function () { MeshServerLog("Remote Desktop Connection forcefully closed by local user (" + this.httprequest.remoteaddr + ")", this.httprequest); for (var i in this.httprequest.desktop.kvm._pipedStreams) { this.httprequest.desktop.kvm._pipedStreams[i].end(); } this.httprequest.desktop.kvm.end(); }); break; } } } }; if (this.httprequest.desktop.kvm.hasOwnProperty('connectionCount')) { this.httprequest.desktop.kvm.connectionCount++; this.httprequest.desktop.kvm.users.push(this.httprequest.username); } else { this.httprequest.desktop.kvm.connectionCount = 1; this.httprequest.desktop.kvm.users = [this.httprequest.username]; } if ((this.httprequest.rights == 0xFFFFFFFF) || (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) != 0) && ((this.httprequest.rights & MESHRIGHT_REMOTEVIEW) == 0))) { // If we have remote control rights, pipe the KVM input this.pipe(this.httprequest.desktop.kvm, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. Pipe the Browser --> KVM input. } else { // We need to only pipe non-mouse & non-keyboard inputs. //sendConsoleText('Warning: No Remote Desktop Input Rights.'); // TODO!!! } // Perform notification if needed. Toast messages may not be supported on all platforms. if (this.httprequest.consent && (this.httprequest.consent & 8)) { // User Consent Prompt is required // Send a console message back using the console channel, "\n" is supported. this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 })); var consentMessage = this.httprequest.username + " requesting remote desktop access. Grant access?", consentTitle = 'MeshCentral'; if (this.httprequest.soptions != null) { if (this.httprequest.soptions.consentTitle != null) { consentTitle = this.httprequest.soptions.consentTitle; } if (this.httprequest.soptions.consentMsgDesktop != null) { consentMessage = this.httprequest.soptions.consentMsgDesktop.replace('{0}', this.httprequest.username); } } var pr = require('message-box').create(consentTitle, consentMessage, 30, null, tsid); pr.ws = this; this.pause(); this._consentpromise = pr; this.prependOnceListener('end', function () { if (this._consentpromise && this._consentpromise.close) { this._consentpromise.close(); }}); pr.then( function () { // Success this.ws._consentpromise = null; MeshServerLog("Starting remote desktop after local user accepted (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest); this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null, msgid: 0 })); if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 1)) { // User Notifications is required var notifyMessage = this.ws.httprequest.username + " started a remote desktop session.", notifyTitle = "MeshCentral"; if (this.ws.httprequest.soptions != null) { if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; } if (this.ws.httprequest.soptions.notifyMsgDesktop != null) { notifyMessage = this.ws.httprequest.soptions.notifyMsgDesktop.replace('{0}', this.ws.httprequest.username); } } try { require('toaster').Toast(notifyTitle, notifyMessage, tsid); } catch (ex) { } } if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 0x40)) { // Connection Bar is required if (this.ws.httprequest.desktop.kvm.connectionBar) { this.ws.httprequest.desktop.kvm.connectionBar.removeAllListeners('close'); this.ws.httprequest.desktop.kvm.connectionBar.close(); } try { this.ws.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(this.ws.httprequest.privacybartext.replace('{0}', this.ws.httprequest.desktop.kvm.users.sort().join(', ')), require('MeshAgent')._tsid); MeshServerLog("Remote Desktop Connection Bar Activated/Updated (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest); } catch (xx) { if (process.platform != 'darwin') { MeshServerLog("Remote Desktop Connection Bar Failed or Not Supported (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest); } } if (this.ws.httprequest.desktop.kvm.connectionBar) { this.ws.httprequest.desktop.kvm.connectionBar.httprequest = this.ws.httprequest; this.ws.httprequest.desktop.kvm.connectionBar.on('close', function () { MeshServerLog("Remote Desktop Connection forcefully closed by local user (" + this.httprequest.remoteaddr + ")", this.httprequest); for (var i in this.httprequest.desktop.kvm._pipedStreams) { this.httprequest.desktop.kvm._pipedStreams[i].end(); } this.httprequest.desktop.kvm.end(); }); } } this.ws.httprequest.desktop.kvm.pipe(this.ws, { dataTypeSkip: 1 }); this.ws.resume(); }, function (e) { // User Consent Denied/Failed this.ws._consentpromise = null; MeshServerLog("Failed to start remote desktop after local user rejected (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest); this.ws.end(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 })); }); } else { // User Consent Prompt is not required if (this.httprequest.consent && (this.httprequest.consent & 1)) { // User Notifications is required MeshServerLog("Started remote desktop with toast notification (" + this.httprequest.remoteaddr + ")", this.httprequest); var notifyMessage = this.httprequest.username + " started a remote desktop session.", notifyTitle = "MeshCentral"; if (this.httprequest.soptions != null) { if (this.httprequest.soptions.notifyTitle != null) { notifyTitle = this.httprequest.soptions.notifyTitle; } if (this.httprequest.soptions.notifyMsgDesktop != null) { notifyMessage = this.httprequest.soptions.notifyMsgDesktop.replace('{0}', this.httprequest.username); } } try { require('toaster').Toast(notifyTitle, notifyMessage, tsid); } catch (ex) { } } else { MeshServerLog("Started remote desktop without notification (" + this.httprequest.remoteaddr + ")", this.httprequest); } if (this.httprequest.consent && (this.httprequest.consent & 0x40)) { // Connection Bar is required if (this.httprequest.desktop.kvm.connectionBar) { this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close'); this.httprequest.desktop.kvm.connectionBar.close(); } try { this.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(this.httprequest.privacybartext.replace('{0}', this.httprequest.desktop.kvm.users.sort().join(', ')), require('MeshAgent')._tsid); MeshServerLog("Remote Desktop Connection Bar Activated/Updated (" + this.httprequest.remoteaddr + ")", this.httprequest); } catch (xx) { MeshServerLog("Remote Desktop Connection Bar Failed or not Supported (" + this.httprequest.remoteaddr + ")", this.httprequest); } if (this.httprequest.desktop.kvm.connectionBar) { this.httprequest.desktop.kvm.connectionBar.httprequest = this.httprequest; this.httprequest.desktop.kvm.connectionBar.on('close', function () { MeshServerLog("Remote Desktop Connection forcefully closed by local user (" + this.httprequest.remoteaddr + ")", this.httprequest); for (var i in this.httprequest.desktop.kvm._pipedStreams) { this.httprequest.desktop.kvm._pipedStreams[i].end(); } this.httprequest.desktop.kvm.end(); }); } } this.httprequest.desktop.kvm.pipe(this, { dataTypeSkip: 1 }); } this.removeAllListeners('data'); this.on('data', onTunnelControlData); //this.write('MeshCore KVM Hello!1'); } else if (this.httprequest.protocol == 5) { // // Remote Files // // Check user access rights for files if (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) || ((this.httprequest.rights != 0xFFFFFFFF) && ((this.httprequest.rights & MESHRIGHT_NOFILES) != 0))) { // Disengage this tunnel, user does not have the rights to do this!! this.httprequest.protocol = 999999; this.httprequest.s.end(); sendConsoleText("Error: No files control rights."); return; } this.descriptorMetadata = "Remote Files"; // Add the files session to the count to update the server if (this.httprequest.userid != null) { if (tunnelUserCount.files[this.httprequest.userid] == null) { tunnelUserCount.files[this.httprequest.userid] = 1; } else { tunnelUserCount.files[this.httprequest.userid]++; } try { mesh.SendCommand({ action: 'sessions', type: 'files', value: tunnelUserCount.files }); } catch (ex) { } } this.end = function () { // Remove the files session from the count to update the server if (this.httprequest.userid != null) { if (tunnelUserCount.files[this.httprequest.userid] != null) { tunnelUserCount.files[this.httprequest.userid]--; if (tunnelUserCount.files[this.httprequest.userid] <= 0) { delete tunnelUserCount.files[this.httprequest.userid]; } } try { mesh.SendCommand({ action: 'sessions', type: 'files', value: tunnelUserCount.files }); } catch (ex) { } } }; // Perform notification if needed. Toast messages may not be supported on all platforms. if (this.httprequest.consent && (this.httprequest.consent & 32)) { // User Consent Prompt is required // Send a console message back using the console channel, "\n" is supported. this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 })); var consentMessage = this.httprequest.username + " requesting remote file Access. Grant access?", consentTitle = 'MeshCentral'; if (this.httprequest.soptions != null) { if (this.httprequest.soptions.consentTitle != null) { consentTitle = this.httprequest.soptions.consentTitle; } if (this.httprequest.soptions.consentMsgFiles != null) { consentMessage = this.httprequest.soptions.consentMsgFiles.replace('{0}', this.httprequest.username); } } var pr = require('message-box').create(consentTitle, consentMessage, 30); pr.ws = this; this.pause(); this._consentpromise = pr; this.prependOnceListener('end', function () { if (this._consentpromise && this._consentpromise.close) { this._consentpromise.close(); } }); pr.then( function () { // Success this.ws._consentpromise = null; MeshServerLog("Starting remote files after local user accepted (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest); this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null })); if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 4)) { // User Notifications is required var notifyMessage = this.ws.httprequest.username + " started a remote file session.", notifyTitle = "MeshCentral"; if (this.ws.httprequest.soptions != null) { if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; } if (this.ws.httprequest.soptions.notifyMsgFiles != null) { notifyMessage = this.ws.httprequest.soptions.notifyMsgFiles.replace('{0}', this.ws.httprequest.username); } } try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (ex) { } } this.ws.resume(); }, function (e) { // User Consent Denied/Failed this.ws._consentpromise = null; MeshServerLog("Failed to start remote files after local user rejected (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest); this.ws.end(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 })); }); } else { // User Consent Prompt is not required if (this.httprequest.consent && (this.httprequest.consent & 4)) { // User Notifications is required MeshServerLog("Started remote files with toast notification (" + this.httprequest.remoteaddr + ")", this.httprequest); var notifyMessage = this.httprequest.username + " started a remote file session.", notifyTitle = "MeshCentral"; if (this.httprequest.soptions != null) { if (this.httprequest.soptions.notifyTitle != null) { notifyTitle = this.httprequest.soptions.notifyTitle; } if (this.httprequest.soptions.notifyMsgFiles != null) { notifyMessage = this.httprequest.soptions.notifyMsgFiles.replace('{0}', this.httprequest.username); } } try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (ex) { } } else { MeshServerLog("Started remote files without notification (" + this.httprequest.remoteaddr + ")", this.httprequest); } this.resume(); } // Setup files // NOP } } else if (this.httprequest.protocol == 1) { // Send data into terminal stdin //this.write(data); // Echo back the keys (Does not seem to be a good idea) } else if (this.httprequest.protocol == 2) { // Send data into remote desktop if (this.httprequest.desktop.state == 0) { this.write(new Buffer(String.fromCharCode(0x11, 0xFE, 0x00, 0x00, 0x4D, 0x45, 0x53, 0x48, 0x00, 0x00, 0x00, 0x00, 0x02))); this.httprequest.desktop.state = 1; } else { this.httprequest.desktop.write(data); } } else if (this.httprequest.protocol == 5) { // Process files commands var cmd = null; try { cmd = JSON.parse(data); } catch (e) { }; if (cmd == null) { return; } if ((cmd.ctrlChannel == '102938') || ((cmd.type == 'offer') && (cmd.sdp != null))) { onTunnelControlData(cmd, this); return; } // If this is control data, handle it now. if (cmd.action == undefined) { return; } //sendConsoleText('CMD: ' + JSON.stringify(cmd)); if ((cmd.path != null) && (process.platform != 'win32') && (cmd.path[0] != '/')) { cmd.path = '/' + cmd.path; } // Add '/' to paths on non-windows //console.log(objToString(cmd, 0, ' ')); switch (cmd.action) { case 'ls': { /* // Close the watcher if required var samepath = ((this.httprequest.watcher != undefined) && (cmd.path == this.httprequest.watcher.path)); if ((this.httprequest.watcher != undefined) && (samepath == false)) { //console.log('Closing watcher: ' + this.httprequest.watcher.path); //this.httprequest.watcher.close(); // TODO: This line causes the agent to crash!!!! delete this.httprequest.watcher; } */ // Send the folder content to the browser var response = getDirectoryInfo(cmd.path); if (cmd.reqid != undefined) { response.reqid = cmd.reqid; } this.write(new Buffer(JSON.stringify(response))); /* // Start the directory watcher if ((cmd.path != '') && (samepath == false)) { var watcher = fs.watch(cmd.path, onFileWatcher); watcher.tunnel = this.httprequest; watcher.path = cmd.path; this.httprequest.watcher = watcher; //console.log('Starting watcher: ' + this.httprequest.watcher.path); } */ break; } case 'mkdir': { // Create a new empty folder fs.mkdirSync(cmd.path); MeshServerLog("Create folder: \"" + cmd.path + "\"", this.httprequest); break; } case 'rm': { // Delete, possibly recursive delete for (var i in cmd.delfiles) { var p = obj.path.join(cmd.path, cmd.delfiles[i]), delcount = 0; try { delcount = deleteFolderRecursive(p, cmd.rec); } catch (e) { } if ((delcount == 1) && !cmd.rec) { MeshServerLog("Delete: \"" + p + "\"", this.httprequest); } else { MeshServerLog((cmd.rec ? "Delete recursive: \"" : "Delete: \"") + p + "\", " + delcount + " element(s) removed", this.httprequest); } } break; } case 'rename': { // Rename a file or folder var oldfullpath = obj.path.join(cmd.path, cmd.oldname); var newfullpath = obj.path.join(cmd.path, cmd.newname); MeshServerLog('Rename: \"' + oldfullpath + '\" to \"' + cmd.newname + '\"', this.httprequest); try { fs.renameSync(oldfullpath, newfullpath); } catch (e) { console.log(e); } break; } case 'download': { // Download a file var sendNextBlock = 0; if (cmd.sub == 'start') { // Setup the download MeshServerLog('Download: \"' + cmd.path + '\"', this.httprequest); if (this.filedownload != null) { this.write({ action: 'download', sub: 'cancel', id: this.filedownload.id }); delete this.filedownload; } this.filedownload = { id: cmd.id, path: cmd.path, ptr: 0 } try { this.filedownload.f = fs.openSync(this.filedownload.path, 'rbN'); } catch (e) { this.write({ action: 'download', sub: 'cancel', id: this.filedownload.id }); delete this.filedownload; } if (this.filedownload) { this.write({ action: 'download', sub: 'start', id: cmd.id }); } } else if ((this.filedownload != null) && (cmd.id == this.filedownload.id)) { // Download commands if (cmd.sub == 'startack') { sendNextBlock = 8; } else if (cmd.sub == 'stop') { delete this.filedownload; } else if (cmd.sub == 'ack') { sendNextBlock = 1; } } // Send the next download block(s) while (sendNextBlock > 0) { sendNextBlock--; var buf = new Buffer(4096); var len = fs.readSync(this.filedownload.f, buf, 4, 4092, null); this.filedownload.ptr += len; if (len < 4092) { buf.writeInt32BE(0x01000001, 0); fs.closeSync(this.filedownload.f); delete this.filedownload; sendNextBlock = 0; } else { buf.writeInt32BE(0x01000000, 0); } this.write(buf.slice(0, len + 4)); // Write as binary } break; } /* case 'download': { // Packet download of a file, agent to browser if (cmd.path == undefined) break; var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path; //console.log('Download: ' + filepath); try { this.httprequest.downloadFile = fs.openSync(filepath, 'rbN'); } catch (e) { this.write(new Buffer(JSON.stringify({ action: 'downloaderror', reqid: cmd.reqid }))); break; } this.httprequest.downloadFileId = cmd.reqid; this.httprequest.downloadFilePtr = 0; if (this.httprequest.downloadFile) { this.write(new Buffer(JSON.stringify({ action: 'downloadstart', reqid: this.httprequest.downloadFileId }))); } break; } case 'download2': { // Stream download of a file, agent to browser if (cmd.path == undefined) break; var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path; try { this.httprequest.downloadFile = fs.createReadStream(filepath, { flags: 'rbN' }); } catch (e) { console.log(e); } this.httprequest.downloadFile.pipe(this); this.httprequest.downloadFile.end = function () { } break; } */ case 'upload': { // Upload a file, browser to agent if (this.httprequest.uploadFile != undefined) { fs.closeSync(this.httprequest.uploadFile); this.httprequest.uploadFile = undefined; } if (cmd.path == undefined) break; var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path; MeshServerLog('Upload: \"' + filepath + '\"', this.httprequest); try { this.httprequest.uploadFile = fs.openSync(filepath, 'wbN'); } catch (e) { this.write(new Buffer(JSON.stringify({ action: 'uploaderror', reqid: cmd.reqid }))); break; } this.httprequest.uploadFileid = cmd.reqid; if (this.httprequest.uploadFile) { this.write(new Buffer(JSON.stringify({ action: 'uploadstart', reqid: this.httprequest.uploadFileid }))); } break; } case 'copy': { // Copy a bunch of files from scpath to dspath for (var i in cmd.names) { var sc = obj.path.join(cmd.scpath, cmd.names[i]), ds = obj.path.join(cmd.dspath, cmd.names[i]); MeshServerLog('Copy: \"' + sc + '\" to \"' + ds + '\"', this.httprequest); if (sc != ds) { try { fs.copyFileSync(sc, ds); } catch (e) { } } } break; } case 'move': { // Move a bunch of files from scpath to dspath for (var i in cmd.names) { var sc = obj.path.join(cmd.scpath, cmd.names[i]), ds = obj.path.join(cmd.dspath, cmd.names[i]); MeshServerLog('Move: \"' + sc + '\" to \"' + ds + '\"', this.httprequest); if (sc != ds) { try { fs.copyFileSync(sc, ds); fs.unlinkSync(sc); } catch (e) { } } } break; } default: // Unknown action, ignore it. break; } } else if (this.httprequest.protocol == 7) { // Plugin data exchange var cmd = null; try { cmd = JSON.parse(data); } catch (e) { }; if (cmd == null) { return; } if ((cmd.ctrlChannel == '102938') || ((cmd.type == 'offer') && (cmd.sdp != null))) { onTunnelControlData(cmd, this); return; } // If this is control data, handle it now. if (cmd.action == undefined) return; switch (cmd.action) { case 'plugin': { try { require(cmd.plugin).consoleaction(cmd, null, null, this); } catch (e) { throw e; } break; } default: { // probably shouldn't happen, but just in case this feature is expanded } } } //sendConsoleText("Got tunnel #" + this.httprequest.index + " data: " + data, this.httprequest.sessionid); } } // Delete a directory with a files and directories within it function deleteFolderRecursive(path, rec) { var count = 0; if (fs.existsSync(path)) { if (rec == true) { fs.readdirSync(obj.path.join(path, '*')).forEach(function (file, index) { var curPath = obj.path.join(path, file); if (fs.statSync(curPath).isDirectory()) { // recurse count += deleteFolderRecursive(curPath, true); } else { // delete file fs.unlinkSync(curPath); count++; } }); } fs.unlinkSync(path); count++; } return count; }; // Called when receiving control data on WebRTC function onTunnelWebRTCControlData(data) { if (typeof data != 'string') return; var obj; try { obj = JSON.parse(data); } catch (e) { sendConsoleText('Invalid control JSON on WebRTC: ' + data); return; } if (obj.type == 'close') { //sendConsoleText('Tunnel #' + this.xrtc.websocket.tunnel.index + ' WebRTC control close'); try { this.close(); } catch (e) { } try { this.xrtc.close(); } catch (e) { } } } // Called when receiving control data on websocket function onTunnelControlData(data, ws) { var obj; if (ws == null) { ws = this; } if (typeof data == 'string') { try { obj = JSON.parse(data); } catch (e) { sendConsoleText('Invalid control JSON: ' + data); return; } } else if (typeof data == 'object') { obj = data; } else { return; } //sendConsoleText('onTunnelControlData(' + ws.httprequest.protocol + '): ' + JSON.stringify(data)); //console.log('onTunnelControlData: ' + JSON.stringify(data)); if (obj.action) { switch (obj.action) { case 'lock': { // Lock the current user out of the desktop try { if (process.platform == 'win32') { MeshServerLog("Locking remote user out of desktop", ws.httprequest); var child = require('child_process'); child.execFile(process.env['windir'] + '\\system32\\cmd.exe', ['/c', 'RunDll32.exe user32.dll,LockWorkStation'], { type: 1 }); } } catch (e) { } break; } default: // Unknown action, ignore it. break; } return; } switch (obj.type) { case 'options': { // These are additional connection options passed in the control channel. //sendConsoleText('options: ' + JSON.stringify(obj)); delete obj.type; ws.httprequest.xoptions = obj; // Set additional user consent options if present if ((obj != null) && (typeof obj.consent == 'number')) { ws.httprequest.consent |= obj.consent; } break; } case 'close': { // We received the close on the websocket //sendConsoleText('Tunnel #' + ws.tunnel.index + ' WebSocket control close'); try { ws.close(); } catch (e) { } break; } case 'termsize': { // Indicates a change in terminal size if (process.platform == 'win32') { if (ws.httprequest._dispatcher == null) return; //sendConsoleText('Win32-TermSize: ' + obj.cols + 'x' + obj.rows); if (ws.httprequest._dispatcher.invoke) { ws.httprequest._dispatcher.invoke('resizeTerminal', [obj.cols, obj.rows]); } } else { if (ws.httprequest.process == null || ws.httprequest.process.pty == 0) return; //sendConsoleText('Linux Resize: ' + obj.cols + 'x' + obj.rows); if (ws.httprequest.process.tcsetsize) { ws.httprequest.process.tcsetsize(obj.rows, obj.cols); } } break; } case 'webrtc0': { // Browser indicates we can start WebRTC switch-over. if (ws.httprequest.protocol == 1) { // Terminal // 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 if (process.platform == 'win32') { ws.httprequest._term.unpipe(ws); } else { ws.httprequest.process.stdout.unpipe(ws); ws.httprequest.process.stderr.unpipe(ws); } } else if (ws.httprequest.protocol == 2) { // Desktop // 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 ws.httprequest.desktop.kvm.unpipe(ws); } else { // Switch things around so all WebRTC data goes to onTunnelData(). ws.rtcchannel.httprequest = ws.httprequest; ws.rtcchannel.removeAllListeners('data'); ws.rtcchannel.on('data', onTunnelData); } ws.write("{\"ctrlChannel\":\"102938\",\"type\":\"webrtc1\"}"); // End of data marker break; } case 'webrtc1': { if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) { // Terminal // Switch the user input from websocket to webrtc at this point. if (process.platform == 'win32') { ws.unpipe(ws.httprequest._term); ws.rtcchannel.pipe(ws.httprequest._term, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text. } else { ws.unpipe(ws.httprequest.process.stdin); ws.rtcchannel.pipe(ws.httprequest.process.stdin, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text. } ws.resume(); // Resume the websocket to keep receiving control data } else if (ws.httprequest.protocol == 2) { // Desktop // Switch the user input from websocket to webrtc at this point. ws.unpipe(ws.httprequest.desktop.kvm); try { ws.webrtc.rtcchannel.pipe(ws.httprequest.desktop.kvm, { dataTypeSkip: 1, end: false }); } catch (e) { sendConsoleText('EX2'); } // 0 = Binary, 1 = Text. ws.resume(); // Resume the websocket to keep receiving control data } ws.write('{\"ctrlChannel\":\"102938\",\"type\":\"webrtc2\"}'); // Indicates we will no longer get any data on websocket, switching to WebRTC at this point. break; } case 'webrtc2': { // Other side received websocket end of data marker, start sending data on WebRTC channel if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) { // Terminal if (process.platform == 'win32') { ws.httprequest._term.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. } else { ws.httprequest.process.stdout.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. ws.httprequest.process.stderr.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. } } else if (ws.httprequest.protocol == 2) { // Desktop ws.httprequest.desktop.kvm.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text. } break; } case 'offer': { // This is a WebRTC offer. if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) return; // TODO: Terminal is currently broken with WebRTC. Reject WebRTC upgrade for now. ws.webrtc = rtc.createConnection(); ws.webrtc.websocket = ws; ws.webrtc.on('connected', function () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC connected');*/ }); ws.webrtc.on('disconnected', function () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC disconnected');*/ }); ws.webrtc.on('dataChannel', function (rtcchannel) { //sendConsoleText('WebRTC Datachannel open, protocol: ' + this.websocket.httprequest.protocol); rtcchannel.maxFragmentSize = 32768; rtcchannel.xrtc = this; rtcchannel.websocket = this.websocket; this.rtcchannel = rtcchannel; this.websocket.rtcchannel = rtcchannel; this.websocket.rtcchannel.on('data', onTunnelWebRTCControlData); this.websocket.rtcchannel.on('end', function () { // The WebRTC channel closed, unpipe the KVM now. This is also done when the web socket closes. //sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC data channel closed'); if (this.websocket.desktop && this.websocket.desktop.kvm) { try { this.unpipe(this.websocket.desktop.kvm); this.websocket.httprequest.desktop.kvm.unpipe(this); } catch (xx) { } } }); this.websocket.write('{\"ctrlChannel\":\"102938\",\"type\":\"webrtc0\"}'); // Indicate we are ready for WebRTC switch-over. }); var sdp = null; try { sdp = ws.webrtc.setOffer(obj.sdp); } catch (ex) { } if (sdp != null) { ws.write({ type: 'answer', ctrlChannel: '102938', sdp: sdp }); } break; } case 'rtt': { ws.write({ type: 'rtt', ctrlChannel: '102938', time: obj.time }); break; } } } // Console state var consoleWebSockets = {}; var consoleHttpRequest = null; // Console HTTP response function consoleHttpResponse(response) { response.data = function (data) { sendConsoleText(rstr2hex(buf2rstr(data)), this.sessionid); consoleHttpRequest = null; } response.close = function () { sendConsoleText('httprequest.response.close', this.sessionid); consoleHttpRequest = null; } }; // Open a web browser to a specified URL on current user's desktop function openUserDesktopUrl(url) { var child = null; try { switch (process.platform) { case 'win32': var user = require('user-sessions').getUsername(require('user-sessions').consoleUid()); child = require('child_process').execFile(process.env['windir'] + '\\system32\\cmd.exe', ['cmd']); child.stderr.on('data', function () { }); child.stdout.on('data', function () { }); child.stdin.write('SCHTASKS /CREATE /F /TN MeshChatTask /SC ONCE /ST 00:00 /RU ' + user + ' /TR "' + process.env['windir'] + '\\system32\\cmd.exe /C START ' + url + '"\r\n'); child.stdin.write('SCHTASKS /RUN /TN MeshChatTask\r\n'); child.stdin.write('SCHTASKS /DELETE /F /TN MeshChatTask\r\n'); child.stdin.write('exit\r\n'); child.waitExit(); break; case 'linux': child = require('child_process').execFile('/usr/bin/xdg-open', ['xdg-open', url], { uid: require('user-sessions').consoleUid() }); break; case 'darwin': child = require('child_process').execFile('/usr/bin/open', ['open', url], { uid: require('user-sessions').consoleUid() }); break; default: // Unknown platform, ignore this command. break; } } catch (ex) { } return child; } // Process a mesh agent console command function processConsoleCommand(cmd, args, rights, sessionid) { try { var response = null; switch (cmd) { case 'help': { // Displays available commands var fin = '', f = '', availcommands = 'service,fdsnapshot,fdcount,startupoptions,alert,agentsize,versions,help,info,osinfo,args,print,type,dbkeys,dbget,dbset,dbcompact,eval,parseuri,httpget,nwslist,plugin,wsconnect,wssend,wsclose,notify,ls,ps,kill,amt,netinfo,location,power,wakeonlan,setdebug,smbios,rawsmbios,toast,lock,users,sendcaps,openurl,amtreset,amtccm,amtacm,amtdeactivate,amtpolicy,getscript,getclip,setclip,log,av,cpuinfo,sysinfo,apf,scanwifi,scanamt,wallpaper'; if (process.platform == 'win32') { availcommands += ',safemode,wpfhwacceleration,uac'; } if (process.platform != 'freebsd') { availcommands += ',vm';} if (require('MeshAgent').maxKvmTileSize != null) { availcommands += ',kvmmode'; } try { require('zip-reader'); availcommands += ',zip,unzip'; } catch (xx) { } availcommands = availcommands.split(',').sort(); while (availcommands.length > 0) { if (f.length > 90) { fin += (f + ',\r\n'); f = ''; } f += (((f != '') ? ', ' : ' ') + availcommands.shift()); } if (f != '') { fin += f; } response = "Available commands: \r\n" + fin + "."; break; } case 'service': if (args['_'].length != 1) { response = "Proper usage: service status|restart"; // Display usage } else { var s = require('service-manager').manager.getService(process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'); switch(args['_'][0].toLowerCase()) { case 'status': response = 'Service ' + (s.isRunning() ? (s.isMe() ? '[SELF]' : '[RUNNING]') : ('[NOT RUNNING]')); break; case 'restart': if (s.isMe()) { s.restart(); } else { response = 'Restarting another agent instance is not allowed'; } break; default: response = "Proper usage: service status|restart"; // Display usage break; } if (process.platform == 'win32') { s.close(); } } break; case 'zip': if (args['_'].length == 0) { response = "Proper usage: zip (output file name), input1 [, input n]"; // Display usage } else { var p = args['_'].join(' ').split(','); var ofile = p.shift(); sendConsoleText('Writing ' + ofile + '...'); var out = require('fs').createWriteStream(ofile, { flags: 'wb' }); out.fname = ofile; out.sessionid = sessionid; out.on('close', function () { sendConsoleText('DONE writing ' + this.fname, this.sessionid); }); var zip = require('zip-writer').write({ files: p }); zip.pipe(out); } break; case 'unzip': if (args['_'].length == 0) { response = "Proper usage: unzip input, destination"; // Display usage } else { var p = args['_'].join(' ').split(','); if (p.length != 2) { response = "Proper usage: unzip input, destination"; // Display usage break; } var prom = require('zip-reader').read(p[0]); prom._dest = p[1]; prom.self = this; prom.sessionid = sessionid; prom.then(function (zipped) { sendConsoleText('Extracting to ' + this._dest + '...', this.sessionid); zipped.extractAll(this._dest).then(function () { sendConsoleText('finished unzipping', this.sessionid); }, function (e) { sendConsoleText('Error unzipping: ' + e, this.sessionid); }).parentPromise.sessionid = this.sessionid; }, function (e) { sendConsoleText('Error unzipping: ' + e, this.sessionid); }); } break; case 'setbattery': // require('MeshAgent').SendCommand({ action: 'battery', state: 'dc', level: 55 }); if ((args['_'].length > 0) && ((args['_'][0] == 'ac') || (args['_'][0] == 'dc'))) { var b = { action: 'battery', state: args['_'][0] }; if (args['_'].length == 2) { b.level = parseInt(args['_'][1]); } require('MeshAgent').SendCommand(b); } else { require('MeshAgent').SendCommand({ action: 'battery' }); } break; case 'fdsnapshot': require('ChainViewer').getSnapshot().then(function (c) { sendConsoleText(c, this.sessionid); }).parentPromise.sessionid = sessionid; break; case 'fdcount': require('DescriptorEvents').getDescriptorCount().then( function (c) { sendConsoleText('Descriptor Count: ' + c, this.sessionid); }, function (e) { sendConsoleText('Error fetching descriptor count: ' + e, this.sessionid); }).parentPromise.sessionid = sessionid; break; case 'uac': if (process.platform != 'win32') { response = 'Unknown command "uac", type "help" for list of avaialble commands.'; break; } if (args['_'].length != 1) { response = 'Proper usage: uac [get|interactive|secure]'; } else { switch(args['_'][0].toUpperCase()) { case 'GET': var secd = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System', 'PromptOnSecureDesktop'); response = "UAC mode: " + (secd == 0 ? "Interactive Desktop" : "Secure Desktop"); break; case 'INTERACTIVE': try { require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System', 'PromptOnSecureDesktop', 0); response = 'UAC mode changed to: Interactive Desktop'; } catch (e) { response = "Unable to change UAC Mode"; } break; case 'SECURE': try { require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System', 'PromptOnSecureDesktop', 1); response = 'UAC mode changed to: Secure Desktop'; } catch(e) { response = "Unable to change UAC Mode"; } break; default: response = 'Proper usage: uac [get|interactive|secure]'; break; } } break; case 'vm': response = 'Virtual Machine = ' + require('identifiers').isVM(); break; case 'startupoptions': response = JSON.stringify(require('MeshAgent').getStartupOptions()); break; case 'kvmmode': if (require('MeshAgent').maxKvmTileSize == null) { response = "Unknown command \"kvmmode\", type \"help\" for list of avaialble commands."; } else { if(require('MeshAgent').maxKvmTileSize == 0) { response = 'KVM Mode: Full JUMBO'; } else { response = 'KVM Mode: ' + (require('MeshAgent').maxKvmTileSize <= 65500 ? 'NO JUMBO' : 'Partial JUMBO'); response += (', TileLimit: ' + (require('MeshAgent').maxKvmTileSize < 1024 ? (require('MeshAgent').maxKvmTileSize + ' bytes') : (Math.round(require('MeshAgent').maxKvmTileSize/1024) + ' Kbytes'))); } } break; case 'alert': if (args['_'].length == 0) { response = "Proper usage: alert TITLE, CAPTION [, TIMEOUT]"; // Display usage } else { var p = args['_'].join(' ').split(','); if(p.length<2) { response = "Proper usage: alert TITLE, CAPTION [, TIMEOUT]"; // Display usage } else { this._alert = require('message-box').create(p[0], p[1], p.length==3?parseInt(p[2]):9999,1); } } break; case 'agentsize': var actualSize = Math.floor(require('fs').statSync(process.execPath).size / 1024); if (process.platform == 'win32') { // Check the Agent Uninstall MetaData for correctness, as the installer may have written an incorrect value var writtenSize = 0; try { writtenSize = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MeshCentralAgent', 'EstimatedSize'); } catch (x) { response = x; } if (writtenSize != actualSize) { response = "Size updated from: " + writtenSize + " to: " + actualSize; try { require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MeshCentralAgent', 'EstimatedSize', actualSize); } catch (x2) { response = x2; } } else { response = "Agent Size: " + actualSize + " kb"; } } else { response = "Agent Size: " + actualSize + " kb"; } break; case 'versions': response = JSON.stringify(process.versions, null, ' '); break; case 'wpfhwacceleration': if (process.platform != 'win32') { throw ("wpfhwacceleration setting is only supported on Windows"); } if (args['_'].length != 1) { response = "Proper usage: wpfhwacceleration (ON|OFF|STATUS)"; // Display usage } else { var reg = require('win-registry'); var uname = require('user-sessions').getUsername(require('user-sessions').consoleUid()); var key = reg.usernameToUserKey(uname); switch (args['_'][0].toUpperCase()) { default: response = "Proper usage: wpfhwacceleration (ON|OFF|STATUS|DEFAULT)"; // Display usage break; case 'ON': try { reg.WriteKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration', 0); response = "OK"; } catch (ex) { response = "FAILED"; } break; case 'OFF': try { reg.WriteKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration', 1); response = 'OK'; } catch (ex) { response = 'FAILED'; } break; case 'STATUS': var s; try { s = reg.QueryKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration') == 1 ? 'DISABLED' : 'ENABLED'; } catch (ex) { s = 'DEFAULT'; } response = "WPF Hardware Acceleration: " + s; break; case 'DEFAULT': try { reg.DeleteKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration'); } catch (ex) { } response = 'OK'; break; } } break; case 'tsid': if (process.platform == 'win32') { if (args['_'].length != 1) { response = "TSID: " + (require('MeshAgent')._tsid == null ? "console" : require('MeshAgent')._tsid); } else { var i = parseInt(args['_'][0]); require('MeshAgent')._tsid = (isNaN(i) ? null : i); response = "TSID set to: " + (require('MeshAgent')._tsid == null ? "console" : require('MeshAgent')._tsid); } } else { response = "TSID command only supported on Windows"; } break; case 'activeusers': if (process.platform == 'win32') { var p = require('user-sessions').enumerateUsers(); p.sessionid = sessionid; p.then(function (u) { var v = []; for (var i in u) { if (u[i].State == 'Active') { v.push({ tsid: i, type: u[i].StationName, user: u[i].Username, domain: u[i].Domain }); } } sendConsoleText(JSON.stringify(v, null, 1), this.sessionid); }); } else { response = "activeusers command only supported on Windows"; } break; case 'wallpaper': if (process.platform != 'win32' && !(process.platform == 'linux' && require('linux-gnome-helpers').available)) { response = "wallpaper command not supported on this platform"; } else { if (args['_'].length != 1) { response = 'Proper usage: wallpaper (GET|TOGGLE)'; // Display usage } else { switch (args['_'][0].toUpperCase()) { default: response = 'Proper usage: wallpaper (GET|TOGGLE)'; // Display usage break; case 'GET': case 'TOGGLE': if (process.platform == 'win32') { 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: id }); 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 (args['_'][0].toUpperCase() == 'GET') { response = current; break; } if (current != '') { require('MeshAgent')._wallpaper = current; response = 'Wallpaper cleared'; } else { response = 'Wallpaper restored'; } child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0=', current != '' ? '""' : require('MeshAgent')._wallpaper], { type: id }); 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 (args['_'][0].toUpperCase() == 'GET') { response = current; break; } if (current != '/dev/null') { require('MeshAgent')._wallpaper = current; response = 'Wallpaper cleared'; } else { response = 'Wallpaper restored'; } require('linux-gnome-helpers').setDesktopWallpaper(id, current != '/dev/null' ? undefined : require('MeshAgent')._wallpaper); } break; } } } break; case 'safemode': if (process.platform != 'win32') { response = 'safemode only supported on Windows Platforms' } else { if (args['_'].length != 1) { response = 'Proper usage: safemode (ON|OFF|STATUS)'; // Display usage } else { switch (args['_'][0].toUpperCase()) { default: response = 'Proper usage: safemode (ON|OFF|STATUS)'; // Display usage break; case 'ON': require('win-bcd').setKey('safeboot', 'Network'); require('win-bcd').enableSafeModeService('Mesh Agent'); break; case 'OFF': require('win-bcd').deleteKey('safeboot'); break; case 'STATUS': var nextboot = require('win-bcd').getKey('safeboot'); if (nextboot) { switch (nextboot) { case 'Network': case 'network': nextboot = 'SAFE_MODE_NETWORK'; break; default: nextboot = 'SAFE_MODE'; break; } } response = 'Current: ' + require('win-bcd').bootMode + ', NextBoot: ' + (nextboot ? nextboot : 'NORMAL'); break; } } } break; /* case 'border': { if ((args['_'].length == 1) && (args['_'][0] == 'on')) { if (meshCoreObj.users.length > 0) { obj.borderManager.Start(meshCoreObj.users[0]); response = 'Border blinking is on.'; } else { response = 'Cannot turn on border blinking, no logged in users.'; } } else if ((args['_'].length == 1) && (args['_'][0] == 'off')) { obj.borderManager.Stop(); response = 'Border blinking is off.'; } else { response = 'Proper usage: border "on|off"'; // Display correct command usage } } break; */ case 'av': if (process.platform == 'win32') { // Windows Command: "wmic /Namespace:\\root\SecurityCenter2 Path AntiVirusProduct get /FORMAT:CSV" response = JSON.stringify(require('win-info').av(), null, 1); } else { response = 'Not supported on the platform'; } break; case 'log': if (args['_'].length != 1) { response = 'Proper usage: log "sample text"'; } else { MeshServerLog(args['_'][0]); response = 'ok'; } break; case 'getclip': if (require('MeshAgent').isService) { require('clipboard').dispatchRead().then(function (str) { sendConsoleText(str, sessionid); }); } else { require("clipboard").read().then(function (str) { sendConsoleText(str, sessionid); }); } break; case 'setclip': { if (args['_'].length != 1) { response = 'Proper usage: setclip "sample text"'; } else { if (require('MeshAgent').isService) { require('clipboard').dispatchWrite(args['_'][0]); response = 'Setting clipboard to: "' + args['_'][0] + '"'; } else { require("clipboard")(args['_'][0]); response = 'Setting clipboard to: "' + args['_'][0] + '"'; } } break; } case 'amtreset': { if (amt != null) { amt.reset(); response = 'Done.'; } break; } case 'amtlmsreset': { if (amt != null) { amt.lmsreset(); response = 'Done.'; } break; } case 'amtccm': { if (amt == null) { response = 'Intel AMT not supported.'; } else { if (args['_'].length != 1) { response = 'Proper usage: amtccm (adminPassword)'; } // Display usage else { amt.setPolicy({ type: 0 }); amt.activeToCCM(args['_'][0]); } } break; } case 'amtacm': { if (amt == null) { response = 'Intel AMT not supported.'; } else { amt.setPolicy({ type: 0 }); amt.getAmtInfo(function (meinfo) { amt.activeToACM(meinfo); }); } break; } case 'amtdeactivate': { if (amt == null) { response = 'Intel AMT not supported.'; } else { amt.setPolicy({ type: 0 }); amt.deactivateCCM(); } break; } case 'amtpolicy': { if (amtPolicy == null) { response = 'No Intel(R) AMT policy.'; } else { response = JSON.stringify(amtPolicy); } break; } case 'openurl': { if (args['_'].length != 1) { response = 'Proper usage: openurl (url)'; } // Display usage else { if (openUserDesktopUrl(args['_'][0]) == null) { response = 'Failed.'; } else { response = 'Success.'; } } break; } case 'users': { if (meshCoreObj.users == null) { response = 'Active users are unknown.'; } else { response = 'Active Users: ' + meshCoreObj.users.join(', ') + '.'; } require('user-sessions').enumerateUsers().then(function (u) { for (var i in u) { sendConsoleText(u[i]); } }); break; } case 'toast': { if (args['_'].length < 1) { response = 'Proper usage: toast "message"'; } else { if (require('MeshAgent')._tsid == null) { require('toaster').Toast('MeshCentral', args['_'][0]).then(sendConsoleText, sendConsoleText); } else { require('toaster').Toast('MeshCentral', args['_'][0], require('MeshAgent')._tsid).then(sendConsoleText, sendConsoleText); } } break; } case 'setdebug': { if (args['_'].length < 1) { response = 'Proper usage: setdebug (target), 0 = Disabled, 1 = StdOut, 2 = This Console, * = All Consoles, 4 = WebLog, 8 = Logfile'; } // Display usage else { if (args['_'][0] == '*') { console.setDestination(2); } else { console.setDestination(parseInt(args['_'][0]), sessionid); } } break; } case 'ps': { processManager.getProcesses(function (plist) { var x = ''; for (var i in plist) { x += i + ((plist[i].user) ? (', ' + plist[i].user) : '') + ', ' + plist[i].cmd + '\r\n'; } sendConsoleText(x, sessionid); }); break; } case 'kill': { if ((args['_'].length < 1)) { response = 'Proper usage: kill [pid]'; // Display correct command usage } else { process.kill(parseInt(args['_'][0])); response = 'Killed process ' + args['_'][0] + '.'; } break; } case 'smbios': { if (SMBiosTables == null) { response = 'SMBios tables not available.'; } else { response = objToString(SMBiosTables, 0, ' ', true); } break; } case 'rawsmbios': { if (SMBiosTablesRaw == null) { response = 'SMBios tables not available.'; } else { response = ''; for (var i in SMBiosTablesRaw) { var header = false; for (var j in SMBiosTablesRaw[i]) { if (SMBiosTablesRaw[i][j].length > 0) { if (header == false) { response += ('Table type #' + i + ((require('smbios').smTableTypes[i] == null) ? '' : (', ' + require('smbios').smTableTypes[i]))) + '\r\n'; header = true; } response += (' ' + SMBiosTablesRaw[i][j].toString('hex')) + '\r\n'; } } } } break; } case 'dump': if (args['_'].length < 1) { response = 'Proper usage: dump [on/off/status]'; // Display correct command usage } else { switch (args['_'][0].toLowerCase()) { case 'on': process.coreDumpLocation = process.platform == 'win32' ? process.execPath.replace('.exe', '.dmp') : (process.execPath + '.dmp'); response = 'enabled'; break; case 'off': process.coreDumpLocation = null; response = 'disabled'; break; case 'status': if (process.coreDumpLocation) { response = 'Core Dump: [ENABLED' + (require('fs').existsSync(process.coreDumpLocation) ? (', (DMP file exists)]') : (']')); } else { response = 'Core Dump: [DISABLED]'; } break; default: response = 'Proper usage: dump [on/off/status]'; // Display correct command usage break; } } break; case 'eval': { // Eval JavaScript if (args['_'].length < 1) { response = 'Proper usage: eval "JavaScript code"'; // Display correct command usage } else { response = JSON.stringify(mesh.eval(args['_'][0])); } break; } case 'uninstallagent': // Uninstall this agent var agentName = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'; if (!require('service-manager').manager.getService(agentName).isMe()) { response = 'Uininstall failed, this instance is not the service instance'; } else { try { diagnosticAgent_uninstall(); } catch (x) { } var js = "require('service-manager').manager.getService('" + agentName + "').stop(); require('service-manager').manager.uninstallService('" + agentName + "'); process.exit();"; 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 }); } break; case 'notify': { // Send a notification message to the mesh if (args['_'].length != 1) { response = 'Proper usage: notify "message" [--session]'; // Display correct command usage } else { var notification = { action: 'msg', type: 'notify', value: args['_'][0], tag: 'console' }; if (args.session) { notification.sessionid = sessionid; } // If "--session" is specified, notify only this session, if not, the server will notify the mesh mesh.SendCommand(notification); // no sessionid or userid specified, notification will go to the entire mesh response = "ok"; } break; } case 'cpuinfo': { // Return system information // CPU & memory utilization pr = require('sysinfo').cpuUtilization(); pr.sessionid = sessionid; pr.then(function (data) { sendConsoleText(JSON.stringify({ cpu: data, memory: require('sysinfo').memUtilization() }, null, 1), this.sessionid); }, function (e) { sendConsoleText(e); }); break; } case 'sysinfo': { // Return system information getSystemInformation(function (results, err) { if (results == null) { sendConsoleText(err, this.sessionid); } else { sendConsoleText(JSON.stringify(results, null, 1), this.sessionid); } }); break; } case 'info': { // Return information about the agent and agent core module 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 + '.'; if (amt != null) { response += '\r\nBuilt-in LMS: ' + ['Disabled', 'Connecting..', 'Connected'][amt.lmsstate] + '.'; } if (meshCoreObj.osdesc) { response += '\r\nOS: ' + meshCoreObj.osdesc + '.'; } response += '\r\nModules: ' + addedModules.join(', ') + '.'; response += '\r\nServer Connection: ' + mesh.isControlChannelConnected + ', State: ' + meshServerConnectionState + '.'; response += '\r\lastMeInfo: ' + lastMeInfo + '.'; var oldNodeId = db.Get('OldNodeId'); if (oldNodeId != null) { response += '\r\nOldNodeID: ' + oldNodeId + '.'; } if (process.platform == 'linux' || process.platform == 'freebsd') { response += '\r\nX11 support: ' + require('monitor-info').kvm_x11_support + '.'; } break; } case 'osinfo': { // Return the operating system information var i = 1; if (args['_'].length > 0) { i = parseInt(args['_'][0]); if (i > 8) { i = 8; } response = 'Calling ' + i + ' times.'; } for (var j = 0; j < i; j++) { var pr = require('os').name(); pr.sessionid = sessionid; pr.then(function (v) { sendConsoleText("OS: " + v + (process.platform == 'win32' ? (require('win-virtual-terminal').supported ? ' [ConPTY: YES]' : ' [ConPTY: NO]') : ''), this.sessionid); }); } break; } case 'sendcaps': { // Send capability flags to the server if (args['_'].length == 0) { response = 'Proper usage: sendcaps (number)'; // Display correct command usage } else { meshCoreObj.caps = parseInt(args['_'][0]); mesh.SendCommand(meshCoreObj); response = JSON.stringify(meshCoreObj); } break; } case 'sendosdesc': { // Send OS description if (args['_'].length > 0) { meshCoreObj.osdesc = args['_'][0]; mesh.SendCommand(meshCoreObj); response = JSON.stringify(meshCoreObj); } else { response = 'Proper usage: sendosdesc [os description]'; // Display correct command usage } break; } case 'args': { // Displays parsed command arguments response = 'args ' + objToString(args, 0, ' ', true); break; } case 'print': { // Print a message on the mesh agent console, does nothing when running in the background var r = []; for (var i in args['_']) { r.push(args['_'][i]); } console.log(r.join(' ')); response = 'Message printed on agent console.'; break; } case 'type': { // Returns the content of a file if (args['_'].length == 0) { response = 'Proper usage: type (filepath) [maxlength]'; // Display correct command usage } else { var max = 4096; if ((args['_'].length > 1) && (typeof args['_'][1] == 'number')) { max = args['_'][1]; } if (max > 4096) max = 4096; var buf = Buffer.alloc(max), fd = fs.openSync(args['_'][0], "r"), r = fs.readSync(fd, buf, 0, max); // Read the file content response = buf.toString(); var i = response.indexOf('\n'); if ((i > 0) && (response[i - 1] != '\r')) { response = response.split('\n').join('\r\n'); } if (r == max) response += '...'; fs.closeSync(fd); } break; } case 'dbkeys': { // Return all data store keys response = JSON.stringify(db.Keys); break; } case 'dbget': { // Return the data store value for a given key if (db == null) { response = 'Database not accessible.'; break; } if (args['_'].length != 1) { response = 'Proper usage: dbget (key)'; // Display the value for a given database key } else { response = db.Get(args['_'][0]); } break; } case 'dbset': { // Set a data store key and value pair if (db == null) { response = 'Database not accessible.'; break; } if (args['_'].length != 2) { response = 'Proper usage: dbset (key) (value)'; // Set a database key } else { var r = db.Put(args['_'][0], args['_'][1]); response = 'Key set: ' + r; } break; } case 'dbcompact': { // Compact the data store if (db == null) { response = 'Database not accessible.'; break; } var r = db.Compact(); response = 'Database compacted: ' + r; break; } case 'httpget': { if (consoleHttpRequest != null) { response = 'HTTP operation already in progress.'; } else { if (args['_'].length != 1) { response = 'Proper usage: httpget (url)'; } else { var options = http.parseUri(args['_'][0]); options.method = 'GET'; if (options == null) { response = 'Invalid url.'; } else { try { consoleHttpRequest = http.request(options, consoleHttpResponse); } catch (e) { response = 'Invalid HTTP GET request'; } consoleHttpRequest.sessionid = sessionid; if (consoleHttpRequest != null) { consoleHttpRequest.end(); response = 'HTTPGET ' + options.protocol + '//' + options.host + ':' + options.port + options.path; } } } } break; } case 'wslist': { // List all web sockets response = ''; for (var i in consoleWebSockets) { var httprequest = consoleWebSockets[i]; response += 'Websocket #' + i + ', ' + httprequest.url + '\r\n'; } if (response == '') { response = 'no websocket sessions.'; } break; } case 'wsconnect': { // Setup a web socket if (args['_'].length == 0) { response = 'Proper usage: wsconnect (url)\r\nFor example: wsconnect wss://localhost:443/meshrelay.ashx?id=abc'; // Display correct command usage } else { var httprequest = null; try { var options = http.parseUri(args['_'][0].split('$').join('%24').split('@').join('%40')); // Escape the $ and @ characters in the URL options.rejectUnauthorized = 0; httprequest = http.request(options); } catch (e) { response = 'Invalid HTTP websocket request'; } if (httprequest != null) { httprequest.upgrade = onWebSocketUpgrade; httprequest.on('error', function (e) { sendConsoleText("ERROR: Unable to connect to: " + this.url + ", " + JSON.stringify(e)); }); var index = 1; while (consoleWebSockets[index]) { index++; } httprequest.sessionid = sessionid; httprequest.index = index; httprequest.url = args['_'][0]; consoleWebSockets[index] = httprequest; response = 'New websocket session #' + index; } } break; } case 'wssend': { // Send data on a web socket if (args['_'].length == 0) { response = 'Proper usage: wssend (socketnumber)\r\n'; // Display correct command usage for (var i in consoleWebSockets) { var httprequest = consoleWebSockets[i]; response += 'Websocket #' + i + ', ' + httprequest.url + '\r\n'; } } else { var i = parseInt(args['_'][0]); var httprequest = consoleWebSockets[i]; if (httprequest != undefined) { httprequest.s.write(args['_'][1]); response = 'ok'; } else { response = 'Invalid web socket number'; } } break; } case 'wsclose': { // Close a websocket if (args['_'].length == 0) { response = 'Proper usage: wsclose (socketnumber)'; // Display correct command usage } else { var i = parseInt(args['_'][0]); var httprequest = consoleWebSockets[i]; if (httprequest != undefined) { if (httprequest.s != null) { httprequest.s.end(); } else { httprequest.end(); } response = 'ok'; } else { response = 'Invalid web socket number'; } } break; } case 'tunnels': { // Show the list of current tunnels response = ''; for (var i in tunnels) { response += 'Tunnel #' + i + ', ' + tunnels[i].url + '\r\n'; } if (response == '') { response = 'No websocket sessions.'; } break; } case 'ls': { // Show list of files and folders response = ''; var xpath = '*'; if (args['_'].length > 0) { xpath = obj.path.join(args['_'][0], '*'); } response = 'List of ' + xpath + '\r\n'; var results = fs.readdirSync(xpath); for (var i = 0; i < results.length; ++i) { var stat = null, p = obj.path.join(args['_'][0], results[i]); try { stat = fs.statSync(p); } catch (e) { } if ((stat == null) || (stat == undefined)) { response += (results[i] + "\r\n"); } else { response += (results[i] + " " + ((stat.isDirectory()) ? "(Folder)" : "(File)") + "\r\n"); } } break; } case 'lsx': { // Show list of files and folders response = objToString(getDirectoryInfo(args['_'][0]), 0, ' ', true); break; } case 'lock': { // Lock the current user out of the desktop if (process.platform == 'win32') { var child = require('child_process'); child.execFile(process.env['windir'] + '\\system32\\cmd.exe', ['/c', 'RunDll32.exe user32.dll,LockWorkStation'], { type: 1 }); response = 'Ok'; } else { response = 'Not supported on the platform'; } break; } case 'amt': { // Show Intel AMT status if (amt != null) { amt.getAmtInfo(function (state) { var resp = 'Intel AMT not detected.'; if (state != null) { resp = objToString(state, 0, ' ', true); } sendConsoleText(resp, sessionid); }); } else { response = 'Intel AMT not detected.'; } break; } case 'netinfo': { // Show network interface information var interfaces = require('os').networkInterfaces(); response = objToString(interfaces, 0, ' ', true); break; } case 'wakeonlan': { // Send wake-on-lan if ((args['_'].length != 1) || (args['_'][0].length != 12)) { response = 'Proper usage: wakeonlan [mac], for example "wakeonlan 010203040506".'; } else { var count = sendWakeOnLan(args['_'][0]); response = 'Sent wake-on-lan on ' + count + ' interface(s).'; } break; } case 'sendall': { // Send a message to all consoles on this mesh sendConsoleText(args['_'].join(' ')); break; } case 'power': { // Execute a power action on this computer if (mesh.ExecPowerState == undefined) { response = 'Power command not supported on this agent.'; } else { if ((args['_'].length == 0) || isNaN(Number(args['_'][0]))) { 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 } else { var r = mesh.ExecPowerState(Number(args['_'][0]), Number(args['_'][1])); response = 'Power action executed with return code: ' + r + '.'; } } break; } case 'location': { getIpLocationData(function (location) { sendConsoleText(objToString({ action: 'iplocation', type: 'publicip', value: location }, 0, ' ')); }); break; } case 'parseuri': { response = JSON.stringify(http.parseUri(args['_'][0])); break; } case 'scanwifi': { if (wifiScanner != null) { var wifiPresent = wifiScanner.hasWireless; if (wifiPresent) { response = "Perfoming Wifi scan..."; wifiScanner.Scan(); } else { response = "Wifi absent."; } } else { response = "Wifi module not present."; } break; } case 'scanamt': { if (amtscanner != null) { if (args['_'].length != 1) { response = 'Usage examples:\r\n scanamt 1.2.3.4\r\n scanamt 1.2.3.0-1.2.3.255\r\n scanamt 1.2.3.0/24\r\n'; // Display correct command usage } else { response = 'Scanning: ' + args['_'][0] + '...'; amtscanner.scan(args['_'][0], 2000, function (data) { if (data.length > 0) { var r = '', pstates = ['NotActivated', 'InActivation', 'Activated']; for (var i in data) { var x = data[i]; if (r != '') { r += '\r\n'; } r += x.address + ' - Intel AMT v' + x.majorVersion + '.' + x.minorVersion; if (x.provisioningState < 3) { r += (', ' + pstates[x.provisioningState]); } if (x.provisioningState == 2) { r += (', ' + x.openPorts.join(', ')); } r += '.'; } } else { r = 'No Intel AMT found.'; } sendConsoleText(r); }); } } else { response = "Intel AMT scanner module not present."; } break; } case 'modules': { response = JSON.stringify(addedModules); break; } case 'listservices': { var services = require('service-manager').manager.enumerateService(); response = JSON.stringify(services, null, 1); break; } case 'getscript': { if (args['_'].length != 1) { response = "Proper usage: getscript [scriptNumber]."; } else { mesh.SendCommand({ action: 'getScript', type: args['_'][0] }); } break; } case 'diagnostic': { if (!mesh.DAIPC.listening) { response = 'Unable to bind to Diagnostic IPC, most likely because the path (' + process.cwd() + ') is not on a local file system'; break; } var diag = diagnosticAgent_installCheck(); if (diag) { if (args['_'].length == 1 && args['_'][0] == 'uninstall') { diagnosticAgent_uninstall(); response = 'Diagnostic Agent uninstalled'; } else { response = 'Diagnostic Agent installed at: ' + diag.appLocation(); } } else { if (args['_'].length == 1 && args['_'][0] == 'install') { diag = diagnosticAgent_installCheck(true); if (diag) { response = 'Diagnostic agent was installed at: ' + diag.appLocation(); } else { response = 'Diagnostic agent installation failed'; } } else { response = 'Diagnostic Agent Not installed. To install: diagnostic install'; } } if (diag) { diag.close(); diag = null; } break; } case 'apf': { if (meshCoreObj.intelamt !== null) { if (args['_'].length == 1) { if (args['_'][0] == 'on') { response = "Starting APF tunnel"; var apfarg = { mpsurl: mesh.ServerUrl.replace('agent.ashx', 'apf.ashx'), mpsuser: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16), mpspass: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16), mpskeepalive: 60000, clientname: require('os').hostname(), clientaddress: '127.0.0.1', clientuuid: meshCoreObj.intelamt.uuid }; var tobj = { debug: false }; // apftunnel = require('apfclient')(tobj, apfarg); try { apftunnel.connect(); response += "..success"; } catch (e) { response += JSON.stringify(e); } } else if (args['_'][0] == 'off') { response = "Stopping APF tunnel"; try { apftunnel.disconnect(); response += "..success"; } catch (e) { response += JSON.stringify(e); } apftunnel = null; } else { response = "Invalid command.\r\nCmd syntax: apf on|off"; } } else { response = "APF tunnel is " + (apftunnel == null ? "off" : "on"); } } else { response = "APF tunnel requires Intel AMT"; } break; } case 'plugin': { if (typeof args['_'][0] == 'string') { try { // Pass off the action to the plugin // for plugin creators, you'll want to have a plugindir/modules_meshcore/plugin.js // to control the output / actions here. response = require(args['_'][0]).consoleaction(args, rights, sessionid, mesh); } catch (e) { response = "There was an error in the plugin (" + e + ")"; } } else { response = "Proper usage: plugin [pluginName] [args]."; } break; } default: { // This is an unknown command, return an error message response = "Unknown command \"" + cmd + "\", type \"help\" for list of avaialble commands."; break; } } } catch (e) { response = "Command returned an exception error: " + e; console.log(e); } if (response != null) { sendConsoleText(response, sessionid); } } // Send a mesh agent console command function sendConsoleText(text, sessionid) { if (typeof text == 'object') { text = JSON.stringify(text); } mesh.SendCommand({ action: 'msg', type: 'console', value: text, sessionid: sessionid }); } // Called before the process exits //process.exit = function (code) { console.log("Exit with code: " + code.toString()); } // Called when the server connection state changes function handleServerConnection(state) { meshServerConnectionState = state; if (meshServerConnectionState == 0) { // Server disconnected if (selfInfoUpdateTimer != null) { clearInterval(selfInfoUpdateTimer); selfInfoUpdateTimer = null; } lastSelfInfo = null; } else { // Server connected, send mesh core information var oldNodeId = db.Get('OldNodeId'); if (oldNodeId != null) { mesh.SendCommand({ action: 'mc1migration', oldnodeid: oldNodeId }); } // Update the server with basic info, logged in users and more. mesh.SendCommand(meshCoreObj); // Send SMBios tables if present if (SMBiosTablesRaw != null) { mesh.SendCommand({ action: 'smbios', value: SMBiosTablesRaw }); } // Update the server on more advanced stuff, like Intel ME and Network Settings meInfoStr = null; sendPeriodicServerUpdate(); if (selfInfoUpdateTimer == null) { selfInfoUpdateTimer = setInterval(sendPeriodicServerUpdate, 1200000); } // 20 minutes } } // Update the server with the latest network interface information var sendNetworkUpdateNagleTimer = null; function sendNetworkUpdateNagle() { if (sendNetworkUpdateNagleTimer != null) { clearTimeout(sendNetworkUpdateNagleTimer); sendNetworkUpdateNagleTimer = null; } sendNetworkUpdateNagleTimer = setTimeout(sendNetworkUpdate, 5000); } function sendNetworkUpdate(force) { sendNetworkUpdateNagleTimer = null; // Update the network interfaces information data var netInfo = { netif2: require('os').networkInterfaces() }; if (netInfo.netif2) { netInfo.action = 'netinfo'; var netInfoStr = JSON.stringify(netInfo); if ((force == true) || (clearGatewayMac(netInfoStr) != clearGatewayMac(lastNetworkInfo))) { mesh.SendCommand(netInfo); lastNetworkInfo = netInfoStr; } } } // Called periodically to check if we need to send updates to the server function sendPeriodicServerUpdate(flags) { if (meshServerConnectionState == 0) return; // Not connected to server, do nothing. if (!flags) { flags = 0xFFFFFFFF; } if ((flags & 1) && (amt != null)) { // If we have a connected MEI, get Intel ME information amt.getAmtInfo(function (meinfo) { try { if (meinfo == null) return; var intelamt = {}, p = false; if ((meinfo.Versions != null) && (meinfo.Versions.AMT != null)) { intelamt.ver = meinfo.Versions.AMT; p = true; if (meinfo.Versions.Sku != null) { intelamt.sku = parseInt(meinfo.Versions.Sku); } } if (meinfo.ProvisioningState != null) { intelamt.state = meinfo.ProvisioningState; p = true; } if (meinfo.Flags != null) { intelamt.flags = meinfo.Flags; p = true; } if (meinfo.OsHostname != null) { intelamt.host = meinfo.OsHostname; p = true; } if (meinfo.UUID != null) { intelamt.uuid = meinfo.UUID; p = true; } if ((meinfo.ProvisioningState == 0) && (meinfo.net0 != null) && (meinfo.net0.enabled == 1)) { // If not activated, look to see if we have wired net working. // Not activated and we have wired ethernet, look for the trusted DNS var dns = meinfo.DNS; if (dns == null) { // Trusted DNS not set, let's look for the OS network DNS suffix var interfaces = require('os').networkInterfaces(); for (var i in interfaces) { for (var j in interfaces[i]) { if ((interfaces[i][j].mac == mestate.net0.mac) && (interfaces[i][j].fqdn != null) && (interfaces[i][j].fqdn != '')) { dns = interfaces[i][j].fqdn; } } } } if (intelamt.dns != dns) { intelamt.dns = dns; p = true; } } else { if (intelamt.dns != null) { delete intelamt.dns; p = true; } } if (p == true) { var meInfoStr = JSON.stringify(intelamt); if (meInfoStr != lastMeInfo) { meshCoreObj.intelamt = intelamt; mesh.SendCommand(meshCoreObj); lastMeInfo = meInfoStr; } } } catch (ex) { } }); } if (flags & 2) { // Update network information sendNetworkUpdateNagle(false); } if ((flags & 4) && (process.platform == 'win32')) { // Update anti-virus information // Windows Command: "wmic /Namespace:\\root\SecurityCenter2 Path AntiVirusProduct get /FORMAT:CSV" var av, pr; try { av = require('win-info').av(); } catch (ex) { av = null; } // Antivirus //if (process.platform == 'win32') { try { pr = require('win-info').pendingReboot(); } catch (ex) { pr = null; } } // Pending reboot if ((meshCoreObj.av == null) || (JSON.stringify(meshCoreObj.av) != JSON.stringify(av))) { meshCoreObj.av = av; mesh.SendCommand(meshCoreObj); } } } // Starting function obj.start = function () { // Setup the mesh agent event handlers mesh.AddCommandHandler(handleServerCommand); mesh.AddConnectHandler(handleServerConnection); // Parse input arguments //var args = parseArgs(process.argv); //console.log(args); //resetMicroLms(); // 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) { 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; mesh.SendCommand(meshCoreObj); }); }); 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) { } } obj.stop = function () { mesh.AddCommandHandler(null); mesh.AddConnectHandler(null); } function onWebSocketClosed() { sendConsoleText("WebSocket #" + this.httprequest.index + " closed.", this.httprequest.sessionid); delete consoleWebSockets[this.httprequest.index]; } function onWebSocketData(data) { sendConsoleText("Got WebSocket #" + this.httprequest.index + " data: " + data, this.httprequest.sessionid); } function onWebSocketSendOk() { sendConsoleText("WebSocket #" + this.index + " SendOK.", this.sessionid); } function onWebSocketUpgrade(response, s, head) { sendConsoleText("WebSocket #" + this.index + " connected.", this.sessionid); this.s = s; s.httprequest = this; s.end = onWebSocketClosed; s.data = onWebSocketData; } return obj; } // // Module startup // try { var xexports = null, mainMeshCore = null; try { xexports = module.exports; } catch (e) { } if (xexports != null) { // If we are running within NodeJS, export the core module.exports.createMeshCore = createMeshCore; } else { // If we are not running in NodeJS, launch the core mainMeshCore = createMeshCore(); mainMeshCore.start(null); } } catch (ex) { require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: "uncaughtException2: " + ex }); }