diff --git a/agents/meshcore.js b/agents/meshcore.js index e9c277d2..e41632f9 100644 --- a/agents/meshcore.js +++ b/agents/meshcore.js @@ -14,7 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -process.on('uncaughtException', function (ex) { +process.on('uncaughtException', function (ex) +{ require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: "uncaughtException1: " + ex }); }); @@ -41,9 +42,11 @@ var MESHRIGHT_CHATNOTIFY = 16384; var MESHRIGHT_UNINSTALL = 32768; var MESHRIGHT_NODESKTOP = 65536; -if (require('MeshAgent').ARCHID == null) { +if (require('MeshAgent').ARCHID == null) +{ var id = null; - switch (process.platform) { + switch (process.platform) + { case 'win32': id = require('_GenericMarshal').PointerSize == 4 ? 3 : 4; break; @@ -51,10 +54,12 @@ if (require('MeshAgent').ARCHID == null) { id = require('_GenericMarshal').PointerSize == 4 ? 31 : 30; break; case 'darwin': - try { + try + { id = require('os').arch() == 'x64' ? 16 : 29; } - catch (xx) { + catch (xx) + { id = 16; } break; break; @@ -62,609 +67,685 @@ if (require('MeshAgent').ARCHID == null) { if (id != null) { Object.defineProperty(require('MeshAgent'), 'ARCHID', { value: id }); } } -function createMeshCore(agent) { - var obj = {}; - var agentFileHttpRequests = {}; // Currently active agent HTTPS GET requests from the server. - var agentFileHttpPendingRequests = []; // Pending HTTPS GET requests from the server. - var debugConsole = (_MSH().debugConsole == 1); +var obj = {}; +var agentFileHttpRequests = {}; // Currently active agent HTTPS GET requests from the server. +var agentFileHttpPendingRequests = []; // Pending HTTPS GET requests from the server. +var debugConsole = (_MSH().debugConsole == 1); - 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 (e) { } - if (writtenSize != actualSize) { try { require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MeshCentralAgent', 'EstimatedSize', actualSize); } catch (e) { } } - } catch (x) { } +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 (e) { } + if (writtenSize != actualSize) { try { require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MeshCentralAgent', 'EstimatedSize', actualSize); } catch (e) { } } + } catch (x) { } - // Check to see if we are the Installed Mesh Agent Service, if we are, make sure we can run in Safe Mode - var svcname = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'; - try { - svcname = require('MeshAgent').serviceName; - } - catch (x) { - } - - try { - var meshCheck = false; - try { meshCheck = require('service-manager').manager.getService(svcname).isMe(); } catch (e) { } - if (meshCheck && require('win-bcd').isSafeModeService && !require('win-bcd').isSafeModeService(svcname)) { require('win-bcd').enableSafeModeService(svcname); } - } catch (e) { } + // Check to see if we are the Installed Mesh Agent Service, if we are, make sure we can run in Safe Mode + var svcname = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'; + try + { + svcname = require('MeshAgent').serviceName; + } + catch (x) + { } - 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']); + try + { + var meshCheck = false; + try { meshCheck = require('service-manager').manager.getService(svcname).isMe(); } catch (e) { } + if (meshCheck && require('win-bcd').isSafeModeService && !require('win-bcd').isSafeModeService(svcname)) { require('win-bcd').enableSafeModeService(svcname); } + } catch (e) { } +} + +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("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.stdin.write("launchctl list | grep 'meshagent' | awk '{ if($3==\"meshagent\"){print $1;}}'\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'); + 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); + 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 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); + 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(); - } + child = require('child_process').execFile('/bin/sh', ['sh']); + child.stdout.str = ''; + child.stdout.on('data', function (chunk) { this.str += chunk.toString(); }); + child.stdin.write("launchctl load /Library/LaunchDaemons/meshagentRepair.plist\nexit\n"); + child.waitExit(); } } } +} - // Add an Intel AMT event to the log - function addAmtEvent(msg) { - if (obj.amtevents == null) { obj.amtevents = []; } - var d = new Date(); - obj.amtevents.push(zeroPad(d.getHours(), 2) + ':' + zeroPad(d.getMinutes(), 2) + ':' + zeroPad(d.getSeconds(), 2) + ', ' + msg); - if (obj.amtevents.length > 100) { obj.amtevents.splice(0, obj.amtevents.length - 100); } +// Add an Intel AMT event to the log +function addAmtEvent(msg) +{ + if (obj.amtevents == null) { obj.amtevents = []; } + var d = new Date(); + obj.amtevents.push(zeroPad(d.getHours(), 2) + ':' + zeroPad(d.getMinutes(), 2) + ':' + zeroPad(d.getSeconds(), 2) + ', ' + msg); + if (obj.amtevents.length > 100) { obj.amtevents.splice(0, obj.amtevents.length - 100); } +} +function zeroPad(num, size) { var s = '000000000' + num; return s.substr(s.length - size); } + + +// Create Secure IPC for Diagnostic Agent Communications +obj.DAIPC = require('net').createServer(); +if (process.platform != 'win32') { try { require('fs').unlinkSync(process.cwd() + '/DAIPC'); } catch (e) { } } +obj.DAIPC.IPCPATH = process.platform == 'win32' ? ('\\\\.\\pipe\\' + require('_agentNodeId')() + '-DAIPC') : (process.cwd() + '/DAIPC'); +try { obj.DAIPC.listen({ path: obj.DAIPC.IPCPATH, writableAll: true, maxConnections: 5 }); } catch (e) { } +obj.DAIPC._daipc = []; +obj.DAIPC.on('connection', function (c) +{ + c._send = function (j) + { + var data = JSON.stringify(j); + var packet = Buffer.alloc(data.length + 4); + packet.writeUInt32LE(data.length + 4, 0); + Buffer.from(data).copy(packet, 4); + this.write(packet); + }; + this._daipc.push(c); + c.parent = this; + c.on('end', function () { removeRegisteredApp(this); }); + c.on('data', function (chunk) + { + if (chunk.length < 4) { this.unshift(chunk); return; } + var len = chunk.readUInt32LE(0); + if (len > 8192) { removeRegisteredApp(this); this.end(); return; } + if (chunk.length < len) { this.unshift(chunk); return; } + + var data = chunk.slice(4, len); + try { data = JSON.parse(data.toString()); } catch (e) { } + if ((data == null) || (typeof data.cmd != 'string')) return; + + try + { + switch (data.cmd) + { + case 'requesthelp': + if (this._registered == null) return; + sendConsoleText('Request Help (' + this._registered + '): ' + data.value); + var help = {}; + help[this._registered] = data.value; + try { mesh.SendCommand({ action: 'sessions', type: 'help', value: help }); } catch (e) { } + MeshServerLogEx(98, [this._registered, data.value], "Help Requested, user: " + this._registered + ", details: " + data.value, null); + break; + case 'cancelhelp': + if (this._registered == null) return; + sendConsoleText('Cancel Help (' + this._registered + ')'); + try { mesh.SendCommand({ action: 'sessions', type: 'help', value: {} }); } catch (e) { } + break; + case 'register': + if (typeof data.value == 'string') + { + this._registered = data.value; + var apps = {}; + apps[data.value] = 1; + try { mesh.SendCommand({ action: 'sessions', type: 'app', value: apps }); } catch (e) { } + this._send({ cmd: 'serverstate', value: meshServerConnectionState, url: require('MeshAgent').ConnectedServer, amt: (amt != null) }); + } + break; + case 'query': + switch (data.value) + { + case 'connection': + data.result = require('MeshAgent').ConnectedServer; + this._send(data); + break; + case 'descriptors': + require('ChainViewer').getSnapshot().then(function (f) + { + this.tag.payload.result = f; + this.tag.ipc._send(this.tag.payload); + }).parentPromise.tag = { ipc: this, payload: data }; + break; + case 'timerinfo': + data.result = require('ChainViewer').getTimerInfo(); + this._send(data); + break; + } + break; + case 'amtstate': + if (amt == null) return; + var func = function amtStateFunc(state) { if (state != null) { amtStateFunc.pipe._send({ cmd: 'amtstate', value: state }); } } + func.pipe = this; + amt.getMeiState(11, func); + break; + case 'sessions': + this._send({ cmd: 'sessions', sessions: tunnelUserCount }); + break; + case 'meshToolInfo': + try { mesh.SendCommand({ action: 'meshToolInfo', name: data.name, hash: data.hash, cookie: data.cookie ? true : false, pipe: true }); } catch (e) { } + break; + case 'console': + if (debugConsole) + { + var args = splitArgs(data.value); + processConsoleCommand(args[0].toLowerCase(), parseArgs(args), 0, 'pipe'); + } + break; + } + } + catch (e) { removeRegisteredApp(this); this.end(); return; } + }); +}); + +// Send current sessions to registered apps +function broadcastSessionsToRegisteredApps(x) +{ + broadcastToRegisteredApps({ cmd: 'sessions', sessions: tunnelUserCount }); +} + +// Send this object to all registered local applications +function broadcastToRegisteredApps(x) +{ + if ((obj.DAIPC == null) || (obj.DAIPC._daipc == null)) return; + for (var i in obj.DAIPC._daipc) { if (obj.DAIPC._daipc[i]._registered != null) { obj.DAIPC._daipc[i]._send(x); } } +} + +// Send this object to a specific registered local applications +function sendToRegisteredApp(appid, x) +{ + if ((obj.DAIPC == null) || (obj.DAIPC._daipc == null)) return; + for (var i in obj.DAIPC._daipc) { if (obj.DAIPC._daipc[i]._registered == appid) { obj.DAIPC._daipc[i]._send(x); } } +} + +// Send list of registered apps to the server +function updateRegisteredAppsToServer() +{ + if ((obj.DAIPC == null) || (obj.DAIPC._daipc == null)) return; + var apps = {}; + for (var i in obj.DAIPC._daipc) { if (apps[obj.DAIPC._daipc[i]._registered] == null) { apps[obj.DAIPC._daipc[i]._registered] = 1; } else { apps[obj.DAIPC._daipc[i]._registered]++; } } + try { mesh.SendCommand({ action: 'sessions', type: 'app', value: apps }); } catch (e) { } +} + +// Remove a registered app +function removeRegisteredApp(pipe) +{ + for (var i = obj.DAIPC._daipc.length - 1; i >= 0; i--) { if (obj.DAIPC._daipc[i] === pipe) { obj.DAIPC._daipc.splice(i, 1); } } + if (pipe._registered != null) updateRegisteredAppsToServer(); +} + +function diagnosticAgent_uninstall() +{ + require('service-manager').manager.uninstallService('meshagentDiagnostic'); + require('task-scheduler').delete('meshagentDiagnostic/periodicStart'); +} +function diagnosticAgent_installCheck(install) +{ + try + { + var diag = require('service-manager').manager.getService('meshagentDiagnostic'); + return (diag); } - function zeroPad(num, size) { var s = '000000000' + num; return s.substr(s.length - size); } + 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'); + } - // Create Secure IPC for Diagnostic Agent Communications - obj.DAIPC = require('net').createServer(); - if (process.platform != 'win32') { try { require('fs').unlinkSync(process.cwd() + '/DAIPC'); } catch (e) { } } - obj.DAIPC.IPCPATH = process.platform == 'win32' ? ('\\\\.\\pipe\\' + require('_agentNodeId')() + '-DAIPC') : (process.cwd() + '/DAIPC'); - try { obj.DAIPC.listen({ path: obj.DAIPC.IPCPATH, writableAll: true, maxConnections: 5 }); } catch (e) { } - obj.DAIPC._daipc = []; - obj.DAIPC.on('connection', function (c) { - c._send = function (j) { - var data = JSON.stringify(j); - var packet = Buffer.alloc(data.length + 4); - packet.writeUInt32LE(data.length + 4, 0); - Buffer.from(data).copy(packet, 4); - this.write(packet); + require('MeshAgent').SendCommand({ action: 'diagnostic', value: { command: 'register', value: nodeid } }); + require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: "Diagnostic Agent Registered [" + nodeid.length + "/" + nodeid + "]" }); + + delete ddb; + + // Set a recurrent task, to run the Diagnostic Agent every 2 days + require('task-scheduler').create({ name: 'meshagentDiagnostic/periodicStart', daily: 2, time: require('tls').generateRandomInteger('0', '23') + ':' + require('tls').generateRandomInteger('0', '59').padStart(2, '0'), service: 'meshagentDiagnostic' }); + //require('task-scheduler').create({ name: 'meshagentDiagnostic/periodicStart', daily: '1', time: '17:16', service: 'meshagentDiagnostic' }); + + return (svc); +} + +// Monitor the file 'batterystate.txt' in the agent's folder and sends battery update when this file is changed. +if ((require('fs').existsSync(process.cwd() + 'batterystate.txt')) && (require('fs').watch != null)) +{ + // Setup manual battery monitoring + require('MeshAgent')._batteryFileWatcher = require('fs').watch(process.cwd(), function () + { + if (require('MeshAgent')._batteryFileTimer != null) return; + require('MeshAgent')._batteryFileTimer = setTimeout(function () + { + try + { + require('MeshAgent')._batteryFileTimer = null; + var data = null; + try { data = require('fs').readFileSync(process.cwd() + 'batterystate.txt').toString(); } catch (e) { } + if ((data != null) && (data.length < 10)) + { + data = data.split(','); + if ((data.length == 2) && ((data[0] == 'ac') || (data[0] == 'dc'))) + { + var level = parseInt(data[1]); + if ((level >= 0) && (level <= 100)) { require('MeshAgent').SendCommand({ action: 'battery', state: data[0], level: level }); } + } + } + } catch (e) { } + }, 1000); + }); +} +else +{ + // Setup normal battery monitoring + if (require('identifiers').isBatteryPowered && require('identifiers').isBatteryPowered()) + { + require('MeshAgent')._battLevelChanged = function _battLevelChanged(val) + { + _battLevelChanged.self._currentBatteryLevel = val; + _battLevelChanged.self.SendCommand({ action: 'battery', state: _battLevelChanged.self._currentPowerState, level: val }); }; - this._daipc.push(c); - c.parent = this; - c.on('end', function () { removeRegisteredApp(this); }); - c.on('data', function (chunk) { - if (chunk.length < 4) { this.unshift(chunk); return; } - var len = chunk.readUInt32LE(0); - if (len > 8192) { removeRegisteredApp(this); this.end(); return; } - if (chunk.length < len) { this.unshift(chunk); return; } + 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); + } + }); + } +} - var data = chunk.slice(4, len); - try { data = JSON.parse(data.toString()); } catch (e) { } - if ((data == null) || (typeof data.cmd != 'string')) return; - try { - switch (data.cmd) { - case 'requesthelp': - if (this._registered == null) return; - sendConsoleText('Request Help (' + this._registered + '): ' + data.value); - var help = {}; - help[this._registered] = data.value; - try { mesh.SendCommand({ action: 'sessions', type: 'help', value: help }); } catch (e) { } - MeshServerLogEx(98, [this._registered, data.value], "Help Requested, user: " + this._registered + ", details: " + data.value, null); - break; - case 'cancelhelp': - if (this._registered == null) return; - sendConsoleText('Cancel Help (' + this._registered + ')'); - try { mesh.SendCommand({ action: 'sessions', type: 'help', value: {} }); } catch (e) { } - break; - case 'register': - if (typeof data.value == 'string') { - this._registered = data.value; - var apps = {}; - apps[data.value] = 1; - try { mesh.SendCommand({ action: 'sessions', type: 'app', value: apps }); } catch (e) { } - this._send({ cmd: 'serverstate', value: meshServerConnectionState, url: require('MeshAgent').ConnectedServer, amt: (amt != null) }); - } - break; - case 'query': - switch (data.value) { - case 'connection': - data.result = require('MeshAgent').ConnectedServer; - this._send(data); - break; - case 'descriptors': - require('ChainViewer').getSnapshot().then(function (f) { - this.tag.payload.result = f; - this.tag.ipc._send(this.tag.payload); - }).parentPromise.tag = { ipc: this, payload: data }; - break; - case 'timerinfo': - data.result = require('ChainViewer').getTimerInfo(); - this._send(data); - break; - } - break; - case 'amtstate': - if (amt == null) return; - var func = function amtStateFunc(state) { if (state != null) { amtStateFunc.pipe._send({ cmd: 'amtstate', value: state }); } } - func.pipe = this; - amt.getMeiState(11, func); - break; - case 'sessions': - this._send({ cmd: 'sessions', sessions: tunnelUserCount }); - break; - case 'meshToolInfo': - try { mesh.SendCommand({ action: 'meshToolInfo', name: data.name, hash: data.hash, cookie: data.cookie ? true : false, pipe: true }); } catch (e) { } - break; - case 'console': - if (debugConsole) { - var args = splitArgs(data.value); - processConsoleCommand(args[0].toLowerCase(), parseArgs(args), 0, 'pipe'); - } - break; +// 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, root: require('user-sessions').isRoot() }; // Capability bitmask: 1 = Desktop, 2 = Terminal, 4 = Files, 8 = Console, 16 = JavaScript, 32 = Temporary Agent, 64 = Recovery Agent + +// Get the operating system description string +try { require('os').name().then(function (v) { meshCoreObj.osdesc = v; meshCoreObjChanged(); }); } catch (e) { } + + +// Setup logged in user monitoring (THIS IS BROKEN IN WIN7) +try +{ + var userSession = require('user-sessions'); + userSession.on('changed', function onUserSessionChanged() + { + userSession.enumerateUsers().then(function (users) + { + if (process.platform == 'linux') + { + if (userSession._startTime == null) + { + userSession._startTime = Date.now(); + userSession._count = users.length; + } + else if (Date.now() - userSession._startTime < 10000 && users.length == userSession._count) + { + userSession.removeAllListeners('changed'); + return; } } - catch (e) { removeRegisteredApp(this); this.end(); return; } + + var u = [], a = users.Active; + for (var i = 0; i < a.length; i++) + { + var un = a[i].Domain ? (a[i].Domain + '\\' + a[i].Username) : (a[i].Username); + if (u.indexOf(un) == -1) { u.push(un); } // Only push users in the list once. + } + meshCoreObj.users = u; + meshCoreObjChanged(); }); }); + userSession.emit('changed'); + //userSession.on('locked', function (user) { sendConsoleText('[' + (user.Domain ? user.Domain + '\\' : '') + user.Username + '] has LOCKED the desktop'); }); + //userSession.on('unlocked', function (user) { sendConsoleText('[' + (user.Domain ? user.Domain + '\\' : '') + user.Username + '] has UNLOCKED the desktop'); }); +} catch (e) { } - // Send current sessions to registered apps - function broadcastSessionsToRegisteredApps(x) { - broadcastToRegisteredApps({ cmd: 'sessions', sessions: tunnelUserCount }); +var meshServerConnectionState = 0; +var tunnels = {}; +var lastNetworkInfo = null; +var lastPublicLocationInfo = null; +var selfInfoUpdateTimer = null; +var http = require('http'); +var net = require('net'); +var fs = require('fs'); +var rtc = require('ILibWebRTC'); +var amt = null; +var processManager = require('process-manager'); +var wifiScannerLib = null; +var wifiScanner = null; +var networkMonitor = null; +var nextTunnelIndex = 1; +var apftunnel = null; +var tunnelUserCount = { terminal: {}, files: {}, tcp: {}, udp: {}, msg: {} }; // List of userid->count sessions for terminal, files and TCP/UDP routing + +// Add to the server event log +function MeshServerLog(msg, state) +{ + if (typeof msg == 'string') { msg = { action: 'log', msg: msg }; } else { msg.action = 'log'; } + if (state) + { + if (state.userid) { msg.userid = state.userid; } + if (state.username) { msg.username = state.username; } + if (state.sessionid) { msg.sessionid = state.sessionid; } + if (state.remoteaddr) { msg.remoteaddr = state.remoteaddr; } } + mesh.SendCommand(msg); +} - // Send this object to all registered local applications - function broadcastToRegisteredApps(x) { - if ((obj.DAIPC == null) || (obj.DAIPC._daipc == null)) return; - for (var i in obj.DAIPC._daipc) { if (obj.DAIPC._daipc[i]._registered != null) { obj.DAIPC._daipc[i]._send(x); } } +// Add to the server event log, use internationalized events +function MeshServerLogEx(id, args, msg, state) +{ + var msg = { action: 'log', msgid: id, msgArgs: args, msg: msg }; + if (state) + { + if (state.userid) { msg.userid = state.userid; } + if (state.username) { msg.username = state.username; } + if (state.sessionid) { msg.sessionid = state.sessionid; } + if (state.remoteaddr) { msg.remoteaddr = state.remoteaddr; } } + mesh.SendCommand(msg); +} - // Send this object to a specific registered local applications - function sendToRegisteredApp(appid, x) { - if ((obj.DAIPC == null) || (obj.DAIPC._daipc == null)) return; - for (var i in obj.DAIPC._daipc) { if (obj.DAIPC._daipc[i]._registered == appid) { obj.DAIPC._daipc[i]._send(x); } } - } +// Import libraries +db = require('SimpleDataStore').Shared(); +sha = require('SHA256Stream'); +mesh = require('MeshAgent'); +childProcess = require('child_process'); - // Send list of registered apps to the server - function updateRegisteredAppsToServer() { - if ((obj.DAIPC == null) || (obj.DAIPC._daipc == null)) return; - var apps = {}; - for (var i in obj.DAIPC._daipc) { if (apps[obj.DAIPC._daipc[i]._registered] == null) { apps[obj.DAIPC._daipc[i]._registered] = 1; } else { apps[obj.DAIPC._daipc[i]._registered]++; } } - try { mesh.SendCommand({ action: 'sessions', type: 'app', value: apps }); } catch (e) { } - } - - // Remove a registered app - function removeRegisteredApp(pipe) { - for (var i = obj.DAIPC._daipc.length - 1; i >= 0; i--) { if (obj.DAIPC._daipc[i] === pipe) { obj.DAIPC._daipc.splice(i, 1); } } - if (pipe._registered != null) updateRegisteredAppsToServer(); - } - - function diagnosticAgent_uninstall() { - require('service-manager').manager.uninstallService('meshagentDiagnostic'); - require('task-scheduler').delete('meshagentDiagnostic/periodicStart'); - }; - function diagnosticAgent_installCheck(install) { - try { - var diag = require('service-manager').manager.getService('meshagentDiagnostic'); - return (diag); +if (mesh.hasKVM == 1) +{ // if the agent is compiled with KVM support + // Check if this computer supports a desktop + try + { + if ((process.platform == 'win32') || (process.platform == 'darwin') || (require('monitor-info').kvm_x11_support)) + { + meshCoreObj.caps |= 1; meshCoreObjChanged(); + } else if (process.platform == 'linux' || process.platform == 'freebsd') + { + require('monitor-info').on('kvmSupportDetected', function (value) { meshCoreObj.caps |= 1; meshCoreObjChanged(); }); } - catch (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); - } - - // Monitor the file 'batterystate.txt' in the agent's folder and sends battery update when this file is changed. - if ((require('fs').existsSync(process.cwd() + 'batterystate.txt')) && (require('fs').watch != null)) { - // Setup manual battery monitoring - require('MeshAgent')._batteryFileWatcher = require('fs').watch(process.cwd(), function () { - if (require('MeshAgent')._batteryFileTimer != null) return; - require('MeshAgent')._batteryFileTimer = setTimeout(function () { - try { - require('MeshAgent')._batteryFileTimer = null; - var data = null; - try { data = require('fs').readFileSync(process.cwd() + 'batterystate.txt').toString(); } catch (e) { } - if ((data != null) && (data.length < 10)) { - data = data.split(','); - if ((data.length == 2) && ((data[0] == 'ac') || (data[0] == 'dc'))) { - var level = parseInt(data[1]); - if ((level >= 0) && (level <= 100)) { require('MeshAgent').SendCommand({ action: 'battery', state: data[0], level: level }); } - } - } - } catch (e) { } - }, 1000); - }); - } else { - // Setup normal battery monitoring - if (require('identifiers').isBatteryPowered && require('identifiers').isBatteryPowered()) { - require('MeshAgent')._battLevelChanged = function _battLevelChanged(val) { - _battLevelChanged.self._currentBatteryLevel = val; - _battLevelChanged.self.SendCommand({ action: 'battery', state: _battLevelChanged.self._currentPowerState, level: val }); - }; - require('MeshAgent')._battLevelChanged.self = require('MeshAgent'); - require('MeshAgent')._powerChanged = function _powerChanged(val) { - _powerChanged.self._currentPowerState = (val == 'AC' ? 'ac' : 'dc'); - _powerChanged.self.SendCommand({ action: 'battery', state: (val == 'AC' ? 'ac' : 'dc'), level: _powerChanged.self._currentBatteryLevel }); - }; - require('MeshAgent')._powerChanged.self = require('MeshAgent'); - require('MeshAgent').on('Connected', function (status) { - if (status == 0) { - require('power-monitor').removeListener('acdc', this._powerChanged); - require('power-monitor').removeListener('batteryLevel', this._battLevelChanged); - } else { - require('power-monitor').on('acdc', this._powerChanged); - require('power-monitor').on('batteryLevel', this._battLevelChanged); - } - }); - } - } - - - /* - 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 (e) { - 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, root: require('user-sessions').isRoot() }; // Capability bitmask: 1 = Desktop, 2 = Terminal, 4 = Files, 8 = Console, 16 = JavaScript, 32 = Temporary Agent, 64 = Recovery Agent - - // Get the operating system description string - try { require('os').name().then(function (v) { meshCoreObj.osdesc = v; meshCoreObjChanged(); }); } catch (e) { } - - // 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; - meshCoreObjChanged(); - }); - }); - userSession.emit('changed'); - //userSession.on('locked', function (user) { sendConsoleText('[' + (user.Domain ? user.Domain + '\\' : '') + user.Username + '] has LOCKED the desktop'); }); - //userSession.on('unlocked', function (user) { sendConsoleText('[' + (user.Domain ? user.Domain + '\\' : '') + user.Username + '] has UNLOCKED the desktop'); }); } catch (e) { } +} +mesh.DAIPC = obj.DAIPC; - var meshServerConnectionState = 0; - var tunnels = {}; - var lastNetworkInfo = null; - var lastPublicLocationInfo = null; - var selfInfoUpdateTimer = null; - var http = require('http'); - var net = require('net'); - var fs = require('fs'); - var rtc = require('ILibWebRTC'); - var amt = null; - var processManager = require('process-manager'); - var wifiScannerLib = null; - var wifiScanner = null; - var networkMonitor = null; - var nextTunnelIndex = 1; - var apftunnel = null; - var tunnelUserCount = { terminal: {}, files: {}, tcp: {}, udp: {}, msg: {} }; // List of userid->count sessions for terminal, files and TCP/UDP routing +/* +// 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; } +*/ - // Add to the server event log - function MeshServerLog(msg, state) { - if (typeof msg == 'string') { msg = { action: 'log', msg: msg }; } else { msg.action = 'log'; } - if (state) { - if (state.userid) { msg.userid = state.userid; } - if (state.username) { msg.username = state.username; } - if (state.sessionid) { msg.sessionid = state.sessionid; } - if (state.remoteaddr) { msg.remoteaddr = state.remoteaddr; } - } - mesh.SendCommand(msg); - } +// Fetch the SMBios Tables +var SMBiosTables = null; +var SMBiosTablesRaw = null; +try +{ + var SMBiosModule = null; + try { SMBiosModule = require('smbios'); } catch (e) { } + 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 }); } - // Add to the server event log, use internationalized events - function MeshServerLogEx(id, args, msg, state) { - var msg = { action: 'log', msgid: id, msgArgs: args, msg: msg }; - if (state) { - if (state.userid) { msg.userid = state.userid; } - if (state.username) { msg.username = state.username; } - if (state.sessionid) { msg.sessionid = state.sessionid; } - if (state.remoteaddr) { msg.remoteaddr = state.remoteaddr; } - } - mesh.SendCommand(msg); - } - - // Import libraries - db = require('SimpleDataStore').Shared(); - sha = require('SHA256Stream'); - mesh = require('MeshAgent'); - childProcess = require('child_process'); - if (mesh.hasKVM == 1) { // if the agent is compiled with KVM support - // Check if this computer supports a desktop - try { - if ((process.platform == 'win32') || (process.platform == 'darwin') || (require('monitor-info').kvm_x11_support)) { - meshCoreObj.caps |= 1; meshCoreObjChanged(); - } else if (process.platform == 'linux' || process.platform == 'freebsd') { - require('monitor-info').on('kvmSupportDetected', function (value) { meshCoreObj.caps |= 1; meshCoreObjChanged(); }); - } - } catch (e) { } - } - mesh.DAIPC = obj.DAIPC; - - /* - // Try to load up the network monitor - try { - networkMonitor = require('NetworkMonitor'); - networkMonitor.on('change', function () { sendNetworkUpdateNagle(); }); - networkMonitor.on('add', function (addr) { sendNetworkUpdateNagle(); }); - networkMonitor.on('remove', function (addr) { sendNetworkUpdateNagle(); }); - } catch (e) { networkMonitor = null; } - */ - - // Fetch the SMBios Tables - var SMBiosTables = null; - var SMBiosTablesRaw = null; - try { - var SMBiosModule = null; - try { SMBiosModule = require('smbios'); } catch (e) { } - if (SMBiosModule != null) { - SMBiosModule.get(function (data) { - if (data != null) { - SMBiosTablesRaw = data; - SMBiosTables = require('smbios').parse(data) - if (mesh.isControlChannelConnected) { mesh.SendCommand({ action: 'smbios', value: SMBiosTablesRaw }); } - - // If SMBios tables say that Intel AMT is present, try to connect MEI - if (SMBiosTables.amtInfo && (SMBiosTables.amtInfo.AMT == true)) { - var amtmodule = require('amt-manage'); - amt = new amtmodule(mesh, db, false); - amt.on('portBinding_LMS', function (map) { mesh.SendCommand({ action: 'lmsinfo', value: { ports: map.keys() } }); }); - amt.on('stateChange_LMS', function (v) { if (!meshCoreObj.intelamt) { meshCoreObj.intelamt = {}; } meshCoreObj.intelamt.microlms = v; meshCoreObjChanged(); }); // 0 = Disabled, 1 = Connecting, 2 = Connected - amt.onStateChange = function (state) { if (state == 2) { sendPeriodicServerUpdate(1); } } // MEI State - amt.reset(); - } + // If SMBios tables say that Intel AMT is present, try to connect MEI + if (SMBiosTables.amtInfo && (SMBiosTables.amtInfo.AMT == true)) + { + var amtmodule = require('amt-manage'); + amt = new amtmodule(mesh, db, false); + amt.on('portBinding_LMS', function (map) { mesh.SendCommand({ action: 'lmsinfo', value: { ports: map.keys() } }); }); + amt.on('stateChange_LMS', function (v) { if (!meshCoreObj.intelamt) { meshCoreObj.intelamt = {}; } meshCoreObj.intelamt.microlms = v; meshCoreObjChanged(); }); // 0 = Disabled, 1 = Connecting, 2 = Connected + amt.onStateChange = function (state) { if (state == 2) { sendPeriodicServerUpdate(1); } } // MEI State + amt.reset(); } - }); - } - } catch (e) { sendConsoleText("ex1: " + e); } - - // 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 (e) { 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; } + } + }); } +} catch (e) { sendConsoleText("ex1: " + e); } - // 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); +// 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 (e) { 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; } +} - 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) { +// 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(null); // Report no location + } + else + { + if (func) func(publicLocationInfo.locationData); // Can't get new location, report the old 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 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 () { +// Polyfill path.join +obj.path = + { + join: function () + { var x = []; - for (var i in arguments) { + for (var i in arguments) + { var w = arguments[i]; - if (w != null) { + if (w != null) + { while (w.endsWith('/') || w.endsWith('\\')) { w = w.substring(0, w.length - 1); } - if (i != 0) { + if (i != 0) + { while (w.startsWith('/') || w.startsWith('\\')) { w = w.substring(1); } } x.push(w); @@ -675,1198 +756,1385 @@ function createMeshCore(agent) { } }; - // 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; } +// 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 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 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 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 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; +} - // 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) + '}'; - } +// 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; +} - // Return p number of spaces - function addPad(p, ret) { var r = ''; for (var i = 0; i < p; i++) { r += ret; } return r; } +// 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; +} - // 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; - } +// Send a wake-on-lan packet +function sendWakeOnLan(hexMac) +{ + hexMac = hexMac.split(':').join(''); + 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'); - // 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)); } + 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.setMulticastInterface(addr.address); + socket.setMulticastTTL(1); + socket.send(magicbin, 7, '255.255.255.255'); + socket.descriptorMetadata = 'WoL (' + addr.address + ' => ' + hexMac + ')'; + count++; + } + catch (e) { } + } + } } } - if (current != null) { results[current] = true; } - return results; - } + } catch (e) { } + return count; +} - // 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) { - hexMac = hexMac.split(':').join(''); - 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.setMulticastInterface(addr.address); - socket.setMulticastTTL(1); - socket.send(magicbin, 7, '255.255.255.255'); - socket.descriptorMetadata = 'WoL (' + addr.address + ' => ' + hexMac + ')'; - count++; - } - catch (e) { } +// 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 'agentupdate': + agentUpdate_Start(data.url, { hash: data.hash, tlshash: data.servertlshash, sessionid: data.sessionid }); + break; + case 'msg': { + switch (data.type) + { + case 'console': { // Process a console command + if (data.value && data.sessionid) + { + MeshServerLogEx(17, [data.value], "Processing console command: " + data.value, data); + var args = splitArgs(data.value); + processConsoleCommand(args[0].toLowerCase(), parseArgs(args), data.rights, data.sessionid); } + break; } - } - } - } 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 'agentupdate': - agentUpdate_Start(data.url, { hash: data.hash, tlshash: data.servertlshash, sessionid: data.sessionid }); - break; - case 'msg': { - switch (data.type) { - case 'console': { // Process a console command - if (data.value && data.sessionid) { - MeshServerLogEx(17, [data.value], "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.perMessageDeflate = false; - if (typeof data.perMessageDeflate == 'boolean') { woptions.perMessageDeflate = data.perMessageDeflate; } - - // Perform manual server TLS certificate checking based on the certificate hash given by the server. - woptions.rejectUnauthorized = 0; - woptions.checkServerIdentity = function checkServerIdentity(certs) { - // If the tunnel certificate matches the control channel certificate, accept the connection - try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.digest == certs[0].digest) return; } catch (ex) { } - try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.fingerprint == certs[0].fingerprint) return; } catch (ex) { } - // Check that the certificate is the one expected by the server, fail if not. - if ((checkServerIdentity.servertlshash != null) && (checkServerIdentity.servertlshash.toLowerCase() != certs[0].digest.split(':').join('').toLowerCase())) { throw new Error('BadCert') } - } - woptions.checkServerIdentity.servertlshash = data.servertlshash; - - //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 + (data.guestname ? (' - ' + data.guestname) : ''); - tunnel.realname = (data.realname ? data.realname : data.username) + (data.guestname ? (' - ' + data.guestname) : ''); - 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) { - MeshServerLogEx(18, [data.title, data.msg], "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 (e) { } - } - 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) { - MeshServerLogEx(19, [data.value], "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': + 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) { - // 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); + xurl = xurl.split('$').join('%24').split('@').join('%40'); // Escape the $ and @ characters + var woptions = http.parseUri(xurl); + woptions.perMessageDeflate = false; + if (typeof data.perMessageDeflate == 'boolean') { woptions.perMessageDeflate = data.perMessageDeflate; } + + // Perform manual server TLS certificate checking based on the certificate hash given by the server. + woptions.rejectUnauthorized = 0; + woptions.checkServerIdentity = function checkServerIdentity(certs) + { + // If the tunnel certificate matches the control channel certificate, accept the connection + try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.digest == certs[0].digest) return; } catch (ex) { } + try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.fingerprint == certs[0].fingerprint) return; } catch (ex) { } + // Check that the certificate is the one expected by the server, fail if not. + if ((checkServerIdentity.servertlshash != null) && (checkServerIdentity.servertlshash.toLowerCase() != certs[0].digest.split(':').join('').toLowerCase())) { throw new Error('BadCert') } } - break; + woptions.checkServerIdentity.servertlshash = data.servertlshash; + + //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 + (data.guestname ? (' - ' + data.guestname) : ''); + tunnel.realname = (data.realname ? data.realname : data.username) + (data.guestname ? (' - ' + data.guestname) : ''); + 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); } - case 'openUrl': { - // Open a local web browser and return success/fail - MeshServerLogEx(20, [data.url], "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) { - MeshServerLogEx(21, [str.length], "Getting clipboard content, " + str.length + " byte(s)", data); - mesh.SendCommand({ action: 'msg', type: 'getclip', sessionid: data.sessionid, data: str, tag: data.tag }); - } - }); - } else { - require("clipboard").read().then(function (str) { - if (str) { - MeshServerLogEx(21, [str.length], "Getting clipboard content, " + str.length + " byte(s)", data); - mesh.SendCommand({ action: 'msg', type: 'getclip', sessionid: data.sessionid, data: str, tag: data.tag }); - } - }); - } - break; + break; + } + case 'messagebox': { + // Display a message box + if (data.title && data.msg) + { + MeshServerLogEx(18, [data.title, data.msg], "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 (e) { } } - case 'setclip': { - // Set the load clipboard to a user value - sendConsoleText('setClip: ' + JSON.stringify(data)); - if (typeof data.data == 'string') { - MeshServerLogEx(22, [data.data.length], "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; - } - case 'cpuinfo': - // CPU & memory utilization - var cpuuse = require('sysinfo').cpuUtilization(); - cpuuse.sessionid = data.sessionid; - cpuuse.tag = data.tag; - cpuuse.then(function (data) + break; + } + case 'ps': { + // Return the list of running processes + if (data.sessionid) + { + processManager.getProcesses(function (plist) { - mesh.SendCommand(JSON.stringify( + mesh.SendCommand({ action: 'msg', type: 'ps', value: JSON.stringify(plist), sessionid: data.sessionid }); + }); + } + break; + } + case 'pskill': { + // Kill a process + if (data.value) + { + MeshServerLogEx(19, [data.value], "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) { - action: 'msg', - type: 'cpuinfo', - cpu: data, - memory: require('sysinfo').memUtilization(), - thermals: require('sysinfo').thermals==null?[]:require('sysinfo').thermals(), - sessionid: this.sessionid, - tag: this.tag - })); - }, function (ex) { }); + 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 'localapp': - // Send a message to a local application - sendConsoleText('localappMsg: ' + data.appid + ', ' + JSON.stringify(data.value)); - if (data.appid != null) { sendToRegisteredApp(data.appid, data.value); } else { broadcastToRegisteredApps(data.value); } - break; - default: - // Unknown action, ignore it. - break; - } - break; - } - case 'acmactivate': { - if (amt != null) { - MeshServerLogEx(23, null, "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 'runcommands': { - if (mesh.cmdchild != null) { sendConsoleText("Run commands can't execute, already busy."); break; } - sendConsoleText("Run commands (" + data.runAsUser + "): " + data.cmds); - - // data.runAsUser: 0=Agent,1=UserOrAgent,2=UserOnly - var options = {}; - if (data.runAsUser > 0) { - try { options.uid = require('user-sessions').consoleUid(); } catch (e) { } - options.type = require('child_process').SpawnTypes.TERM; - } - if (data.runAsUser == 2) { - if (options.uid == null) break; - if (((require('user-sessions').minUid != null) && (options.uid < require('user-sessions').minUid()))) break; // This command can only run as user. - } - - if (process.platform == 'win32') { - if (data.type == 1) { - // Windows command shell - mesh.cmdchild = require('child_process').execFile(process.env['windir'] + '\\system32\\cmd.exe', ['cmd'], options); - mesh.cmdchild.descriptorMetadata = 'UserCommandsShell'; - mesh.cmdchild.stdout.on('data', function (c) { sendConsoleText(c.toString()); }); - mesh.cmdchild.stderr.on('data', function (c) { sendConsoleText(c.toString()); }); - mesh.cmdchild.stdin.write(data.cmds + '\r\nexit\r\n'); - mesh.cmdchild.on('exit', function () { sendConsoleText("Run commands completed."); delete mesh.cmdchild; }); - } else if (data.type == 2) { - // Windows Powershell - mesh.cmdchild = require('child_process').execFile(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', ['powershell', '-noprofile', '-nologo', '-command', '-'], options); - mesh.cmdchild.descriptorMetadata = 'UserCommandsPowerShell'; - mesh.cmdchild.stdout.on('data', function (c) { sendConsoleText(c.toString()); }); - mesh.cmdchild.stderr.on('data', function (c) { sendConsoleText(c.toString()); }); - mesh.cmdchild.stdin.write(data.cmds + '\r\nexit\r\n'); - mesh.cmdchild.on('exit', function () { sendConsoleText("Run commands completed."); delete mesh.cmdchild; }); } - } else if (data.type == 3) { - // Linux shell - mesh.cmdchild = require('child_process').execFile('/bin/sh', ['sh'], options); + case 'openUrl': { + // Open a local web browser and return success/fail + MeshServerLogEx(20, [data.url], "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) + { + MeshServerLogEx(21, [str.length], "Getting clipboard content, " + str.length + " byte(s)", data); + mesh.SendCommand({ action: 'msg', type: 'getclip', sessionid: data.sessionid, data: str, tag: data.tag }); + } + }); + } else + { + require("clipboard").read().then(function (str) + { + if (str) + { + MeshServerLogEx(21, [str.length], "Getting clipboard content, " + str.length + " byte(s)", data); + mesh.SendCommand({ action: 'msg', type: 'getclip', sessionid: data.sessionid, data: str, tag: data.tag }); + } + }); + } + break; + } + case 'setclip': { + // Set the load clipboard to a user value + sendConsoleText('setClip: ' + JSON.stringify(data)); + if (typeof data.data == 'string') + { + MeshServerLogEx(22, [data.data.length], "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; + } + case 'cpuinfo': + // CPU & memory utilization + var cpuuse = require('sysinfo').cpuUtilization(); + cpuuse.sessionid = data.sessionid; + cpuuse.tag = data.tag; + cpuuse.then(function (data) + { + mesh.SendCommand(JSON.stringify( + { + action: 'msg', + type: 'cpuinfo', + cpu: data, + memory: require('sysinfo').memUtilization(), + thermals: require('sysinfo').thermals == null ? [] : require('sysinfo').thermals(), + sessionid: this.sessionid, + tag: this.tag + })); + }, function (ex) { }); + break; + case 'localapp': + // Send a message to a local application + sendConsoleText('localappMsg: ' + data.appid + ', ' + JSON.stringify(data.value)); + if (data.appid != null) { sendToRegisteredApp(data.appid, data.value); } else { broadcastToRegisteredApps(data.value); } + break; + default: + // Unknown action, ignore it. + break; + } + break; + } + case 'acmactivate': { + if (amt != null) + { + MeshServerLogEx(23, null, "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 'runcommands': { + if (mesh.cmdchild != null) { sendConsoleText("Run commands can't execute, already busy."); break; } + sendConsoleText("Run commands (" + data.runAsUser + "): " + data.cmds); + + // data.runAsUser: 0=Agent,1=UserOrAgent,2=UserOnly + var options = {}; + if (data.runAsUser > 0) + { + try { options.uid = require('user-sessions').consoleUid(); } catch (e) { } + options.type = require('child_process').SpawnTypes.TERM; + } + if (data.runAsUser == 2) + { + if (options.uid == null) break; + if (((require('user-sessions').minUid != null) && (options.uid < require('user-sessions').minUid()))) break; // This command can only run as user. + } + + if (process.platform == 'win32') + { + if (data.type == 1) + { + // Windows command shell + mesh.cmdchild = require('child_process').execFile(process.env['windir'] + '\\system32\\cmd.exe', ['cmd'], options); mesh.cmdchild.descriptorMetadata = 'UserCommandsShell'; mesh.cmdchild.stdout.on('data', function (c) { sendConsoleText(c.toString()); }); mesh.cmdchild.stderr.on('data', function (c) { sendConsoleText(c.toString()); }); - mesh.cmdchild.stdin.write(data.cmds.split('\r').join('') + '\nexit\n'); + mesh.cmdchild.stdin.write(data.cmds + '\r\nexit\r\n'); + mesh.cmdchild.on('exit', function () { sendConsoleText("Run commands completed."); delete mesh.cmdchild; }); + } else if (data.type == 2) + { + // Windows Powershell + mesh.cmdchild = require('child_process').execFile(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', ['powershell', '-noprofile', '-nologo', '-command', '-'], options); + mesh.cmdchild.descriptorMetadata = 'UserCommandsPowerShell'; + mesh.cmdchild.stdout.on('data', function (c) { sendConsoleText(c.toString()); }); + mesh.cmdchild.stderr.on('data', function (c) { sendConsoleText(c.toString()); }); + mesh.cmdchild.stdin.write(data.cmds + '\r\nexit\r\n'); mesh.cmdchild.on('exit', function () { sendConsoleText("Run commands completed."); delete mesh.cmdchild; }); } - break; + } else if (data.type == 3) + { + // Linux shell + mesh.cmdchild = require('child_process').execFile('/bin/sh', ['sh'], options); + mesh.cmdchild.descriptorMetadata = 'UserCommandsShell'; + mesh.cmdchild.stdout.on('data', function (c) { sendConsoleText(c.toString()); }); + mesh.cmdchild.stderr.on('data', function (c) { sendConsoleText(c.toString()); }); + mesh.cmdchild.stdin.write(data.cmds.split('\r').join('') + '\nexit\n'); + mesh.cmdchild.on('exit', function () { sendConsoleText("Run commands completed."); delete mesh.cmdchild; }); + } + break; + } + case 'uninstallagent': + // Uninstall this agent + var agentName = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'; + try + { + agentName = require('MeshAgent').serviceName; + } + catch (x) + { } - case 'uninstallagent': - // Uninstall this agent - var agentName = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'; - try { - agentName = require('MeshAgent').serviceName; - } - catch (x) { - } - if (require('service-manager').manager.getService(agentName).isMe()) { - try { diagnosticAgent_uninstall(); } catch (e) { } - 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); - MeshServerLogEx(25, [data.actiontype, forced], "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; + if (require('service-manager').manager.getService(agentName).isMe()) + { + try { diagnosticAgent_uninstall(); } catch (e) { } + 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 }); } - 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; + 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); + MeshServerLogEx(25, [data.actiontype, forced], "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); } - case 'toast': { - // Display a toast message - if (data.title && data.msg) { - MeshServerLogEx(26, [data.title, data.msg], "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 (e) { } - } - break; + 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) + { + MeshServerLogEx(26, [data.title, data.msg], "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 (e) { } } - case 'openUrl': { - // Open a local web browser and return success/fail - //sendConsoleText('OpenURL: ' + data.url); - MeshServerLogEx(20, [data.url], "Opening: " + data.url, data); - if (data.url) { mesh.SendCommand({ action: 'openUrl', url: data.url, sessionid: data.sessionid, success: (openUserDesktopUrl(data.url) != null) }); } - break; - } - case 'amtconfig': { - // Perform Intel AMT activation and/or configuration - if ((apftunnel != null) || (amt == null) || (typeof data.user != 'string') || (typeof data.pass != 'string')) break; - amt.getMeiState(15, function (state) { - if ((apftunnel != null) || (amt == null)) return; - if ((state == null) || (state.ProvisioningState == null)) return; - if ((state.UUID == null) || (state.UUID.length != 36)) return; // Bad UUID - var apfarg = { - mpsurl: mesh.ServerUrl.replace('/agent.ashx', '/apf.ashx'), - mpsuser: data.user, // Agent user name - mpspass: data.pass, // Encrypted login cookie - mpskeepalive: 60000, - clientname: state.OsHostname, - clientaddress: '127.0.0.1', - clientuuid: state.UUID, - conntype: 2, // 0 = CIRA, 1 = Relay, 2 = LMS. The correct value is 2 since we are performing an LMS relay, other values for testing. - meiState: state // MEI state will be passed to MPS server - }; - addAmtEvent('LMS tunnel start.'); - apftunnel = require('amt-apfclient')({ debug: false }, apfarg); - apftunnel.onJsonControl = function (data) { - if (data.action == 'console') { addAmtEvent(data.msg); } // Add console message to AMT event log - if (data.action == 'mestate') { amt.getMeiState(15, function (state) { apftunnel.updateMeiState(state); }); } // Update the MEI state - if (data.action == 'deactivate') { // Request CCM deactivation - var amtMeiModule, amtMei; - try { amtMeiModule = require('amt-mei'); amtMei = new amtMeiModule(); } catch (ex) { if (apftunnel) apftunnel.sendMeiDeactivationState(1); return; } - amtMei.on('error', function (e) { if (apftunnel) apftunnel.sendMeiDeactivationState(1); }); - amtMei.unprovision(1, function (status) { if (apftunnel) apftunnel.sendMeiDeactivationState(status); }); // 0 = Success - } - if (data.action == 'close') { try { apftunnel.disconnect(); } catch (e) { } apftunnel = null; } // Close the CIRA-LMS connection + break; + } + case 'openUrl': { + // Open a local web browser and return success/fail + //sendConsoleText('OpenURL: ' + data.url); + MeshServerLogEx(20, [data.url], "Opening: " + data.url, data); + if (data.url) { mesh.SendCommand({ action: 'openUrl', url: data.url, sessionid: data.sessionid, success: (openUserDesktopUrl(data.url) != null) }); } + break; + } + case 'amtconfig': { + // Perform Intel AMT activation and/or configuration + if ((apftunnel != null) || (amt == null) || (typeof data.user != 'string') || (typeof data.pass != 'string')) break; + amt.getMeiState(15, function (state) + { + if ((apftunnel != null) || (amt == null)) return; + if ((state == null) || (state.ProvisioningState == null)) return; + if ((state.UUID == null) || (state.UUID.length != 36)) return; // Bad UUID + var apfarg = { + mpsurl: mesh.ServerUrl.replace('/agent.ashx', '/apf.ashx'), + mpsuser: data.user, // Agent user name + mpspass: data.pass, // Encrypted login cookie + mpskeepalive: 60000, + clientname: state.OsHostname, + clientaddress: '127.0.0.1', + clientuuid: state.UUID, + conntype: 2, // 0 = CIRA, 1 = Relay, 2 = LMS. The correct value is 2 since we are performing an LMS relay, other values for testing. + meiState: state // MEI state will be passed to MPS server + }; + addAmtEvent('LMS tunnel start.'); + apftunnel = require('amt-apfclient')({ debug: false }, apfarg); + apftunnel.onJsonControl = function (data) + { + if (data.action == 'console') { addAmtEvent(data.msg); } // Add console message to AMT event log + if (data.action == 'mestate') { amt.getMeiState(15, function (state) { apftunnel.updateMeiState(state); }); } // Update the MEI state + if (data.action == 'deactivate') + { // Request CCM deactivation + var amtMeiModule, amtMei; + try { amtMeiModule = require('amt-mei'); amtMei = new amtMeiModule(); } catch (ex) { if (apftunnel) apftunnel.sendMeiDeactivationState(1); return; } + amtMei.on('error', function (e) { if (apftunnel) apftunnel.sendMeiDeactivationState(1); }); + amtMei.unprovision(1, function (status) { if (apftunnel) apftunnel.sendMeiDeactivationState(status); }); // 0 = Success } - apftunnel.onChannelClosed = function () { addAmtEvent('LMS tunnel closed.'); apftunnel = null; } - try { apftunnel.connect(); } catch (ex) { } - }); - 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; - } - case 'coredump': - // Set the current agent coredump situation. - if (data.value === true) { - if (process.platform == 'win32') { - // TODO: This replace() below is not ideal, would be better to remove the .exe at the end instead of replace. - process.coreDumpLocation = process.execPath.replace('.exe', '.dmp'); - } else { - process.coreDumpLocation = (process.cwd() != '//') ? (process.cwd() + 'core') : null; - } - } else if (data.value === false) { - process.coreDumpLocation = null; + if (data.action == 'close') { try { apftunnel.disconnect(); } catch (e) { } apftunnel = null; } // Close the CIRA-LMS connection } - break; - case 'getcoredump': - // Ask the agent if a core dump is currently available, if yes, also return the hash of the agent. - var r = { action: 'getcoredump', value: (process.coreDumpLocation != null) }; - var coreDumpPath = null; - if (process.platform == 'win32') { coreDumpPath = process.coreDumpLocation; } else { coreDumpPath = (process.cwd() != '//') ? fs.existsSync(process.cwd() + 'core') : null; } - if ((coreDumpPath != null) && (fs.existsSync(coreDumpPath))) { - try { - var coredate = fs.statSync(coreDumpPath).mtime; - var coretime = new Date(coredate).getTime(); - var agenttime = new Date(fs.statSync(process.execPath).mtime).getTime(); - if (coretime > agenttime) { r.exists = (db.Get('CoreDumpTime') != coredate); } - } catch (ex) { } + apftunnel.onChannelClosed = function () { addAmtEvent('LMS tunnel closed.'); apftunnel = null; } + try { apftunnel.connect(); } catch (ex) { } + }); + 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; + } + case 'coredump': + // Set the current agent coredump situation. + if (data.value === true) + { + if (process.platform == 'win32') + { + // TODO: This replace() below is not ideal, would be better to remove the .exe at the end instead of replace. + process.coreDumpLocation = process.execPath.replace('.exe', '.dmp'); + } else + { + process.coreDumpLocation = (process.cwd() != '//') ? (process.cwd() + 'core') : null; } - if (r.exists == true) { - r.agenthashhex = getSHA384FileHash(process.execPath).toString('hex'); // Hash of current agent - r.corehashhex = getSHA384FileHash(coreDumpPath).toString('hex'); // Hash of core dump file - } - mesh.SendCommand(JSON.stringify(r)); - break; - case 'meshToolInfo': - if (data.pipe == true) { delete data.pipe; delete data.action; data.cmd = 'meshToolInfo'; broadcastToRegisteredApps(data); } - break; - case 'wget': // Server uses this command to tell the agent to download a file using HTTPS/GET and place it in a given path. This is used for one-to-many file uploads. - agentFileHttpPendingRequests.push(data); - serverFetchFile(); - break; - default: - // Unknown action, ignore it. - break; - } - } - } - - // Agent just get a file from the server and save it locally. - function serverFetchFile() { - if ((Object.keys(agentFileHttpRequests).length > 4) || (agentFileHttpPendingRequests.length == 0)) return; // No more than 4 active HTTPS requests to the server. - var data = agentFileHttpPendingRequests.shift(); - if ((data.overwrite !== true) && fs.existsSync(data.path)) return; // Don't overwrite an existing file. - if (data.createFolder) { try { fs.mkdirSync(data.folder); } catch (ex) { } } // If requested, create the local folder. - data.url = 'http' + getServerTargetUrlEx('*/').substring(2); - var agentFileHttpOptions = http.parseUri(data.url); - agentFileHttpOptions.path = data.urlpath; - - // Perform manual server TLS certificate checking based on the certificate hash given by the server. - agentFileHttpOptions.rejectUnauthorized = 0; - agentFileHttpOptions.checkServerIdentity = function checkServerIdentity(certs) { - // If the tunnel certificate matches the control channel certificate, accept the connection - try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.digest == certs[0].digest) return; } catch (ex) { } - try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.fingerprint == certs[0].fingerprint) return; } catch (ex) { } - // Check that the certificate is the one expected by the server, fail if not. - if ((checkServerIdentity.servertlshash != null) && (checkServerIdentity.servertlshash.toLowerCase() != certs[0].digest.split(':').join('').toLowerCase())) { throw new Error('BadCert') } - } - agentFileHttpOptions.checkServerIdentity.servertlshash = data.servertlshash; - - if (agentFileHttpOptions == null) return; - var agentFileHttpRequest = http.request(agentFileHttpOptions, - function (response) { - response.xparent = this; - try { - response.xfile = fs.createWriteStream(this.xpath, { flags: 'wbN' }) - response.pipe(response.xfile); - response.end = function () { delete agentFileHttpRequests[this.xparent.xurlpath]; delete this.xparent; serverFetchFile(); } - } catch (ex) { delete agentFileHttpRequests[this.xurlpath]; delete response.xparent; serverFetchFile(); return; } - } - ); - agentFileHttpRequest.on('error', function (ex) { delete agentFileHttpRequests[this.xurlpath]; serverFetchFile(); }); - agentFileHttpRequest.end(); - agentFileHttpRequest.xurlpath = data.urlpath; - agentFileHttpRequest.xpath = data.path; - agentFileHttpRequests[data.urlpath] = agentFileHttpRequest; - } - - // 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 (e) { } - try { delete x.FreeSpaceInPagingFiles; } catch (e) { } - try { delete x.FreeVirtualMemory; } catch (e) { } - try { delete x.LocalDateTime; } catch (e) { } - try { delete x.MaxProcessMemorySize; } catch (e) { } - try { delete x.TotalVirtualMemorySize; } catch (e) { } - try { delete x.TotalVisibleMemorySize; } catch (e) { } - 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 (e) { } - } - if (process.platform == 'win32') - { - results.pendingReboot = require('win-info').pendingReboot(); // Pending reboot - if (require('identifiers').volumes != null) + } else if (data.value === false) + { + process.coreDumpLocation = null; + } + break; + case 'getcoredump': + // Ask the agent if a core dump is currently available, if yes, also return the hash of the agent. + var r = { action: 'getcoredump', value: (process.coreDumpLocation != null) }; + var coreDumpPath = null; + if (process.platform == 'win32') { coreDumpPath = process.coreDumpLocation; } else { coreDumpPath = (process.cwd() != '//') ? fs.existsSync(process.cwd() + 'core') : null; } + if ((coreDumpPath != null) && (fs.existsSync(coreDumpPath))) { try { - results.volumes = require('identifiers').volumes(); - } - catch (e) - { - } + var coredate = fs.statSync(coreDumpPath).mtime; + var coretime = new Date(coredate).getTime(); + var agenttime = new Date(fs.statSync(process.execPath).mtime).getTime(); + if (coretime > agenttime) { r.exists = (db.Get('CoreDumpTime') != coredate); } + } catch (ex) { } } - } - - /* - 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); - } + if (r.exists == true) + { + r.agenthashhex = getSHA384FileHash(process.execPath).toString('hex'); // Hash of current agent + r.corehashhex = getSHA384FileHash(coreDumpPath).toString('hex'); // Hash of core dump file } - 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 (e) { func(null, e); } + mesh.SendCommand(JSON.stringify(r)); + break; + case 'meshToolInfo': + if (data.pipe == true) { delete data.pipe; delete data.action; data.cmd = 'meshToolInfo'; broadcastToRegisteredApps(data); } + break; + case 'wget': // Server uses this command to tell the agent to download a file using HTTPS/GET and place it in a given path. This is used for one-to-many file uploads. + agentFileHttpPendingRequests.push(data); + serverFetchFile(); + break; + default: + // Unknown action, ignore it. + break; + } } +} - // 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); +// Agent just get a file from the server and save it locally. +function serverFetchFile() +{ + if ((Object.keys(agentFileHttpRequests).length > 4) || (agentFileHttpPendingRequests.length == 0)) return; // No more than 4 active HTTPS requests to the server. + var data = agentFileHttpPendingRequests.shift(); + if ((data.overwrite !== true) && fs.existsSync(data.path)) return; // Don't overwrite an existing file. + if (data.createFolder) { try { fs.mkdirSync(data.folder); } catch (ex) { } } // If requested, create the local folder. + data.url = 'http' + getServerTargetUrlEx('*/').substring(2); + var agentFileHttpOptions = http.parseUri(data.url); + agentFileHttpOptions.path = data.urlpath; + + // Perform manual server TLS certificate checking based on the certificate hash given by the server. + agentFileHttpOptions.rejectUnauthorized = 0; + agentFileHttpOptions.checkServerIdentity = function checkServerIdentity(certs) + { + // If the tunnel certificate matches the control channel certificate, accept the connection + try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.digest == certs[0].digest) return; } catch (ex) { } + try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.fingerprint == certs[0].fingerprint) return; } catch (ex) { } + // Check that the certificate is the one expected by the server, fail if not. + if ((checkServerIdentity.servertlshash != null) && (checkServerIdentity.servertlshash.toLowerCase() != certs[0].digest.split(':').join('').toLowerCase())) { throw new Error('BadCert') } + } + agentFileHttpOptions.checkServerIdentity.servertlshash = data.servertlshash; + + if (agentFileHttpOptions == null) return; + var agentFileHttpRequest = http.request(agentFileHttpOptions, + function (response) + { + response.xparent = this; + try + { + response.xfile = fs.createWriteStream(this.xpath, { flags: 'wbN' }) + response.pipe(response.xfile); + response.end = function () { delete agentFileHttpRequests[this.xparent.xurlpath]; delete this.xparent; serverFetchFile(); } + } catch (ex) { delete agentFileHttpRequests[this.xurlpath]; delete response.xparent; serverFetchFile(); return; } + } + ); + agentFileHttpRequest.on('error', function (ex) { delete agentFileHttpRequests[this.xurlpath]; serverFetchFile(); }); + agentFileHttpRequest.end(); + agentFileHttpRequest.xurlpath = data.urlpath; + agentFileHttpRequest.xpath = data.path; + agentFileHttpRequests[data.urlpath] = agentFileHttpRequest; +} + +// 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 (e) { } + try { delete x.FreeSpaceInPagingFiles; } catch (e) { } + try { delete x.FreeVirtualMemory; } catch (e) { } + try { delete x.LocalDateTime; } catch (e) { } + try { delete x.MaxProcessMemorySize; } catch (e) { } + try { delete x.TotalVirtualMemorySize; } catch (e) { } + try { delete x.TotalVisibleMemorySize; } catch (e) { } + 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 (e) { } + } + if (process.platform == 'win32') + { + results.pendingReboot = require('win-info').pendingReboot(); // Pending reboot + if (require('identifiers').volumes != null) + { + try + { + results.volumes = require('identifiers').volumes(); } - } - } 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 }); - } - } - } + catch (e) + { } } } - 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; - - // Add the TCP session to the count and update the server - if (s.httprequest.userid != null) { - if (tunnelUserCount.tcp[s.httprequest.userid] == null) { tunnelUserCount.tcp[s.httprequest.userid] = 1; } else { tunnelUserCount.tcp[s.httprequest.userid]++; } - try { mesh.SendCommand({ action: 'sessions', type: 'tcp', value: tunnelUserCount.tcp }); } catch (e) { } - broadcastSessionsToRegisteredApps(); - } - } 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; - - // Add the UDP session to the count and update the server - if (s.httprequest.userid != null) { - if (tunnelUserCount.udp[s.httprequest.userid] == null) { tunnelUserCount.udp[s.httprequest.userid] = 1; } else { tunnelUserCount.udp[s.httprequest.userid]++; } - try { mesh.SendCommand({ action: 'sessions', type: 'udp', value: tunnelUserCount.tcp }); } catch (e) { } - broadcastSessionsToRegisteredApps(); - } - } 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, { dataTypeSkip: 1 }); // Pipe Server --> Target (don't pipe text type websocket frames) - } - } - - function onTunnelClosed() { - var tunnel = tunnels[this.httprequest.index]; - if (tunnel == null) return; // Stop duplicate calls. - - // If this is a routing session, clean up and send the new session counts. - if (this.httprequest.userid != null) { - if (this.httprequest.tcpport != null) { - if (tunnelUserCount.tcp[this.httprequest.userid] != null) { tunnelUserCount.tcp[this.httprequest.userid]--; if (tunnelUserCount.tcp[this.httprequest.userid] <= 0) { delete tunnelUserCount.tcp[this.httprequest.userid]; } } - try { mesh.SendCommand({ action: 'sessions', type: 'tcp', value: tunnelUserCount.tcp }); } catch (e) { } - broadcastSessionsToRegisteredApps(); - } else if (this.httprequest.udpport != null) { - if (tunnelUserCount.udp[this.httprequest.userid] != null) { tunnelUserCount.udp[this.httprequest.userid]--; if (tunnelUserCount.udp[this.httprequest.userid] <= 0) { delete tunnelUserCount.udp[this.httprequest.userid]; } } - try { mesh.SendCommand({ action: 'sessions', type: 'udp', value: tunnelUserCount.udp }); } catch (e) { } - broadcastSessionsToRegisteredApps(); - } - } - - // Sent tunnel statistics to the server, only send this if compression was used. - if ((this.bytesSent_uncompressed) && (this.bytesSent_uncompressed.toString() != this.bytesSent_actual.toString())) { - mesh.SendCommand({ - action: 'tunnelCloseStats', - url: tunnel.url, - userid: tunnel.userid, - protocol: tunnel.protocol, - sessionid: tunnel.sessionid, - sent: this.bytesSent_uncompressed.toString(), - sentActual: this.bytesSent_actual.toString(), - sentRatio: this.bytesSent_ratio, - received: this.bytesReceived_uncompressed.toString(), - receivedActual: this.bytesReceived_actual.toString(), - receivedRatio: this.bytesReceived_ratio - }); - } - - //sendConsoleText("Tunnel #" + this.httprequest.index + " closed. Sent -> " + this.bytesSent_uncompressed + ' bytes (uncompressed), ' + this.bytesSent_actual + ' bytes (actual), ' + this.bytesSent_ratio + '% compression', this.httprequest.sessionid); - if (this.httprequest.index) { 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); delete this.httprequest.uploadFile; delete this.httprequest.uploadFileid; delete this.httprequest.uploadFilePath; } - if (this.httprequest.downloadFile) { delete this.httprequest.downloadFile; } - - // 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) && (typeof data == 'object') && (data[0] != 123)) { - // Save the data to file being uploaded. - if (data[0] == 0) { - // If data starts with zero, skip the first byte. This is used to escape binary file data from JSON. - try { fs.writeSync(this.httprequest.uploadFile, data, 1, data.length - 1); } catch (e) { sendConsoleText('FileUpload Error'); this.write(Buffer.from(JSON.stringify({ action: 'uploaderror' }))); return; } // Write to the file, if there is a problem, error out. - } else { - // If data does not start with zero, save as-is. - try { fs.writeSync(this.httprequest.uploadFile, data); } catch (e) { sendConsoleText('FileUpload Error'); this.write(Buffer.from(JSON.stringify({ action: 'uploaderror' }))); return; } // Write to the file, if there is a problem, error out. + 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); + } } - this.write(Buffer.from(JSON.stringify({ action: 'uploadack', reqid: this.httprequest.uploadFileid }))); // Ask for more data. - return; - } + 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 (e) { func(null, e); } +} - 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);*/ } +// 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 { - // 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), 10 = FileTransfer - // 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 == 10) { - // - // Basic file transfer - // - var stats = null; - if ((process.platform != 'win32') && (this.httprequest.xoptions.file.startsWith('/') == false)) { this.httprequest.xoptions.file = '/' + this.httprequest.xoptions.file; } - try { stats = require('fs').statSync(this.httprequest.xoptions.file) } catch (e) { } - try { if (stats) { this.httprequest.downloadFile = fs.createReadStream(this.httprequest.xoptions.file, { flags: 'rbN' }); } } catch (e) { } - if (this.httprequest.downloadFile) { - //sendConsoleText('BasicFileTransfer, ok, ' + this.httprequest.xoptions.file + ', ' + JSON.stringify(stats)); - this.write(JSON.stringify({ op: 'ok', size: stats.size })); - this.httprequest.downloadFile.pipe(this); - this.httprequest.downloadFile.end = function () { } - } else { - //sendConsoleText('BasicFileTransfer, cancel, ' + this.httprequest.xoptions.file); - this.write(JSON.stringify({ op: 'cancel' })); + } 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 }); + } } } - else if ((this.httprequest.protocol == 1) || (this.httprequest.protocol == 6) || (this.httprequest.protocol == 8) || (this.httprequest.protocol == 9)) { - // - // Remote Terminal - // + } + } + } + return response; +} - // 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; +// 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; + + // Add the TCP session to the count and update the server + if (s.httprequest.userid != null) + { + if (tunnelUserCount.tcp[s.httprequest.userid] == null) { tunnelUserCount.tcp[s.httprequest.userid] = 1; } else { tunnelUserCount.tcp[s.httprequest.userid]++; } + try { mesh.SendCommand({ action: 'sessions', type: 'tcp', value: tunnelUserCount.tcp }); } catch (e) { } + broadcastSessionsToRegisteredApps(); + } + } 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; + + // Add the UDP session to the count and update the server + if (s.httprequest.userid != null) + { + if (tunnelUserCount.udp[s.httprequest.userid] == null) { tunnelUserCount.udp[s.httprequest.userid] = 1; } else { tunnelUserCount.udp[s.httprequest.userid]++; } + try { mesh.SendCommand({ action: 'sessions', type: 'udp', value: tunnelUserCount.tcp }); } catch (e) { } + broadcastSessionsToRegisteredApps(); + } + } 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, { dataTypeSkip: 1 }); // Pipe Server --> Target (don't pipe text type websocket frames) + } +} + +function onTunnelClosed() +{ + var tunnel = tunnels[this.httprequest.index]; + if (tunnel == null) return; // Stop duplicate calls. + + // If this is a routing session, clean up and send the new session counts. + if (this.httprequest.userid != null) + { + if (this.httprequest.tcpport != null) + { + if (tunnelUserCount.tcp[this.httprequest.userid] != null) { tunnelUserCount.tcp[this.httprequest.userid]--; if (tunnelUserCount.tcp[this.httprequest.userid] <= 0) { delete tunnelUserCount.tcp[this.httprequest.userid]; } } + try { mesh.SendCommand({ action: 'sessions', type: 'tcp', value: tunnelUserCount.tcp }); } catch (e) { } + broadcastSessionsToRegisteredApps(); + } else if (this.httprequest.udpport != null) + { + if (tunnelUserCount.udp[this.httprequest.userid] != null) { tunnelUserCount.udp[this.httprequest.userid]--; if (tunnelUserCount.udp[this.httprequest.userid] <= 0) { delete tunnelUserCount.udp[this.httprequest.userid]; } } + try { mesh.SendCommand({ action: 'sessions', type: 'udp', value: tunnelUserCount.udp }); } catch (e) { } + broadcastSessionsToRegisteredApps(); + } + } + + // Sent tunnel statistics to the server, only send this if compression was used. + if ((this.bytesSent_uncompressed) && (this.bytesSent_uncompressed.toString() != this.bytesSent_actual.toString())) + { + mesh.SendCommand({ + action: 'tunnelCloseStats', + url: tunnel.url, + userid: tunnel.userid, + protocol: tunnel.protocol, + sessionid: tunnel.sessionid, + sent: this.bytesSent_uncompressed.toString(), + sentActual: this.bytesSent_actual.toString(), + sentRatio: this.bytesSent_ratio, + received: this.bytesReceived_uncompressed.toString(), + receivedActual: this.bytesReceived_actual.toString(), + receivedRatio: this.bytesReceived_ratio + }); + } + + //sendConsoleText("Tunnel #" + this.httprequest.index + " closed. Sent -> " + this.bytesSent_uncompressed + ' bytes (uncompressed), ' + this.bytesSent_actual + ' bytes (actual), ' + this.bytesSent_ratio + '% compression', this.httprequest.sessionid); + if (this.httprequest.index) { 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); delete this.httprequest.uploadFile; delete this.httprequest.uploadFileid; delete this.httprequest.uploadFilePath; } + if (this.httprequest.downloadFile) { delete this.httprequest.downloadFile; } + + // 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) && (typeof data == 'object') && (data[0] != 123)) + { + // Save the data to file being uploaded. + if (data[0] == 0) + { + // If data starts with zero, skip the first byte. This is used to escape binary file data from JSON. + try { fs.writeSync(this.httprequest.uploadFile, data, 1, data.length - 1); } catch (e) { sendConsoleText('FileUpload Error'); this.write(Buffer.from(JSON.stringify({ action: 'uploaderror' }))); return; } // Write to the file, if there is a problem, error out. + } else + { + // If data does not start with zero, save as-is. + try { fs.writeSync(this.httprequest.uploadFile, data); } catch (e) { sendConsoleText('FileUpload Error'); this.write(Buffer.from(JSON.stringify({ action: 'uploaderror' }))); return; } // Write to the file, if there is a problem, error out. + } + this.write(Buffer.from(JSON.stringify({ action: 'uploadack', reqid: this.httprequest.uploadFileid }))); // Ask for more data. + 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), 10 = FileTransfer + // 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 == 10) + { + // + // Basic file transfer + // + var stats = null; + if ((process.platform != 'win32') && (this.httprequest.xoptions.file.startsWith('/') == false)) { this.httprequest.xoptions.file = '/' + this.httprequest.xoptions.file; } + try { stats = require('fs').statSync(this.httprequest.xoptions.file) } catch (e) { } + try { if (stats) { this.httprequest.downloadFile = fs.createReadStream(this.httprequest.xoptions.file, { flags: 'rbN' }); } } catch (e) { } + if (this.httprequest.downloadFile) + { + //sendConsoleText('BasicFileTransfer, ok, ' + this.httprequest.xoptions.file + ', ' + JSON.stringify(stats)); + this.write(JSON.stringify({ op: 'ok', size: stats.size })); + this.httprequest.downloadFile.pipe(this); + this.httprequest.downloadFile.end = function () { } + } else + { + //sendConsoleText('BasicFileTransfer, cancel, ' + this.httprequest.xoptions.file); + this.write(JSON.stringify({ op: 'cancel' })); + } + } + else 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(); - sendConsoleText("Error: No Terminal Control Rights."); return; } + } - this.descriptorMetadata = "Remote Terminal"; + 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; - 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; - } + 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 (e) { } + broadcastSessionsToRegisteredApps(); } - 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; + if (process.platform == 'win32') + { + // Unpipe the web socket + this.unpipe(this.httprequest._term); + if (this.httprequest._term) { this.httprequest._term.unpipe(this); } - 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 (e) { } - broadcastSessionsToRegisteredApps(); + // 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); } } - 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.realname).replace('{1}', 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 - MeshServerLogEx(27, null, "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 - MeshServerLogEx(28, null, "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()); - }); + // Clean up + if (this.httprequest._term) { this.httprequest._term.end(); } + this.httprequest._term = null; } - else { - // User-Consent is not required, so just resolve this promise - this.httprequest.tpromise._res(); + }; + + // 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.realname).replace('{1}', 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 + MeshServerLogEx(27, null, "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 + MeshServerLogEx(28, null, "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; + 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; } - } + // 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); + 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); - } - }); + // 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); } }); } - } - 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'); + else + { + // Legacy Terminal + this.httprequest.connectionPromise._res(require('win-terminal')[this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'](cols, rows)); } } - 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 (e) { } - broadcastSessionsToRegisteredApps(); - } - - // 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.realname).replace('{1}', this.ws.httprequest.username); } + 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); + } + }); } - try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (e) { } + }); + } + } + 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 (e) { } + broadcastSessionsToRegisteredApps(); + } + + // 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.realname).replace('{1}', this.ws.httprequest.username); } } - }, - 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(); - }); + try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (e) { } + } + }, + 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; } - 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"; - 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; - // 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; - // 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); - // 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 (e) { } } + 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 (e) { } } + tunnelUserCount.desktop = users; + try { mesh.SendCommand({ action: 'sessions', type: 'kvm', value: users }); } catch (e) { } + broadcastSessionsToRegisteredApps(); + } + + 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) { + 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 (e) { } } 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 (e) { } } tunnelUserCount.desktop = users; @@ -1874,1258 +2142,1444 @@ function createMeshCore(agent) { broadcastSessionsToRegisteredApps(); } - this.end = function () { - --this.desktop.kvm.connectionCount; + // Unpipe the web socket + try + { + this.unpipe(this.httprequest.desktop.kvm); + this.httprequest.desktop.kvm.unpipe(this); + } + catch (e) { } - // 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 (e) { } } - 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 (e) { } } - tunnelUserCount.desktop = users; - try { mesh.SendCommand({ action: 'sessions', type: 'kvm', value: users }); } catch (e) { } - broadcastSessionsToRegisteredApps(); - } - - // Unpipe the web socket - try { - this.unpipe(this.httprequest.desktop.kvm); - this.httprequest.desktop.kvm.unpipe(this); + // 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 (e) { } - - // 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 (e) { } - } - - // 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 (e) { } - - 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) { - for (var j in this.httprequest.desktop.kvm.rusers) { if (this.httprequest.desktop.kvm.rusers[j] == this.httprequest.realname) { this.httprequest.desktop.kvm.rusers.splice(j, 1); break; } } - 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.join(', ')).replace('{1}', this.httprequest.desktop.kvm.rusers.join(', ')), require('MeshAgent')._tsid); - this.httprequest.desktop.kvm.connectionBar.httprequest = this.httprequest; - this.httprequest.desktop.kvm.connectionBar.on('close', function () { - MeshServerLogEx(29, null, "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.rusers.push(this.httprequest.realname); - this.httprequest.desktop.kvm.users.push(this.httprequest.username); - this.httprequest.desktop.kvm.rusers.sort(); - this.httprequest.desktop.kvm.users.sort(); - } else { - this.httprequest.desktop.kvm.connectionCount = 1; - this.httprequest.desktop.kvm.rusers = [this.httprequest.realname]; - 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!!! - } + // Place wallpaper back if needed + // 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.realname + " 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.realname).replace('{1}', this.httprequest.username); } + 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 (e) { } + + 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; } - 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; - MeshServerLogEx(30, null, "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.realname + " 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.realname).replace('{1}', this.ws.httprequest.username); } - } - try { require('toaster').Toast(notifyTitle, notifyMessage, tsid); } catch (e) { } - } - 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.join(', ')).replace('{1}', this.ws.httprequest.desktop.kvm.rusers.join(', ')), require('MeshAgent')._tsid); - MeshServerLogEx(31, null, "Remote Desktop Connection Bar Activated/Updated (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest); - } - catch (e) { - if (process.platform != 'darwin') { - MeshServerLogEx(32, null, "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 () { - MeshServerLogEx(33, null, "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; - MeshServerLogEx(34, null, "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 - MeshServerLogEx(35, null, "Started remote desktop with toast notification (" + this.httprequest.remoteaddr + ")", this.httprequest); - var notifyMessage = this.httprequest.realname + " 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.realname).replace('{1}', this.httprequest.username); } - } - try { require('toaster').Toast(notifyTitle, notifyMessage, tsid); } catch (e) { } - } else { - MeshServerLogEx(36, null, "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) { + } 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) + { + for (var j in this.httprequest.desktop.kvm.rusers) { if (this.httprequest.desktop.kvm.rusers[j] == this.httprequest.realname) { this.httprequest.desktop.kvm.rusers.splice(j, 1); break; } } + this.httprequest.desktop.kvm.users.splice(i, 1); 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.rusers.join(', ')).replace('{1}', this.httprequest.desktop.kvm.users.join(', ')), require('MeshAgent')._tsid); - MeshServerLogEx(37, null, "Remote Desktop Connection Bar Activated/Updated (" + this.httprequest.remoteaddr + ")", this.httprequest); - } - catch (e) { - MeshServerLogEx(38, null, "Remote Desktop Connection Bar Failed or not Supported (" + this.httprequest.remoteaddr + ")", this.httprequest); - } - if (this.httprequest.desktop.kvm.connectionBar) { + this.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(this.httprequest.privacybartext.replace('{0}', this.httprequest.desktop.kvm.users.join(', ')).replace('{1}', this.httprequest.desktop.kvm.rusers.join(', ')), require('MeshAgent')._tsid); this.httprequest.desktop.kvm.connectionBar.httprequest = this.httprequest; - this.httprequest.desktop.kvm.connectionBar.on('close', function () { - MeshServerLogEx(39, null, "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.connectionBar.on('close', function () + { + MeshServerLogEx(29, null, "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; } } - this.httprequest.desktop.kvm.pipe(this, { dataTypeSkip: 1 }); } + }; + if (this.httprequest.desktop.kvm.hasOwnProperty('connectionCount')) + { + this.httprequest.desktop.kvm.connectionCount++; + this.httprequest.desktop.kvm.rusers.push(this.httprequest.realname); + this.httprequest.desktop.kvm.users.push(this.httprequest.username); + this.httprequest.desktop.kvm.rusers.sort(); + this.httprequest.desktop.kvm.users.sort(); + } else + { + this.httprequest.desktop.kvm.connectionCount = 1; + this.httprequest.desktop.kvm.rusers = [this.httprequest.realname]; + this.httprequest.desktop.kvm.users = [this.httprequest.username]; + } - this.removeAllListeners('data'); - this.on('data', onTunnelControlData); - //this.write('MeshCore KVM Hello!1'); + 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!!! + } - } 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; + // 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.realname + " 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.realname).replace('{1}', 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; + MeshServerLogEx(30, null, "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.realname + " 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.realname).replace('{1}', this.ws.httprequest.username); } + } + try { require('toaster').Toast(notifyTitle, notifyMessage, tsid); } catch (e) { } + } + 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.join(', ')).replace('{1}', this.ws.httprequest.desktop.kvm.rusers.join(', ')), require('MeshAgent')._tsid); + MeshServerLogEx(31, null, "Remote Desktop Connection Bar Activated/Updated (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest); + } + catch (e) + { + if (process.platform != 'darwin') + { + MeshServerLogEx(32, null, "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 () + { + MeshServerLogEx(33, null, "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; + MeshServerLogEx(34, null, "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 + MeshServerLogEx(35, null, "Started remote desktop with toast notification (" + this.httprequest.remoteaddr + ")", this.httprequest); + var notifyMessage = this.httprequest.realname + " 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.realname).replace('{1}', this.httprequest.username); } + } + try { require('toaster').Toast(notifyTitle, notifyMessage, tsid); } catch (e) { } + } else + { + MeshServerLogEx(36, null, "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.rusers.join(', ')).replace('{1}', this.httprequest.desktop.kvm.users.join(', ')), require('MeshAgent')._tsid); + MeshServerLogEx(37, null, "Remote Desktop Connection Bar Activated/Updated (" + this.httprequest.remoteaddr + ")", this.httprequest); + } + catch (e) + { + MeshServerLogEx(38, null, "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 () + { + MeshServerLogEx(39, null, "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.descriptorMetadata = "Remote Files"; + this.removeAllListeners('data'); + this.on('data', onTunnelControlData); + //this.write('MeshCore KVM Hello!1'); - // 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]++; } + } 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 (e) { } + broadcastSessionsToRegisteredApps(); + } + + 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 (e) { } broadcastSessionsToRegisteredApps(); } + }; - 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 (e) { } - broadcastSessionsToRegisteredApps(); - } - }; - - // 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.realname + " 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.realname).replace('{1}', 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; - MeshServerLogEx(40, null, "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.realname + " 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.realname).replace('{1}', this.ws.httprequest.username); } - } - try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (e) { } + // 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.realname + " 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.realname).replace('{1}', 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; + MeshServerLogEx(40, null, "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.realname + " 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.realname).replace('{1}', this.ws.httprequest.username); } } - this.ws.resume(); - }, - function (e) { - // User Consent Denied/Failed - this.ws._consentpromise = null; - MeshServerLogEx(41, null, "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 - MeshServerLogEx(42, null, "Started remote files with toast notification (" + this.httprequest.remoteaddr + ")", this.httprequest); - var notifyMessage = this.httprequest.realname + " 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.realname).replace('{1}', this.httprequest.username); } + try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (e) { } } - try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (e) { } - } else { - MeshServerLogEx(43, null, "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(Buffer.from(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(Buffer.from(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); - MeshServerLogEx(44, [cmd.path], "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) { - MeshServerLogEx(45, [p], "Delete: \"" + p + "\"", this.httprequest); - } else { - if (cmd.rec) { - MeshServerLogEx(46, [p, delcount], "Delete recursive: \"" + p + "\", " + delcount + " element(s) removed", this.httprequest); - } else { - MeshServerLogEx(47, [p, delcount], "Delete: \"" + p + "\", " + delcount + " element(s) removed", this.httprequest); - } - } - } - break; - } - case 'markcoredump': { - // If we are asking for the coredump file, set the right path. - var coreDumpPath = null; - if (process.platform == 'win32') { - if (fs.existsSync(process.coreDumpLocation)) { coreDumpPath = process.coreDumpLocation; } - } else { - if ((process.cwd() != '//') && fs.existsSync(process.cwd() + 'core')) { coreDumpPath = process.cwd() + 'core'; } - } - if (coreDumpPath != null) { db.Put('CoreDumpTime', require('fs').statSync(coreDumpPath).mtime); } - break; - } - case 'rename': + this.ws.resume(); + }, + function (e) { - // Rename a file or folder - var oldfullpath = obj.path.join(cmd.path, cmd.oldname); - var newfullpath = obj.path.join(cmd.path, cmd.newname); - MeshServerLogEx(48, [oldfullpath, cmd.newname], 'Rename: \"' + oldfullpath + '\" to \"' + cmd.newname + '\"', this.httprequest); - try { fs.renameSync(oldfullpath, newfullpath); } catch (e) { console.log(e); } - break; - } - case 'findfile': - { - // Search for files - var r = require('file-search').find('"' + cmd.path + '"', cmd.filter); - if (!r.cancel) { r.cancel = function cancel() { this.child.kill(); }; } - this._search = r; - r.socket = this; - r.socket.reqid = cmd.reqid; // Search request id. This is used to send responses and cancel the request. - r.socket.path = cmd.path; // Search path - r.on('result', function (str) { try { this.socket.write(Buffer.from(JSON.stringify({ action: 'findfile', r: str.substring(this.socket.path.length), reqid: this.socket.reqid }))); } catch (ex) { } }); - r.then(function () { try { this.socket.write(Buffer.from(JSON.stringify({ action: 'findfile', r: null, reqid: this.socket.reqid }))); } catch (ex) { } }); - break; - } - case 'cancelfindfile': - { - if (this._search) { this._search.cancel(); this._search = null; } - break; - } - case 'download': - { - // Download a file - var sendNextBlock = 0; - if (cmd.sub == 'start') { // Setup the download - if ((cmd.path == null) && (cmd.ask == 'coredump')) { // If we are asking for the coredump file, set the right path. - if (process.platform == 'win32') { - if (fs.existsSync(process.coreDumpLocation)) { cmd.path = process.coreDumpLocation; } - } else { - if ((process.cwd() != '//') && fs.existsSync(process.cwd() + 'core')) { cmd.path = process.cwd() + 'core'; } - } - } - MeshServerLogEx((cmd.ask == 'coredump') ? 104 : 49, [cmd.path], 'Download: \"' + cmd.path + '\"', this.httprequest); - if ((cmd.path == null) || (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 = ((typeof cmd.ack == 'number') ? cmd.ack : 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 = Buffer.alloc(16384); - var len = fs.readSync(this.filedownload.f, buf, 4, 16380, null); - this.filedownload.ptr += len; - if (len < 16380) { 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 'upload': - { - // Upload a file, browser to agent - if (this.httprequest.uploadFile != null) { fs.closeSync(this.httprequest.uploadFile); delete this.httprequest.uploadFile; } - if (cmd.path == undefined) break; - var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path; - this.httprequest.uploadFilePath = filepath; - MeshServerLogEx(50, [filepath], 'Upload: \"' + filepath + '\"', this.httprequest); - try { this.httprequest.uploadFile = fs.openSync(filepath, 'wbN'); } catch (e) { this.write(Buffer.from(JSON.stringify({ action: 'uploaderror', reqid: cmd.reqid }))); break; } - this.httprequest.uploadFileid = cmd.reqid; - if (this.httprequest.uploadFile) { this.write(Buffer.from(JSON.stringify({ action: 'uploadstart', reqid: this.httprequest.uploadFileid }))); } - break; - } - case 'uploaddone': - { - // Indicates that an upload is done - if (this.httprequest.uploadFile) { - fs.closeSync(this.httprequest.uploadFile); - this.write(Buffer.from(JSON.stringify({ action: 'uploaddone', reqid: this.httprequest.uploadFileid }))); // Indicate that we closed the file. - delete this.httprequest.uploadFile; - delete this.httprequest.uploadFileid; - delete this.httprequest.uploadFilePath; - } - break; - } - case 'uploadcancel': - { - // Indicates that an upload is canceled - if (this.httprequest.uploadFile) { - fs.closeSync(this.httprequest.uploadFile); - fs.unlinkSync(this.httprequest.uploadFilePath); - this.write(Buffer.from(JSON.stringify({ action: 'uploadcancel', reqid: this.httprequest.uploadFileid }))); // Indicate that we closed the file. - delete this.httprequest.uploadFile; - delete this.httprequest.uploadFileid; - delete this.httprequest.uploadFilePath; - } - 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]); - MeshServerLogEx(51, [sc, ds], '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]); - MeshServerLogEx(52, [sc, ds], 'Move: \"' + sc + '\" to \"' + ds + '\"', this.httprequest); - if (sc != ds) { try { fs.copyFileSync(sc, ds); fs.unlinkSync(sc); } catch (e) { } } - } - break; - } - case 'zip': - // Zip a bunch of files - if (this.zip != null) return; // Zip operating is currently running, exit now. - - // Check that the specified files exist & build full paths - var fp, stat, p = []; - for (var i in cmd.files) { fp = cmd.path + '/' + cmd.files[i]; stat = null; try { stat = fs.statSync(fp); } catch (e) { } if (stat != null) { p.push(fp); } } - if (p.length == 0) return; // No files, quit now. - - // Setup file compression - var ofile = cmd.path + '/' + cmd.output; - this.write(Buffer.from(JSON.stringify({ action: 'dialogmessage', msg: 'zipping' }))); - this.zipfile = ofile; - delete this.zipcancel; - var out = require('fs').createWriteStream(ofile, { flags: 'wb' }); - out.xws = this; - out.on('close', function () { - this.xws.write(Buffer.from(JSON.stringify({ action: 'dialogmessage', msg: null }))); - this.xws.write(Buffer.from(JSON.stringify({ action: 'refresh' }))); - if (this.xws.zipcancel === true) { fs.unlinkSync(this.xws.zipfile); } // Delete the complete file. - delete this.xws.zipcancel; - delete this.xws.zipfile; - delete this.xws.zip; + // User Consent Denied/Failed + this.ws._consentpromise = null; + MeshServerLogEx(41, null, "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 })); }); - this.zip = require('zip-writer').write({ files: p, basePath: cmd.path }); - this.zip.xws = this; - this.zip.on('progress', require('events').moderated(function (name, p) { this.xws.write(Buffer.from(JSON.stringify({ action: 'dialogmessage', msg: 'zippingFile', file: ((process.platform == 'win32') ? (name.split('/').join('\\')) : name), progress: p }))); }, 1000)); - this.zip.pipe(out); - break; - case 'cancel': - // Cancel zip operation if present - try { this.zipcancel = true; this.zip.cancel(function () { }); } catch (e) { } - this.zip = null; - 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') { - MeshServerLogEx(53, null, "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 }); + } else + { + // User Consent Prompt is not required + if (this.httprequest.consent && (this.httprequest.consent & 4)) + { + // User Notifications is required + MeshServerLogEx(42, null, "Started remote files with toast notification (" + this.httprequest.remoteaddr + ")", this.httprequest); + var notifyMessage = this.httprequest.realname + " 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.realname).replace('{1}', this.httprequest.username); } } - } catch (e) { } + try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (e) { } + } else + { + MeshServerLogEx(43, null, "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(Buffer.from(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(Buffer.from(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); + MeshServerLogEx(44, [cmd.path], "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) + { + MeshServerLogEx(45, [p], "Delete: \"" + p + "\"", this.httprequest); + } else + { + if (cmd.rec) + { + MeshServerLogEx(46, [p, delcount], "Delete recursive: \"" + p + "\", " + delcount + " element(s) removed", this.httprequest); + } else + { + MeshServerLogEx(47, [p, delcount], "Delete: \"" + p + "\", " + delcount + " element(s) removed", this.httprequest); + } + } + } + break; + } + case 'markcoredump': { + // If we are asking for the coredump file, set the right path. + var coreDumpPath = null; + if (process.platform == 'win32') + { + if (fs.existsSync(process.coreDumpLocation)) { coreDumpPath = process.coreDumpLocation; } + } else + { + if ((process.cwd() != '//') && fs.existsSync(process.cwd() + 'core')) { coreDumpPath = process.cwd() + 'core'; } + } + if (coreDumpPath != null) { db.Put('CoreDumpTime', require('fs').statSync(coreDumpPath).mtime); } + 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); + MeshServerLogEx(48, [oldfullpath, cmd.newname], 'Rename: \"' + oldfullpath + '\" to \"' + cmd.newname + '\"', this.httprequest); + try { fs.renameSync(oldfullpath, newfullpath); } catch (e) { console.log(e); } + break; + } + case 'findfile': + { + // Search for files + var r = require('file-search').find('"' + cmd.path + '"', cmd.filter); + if (!r.cancel) { r.cancel = function cancel() { this.child.kill(); }; } + this._search = r; + r.socket = this; + r.socket.reqid = cmd.reqid; // Search request id. This is used to send responses and cancel the request. + r.socket.path = cmd.path; // Search path + r.on('result', function (str) { try { this.socket.write(Buffer.from(JSON.stringify({ action: 'findfile', r: str.substring(this.socket.path.length), reqid: this.socket.reqid }))); } catch (ex) { } }); + r.then(function () { try { this.socket.write(Buffer.from(JSON.stringify({ action: 'findfile', r: null, reqid: this.socket.reqid }))); } catch (ex) { } }); + break; + } + case 'cancelfindfile': + { + if (this._search) { this._search.cancel(); this._search = null; } + break; + } + case 'download': + { + // Download a file + var sendNextBlock = 0; + if (cmd.sub == 'start') + { // Setup the download + if ((cmd.path == null) && (cmd.ask == 'coredump')) + { // If we are asking for the coredump file, set the right path. + if (process.platform == 'win32') + { + if (fs.existsSync(process.coreDumpLocation)) { cmd.path = process.coreDumpLocation; } + } else + { + if ((process.cwd() != '//') && fs.existsSync(process.cwd() + 'core')) { cmd.path = process.cwd() + 'core'; } + } + } + MeshServerLogEx((cmd.ask == 'coredump') ? 104 : 49, [cmd.path], 'Download: \"' + cmd.path + '\"', this.httprequest); + if ((cmd.path == null) || (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 = ((typeof cmd.ack == 'number') ? cmd.ack : 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 = Buffer.alloc(16384); + var len = fs.readSync(this.filedownload.f, buf, 4, 16380, null); + this.filedownload.ptr += len; + if (len < 16380) { 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 'upload': + { + // Upload a file, browser to agent + if (this.httprequest.uploadFile != null) { fs.closeSync(this.httprequest.uploadFile); delete this.httprequest.uploadFile; } + if (cmd.path == undefined) break; + var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path; + this.httprequest.uploadFilePath = filepath; + MeshServerLogEx(50, [filepath], 'Upload: \"' + filepath + '\"', this.httprequest); + try { this.httprequest.uploadFile = fs.openSync(filepath, 'wbN'); } catch (e) { this.write(Buffer.from(JSON.stringify({ action: 'uploaderror', reqid: cmd.reqid }))); break; } + this.httprequest.uploadFileid = cmd.reqid; + if (this.httprequest.uploadFile) { this.write(Buffer.from(JSON.stringify({ action: 'uploadstart', reqid: this.httprequest.uploadFileid }))); } + break; + } + case 'uploaddone': + { + // Indicates that an upload is done + if (this.httprequest.uploadFile) + { + fs.closeSync(this.httprequest.uploadFile); + this.write(Buffer.from(JSON.stringify({ action: 'uploaddone', reqid: this.httprequest.uploadFileid }))); // Indicate that we closed the file. + delete this.httprequest.uploadFile; + delete this.httprequest.uploadFileid; + delete this.httprequest.uploadFilePath; + } + break; + } + case 'uploadcancel': + { + // Indicates that an upload is canceled + if (this.httprequest.uploadFile) + { + fs.closeSync(this.httprequest.uploadFile); + fs.unlinkSync(this.httprequest.uploadFilePath); + this.write(Buffer.from(JSON.stringify({ action: 'uploadcancel', reqid: this.httprequest.uploadFileid }))); // Indicate that we closed the file. + delete this.httprequest.uploadFile; + delete this.httprequest.uploadFileid; + delete this.httprequest.uploadFilePath; + } + 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]); + MeshServerLogEx(51, [sc, ds], '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]); + MeshServerLogEx(52, [sc, ds], 'Move: \"' + sc + '\" to \"' + ds + '\"', this.httprequest); + if (sc != ds) { try { fs.copyFileSync(sc, ds); fs.unlinkSync(sc); } catch (e) { } } + } + break; + } + case 'zip': + // Zip a bunch of files + if (this.zip != null) return; // Zip operating is currently running, exit now. + + // Check that the specified files exist & build full paths + var fp, stat, p = []; + for (var i in cmd.files) { fp = cmd.path + '/' + cmd.files[i]; stat = null; try { stat = fs.statSync(fp); } catch (e) { } if (stat != null) { p.push(fp); } } + if (p.length == 0) return; // No files, quit now. + + // Setup file compression + var ofile = cmd.path + '/' + cmd.output; + this.write(Buffer.from(JSON.stringify({ action: 'dialogmessage', msg: 'zipping' }))); + this.zipfile = ofile; + delete this.zipcancel; + var out = require('fs').createWriteStream(ofile, { flags: 'wb' }); + out.xws = this; + out.on('close', function () + { + this.xws.write(Buffer.from(JSON.stringify({ action: 'dialogmessage', msg: null }))); + this.xws.write(Buffer.from(JSON.stringify({ action: 'refresh' }))); + if (this.xws.zipcancel === true) { fs.unlinkSync(this.xws.zipfile); } // Delete the complete file. + delete this.xws.zipcancel; + delete this.xws.zipfile; + delete this.xws.zip; + }); + this.zip = require('zip-writer').write({ files: p, basePath: cmd.path }); + this.zip.xws = this; + this.zip.on('progress', require('events').moderated(function (name, p) { this.xws.write(Buffer.from(JSON.stringify({ action: 'dialogmessage', msg: 'zippingFile', file: ((process.platform == 'win32') ? (name.split('/').join('\\')) : name), progress: p }))); }, 1000)); + this.zip.pipe(out); + break; + case 'cancel': + // Cancel zip operation if present + try { this.zipcancel = true; this.zip.cancel(function () { }); } catch (e) { } + this.zip = null; + break; default: // Unknown action, ignore it. break; } - return; + } 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); + } +} - 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); } +// 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++; } - 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); + }); + } + 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') + { + MeshServerLogEx(53, null, "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 }); } - } 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 + } catch (e) { } 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. + 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); } } - 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 '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); } - break; + } 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); } - 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 (e) { } + 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); } - }); - this.websocket.write('{\"ctrlChannel\":\"102938\",\"type\":\"webrtc0\"}'); // Indicate we are ready for WebRTC switch-over. + catch (e) { } + } }); - var sdp = null; - try { sdp = ws.webrtc.setOffer(obj.sdp); } catch (e) { } - if (sdp != null) { ws.write({ type: 'answer', ctrlChannel: '102938', sdp: sdp }); } - break; - } - case 'ping': { - ws.write("{\"ctrlChannel\":\"102938\",\"type\":\"pong\"}"); // Send pong response - break; - } - case 'pong': { // NOP - break; - } - case 'rtt': { - ws.write({ type: 'rtt', ctrlChannel: '102938', time: obj.time }); - break; - } + 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 (e) { } + if (sdp != null) { ws.write({ type: 'answer', ctrlChannel: '102938', sdp: sdp }); } + break; + } + case 'ping': { + ws.write("{\"ctrlChannel\":\"102938\",\"type\":\"pong\"}"); // Send pong response + break; + } + case 'pong': { // NOP + break; + } + case 'rtt': { + ws.write({ type: 'rtt', ctrlChannel: '102938', time: obj.time }); + break; } } +} - // Console state - var consoleWebSockets = {}; - var consoleHttpRequest = null; +// 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; } - }; +// 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) { - if ((url.toLowerCase().startsWith('http://') == false) && (url.toLowerCase().startsWith('https://') == false)) { return null; } - 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; +// Open a web browser to a specified URL on current user's desktop +function openUserDesktopUrl(url) +{ + if ((url.toLowerCase().startsWith('http://') == false) && (url.toLowerCase().startsWith('https://') == false)) { return null; } + 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 (e) { } + 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 = 'agentupdate,msh,timerinfo,coreinfo,coredump,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,netinfo,location,power,wakeonlan,setdebug,smbios,rawsmbios,toast,lock,users,openurl,getscript,getclip,setclip,log,av,cpuinfo,sysinfo,apf,scanwifi,wallpaper,agentmsg'; + if (process.platform == 'win32') { availcommands += ',safemode,wpfhwacceleration,uac'; } + if (amt != null) { availcommands += ',amt,amtconfig,amtevents'; } + if (process.platform != 'freebsd') { availcommands += ',vm'; } + if (require('MeshAgent').maxKvmTileSize != null) { availcommands += ',kvmmode'; } + try { require('zip-reader'); availcommands += ',zip,unzip'; } catch (e) { } + + 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; } - } catch (e) { } - 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 = 'agentupdate,msh,timerinfo,coreinfo,coredump,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,netinfo,location,power,wakeonlan,setdebug,smbios,rawsmbios,toast,lock,users,openurl,getscript,getclip,setclip,log,av,cpuinfo,sysinfo,apf,scanwifi,wallpaper,agentmsg'; - if (process.platform == 'win32') { availcommands += ',safemode,wpfhwacceleration,uac'; } - if (amt != null) { availcommands += ',amt,amtconfig,amtevents'; } - if (process.platform != 'freebsd') { availcommands += ',vm'; } - if (require('MeshAgent').maxKvmTileSize != null) { availcommands += ',kvmmode'; } - try { require('zip-reader'); availcommands += ',zip,unzip'; } catch (e) { } - - 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 'agentupdate': + require('MeshAgent').SendCommand({ action: 'agentupdate', sessionid: sessionid }); + break; + case 'agentupdateex': + // Perform an direct agent update without requesting any information from the server, this should not typically be used. + agentUpdate_Start(null, { sessionid: sessionid }); + break; + case 'msh': + response = JSON.stringify(_MSH(), null, 2); + break; + case 'timerinfo': + response = require('ChainViewer').getTimerInfo(); + break; + case 'find': + if (args['_'].length <= 1) + { + response = "Proper usage:\r\n find root criteria [criteria2] [criteria n...]"; } - case 'agentupdate': - require('MeshAgent').SendCommand({ action: 'agentupdate', sessionid: sessionid }); - break; - case 'agentupdateex': - // Perform an direct agent update without requesting any information from the server, this should not typically be used. - agentUpdate_Start(null, { sessionid: sessionid }); - break; - case 'msh': - response = JSON.stringify(_MSH(), null, 2); - break; - case 'timerinfo': - response = require('ChainViewer').getTimerInfo(); - break; - case 'find': - if (args['_'].length <= 1) { - response = "Proper usage:\r\n find root criteria [criteria2] [criteria n...]"; - } - else { - var root = args['_'][0]; - var p = args['_'].slice(1); - var r = require('file-search').find(root, p); - r.sid = sessionid; - r.on('result', function (str) { sendConsoleText(str, this.sid); }); - r.then(function () { sendConsoleText('*** End Results ***', this.sid); }); - response = "Find: [" + root + "] " + JSON.stringify(p); - } - break; - case 'coreinfo': { - response = JSON.stringify(meshCoreObj, null, 2); - break; + else + { + var root = args['_'][0]; + var p = args['_'].slice(1); + var r = require('file-search').find(root, p); + r.sid = sessionid; + r.on('result', function (str) { sendConsoleText(str, this.sid); }); + r.then(function () { sendConsoleText('*** End Results ***', this.sid); }); + response = "Find: [" + root + "] " + JSON.stringify(p); } - case 'coreinfoupdate': { - sendPeriodicServerUpdate(); - break; - } - case 'agentmsg': { - if (args['_'].length == 0) { - response = "Proper usage:\r\n agentmsg add \"[message]\" [iconIndex]\r\n agentmsg remove [index]\r\n agentmsg list"; // Display usage - } else { - if ((args['_'][0] == 'add') && (args['_'].length > 1)) { - var msgIndex = 1, iconIndex = 0; - while (tunnelUserCount.msg[msgIndex] != null) { msgIndex++; } - if (args['_'].length >= 3) { try { iconIndex = parseInt(args['_'][2]); } catch (e) { } } - if (typeof iconIndex != 'number') { iconIndex = 0; } - tunnelUserCount.msg[msgIndex] = { msg: args['_'][1], icon: iconIndex }; - response = 'Agent message ' + msgIndex + ' added.'; - } else if ((args['_'][0] == 'remove') && (args['_'].length > 1)) { - var msgIndex = 0; - try { msgIndex = parseInt(args['_'][1]); } catch (x) { } - if (tunnelUserCount.msg[msgIndex] == null) { response = "Message not found."; } else { delete tunnelUserCount.msg[msgIndex]; response = "Message removed."; } - } else if (args['_'][0] == 'list') { - response = JSON.stringify(tunnelUserCount.msg, null, 2); - } - try { mesh.SendCommand({ action: 'sessions', type: 'msg', value: tunnelUserCount.msg }); } catch (x) { } - broadcastSessionsToRegisteredApps(); + break; + case 'coreinfo': { + response = JSON.stringify(meshCoreObj, null, 2); + break; + } + case 'coreinfoupdate': { + sendPeriodicServerUpdate(); + break; + } + case 'agentmsg': { + if (args['_'].length == 0) + { + response = "Proper usage:\r\n agentmsg add \"[message]\" [iconIndex]\r\n agentmsg remove [index]\r\n agentmsg list"; // Display usage + } else + { + if ((args['_'][0] == 'add') && (args['_'].length > 1)) + { + var msgIndex = 1, iconIndex = 0; + while (tunnelUserCount.msg[msgIndex] != null) { msgIndex++; } + if (args['_'].length >= 3) { try { iconIndex = parseInt(args['_'][2]); } catch (e) { } } + if (typeof iconIndex != 'number') { iconIndex = 0; } + tunnelUserCount.msg[msgIndex] = { msg: args['_'][1], icon: iconIndex }; + response = 'Agent message ' + msgIndex + ' added.'; + } else if ((args['_'][0] == 'remove') && (args['_'].length > 1)) + { + var msgIndex = 0; + try { msgIndex = parseInt(args['_'][1]); } catch (x) { } + if (tunnelUserCount.msg[msgIndex] == null) { response = "Message not found."; } else { delete tunnelUserCount.msg[msgIndex]; response = "Message removed."; } + } else if (args['_'][0] == 'list') + { + response = JSON.stringify(tunnelUserCount.msg, null, 2); } - break; - } - case 'clearagentmsg': { - tunnelUserCount.msg = {}; try { mesh.SendCommand({ action: 'sessions', type: 'msg', value: tunnelUserCount.msg }); } catch (x) { } broadcastSessionsToRegisteredApps(); + } + break; + } + case 'clearagentmsg': { + tunnelUserCount.msg = {}; + try { mesh.SendCommand({ action: 'sessions', type: 'msg', value: tunnelUserCount.msg }); } catch (x) { } + broadcastSessionsToRegisteredApps(); + break; + } + case 'coredump': + if (args['_'].length != 1) + { + response = "Proper usage: coredump on|off|status|clear"; // Display usage + } else + { + switch (args['_'][0].toLowerCase()) + { + case 'on': + process.coreDumpLocation = (process.platform == 'win32') ? (process.execPath.replace('.exe', '.dmp')) : (process.execPath + '.dmp'); + response = 'coredump is now on'; + break; + case 'off': + process.coreDumpLocation = null; + response = 'coredump is now off'; + break; + case 'status': + response = 'coredump is: ' + ((process.coreDumpLocation == null) ? 'off' : 'on'); + if (process.coreDumpLocation != null) + { + if (process.platform == 'win32') + { + if (fs.existsSync(process.coreDumpLocation)) + { + response += '\r\n CoreDump present at: ' + process.coreDumpLocation; + response += '\r\n CoreDump Time: ' + new Date(fs.statSync(process.coreDumpLocation).mtime).getTime(); + response += '\r\n Agent Time : ' + new Date(fs.statSync(process.execPath).mtime).getTime(); + } + } else + { + if ((process.cwd() != '//') && fs.existsSync(process.cwd() + 'core')) + { + response += '\r\n CoreDump present at: ' + process.cwd() + 'core'; + response += '\r\n CoreDump Time: ' + new Date(fs.statSync(process.cwd() + 'core').mtime).getTime(); + response += '\r\n Agent Time : ' + new Date(fs.statSync(process.execPath).mtime).getTime(); + } + } + } + break; + case 'clear': + db.Put('CoreDumpTime', null); + response = 'coredump db cleared'; + break; + default: + response = "Proper usage: coredump on|off|status"; // Display usage + break; + } + } + break; + case 'service': + if (args['_'].length != 1) + { + response = "Proper usage: service status|restart"; // Display usage + } + else + { + var svcname = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'; + try + { + svcname = require('MeshAgent').serviceName; + } + catch (x) + { + } + var s = require('service-manager').manager.getService(svcname); + 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"; break; } // Display usage + 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; } - case 'coredump': - if (args['_'].length != 1) { - response = "Proper usage: coredump on|off|status|clear"; // Display usage - } else { - switch (args['_'][0].toLowerCase()) { - case 'on': - process.coreDumpLocation = (process.platform == 'win32') ? (process.execPath.replace('.exe', '.dmp')) : (process.execPath + '.dmp'); - response = 'coredump is now on'; - break; - case 'off': - process.coreDumpLocation = null; - response = 'coredump is now off'; - break; - case 'status': - response = 'coredump is: ' + ((process.coreDumpLocation == null) ? 'off' : 'on'); - if (process.coreDumpLocation != null) { - if (process.platform == 'win32') { - if (fs.existsSync(process.coreDumpLocation)) { - response += '\r\n CoreDump present at: ' + process.coreDumpLocation; - response += '\r\n CoreDump Time: ' + new Date(fs.statSync(process.coreDumpLocation).mtime).getTime(); - response += '\r\n Agent Time : ' + new Date(fs.statSync(process.execPath).mtime).getTime(); - } - } else { - if ((process.cwd() != '//') && fs.existsSync(process.cwd() + 'core')) { - response += '\r\n CoreDump present at: ' + process.cwd() + 'core'; - response += '\r\n CoreDump Time: ' + new Date(fs.statSync(process.cwd() + 'core').mtime).getTime(); - response += '\r\n Agent Time : ' + new Date(fs.statSync(process.execPath).mtime).getTime(); - } - } - } - break; - case 'clear': - db.Put('CoreDumpTime', null); - response = 'coredump db cleared'; - break; - default: - response = "Proper usage: coredump on|off|status"; // Display usage - 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 'service': - if (args['_'].length != 1) { - response = "Proper usage: service status|restart"; // Display usage + } + 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 { - var svcname = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'; - try { - svcname = require('MeshAgent').serviceName; - } - catch (x) { - } - var s = require('service-manager').manager.getService(svcname); - 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(); } + 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 '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"; break; } // Display usage - 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) { + } + 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 { - 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); - } + 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 (e) { response = e; } - 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 (e) { response = e; } - } else - { response = "Agent Size: " + actualSize + " kb"; } + } + 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 (e) { response = e; } + 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 (e) { response = e; } } 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); + } 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()) { + 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 (e) { response = "FAILED"; } + break; + case 'OFF': + try + { + reg.WriteKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration', 1); + response = 'OK'; + } catch (e) { response = 'FAILED'; } + break; + case 'STATUS': + var s; + try { s = reg.QueryKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration') == 1 ? 'DISABLED' : 'ENABLED'; } catch (e) { s = 'DEFAULT'; } + response = "WPF Hardware Acceleration: " + s; + break; + case 'DEFAULT': + try { reg.DeleteKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration'); } catch (e) { } + 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: wpfhwacceleration (ON|OFF|STATUS|DEFAULT)"; // Display usage + 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 + { + var svcname = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'; + try + { + svcname = require('MeshAgent').serviceName; + } + catch (x) + { + } + + switch (args['_'][0].toUpperCase()) + { + default: + response = 'Proper usage: safemode (ON|OFF|STATUS)'; // Display usage break; case 'ON': - try { - reg.WriteKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration', 0); - response = "OK"; - } catch (e) { response = "FAILED"; } + require('win-bcd').setKey('safeboot', 'Network'); + require('win-bcd').enableSafeModeService(svcname); break; case 'OFF': - try { - reg.WriteKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration', 1); - response = 'OK'; - } catch (e) { response = 'FAILED'; } + require('win-bcd').deleteKey('safeboot'); break; case 'STATUS': - var s; - try { s = reg.QueryKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration') == 1 ? 'DISABLED' : 'ENABLED'; } catch (e) { s = 'DEFAULT'; } - response = "WPF Hardware Acceleration: " + s; - break; - case 'DEFAULT': - try { reg.DeleteKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration'); } catch (e) { } - response = 'OK'; + 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 '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 { - var svcname = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'; - try { - svcname = require('MeshAgent').serviceName; - } - catch (x) { - } - - 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(svcname); - 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; + } + break; /* case 'border': { @@ -3145,1120 +3599,1221 @@ function createMeshCore(agent) { } 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 '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'; } - 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; + 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); }); } - 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 '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])); // This can only be run by trusted administrator. - } - break; - } - case 'uninstallagent': // Uninstall this agent - var agentName = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'; - try { - agentName = require('MeshAgent').serviceName; - } - catch (x) { - } - - if (!require('service-manager').manager.getService(agentName).isMe()) { - response = 'Uininstall failed, this instance is not the service instance'; - } else { - try { diagnosticAgent_uninstall(); } catch (e) { } - 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) + break; + case 'setclip': { + if (args['_'].length != 1) + { + response = 'Proper usage: setclip "sample text"'; + } else + { + if (require('MeshAgent').isService) { - sendConsoleText(JSON.stringify( + require('clipboard').dispatchWrite(args['_'][0]); + response = 'Setting clipboard to: "' + args['_'][0] + '"'; + } + else + { + require("clipboard")(args['_'][0]); response = 'Setting clipboard to: "' + args['_'][0] + '"'; + } + } + 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) { - cpu: data, - memory: require('sysinfo').memUtilization(), - thermals: require('sysinfo').thermals == null ? [] : require('sysinfo').thermals() - }, 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 + '.'; - 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 + '.'; } - //response += '\r\Debug Console: ' + debugConsole + '.'; - 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 '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; - } + 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 'wslist': { // List all web sockets - response = ''; - for (var i in consoleWebSockets) { + 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])); // This can only be run by trusted administrator. + } + break; + } + case 'uninstallagent': // Uninstall this agent + var agentName = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'; + try + { + agentName = require('MeshAgent').serviceName; + } + catch (x) + { + } + + if (!require('service-manager').manager.getService(agentName).isMe()) + { + response = 'Uininstall failed, this instance is not the service instance'; + } else + { + try { diagnosticAgent_uninstall(); } catch (e) { } + 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(), + thermals: require('sysinfo').thermals == null ? [] : require('sysinfo').thermals() + }, 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 + '.'; + 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 + '.'; } + //response += '\r\Debug Console: ' + debugConsole + '.'; + 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 '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'; } - 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.getMeiState(9, 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 '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': + } else + { + var i = parseInt(args['_'][0]); + var httprequest = consoleWebSockets[i]; + if (httprequest != undefined) { - 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; } + 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.getMeiState(9, 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 '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; } - case 'amtevents': { - if (obj.amtevents == null) { response = 'No events.'; } else { response = obj.amtevents.join('\r\n'); } + 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 'amtconfig': { - if (amt == null) { response = "Intel AMT not detected."; break; } - if (apftunnel != null) { response = "Intel AMT server tunnel already active"; break; } - amt.getMeiState(15, function (state) { - var rx = ''; - if ((state == null) || (state.ProvisioningState == null)) { rx = "Intel AMT not ready for configuration."; } else { + case 'amtevents': { + if (obj.amtevents == null) { response = 'No events.'; } else { response = obj.amtevents.join('\r\n'); } + break; + } + case 'amtconfig': { + if (amt == null) { response = "Intel AMT not detected."; break; } + if (apftunnel != null) { response = "Intel AMT server tunnel already active"; break; } + amt.getMeiState(15, function (state) + { + var rx = ''; + if ((state == null) || (state.ProvisioningState == null)) { rx = "Intel AMT not ready for configuration."; } else { + 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: state.OsHostname, + clientaddress: '127.0.0.1', + clientuuid: state.UUID, + conntype: 2, // 0 = CIRA, 1 = Relay, 2 = LMS. The correct value is 2 since we are performing an LMS relay, other values for testing. + meiState: state // MEI state will be passed to MPS server + }; + if ((state.UUID == null) || (state.UUID.length != 36)) + { + rx = "Unable to get Intel AMT UUID"; + } else + { + addAmtEvent('User LMS tunnel start.'); + apftunnel = require('amt-apfclient')({ debug: false }, apfarg); + apftunnel.onJsonControl = function (data) + { + if (data.action == 'console') { addAmtEvent(data.msg); require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: data.msg }); } // Display a console message + if (data.action == 'mestate') { amt.getMeiState(15, function (state) { apftunnel.updateMeiState(state); }); } // Update the MEI state + if (data.action == 'deactivate') + { // Request CCM deactivation + var amtMeiModule, amtMei; + try { amtMeiModule = require('amt-mei'); amtMei = new amtMeiModule(); } catch (ex) { apftunnel.sendMeiDeactivationState(1); return; } + amtMei.on('error', function (e) { apftunnel.sendMeiDeactivationState(1); }); + amtMei.unprovision(1, function (status) { apftunnel.sendMeiDeactivationState(status); }); // 0 = Success + } + if (data.action == 'close') { try { apftunnel.disconnect(); } catch (e) { } apftunnel = null; } // Close the CIRA-LMS connection + } + apftunnel.onChannelClosed = function () { addAmtEvent('User LMS tunnel closed.'); apftunnel = null; } + try + { + apftunnel.connect(); + rx = "Started Intel AMT configuration"; + } catch (ex) + { + rx = JSON.stringify(ex); + } + } + } + if (rx != '') { require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: rx }); } + }); + break; + } + case 'apf': { + if (meshCoreObj.intelamt !== null) + { + if (args['_'].length == 1) + { + var connType = -1, connTypeStr = args['_'][0].toLowerCase(); + if (connTypeStr == 'lms') { connType = 2; } + if (connTypeStr == 'relay') { connType = 1; } + if (connTypeStr == 'cira') { connType = 0; } + if (connTypeStr == 'off') { connType = -2; } + if (connType >= 0) + { // Connect 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: state.OsHostname, + clientname: require('os').hostname(), clientaddress: '127.0.0.1', - clientuuid: state.UUID, - conntype: 2, // 0 = CIRA, 1 = Relay, 2 = LMS. The correct value is 2 since we are performing an LMS relay, other values for testing. - meiState: state // MEI state will be passed to MPS server + clientuuid: meshCoreObj.intelamt.uuid, + conntype: connType // 0 = CIRA, 1 = Relay, 2 = LMS. The correct value is 2 since we are performing an LMS relay, other values for testing. }; - if ((state.UUID == null) || (state.UUID.length != 36)) { - rx = "Unable to get Intel AMT UUID"; - } else { - addAmtEvent('User LMS tunnel start.'); + if ((apfarg.clientuuid == null) || (apfarg.clientuuid.length != 36)) + { + response = "Unable to get Intel AMT UUID: " + apfarg.clientuuid; + } else + { apftunnel = require('amt-apfclient')({ debug: false }, apfarg); - apftunnel.onJsonControl = function (data) { - if (data.action == 'console') { addAmtEvent(data.msg); require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: data.msg }); } // Display a console message - if (data.action == 'mestate') { amt.getMeiState(15, function (state) { apftunnel.updateMeiState(state); }); } // Update the MEI state - if (data.action == 'deactivate') { // Request CCM deactivation - var amtMeiModule, amtMei; - try { amtMeiModule = require('amt-mei'); amtMei = new amtMeiModule(); } catch (ex) { apftunnel.sendMeiDeactivationState(1); return; } - amtMei.on('error', function (e) { apftunnel.sendMeiDeactivationState(1); }); - amtMei.unprovision(1, function (status) { apftunnel.sendMeiDeactivationState(status); }); // 0 = Success - } - if (data.action == 'close') { try { apftunnel.disconnect(); } catch (e) { } apftunnel = null; } // Close the CIRA-LMS connection + apftunnel.onJsonControl = function (data) + { + if (data.action == 'console') { require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: data.msg }); } + if (data.action == 'close') { try { apftunnel.disconnect(); } catch (e) { } apftunnel = null; } } - apftunnel.onChannelClosed = function () { addAmtEvent('User LMS tunnel closed.'); apftunnel = null; } - try { + apftunnel.onChannelClosed = function () { apftunnel = null; } + try + { apftunnel.connect(); - rx = "Started Intel AMT configuration"; - } catch (ex) { - rx = JSON.stringify(ex); - } - } - } - if (rx != '') { require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: rx }); } - }); - break; - } - case 'apf': { - if (meshCoreObj.intelamt !== null) { - if (args['_'].length == 1) { - var connType = -1, connTypeStr = args['_'][0].toLowerCase(); - if (connTypeStr == 'lms') { connType = 2; } - if (connTypeStr == 'relay') { connType = 1; } - if (connTypeStr == 'cira') { connType = 0; } - if (connTypeStr == 'off') { connType = -2; } - if (connType >= 0) { // Connect - 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, - conntype: connType // 0 = CIRA, 1 = Relay, 2 = LMS. The correct value is 2 since we are performing an LMS relay, other values for testing. - }; - if ((apfarg.clientuuid == null) || (apfarg.clientuuid.length != 36)) { - response = "Unable to get Intel AMT UUID: " + apfarg.clientuuid; - } else { - apftunnel = require('amt-apfclient')({ debug: false }, apfarg); - apftunnel.onJsonControl = function (data) { - if (data.action == 'console') { require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: data.msg }); } - if (data.action == 'close') { try { apftunnel.disconnect(); } catch (e) { } apftunnel = null; } - } - apftunnel.onChannelClosed = function () { apftunnel = null; } - try { - apftunnel.connect(); - response = "Started APF tunnel"; - } catch (e) { - response = JSON.stringify(e); - } - } - } else if (connType == -2) { // Disconnect - try { - apftunnel.disconnect(); - response = "Stopped APF tunnel"; - } catch (e) { + response = "Started APF tunnel"; + } catch (e) + { response = JSON.stringify(e); } - apftunnel = null; - } else { - response = "Invalid command.\r\nUse: apf lms|relay|cira|off"; } - } else { - response = "APF tunnel is " + (apftunnel == null ? "off" : "on") + "\r\nUse: apf lms|relay|cira|off"; + } else if (connType == -2) + { // Disconnect + try + { + apftunnel.disconnect(); + response = "Stopped APF tunnel"; + } catch (e) + { + response = JSON.stringify(e); + } + apftunnel = null; + } else + { + response = "Invalid command.\r\nUse: apf lms|relay|cira|off"; } - } else { - response = "APF tunnel requires Intel AMT"; + } else + { + response = "APF tunnel is " + (apftunnel == null ? "off" : "on") + "\r\nUse: apf lms|relay|cira|off"; } - break; + } else + { + response = "APF tunnel requires Intel AMT"; } - 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); } - if (debugConsole && ((sessionid == null) || (sessionid == 'pipe'))) { broadcastToRegisteredApps({ cmd: 'console', value: text }); } - if (sessionid != 'pipe') { require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: text, sessionid: sessionid }); } - } - - // Send a mesh agent message to server, placing a bubble/badge on the agent device - function sendAgentMessage(msg, icon) { - if (sendAgentMessage.messages == null) { - sendAgentMessage.messages = {}; - sendAgentMessage.nextid = 1; - } - sendAgentMessage.messages[sendAgentMessage.nextid++] = { msg: msg, icon: icon }; - require('MeshAgent').SendCommand({ action: 'sessions', type: 'msg', value: sendAgentMessage.messages }); - } - - function linux_execv(name, agentfilename, sessionid) - { - var libs = require('monitor-info').getLibInfo('libc'); - var libc = null; - - while (libs.length > 0) - { - try - { - libc = require('_GenericMarshal').CreateNativeProxy(libs.pop().path); break; } - catch (e) - { - libc = null; - continue; - } - } - if (libc != null) - { - try - { - libc.CreateMethod('execv'); - } - catch (e) - { - libc = null; + 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); } +} - if (libc == null) - { - // Couldn't find libc.so, fallback to using service manager to restart agent - if (sessionid != null) { sendConsoleText('Restarting service via service-manager...', sessionid) } - try - { - // restart service - var s = require('service-manager').manager.getService(name); - s.restart(); - } - catch (zz) - { - sendConsoleText('Self Update encountered an error trying to restart service', sessionid); - sendAgentMessage('Self Update encountered an error trying to restart service', 3); - } - return; - } +// Send a mesh agent console command +function sendConsoleText(text, sessionid) +{ + if (typeof text == 'object') { text = JSON.stringify(text); } + if (debugConsole && ((sessionid == null) || (sessionid == 'pipe'))) { broadcastToRegisteredApps({ cmd: 'console', value: text }); } + if (sessionid != 'pipe') { require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: text, sessionid: sessionid }); } +} - if (sessionid != null) { sendConsoleText('Restarting service via execv()...', sessionid) } - - var i; - var args; - var argarr = []; - var path = require('_GenericMarshal').CreateVariable(process.execPath); - - if (require('MeshAgent').getStartupOptions != null) - { - var options = require('MeshAgent').getStartupOptions(); - for (i in options) - { - argarr.push('--' + i + '="' + options[i] + '"'); - } - } - - args = require('_GenericMarshal').CreateVariable((1 + argarr.length) * require('_GenericMarshal').PointerSize); - for (i = 0; i < argarr.length; ++i) - { - var arg = require('_GenericMarshal').CreateVariable(argarr[i]); - arg.pointerBuffer().copy(args.toBuffer(), i * require('_GenericMarshal').PointerSize); - } - - libc.execv(path, args); - if (sessionid != null) { sendConsoleText('Self Update failed because execv() failed', sessionid) } - sendAgentMessage('Self Update failed because execv() failed', 3); - } - - function bsd_execv(name, agentfilename, sessionid) +// Send a mesh agent message to server, placing a bubble/badge on the agent device +function sendAgentMessage(msg, icon) +{ + if (sendAgentMessage.messages == null) { - var child = require('child_process').execFile('/bin/sh', ['sh']); - child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); - child.stderr.str = ''; child.stderr.on('data', function (c) { this.str += c.toString(); }); - child.stdin.write("cat /usr/lib/libc.so | awk '"); - child.stdin.write('{'); - child.stdin.write(' a=split($0, tok, "(");'); - child.stdin.write(' if(a>1)'); - child.stdin.write(' {'); - child.stdin.write(' split(tok[2], b, ")");'); - child.stdin.write(' split(b[1], c, " ");'); - child.stdin.write(' print c[1];'); - child.stdin.write(' }'); - child.stdin.write("}'\nexit\n"); - child.waitExit(); - if (child.stdout.str.trim() == '') - { - if (sessionid != null) { sendConsoleText('Self Update failed because cannot find libc.so', sessionid) } - sendAgentMessage('Self Update failed because cannot find libc.so', 3); - return; - } + sendAgentMessage.messages = {}; + sendAgentMessage.nextid = 1; + } + sendAgentMessage.messages[sendAgentMessage.nextid++] = { msg: msg, icon: icon }; + require('MeshAgent').SendCommand({ action: 'sessions', type: 'msg', value: sendAgentMessage.messages }); +} - var libc = null; +function linux_execv(name, agentfilename, sessionid) +{ + var libs = require('monitor-info').getLibInfo('libc'); + var libc = null; + + while (libs.length > 0) + { + try + { + libc = require('_GenericMarshal').CreateNativeProxy(libs.pop().path); + break; + } + catch (e) + { + libc = null; + continue; + } + } + if (libc != null) + { try { - libc = require('_GenericMarshal').CreateNativeProxy(child.stdout.str.trim()); libc.CreateMethod('execv'); } catch (e) { - if (sessionid != null) { sendConsoleText('Self Update failed: ' + e.toString(), sessionid) } - sendAgentMessage('Self Update failed: ' + e.toString(), 3); - return; + libc = null; } + } - var i; - var path = require('_GenericMarshal').CreateVariable(process.execPath); - var argarr = []; - var args; + if (libc == null) + { + // Couldn't find libc.so, fallback to using service manager to restart agent + if (sessionid != null) { sendConsoleText('Restarting service via service-manager...', sessionid) } + try + { + // restart service + var s = require('service-manager').manager.getService(name); + s.restart(); + } + catch (zz) + { + sendConsoleText('Self Update encountered an error trying to restart service', sessionid); + sendAgentMessage('Self Update encountered an error trying to restart service', 3); + } + return; + } + + if (sessionid != null) { sendConsoleText('Restarting service via execv()...', sessionid) } + + var i; + var args; + var argarr = []; + var path = require('_GenericMarshal').CreateVariable(process.execPath); + + if (require('MeshAgent').getStartupOptions != null) + { var options = require('MeshAgent').getStartupOptions(); for (i in options) { argarr.push('--' + i + '="' + options[i] + '"'); } - args = require('_GenericMarshal').CreateVariable((1 + argarr.length) * require('_GenericMarshal').PointerSize); - for (i = 0; i < argarr.length; ++i) - { - var arg = require('_GenericMarshal').CreateVariable(argarr[i]); - arg.pointerBuffer().copy(args.toBuffer(), i * require('_GenericMarshal').PointerSize); - } - - if (sessionid != null) { sendConsoleText('Restarting service via service-manager', sessionid) } - libc.execv(path, args); - if (sessionid != null) { sendConsoleText('Self Update failed because execv() failed', sessionid) } - sendAgentMessage('Self Update failed because execv() failed', 3); } - function windows_execve(name, agentfilename, sessionid) + args = require('_GenericMarshal').CreateVariable((1 + argarr.length) * require('_GenericMarshal').PointerSize); + for (i = 0; i < argarr.length; ++i) { - var libc; - try - { - libc = require('_GenericMarshal').CreateNativeProxy('msvcrt.dll'); - libc.CreateMethod('_wexecve'); - } - catch (xx) - { - sendConsoleText('Self Update failed because msvcrt.dll is missing', sessionid); - sendAgentMessage('Self Update failed because msvcrt.dll is missing', 3); - return; - } - - var cmd = require('_GenericMarshal').CreateVariable(process.env['windir'] + '\\system32\\cmd.exe', { wide: true }); - var args = require('_GenericMarshal').CreateVariable(3 * require('_GenericMarshal').PointerSize); - var arg1 = require('_GenericMarshal').CreateVariable('cmd.exe', { wide: true }); - var arg2 = require('_GenericMarshal').CreateVariable('/C wmic service "' + name + '" call stopservice & copy "' + process.cwd() + agentfilename + '.update" "' + process.execPath + '" & wmic service "' + name + '" call startservice & erase "' + process.cwd() + agentfilename + '.update"', { wide: true }); - - arg1.pointerBuffer().copy(args.toBuffer()); - arg2.pointerBuffer().copy(args.toBuffer(), require('_GenericMarshal').PointerSize); - - libc._wexecve(cmd, args, 0); + var arg = require('_GenericMarshal').CreateVariable(argarr[i]); + arg.pointerBuffer().copy(args.toBuffer(), i * require('_GenericMarshal').PointerSize); } - // Start a JavaScript based Agent Self-Update - function agentUpdate_Start(updateurl, updateoptions) - { - // If this value is null - var sessionid = (updateoptions != null) ? updateoptions.sessionid : null; // If this is null, messages will be broadcast. Otherwise they will be unicasted + libc.execv(path, args); + if (sessionid != null) { sendConsoleText('Self Update failed because execv() failed', sessionid) } + sendAgentMessage('Self Update failed because execv() failed', 3); +} - if (agentUpdate_Start._selfupdate != null) +function bsd_execv(name, agentfilename, sessionid) +{ + var child = require('child_process').execFile('/bin/sh', ['sh']); + child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); + child.stderr.str = ''; child.stderr.on('data', function (c) { this.str += c.toString(); }); + child.stdin.write("cat /usr/lib/libc.so | awk '"); + child.stdin.write('{'); + child.stdin.write(' a=split($0, tok, "(");'); + child.stdin.write(' if(a>1)'); + child.stdin.write(' {'); + child.stdin.write(' split(tok[2], b, ")");'); + child.stdin.write(' split(b[1], c, " ");'); + child.stdin.write(' print c[1];'); + child.stdin.write(' }'); + child.stdin.write("}'\nexit\n"); + child.waitExit(); + if (child.stdout.str.trim() == '') + { + if (sessionid != null) { sendConsoleText('Self Update failed because cannot find libc.so', sessionid) } + sendAgentMessage('Self Update failed because cannot find libc.so', 3); + return; + } + + var libc = null; + try + { + libc = require('_GenericMarshal').CreateNativeProxy(child.stdout.str.trim()); + libc.CreateMethod('execv'); + } + catch (e) + { + if (sessionid != null) { sendConsoleText('Self Update failed: ' + e.toString(), sessionid) } + sendAgentMessage('Self Update failed: ' + e.toString(), 3); + return; + } + + var i; + var path = require('_GenericMarshal').CreateVariable(process.execPath); + var argarr = []; + var args; + var options = require('MeshAgent').getStartupOptions(); + for (i in options) + { + argarr.push('--' + i + '="' + options[i] + '"'); + } + args = require('_GenericMarshal').CreateVariable((1 + argarr.length) * require('_GenericMarshal').PointerSize); + for (i = 0; i < argarr.length; ++i) + { + var arg = require('_GenericMarshal').CreateVariable(argarr[i]); + arg.pointerBuffer().copy(args.toBuffer(), i * require('_GenericMarshal').PointerSize); + } + + if (sessionid != null) { sendConsoleText('Restarting service via service-manager', sessionid) } + libc.execv(path, args); + if (sessionid != null) { sendConsoleText('Self Update failed because execv() failed', sessionid) } + sendAgentMessage('Self Update failed because execv() failed', 3); +} + +function windows_execve(name, agentfilename, sessionid) +{ + var libc; + try + { + libc = require('_GenericMarshal').CreateNativeProxy('msvcrt.dll'); + libc.CreateMethod('_wexecve'); + } + catch (xx) + { + sendConsoleText('Self Update failed because msvcrt.dll is missing', sessionid); + sendAgentMessage('Self Update failed because msvcrt.dll is missing', 3); + return; + } + + var cmd = require('_GenericMarshal').CreateVariable(process.env['windir'] + '\\system32\\cmd.exe', { wide: true }); + var args = require('_GenericMarshal').CreateVariable(3 * require('_GenericMarshal').PointerSize); + var arg1 = require('_GenericMarshal').CreateVariable('cmd.exe', { wide: true }); + var arg2 = require('_GenericMarshal').CreateVariable('/C wmic service "' + name + '" call stopservice & copy "' + process.cwd() + agentfilename + '.update" "' + process.execPath + '" & wmic service "' + name + '" call startservice & erase "' + process.cwd() + agentfilename + '.update"', { wide: true }); + + arg1.pointerBuffer().copy(args.toBuffer()); + arg2.pointerBuffer().copy(args.toBuffer(), require('_GenericMarshal').PointerSize); + + libc._wexecve(cmd, args, 0); +} + +// Start a JavaScript based Agent Self-Update +function agentUpdate_Start(updateurl, updateoptions) +{ + // If this value is null + var sessionid = (updateoptions != null) ? updateoptions.sessionid : null; // If this is null, messages will be broadcast. Otherwise they will be unicasted + + if (agentUpdate_Start._selfupdate != null) + { + // We were already called, so we will ignore this duplicate request + if (sessionid != null) { sendConsoleText('Self update already in progress...', sessionid); } + } + else + { + if (agentUpdate_Start._retryCount == null) { agentUpdate_Start._retryCount = 0; } + if (require('MeshAgent').ARCHID == null && updateurl == null) { - // We were already called, so we will ignore this duplicate request - if (sessionid != null) { sendConsoleText('Self update already in progress...', sessionid); } + // This agent doesn't have the ability to tell us which ARCHID it is, so we don't know which agent to pull + sendConsoleText('Unable to initiate update, agent ARCHID is not defined', sessionid); } else { - if (agentUpdate_Start._retryCount == null) { agentUpdate_Start._retryCount = 0; } - if (require('MeshAgent').ARCHID == null && updateurl == null) + var agentfilename = process.execPath.split(process.platform == 'win32' ? '\\' : '/').pop(); // Local File Name, ie: MeshAgent.exe + var name = require('MeshAgent').serviceName; + if (name == null) { name = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'; } // This is an older agent that doesn't expose the service name, so use the default + try { - // This agent doesn't have the ability to tell us which ARCHID it is, so we don't know which agent to pull - sendConsoleText('Unable to initiate update, agent ARCHID is not defined', sessionid); - } - else - { - var agentfilename = process.execPath.split(process.platform == 'win32' ? '\\' : '/').pop(); // Local File Name, ie: MeshAgent.exe - var name = require('MeshAgent').serviceName; - if (name == null) { name = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'; } // This is an older agent that doesn't expose the service name, so use the default - try + var s = require('service-manager').manager.getService(name); + if (!s.isMe()) { - var s = require('service-manager').manager.getService(name); - if (!s.isMe()) - { - if (process.platform == 'win32') { s.close(); } - sendConsoleText('Self Update cannot continue, this agent is not an instance of (' + name + ')', sessionid); - return; - } if (process.platform == 'win32') { s.close(); } - } - catch (zz) - { - sendConsoleText('Self Update Failed because this agent is not an instance of (' + name + ')', sessionid); - sendAgentMessage('Self Update Failed because this agent is not an instance of (' + name + ')', 3); + sendConsoleText('Self Update cannot continue, this agent is not an instance of (' + name + ')', sessionid); return; } + if (process.platform == 'win32') { s.close(); } + } + catch (zz) + { + sendConsoleText('Self Update Failed because this agent is not an instance of (' + name + ')', sessionid); + sendAgentMessage('Self Update Failed because this agent is not an instance of (' + name + ')', 3); + return; + } - if (sessionid != null) { sendConsoleText('Downloading update...', sessionid); } - var options = require('http').parseUri(updateurl != null ? updateurl : require('MeshAgent').ServerUrl); - options.protocol = 'https:'; - if (updateurl == null) { options.path = ('/meshagents?id=' + require('MeshAgent').ARCHID); } - options.rejectUnauthorized = false; - options.checkServerIdentity = function checkServerIdentity(certs) + if (sessionid != null) { sendConsoleText('Downloading update...', sessionid); } + var options = require('http').parseUri(updateurl != null ? updateurl : require('MeshAgent').ServerUrl); + options.protocol = 'https:'; + if (updateurl == null) { options.path = ('/meshagents?id=' + require('MeshAgent').ARCHID); } + options.rejectUnauthorized = false; + options.checkServerIdentity = function checkServerIdentity(certs) + { + // If the tunnel certificate matches the control channel certificate, accept the connection + try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.digest == certs[0].digest) return; } catch (ex) { } + try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.fingerprint == certs[0].fingerprint) return; } catch (ex) { } + + // Check that the certificate is the one expected by the server, fail if not. + if (checkServerIdentity.servertlshash == null) { - // If the tunnel certificate matches the control channel certificate, accept the connection - try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.digest == certs[0].digest) return; } catch (ex) { } - try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.fingerprint == certs[0].fingerprint) return; } catch (ex) { } + if (require('MeshAgent').ServerInfo == null || require('MeshAgent').ServerInfo.ControlChannelCertificate == null) { return; } - // Check that the certificate is the one expected by the server, fail if not. - if (checkServerIdentity.servertlshash == null) - { - if (require('MeshAgent').ServerInfo == null || require('MeshAgent').ServerInfo.ControlChannelCertificate == null) { return; } - - sendConsoleText('Self Update failed, because the url cannot be verified', sessionid); - sendAgentMessage('Self Update failed, because the url cannot be verified', 3); - throw new Error('BadCert'); - } - if (certs[0].digest == null) { return; } - if ((checkServerIdentity.servertlshash != null) && (checkServerIdentity.servertlshash.toLowerCase() != certs[0].digest.split(':').join('').toLowerCase())) - { - sendConsoleText('Self Update failed, because the supplied certificate does not match', sessionid); - sendAgentMessage('Self Update failed, because the supplied certificate does not match', 3); - throw new Error('BadCert') - } + sendConsoleText('Self Update failed, because the url cannot be verified', sessionid); + sendAgentMessage('Self Update failed, because the url cannot be verified', 3); + throw new Error('BadCert'); } - options.checkServerIdentity.servertlshash = (updateoptions != null ? updateoptions.tlshash : null); - agentUpdate_Start._selfupdate = require('https').get(options); - agentUpdate_Start._selfupdate.on('error', function (e) + if (certs[0].digest == null) { return; } + if ((checkServerIdentity.servertlshash != null) && (checkServerIdentity.servertlshash.toLowerCase() != certs[0].digest.split(':').join('').toLowerCase())) { - sendConsoleText('Self Update failed, because there was a problem trying to download the update', sessionid); - sendAgentMessage('Self Update failed, because there was a problem trying to download the update', 3); - }); - agentUpdate_Start._selfupdate.on('response', function (img) + sendConsoleText('Self Update failed, because the supplied certificate does not match', sessionid); + sendAgentMessage('Self Update failed, because the supplied certificate does not match', 3); + throw new Error('BadCert') + } + } + options.checkServerIdentity.servertlshash = (updateoptions != null ? updateoptions.tlshash : null); + agentUpdate_Start._selfupdate = require('https').get(options); + agentUpdate_Start._selfupdate.on('error', function (e) + { + sendConsoleText('Self Update failed, because there was a problem trying to download the update', sessionid); + sendAgentMessage('Self Update failed, because there was a problem trying to download the update', 3); + }); + agentUpdate_Start._selfupdate.on('response', function (img) + { + this._file = require('fs').createWriteStream(agentfilename + '.update', { flags: 'wb' }); + this._filehash = require('SHA384Stream').create(); + this._filehash.on('hash', function (h) { - this._file = require('fs').createWriteStream(agentfilename + '.update', { flags: 'wb' }); - this._filehash = require('SHA384Stream').create(); - this._filehash.on('hash', function (h) + if (updateoptions != null && updateoptions.hash != null) { - if (updateoptions != null && updateoptions.hash != null) + if (updateoptions.hash.toLowerCase() == h.toString('hex').toLowerCase()) { - if (updateoptions.hash.toLowerCase() == h.toString('hex').toLowerCase()) + if (sessionid != null) { sendConsoleText('Download complete. HASH verified.', sessionid); } + } + else + { + agentUpdate_Start._retryCount++; + sendConsoleText('Self Update FAILED because the downloaded agent FAILED hash check (' + agentUpdate_Start._retryCount + ')', sessionid); + sendAgentMessage('Self Update FAILED because the downloaded agent FAILED hash check (' + agentUpdate_Start._retryCount + ')', 3); + agentUpdate_Start._selfupdate = null; + + if (agentUpdate_Start._retryCount < 4) { - if (sessionid != null) { sendConsoleText('Download complete. HASH verified.', sessionid); } + // Retry the download again + sendConsoleText('Self Update will try again in 60 seconds...', sessionid); + agentUpdate_Start._timeout = setTimeout(agentUpdate_Start, 60000, updateurl, updateoptions); } else { - agentUpdate_Start._retryCount++; - sendConsoleText('Self Update FAILED because the downloaded agent FAILED hash check (' + agentUpdate_Start._retryCount + ')', sessionid); - sendAgentMessage('Self Update FAILED because the downloaded agent FAILED hash check (' + agentUpdate_Start._retryCount + ')', 3); - agentUpdate_Start._selfupdate = null; - - if (agentUpdate_Start._retryCount < 4) - { - // Retry the download again - sendConsoleText('Self Update will try again in 60 seconds...', sessionid); - agentUpdate_Start._timeout = setTimeout(agentUpdate_Start, 60000, updateurl, updateoptions); - } - else - { - sendConsoleText('Self Update giving up, too many failures...', sessionid); - sendAgentMessage('Self Update giving up, too many failures...', 3); - } - return; + sendConsoleText('Self Update giving up, too many failures...', sessionid); + sendAgentMessage('Self Update giving up, too many failures...', 3); } + return; } - else + } + else + { + sendConsoleText('Download complete. HASH=' + h.toString('hex'), sessionid); + } + + // Send an indication to the server that we got the update download correctly. + try { require('MeshAgent').SendCommand({ action: 'agentupdatedownloaded' }); } catch (e) { } + + if (sessionid != null) { sendConsoleText('Updating and restarting agent...', sessionid); } + if (process.platform == 'win32') + { + // Use _wexecve() equivalent to perform the update + windows_execve(name, agentfilename, sessionid); + } + else + { + var m = require('fs').statSync(process.execPath).mode; + require('fs').chmodSync(process.cwd() + agentfilename + '.update', m); + + // remove binary + require('fs').unlinkSync(process.execPath); + + // copy update + require('fs').copyFileSync(process.cwd() + agentfilename + '.update', process.execPath); + require('fs').chmodSync(process.execPath, m); + + // erase update + require('fs').unlinkSync(process.cwd() + agentfilename + '.update'); + + switch (process.platform) { - sendConsoleText('Download complete. HASH=' + h.toString('hex'), sessionid); + case 'freebsd': + bsd_execv(name, agentfilename, sessionid); + break; + case 'linux': + linux_execv(name, agentfilename, sessionid); + break; + default: + try + { + // restart service + var s = require('service-manager').manager.getService(name); + s.restart(); + } + catch (zz) + { + sendConsoleText('Self Update encountered an error trying to restart service', sessionid); + sendAgentMessage('Self Update encountered an error trying to restart service', 3); + } + break; } - - // Send an indication to the server that we got the update download correctly. - try { require('MeshAgent').SendCommand({ action: 'agentupdatedownloaded' }); } catch (e) { } - - if (sessionid != null) { sendConsoleText('Updating and restarting agent...', sessionid); } - if (process.platform == 'win32') - { - // Use _wexecve() equivalent to perform the update - windows_execve(name, agentfilename, sessionid); - } - else - { - var m = require('fs').statSync(process.execPath).mode; - require('fs').chmodSync(process.cwd() + agentfilename + '.update', m); - - // remove binary - require('fs').unlinkSync(process.execPath); - - // copy update - require('fs').copyFileSync(process.cwd() + agentfilename + '.update', process.execPath); - require('fs').chmodSync(process.execPath, m); - - // erase update - require('fs').unlinkSync(process.cwd() + agentfilename + '.update'); - - switch (process.platform) - { - case 'freebsd': - bsd_execv(name, agentfilename, sessionid); - break; - case 'linux': - linux_execv(name, agentfilename, sessionid); - break; - default: - try - { - // restart service - var s = require('service-manager').manager.getService(name); - s.restart(); - } - catch (zz) - { - sendConsoleText('Self Update encountered an error trying to restart service', sessionid); - sendAgentMessage('Self Update encountered an error trying to restart service', 3); - } - break; - } - } - }); - img.pipe(this._file); - img.pipe(this._filehash); + } }); - } - } - } - - - - - // 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 }); } - - // Send SMBios tables if present - if (SMBiosTablesRaw != null) { mesh.SendCommand({ action: 'smbios', value: SMBiosTablesRaw }); } - - // Update the server on with basic info, logged in users and more advanced stuff, like Intel ME and Network Settings - meInfoStr = null; - sendPeriodicServerUpdate(null, true); - if (selfInfoUpdateTimer == null) { - selfInfoUpdateTimer = setInterval(sendPeriodicServerUpdate, 1200000); // 20 minutes - selfInfoUpdateTimer.metadata = 'meshcore (InfoUpdate Timer)'; - } - - // Send any state messages - if (Object.keys(tunnelUserCount.msg).length > 0) { - try { mesh.SendCommand({ action: 'sessions', type: 'msg', value: tunnelUserCount.msg }); } catch (e) { } - broadcastSessionsToRegisteredApps(); - } - - // Send update of registered applications to the server - updateRegisteredAppsToServer(); - } - - // Send server state update to registered applications - broadcastToRegisteredApps({ cmd: 'serverstate', value: meshServerConnectionState, url: require('MeshAgent').ConnectedServer }); - } - - // 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, force) - { - if (meshServerConnectionState == 0) return; // Not connected to server, do nothing. - if (!flags) { flags = 0xFFFFFFFF; } - - // If we have a connected MEI, get Intel ME information - if ((flags & 1) && (amt != null) && (amt.state == 2)) - { - delete meshCoreObj.intelamt; - amt.getMeiState(9, function (meinfo) - { - meshCoreObj.intelamt = meinfo; - meshCoreObj.intelamt.microlms = amt.lmsstate; - meshCoreObjChanged(); + img.pipe(this._file); + img.pipe(this._filehash); }); } + } +} - // Update network information - if (flags & 2) { sendNetworkUpdateNagle(false); } - // Update anti-virus information - if ((flags & 4) && (process.platform == 'win32')) + + +// 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 }); } + + // Send SMBios tables if present + if (SMBiosTablesRaw != null) { mesh.SendCommand({ action: 'smbios', value: SMBiosTablesRaw }); } + + // Update the server on with basic info, logged in users and more advanced stuff, like Intel ME and Network Settings + meInfoStr = null; + sendPeriodicServerUpdate(null, true); + if (selfInfoUpdateTimer == null) { - // Windows Command: "wmic /Namespace:\\root\SecurityCenter2 Path AntiVirusProduct get /FORMAT:CSV" - try { meshCoreObj.av = require('win-info').av(); meshCoreObjChanged(); } catch (e) { av = null; } // Antivirus - //if (process.platform == 'win32') { try { meshCoreObj.pr = require('win-info').pendingReboot(); meshCoreObjChanged(); } catch (e) { meshCoreObj.pr = null; } } // Pending reboot + selfInfoUpdateTimer = setInterval(sendPeriodicServerUpdate, 1200000); // 20 minutes + selfInfoUpdateTimer.metadata = 'meshcore (InfoUpdate Timer)'; } - if (process.platform == 'win32') + + // Send any state messages + if (Object.keys(tunnelUserCount.msg).length > 0) { - if(require('MeshAgent')._securitycenter == null) + try { mesh.SendCommand({ action: 'sessions', type: 'msg', value: tunnelUserCount.msg }); } catch (e) { } + broadcastSessionsToRegisteredApps(); + } + + // Send update of registered applications to the server + updateRegisteredAppsToServer(); + } + + // Send server state update to registered applications + broadcastToRegisteredApps({ cmd: 'serverstate', value: meshServerConnectionState, url: require('MeshAgent').ConnectedServer }); +} + +// 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, force) +{ + if (meshServerConnectionState == 0) return; // Not connected to server, do nothing. + if (!flags) { flags = 0xFFFFFFFF; } + + // If we have a connected MEI, get Intel ME information + if ((flags & 1) && (amt != null) && (amt.state == 2)) + { + delete meshCoreObj.intelamt; + amt.getMeiState(9, function (meinfo) + { + meshCoreObj.intelamt = meinfo; + meshCoreObj.intelamt.microlms = amt.lmsstate; + meshCoreObjChanged(); + }); + } + + // Update network information + if (flags & 2) { sendNetworkUpdateNagle(false); } + + // Update anti-virus information + if ((flags & 4) && (process.platform == 'win32')) + { + // Windows Command: "wmic /Namespace:\\root\SecurityCenter2 Path AntiVirusProduct get /FORMAT:CSV" + try { meshCoreObj.av = require('win-info').av(); meshCoreObjChanged(); } catch (e) { av = null; } // Antivirus + //if (process.platform == 'win32') { try { meshCoreObj.pr = require('win-info').pendingReboot(); meshCoreObjChanged(); } catch (e) { meshCoreObj.pr = null; } } // Pending reboot + } + if (process.platform == 'win32') + { + if (require('MeshAgent')._securitycenter == null) + { + try { - try + require('MeshAgent')._securitycenter = require('win-securitycenter').status(); + meshCoreObj['windowsSecurityCenter'] = require('MeshAgent')._securitycenter; + require('win-securitycenter').on('changed', function () { require('MeshAgent')._securitycenter = require('win-securitycenter').status(); meshCoreObj['windowsSecurityCenter'] = require('MeshAgent')._securitycenter; - require('win-securitycenter').on('changed', function () - { - require('MeshAgent')._securitycenter = require('win-securitycenter').status(); - meshCoreObj['windowsSecurityCenter'] = require('MeshAgent')._securitycenter; - require('MeshAgent').SendCommand({ action: 'coreinfo', windowsSecurityCenter: require('MeshAgent')._securitycenter }); - }); - } - catch(e) - { - } + require('MeshAgent').SendCommand({ action: 'coreinfo', windowsSecurityCenter: require('MeshAgent')._securitycenter }); + }); } - } - - // Send available data right now - if (force) - { - meshCoreObj = sortObjRec(meshCoreObj); - var x = JSON.stringify(meshCoreObj); - if (x != LastPeriodicServerUpdate) + catch (e) { - LastPeriodicServerUpdate = x; - mesh.SendCommand(meshCoreObj); } } } - // Once we are done collecting all the data, send to server if needed - var LastPeriodicServerUpdate = null; - var PeriodicServerUpdateNagleTimer = null; - function meshCoreObjChanged() { if (PeriodicServerUpdateNagleTimer == null) { PeriodicServerUpdateNagleTimer = setTimeout(meshCoreObjChangedEx, 500); } } - function meshCoreObjChangedEx() + // Send available data right now + if (force) { - PeriodicServerUpdateNagleTimer = null; meshCoreObj = sortObjRec(meshCoreObj); var x = JSON.stringify(meshCoreObj); if (x != LastPeriodicServerUpdate) { - try { LastPeriodicServerUpdate = x; mesh.SendCommand(meshCoreObj); } catch (ex) { } + LastPeriodicServerUpdate = x; + mesh.SendCommand(meshCoreObj); } } - - function sortObjRec(o) { if ((typeof o != 'object') || (Array.isArray(o))) return o; for (var i in o) { if (typeof o[i] == 'object') { o[i] = sortObjRec(o[i]); } } return sortObj(o); } - function sortObj(o) { return Object.keys(o).sort().reduce(function (result, key) { result[key] = o[key]; return result; }, {}); } - - // Starting function - obj.start = function () { - // Setup the mesh agent event handlers - mesh.AddCommandHandler(handleServerCommand); - mesh.AddConnectHandler(handleServerConnection); - } - - 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; } -// -// Startup for Duktape only. This file is not intended to run in NodeJS. -// -try { - mainMeshCore = createMeshCore(); - mainMeshCore.start(null); -} catch (e) { - require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: "uncaughtException2: " + ex }); +// Once we are done collecting all the data, send to server if needed +var LastPeriodicServerUpdate = null; +var PeriodicServerUpdateNagleTimer = null; +function meshCoreObjChanged() +{ + if (PeriodicServerUpdateNagleTimer == null) + { + PeriodicServerUpdateNagleTimer = setTimeout(meshCoreObjChangedEx, 500); + } } +function meshCoreObjChangedEx() +{ + PeriodicServerUpdateNagleTimer = null; + meshCoreObj = sortObjRec(meshCoreObj); + var x = JSON.stringify(meshCoreObj); + if (x != LastPeriodicServerUpdate) + { + try { LastPeriodicServerUpdate = x; mesh.SendCommand(meshCoreObj); } catch (ex) { } + } +} + +function sortObjRec(o) { if ((typeof o != 'object') || (Array.isArray(o))) return o; for (var i in o) { if (typeof o[i] == 'object') { o[i] = sortObjRec(o[i]); } } return sortObj(o); } +function sortObj(o) { return Object.keys(o).sort().reduce(function (result, key) { result[key] = o[key]; return result; }, {}); } + +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; +} + +mesh.AddCommandHandler(handleServerCommand); +mesh.AddConnectHandler(handleServerConnection); +