diff --git a/agents/MeshCmd-signed.exe b/agents/MeshCmd-signed.exe index ca82f87a..cc6274f8 100644 Binary files a/agents/MeshCmd-signed.exe and b/agents/MeshCmd-signed.exe differ diff --git a/agents/MeshCmd64-signed.exe b/agents/MeshCmd64-signed.exe index 84e55186..f270404b 100644 Binary files a/agents/MeshCmd64-signed.exe and b/agents/MeshCmd64-signed.exe differ diff --git a/agents/MeshService-signed.exe b/agents/MeshService-signed.exe index 3ed2476e..289bd3c5 100644 Binary files a/agents/MeshService-signed.exe and b/agents/MeshService-signed.exe differ diff --git a/agents/MeshService.exe b/agents/MeshService.exe index dd6028df..969ec431 100644 Binary files a/agents/MeshService.exe and b/agents/MeshService.exe differ diff --git a/agents/MeshService64-signed.exe b/agents/MeshService64-signed.exe index 3700801e..4b511c05 100644 Binary files a/agents/MeshService64-signed.exe and b/agents/MeshService64-signed.exe differ diff --git a/agents/MeshService64.exe b/agents/MeshService64.exe index cab74744..1f733145 100644 Binary files a/agents/MeshService64.exe and b/agents/MeshService64.exe differ diff --git a/agents/meshcore-01.js b/agents/meshcore-01.js new file mode 100644 index 00000000..7dd688db --- /dev/null +++ b/agents/meshcore-01.js @@ -0,0 +1,1892 @@ +/* +Copyright 2018 Intel Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + + +process.on('uncaughtException', function (ex) { + require('MeshAgent').SendCommand({ "action": "msg", "type": "console", "value": "uncaughtException1: " + ex }); +}); + +// NOTE: This seems to cause big problems, don't enable the debugger in the server's meshcore. +//attachDebugger({ webport: 9999, wait: 1 }).then(function (prt) { console.log('Point Browser for Debug to port: ' + prt); }); + +// Mesh Rights +const MESHRIGHT_EDITMESH = 1; +const MESHRIGHT_MANAGEUSERS = 2; +const MESHRIGHT_MANAGECOMPUTERS = 4; +const MESHRIGHT_REMOTECONTROL = 8; +const MESHRIGHT_AGENTCONSOLE = 16; +const MESHRIGHT_SERVERFILES = 32; +const MESHRIGHT_WAKEDEVICE = 64; +const MESHRIGHT_SETNOTES = 128; +const MESHRIGHT_REMOTEVIEW = 256; + +function createMeshCore(agent) { + var obj = {}; + + /* + function borderController() { + this.container = null; + this.Start = function Start(user) { + if (this.container == null) { + if (process.platform == 'win32') { + try { + this.container = require('ScriptContainer').Create({ processIsolation: 1, sessionId: user.SessionId }); + } catch (ex) { + this.container = require('ScriptContainer').Create({ processIsolation: 1 }); + } + } else { + this.container = require('ScriptContainer').Create({ processIsolation: 1, sessionId: user.uid }); + } + this.container.parent = this; + this.container.addModule('monitor-info', getJSModule('monitor-info')); + this.container.addModule('monitor-border', getJSModule('monitor-border')); + this.container.addModule('promise', getJSModule('promise')); + this.container.once('exit', function (code) { sendConsoleText('Border Process Exited with code: ' + code); this.parent.container = this.parent._container = null; }); + this.container.ExecuteString("var border = require('monitor-border'); border.Start();"); + } + } + this.Stop = function Stop() { + if (this.container != null) { + this._container = this.container; + this._container.parent = this; + this.container = null; + this._container.exit(); + } + } + } + obj.borderManager = new borderController(); + */ + + // MeshAgent JavaScript Core Module. This code is sent to and running on the mesh agent. + var meshCoreObj = { "action": "coreinfo", "value": "MeshCore v6", "caps": 14 }; // Capability bitmask: 1 = Desktop, 2 = Terminal, 4 = Files, 8 = Console, 16 = JavaScript + + // Get the operating system description string + try { require('os').name().then(function (v) { meshCoreObj.osdesc = v; }); } catch (ex) { } + + var meshServerConnectionState = 0; + var tunnels = {}; + var lastMeInfo = null; + var lastNetworkInfo = null; + var lastPublicLocationInfo = null; + var selfInfoUpdateTimer = null; + var http = require('http'); + var net = require('net'); + var fs = require('fs'); + var rtc = require('ILibWebRTC'); + var processManager = require('process-manager'); + var amtMei = null, amtLms = null, amtLmsState = 0; + var amtMeiConnected = 0, amtMeiTmpState = null; + var wifiScannerLib = null; + var wifiScanner = null; + var networkMonitor = null; + var amtscanner = null; + var nextTunnelIndex = 1; + + // If we are running in Duktape, agent will be null + if (agent == null) { + // Running in native agent, Import libraries + db = require('SimpleDataStore').Shared(); + sha = require('SHA256Stream'); + mesh = require('MeshAgent'); + childProcess = require('child_process'); + if (mesh.hasKVM == 1) { // if the agent is compiled with KVM support + // Check if this computer supports a desktop + try { if ((process.platform == 'win32') || (process.platform == 'darwin') || (require('monitor-info').kvm_x11_support)) { meshCoreObj.caps |= 1; } } catch (ex) { } + } + } else { + // Running in nodejs + meshCoreObj.value += '-NodeJS'; + meshCoreObj.caps = 8; + mesh = agent.getMeshApi(); + } + + /* + var AMTScanner = require("AMTScanner"); + var scan = new AMTScanner(); + + scan.on("found", function (data) { + if (typeof data === 'string') { + console.log(data); + } else { + console.log(JSON.stringify(data, null, " ")); + } + }); + scan.scan("10.2.55.140", 1000); + scan.scan("10.2.55.139-10.2.55.145", 1000); + scan.scan("10.2.55.128/25", 2000); + */ + + /* + // Try to load up the network monitor + try { + networkMonitor = require('NetworkMonitor'); + networkMonitor.on('change', function () { sendNetworkUpdateNagle(); }); + networkMonitor.on('add', function (addr) { sendNetworkUpdateNagle(); }); + networkMonitor.on('remove', function (addr) { sendNetworkUpdateNagle(); }); + } catch (e) { networkMonitor = null; } + */ + + // Try to load up the Intel AMT scanner + try { + var AMTScannerModule = require('amt-scanner'); + amtscanner = new AMTScannerModule(); + //amtscanner.on('found', function (data) { if (typeof data != 'string') { data = JSON.stringify(data, null, " "); } sendConsoleText(data); }); + } catch (ex) { amtscanner = null; } + + // Fetch the SMBios Tables + var SMBiosTables = null; + var SMBiosTablesRaw = null; + try { + require('smbios').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 AMT is present, try to connect MEI + if (SMBiosTables.amtInfo && (SMBiosTables.amtInfo.AMT == true)) { + // Try to load up the MEI module + try { + var amtMeiLib = require('amt-mei'); + amtMei = new amtMeiLib(); + amtMei.on('error', function (e) { amtMeiLib = null; amtMei = null; amtMeiConnected = -1; }); + amtMeiConnected = 2; + sendPeriodicServerUpdate(1); + } catch (ex) { amtMeiLib = null; amtMei = null; amtMeiConnected = -1; } + } + } + }); + } catch (ex) { sendConsoleText(ex); } + + // Try to load up the WIFI scanner + try { + var wifiScannerLib = require('wifi-scanner'); + wifiScanner = new wifiScannerLib(); + wifiScanner.on('accessPoint', function (data) { sendConsoleText(data); }); + } catch (ex) { wifiScannerLib = null; wifiScanner = null; } + + // Get our location (lat/long) using our public IP address + var getIpLocationDataExInProgress = false; + var getIpLocationDataExCounts = [0, 0]; + function getIpLocationDataEx(func) { + if (getIpLocationDataExInProgress == true) { return false; } + try { + getIpLocationDataExInProgress = true; + getIpLocationDataExCounts[0]++; + var options = http.parseUri("http://ipinfo.io/json"); + options.method = 'GET'; + http.request(options, function (resp) { + if (resp.statusCode == 200) { + var geoData = ''; + resp.data = function (geoipdata) { geoData += geoipdata; }; + resp.end = function () { + var location = null; + try { + if (typeof geoData == 'string') { + var result = JSON.parse(geoData); + if (result.ip && result.loc) { location = result; } + } + } catch (e) { } + if (func) { getIpLocationDataExCounts[1]++; func(location); } + } + } else { func(null); } + getIpLocationDataExInProgress = false; + }).end(); + return true; + } + catch (e) { return false; } + } + + // Remove all Gateway MAC addresses for interface list. This is useful because the gateway MAC is not always populated reliably. + function clearGatewayMac(str) { + if (str == null) return null; + var x = JSON.parse(str); + for (var i in x.netif) { if (x.netif[i].gatewaymac) { delete x.netif[i].gatewaymac } } + return JSON.stringify(x); + } + + function getIpLocationData(func) { + // Get the location information for the cache if possible + var publicLocationInfo = db.Get('publicLocationInfo'); + if (publicLocationInfo != null) { publicLocationInfo = JSON.parse(publicLocationInfo); } + if (publicLocationInfo == null) { + // Nothing in the cache, fetch the data + getIpLocationDataEx(function (locationData) { + if (locationData != null) { + publicLocationInfo = {}; + publicLocationInfo.netInfoStr = lastNetworkInfo; + publicLocationInfo.locationData = locationData; + var x = db.Put('publicLocationInfo', JSON.stringify(publicLocationInfo)); // Save to database + if (func) func(locationData); // Report the new location + } else { + if (func) func(null); // Report no location + } + }); + } else { + // Check the cache + if (clearGatewayMac(publicLocationInfo.netInfoStr) == clearGatewayMac(lastNetworkInfo)) { + // Cache match + if (func) func(publicLocationInfo.locationData); + } else { + // Cache mismatch + getIpLocationDataEx(function (locationData) { + if (locationData != null) { + publicLocationInfo = {}; + publicLocationInfo.netInfoStr = lastNetworkInfo; + publicLocationInfo.locationData = locationData; + var x = db.Put('publicLocationInfo', JSON.stringify(publicLocationInfo)); // Save to database + if (func) func(locationData); // Report the new location + } else { + if (func) func(publicLocationInfo.locationData); // Can't get new location, report the old location + } + }); + } + } + } + + // Polyfill String.endsWith + if (!String.prototype.endsWith) { + String.prototype.endsWith = function (searchString, position) { + var subjectString = this.toString(); + if (typeof position !== 'number' || !isFinite(position) || Math.floor(position) !== position || position > subjectString.length) { position = subjectString.length; } + position -= searchString.length; + var lastIndex = subjectString.lastIndexOf(searchString, position); + return lastIndex !== -1 && lastIndex === position; + }; + } + + // Polyfill path.join + obj.path = { + join: function () { + var x = []; + for (var i in arguments) { + var w = arguments[i]; + if (w != null) { + while (w.endsWith('/') || w.endsWith('\\')) { w = w.substring(0, w.length - 1); } + if (i != 0) { + while (w.startsWith('/') || w.startsWith('\\')) { w = w.substring(1); } + } + x.push(w); + } + } + if (x.length == 0) return '/'; + return x.join('/'); + } + }; + + // Replace a string with a number if the string is an exact number + function toNumberIfNumber(x) { if ((typeof x == 'string') && (+parseInt(x) === x)) { x = parseInt(x); } return x; } + + // Convert decimal to hex + function char2hex(i) { return (i + 0x100).toString(16).substr(-2).toUpperCase(); } + + // Convert a raw string to a hex string + function rstr2hex(input) { var r = '', i; for (i = 0; i < input.length; i++) { r += char2hex(input.charCodeAt(i)); } return r; } + + // Convert a buffer into a string + function buf2rstr(buf) { var r = ''; for (var i = 0; i < buf.length; i++) { r += String.fromCharCode(buf[i]); } return r; } + + // Convert a hex string to a raw string // TODO: Do this using Buffer(), will be MUCH faster + function hex2rstr(d) { + if (typeof d != "string" || d.length == 0) return ''; + var r = '', m = ('' + d).match(/../g), t; + while (t = m.shift()) r += String.fromCharCode('0x' + t); + return r + } + + // Convert an object to string with all functions + function objToString(x, p, pad, ret) { + if (ret == undefined) ret = ''; + if (p == undefined) p = 0; + if (x == null) { return '[null]'; } + if (p > 8) { return '[...]'; } + if (x == undefined) { return '[undefined]'; } + if (typeof x == 'string') { if (p == 0) return x; return '"' + x + '"'; } + if (typeof x == 'buffer') { return '[buffer]'; } + if (typeof x != 'object') { return x; } + var r = '{' + (ret ? '\r\n' : ' '); + for (var i in x) { if (i != '_ObjectID') { r += (addPad(p + 2, pad) + i + ': ' + objToString(x[i], p + 2, pad, ret) + (ret ? '\r\n' : ' ')); } } + return r + addPad(p, pad) + '}'; + } + + // Return p number of spaces + function addPad(p, ret) { var r = ''; for (var i = 0; i < p; i++) { r += ret; } return r; } + + // Split a string taking into account the quoats. Used for command line parsing + function splitArgs(str) { + var myArray = [], myRegexp = /[^\s"]+|"([^"]*)"/gi; + do { var match = myRegexp.exec(str); if (match != null) { myArray.push(match[1] ? match[1] : match[0]); } } while (match != null); + return myArray; + } + + // Parse arguments string array into an object + function parseArgs(argv) { + var results = { '_': [] }, current = null; + for (var i = 1, len = argv.length; i < len; i++) { + var x = argv[i]; + if (x.length > 2 && x[0] == '-' && x[1] == '-') { + if (current != null) { results[current] = true; } + current = x.substring(2); + } else { + if (current != null) { results[current] = toNumberIfNumber(x); current = null; } else { results['_'].push(toNumberIfNumber(x)); } + } + } + if (current != null) { results[current] = true; } + return results; + } + + // Get server target url with a custom path + function getServerTargetUrl(path) { + var x = mesh.ServerUrl; + //sendConsoleText("mesh.ServerUrl: " + mesh.ServerUrl); + if (x == null) { return null; } + if (path == null) { path = ''; } + x = http.parseUri(x); + if (x == null) return null; + return x.protocol + '//' + x.host + ':' + x.port + '/' + path; + } + + // Get server url. If the url starts with "*/..." change it, it not use the url as is. + function getServerTargetUrlEx(url) { + if (url.substring(0, 2) == '*/') { return getServerTargetUrl(url.substring(2)); } + return url; + } + + // Send a wake-on-lan packet + function sendWakeOnLan(hexMac) { + var count = 0; + try { + var interfaces = require('os').networkInterfaces(); + var magic = 'FFFFFFFFFFFF'; + for (var x = 1; x <= 16; ++x) { magic += hexMac; } + var magicbin = Buffer.from(magic, 'hex'); + + for (var adapter in interfaces) { + if (interfaces.hasOwnProperty(adapter)) { + for (var i = 0; i < interfaces[adapter].length; ++i) { + var addr = interfaces[adapter][i]; + if ((addr.family == 'IPv4') && (addr.mac != '00:00:00:00:00:00')) { + var socket = require('dgram').createSocket({ type: "udp4" }); + socket.bind({ address: addr.address }); + socket.setBroadcast(true); + socket.send(magicbin, 7, "255.255.255.255"); + count++; + } + } + } + } + } catch (e) { } + return count; + } + + // Handle a mesh agent command + function handleServerCommand(data) { + if (typeof data == 'object') { + // If this is a console command, parse it and call the console handler + switch (data.action) { + case 'msg': { + switch (data.type) { + case 'console': { // Process a console command + if (data.value && data.sessionid) { + var args = splitArgs(data.value); + processConsoleCommand(args[0].toLowerCase(), parseArgs(args), data.rights, data.sessionid); + } + break; + } + case 'tunnel': { + if (data.value != null) { // Process a new tunnel connection request + // Create a new tunnel object + var xurl = getServerTargetUrlEx(data.value); + if (xurl != null) { + var woptions = http.parseUri(xurl); + woptions.rejectUnauthorized = 0; + //sendConsoleText(JSON.stringify(woptions)); + var tunnel = http.request(woptions); + tunnel.upgrade = onTunnelUpgrade; + tunnel.onerror = function (e) { sendConsoleText('ERROR: ' + JSON.stringify(e)); } + tunnel.sessionid = data.sessionid; + tunnel.rights = data.rights; + tunnel.state = 0; + tunnel.url = xurl; + tunnel.protocol = 0; + tunnel.tcpaddr = data.tcpaddr; + tunnel.tcpport = data.tcpport; + 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 '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) { + try { process.kill(data.value); } catch (e) { sendConsoleText(JSON.stringify(e)); } + } + break; + } + case 'openUrl': { + // Open a local web browser and return success/fail + 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; + } + } + 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 '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); + sendConsoleText('Performing power action=' + data.actiontype + ', forced=' + forced + '.'); + var r = mesh.ExecPowerState(data.actiontype, forced); + sendConsoleText('ExecPowerState returned code: ' + r); + } + break; + } + case 'iplocation': { + // Update the IP location information of this node. Only do this when requested by the server since we have a limited amount of time we can call this per day + getIpLocationData(function (location) { mesh.SendCommand({ "action": "iplocation", "type": "publicip", "value": location }); }); + break; + } + case 'toast': { + // Display a toast message + if (data.title && data.msg) { require('toaster').Toast(data.title, data.msg); } + break; + } + case 'openUrl': { + // Open a local web browser and return success/fail + sendConsoleText('OpenURL: ' + data.url); + if (data.url) { mesh.SendCommand({ "action": "openUrl", "url": data.url, "sessionid": data.sessionid, "success": (openUserDesktopUrl(data.url) != null) }); } + break; + } + } + } + } + + // Called when a file changed in the file system + /* + function onFileWatcher(a, b) { + console.log('onFileWatcher', a, b, this.path); + var response = getDirectoryInfo(this.path); + if ((response != undefined) && (response != null)) { this.tunnel.s.write(JSON.stringify(response)); } + } + */ + + // Get a formated response for a given directory path + function getDirectoryInfo(reqpath) { + var response = { path: reqpath, dir: [] }; + if (((reqpath == undefined) || (reqpath == '')) && (process.platform == 'win32')) { + // List all the drives in the root, or the root itself + var results = null; + try { results = fs.readDrivesSync(); } catch (e) { } // TODO: Anyway to get drive total size and free space? Could draw a progress bar. + if (results != null) { + for (var i = 0; i < results.length; ++i) { + var drive = { n: results[i].name, t: 1 }; + if (results[i].type == 'REMOVABLE') { drive.dt = 'removable'; } // TODO: See if this is USB/CDROM or something else, we can draw icons. + response.dir.push(drive); + } + } + } else { + // List all the files and folders in this path + if (reqpath == '') { reqpath = '/'; } + var results = null, xpath = obj.path.join(reqpath, '*'); + //if (process.platform == "win32") { xpath = xpath.split('/').join('\\'); } + try { results = fs.readdirSync(xpath); } catch (e) { } + if (results != null) { + for (var i = 0; i < results.length; ++i) { + if ((results[i] != '.') && (results[i] != '..')) { + var stat = null, p = obj.path.join(reqpath, results[i]); + //if (process.platform == "win32") { p = p.split('/').join('\\'); } + try { stat = fs.statSync(p); } catch (e) { } // TODO: Get file size/date + if ((stat != null) && (stat != undefined)) { + if (stat.isDirectory() == true) { + response.dir.push({ n: results[i], t: 2, d: stat.mtime }); + } else { + response.dir.push({ n: results[i], t: 3, s: stat.size, d: stat.mtime }); + } + } + } + } + } + } + return response; + } + + // Tunnel callback operations + function onTunnelUpgrade(response, s, head) { + this.s = s; + s.httprequest = this; + s.end = onTunnelClosed; + s.tunnel = this; + + 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; + } else { + // This is a normal connect for KVM/Terminal/Files + s.data = onTunnelData; + } + } + + // Called when the TCP relay target is connected + function onTcpRelayTargetTunnelConnect() { + var peerTunnel = tunnels[this.peerindex]; + this.pipe(peerTunnel.s); // Pipe Target --> Server + peerTunnel.s.first = true; + peerTunnel.s.resume(); + } + + // Called when we get data from the server for a TCP relay (We have to skip the first received 'c' and pipe the rest) + function onTcpRelayServerTunnelData(data) { + if (this.first == true) { this.first = false; this.pipe(this.tcprelay); } // Pipe Server --> Target + } + + function onTunnelClosed() { + if (tunnels[this.httprequest.index] == null) return; // Stop duplicate calls. + //sendConsoleText("Tunnel #" + this.httprequest.index + " closed.", this.httprequest.sessionid); + delete tunnels[this.httprequest.index]; + + /* + // Close the watcher if required + if (this.httprequest.watcher != undefined) { + //console.log('Closing watcher: ' + this.httprequest.watcher.path); + //this.httprequest.watcher.close(); // TODO: This line causes the agent to crash!!!! + delete this.httprequest.watcher; + } + */ + + // If there is a upload or download active on this connection, close the file + if (this.httprequest.uploadFile) { fs.closeSync(this.httprequest.uploadFile); this.httprequest.uploadFile = undefined; } + if (this.httprequest.downloadFile) { fs.closeSync(this.httprequest.downloadFile); this.httprequest.downloadFile = undefined; } + + // Clean up WebRTC + if (this.webrtc != null) { + if (this.webrtc.rtcchannel) { try { this.webrtc.rtcchannel.close(); } catch (e) { } this.webrtc.rtcchannel.removeAllListeners('data'); this.webrtc.rtcchannel.removeAllListeners('end'); delete this.webrtc.rtcchannel; } + if (this.webrtc.websocket) { delete this.webrtc.websocket; } + try { this.webrtc.close(); } catch (e) { } + this.webrtc.removeAllListeners('connected'); + this.webrtc.removeAllListeners('disconnected'); + this.webrtc.removeAllListeners('dataChannel'); + delete this.webrtc; + } + + // Clean up WebSocket + this.removeAllListeners('data'); + } + function onTunnelSendOk() { /*sendConsoleText("Tunnel #" + this.index + " SendOK.", this.sessionid);*/ } + function onTunnelData(data) { + //console.log("OnTunnelData"); + //sendConsoleText('OnTunnelData, ' + data.length + ', ' + typeof data + ', ' + data); + + // If this is upload data, save it to file + if (this.httprequest.uploadFile) { + try { fs.writeSync(this.httprequest.uploadFile, data); } catch (e) { this.write(new Buffer(JSON.stringify({ action: 'uploaderror' }))); return; } // Write to the file, if there is a problem, error out. + this.write(new Buffer(JSON.stringify({ action: 'uploadack', reqid: this.httprequest.uploadFileid }))); // Ask for more data + return; + } + /* + // If this is a download, send more of the file + if (this.httprequest.downloadFile) { + var buf = new Buffer(4096); + var len = fs.readSync(this.httprequest.downloadFile, buf, 0, 4096, null); + this.httprequest.downloadFilePtr += len; + if (len > 0) { this.write(buf.slice(0, len)); } else { fs.closeSync(this.httprequest.downloadFile); this.httprequest.downloadFile = undefined; this.end(); } + return; + } + */ + + if (this.httprequest.state == 0) { + // Check if this is a relay connection + if (data == 'c') { this.httprequest.state = 1; /*sendConsoleText("Tunnel #" + this.httprequest.index + " now active", this.httprequest.sessionid);*/ } + } else { + // Handle tunnel data + if (this.httprequest.protocol == 0) { // 1 = SOL, 2 = KVM, 3 = IDER, 4 = Files, 5 = FileTransfer + // Take a look at the protocol + this.httprequest.protocol = parseInt(data); + if (typeof this.httprequest.protocol != 'number') { this.httprequest.protocol = 0; } + if (this.httprequest.protocol == 1) { + // Check user access rights + if ((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) { + // Disengage this tunnel, user does not have the rights to do this!! + this.httprequest.protocol = 999999; + sendConsoleText('Error: No Remote Control Rights.'); + return; + } + + // Remote terminal using native pipes + if (process.platform == "win32") { + this.httprequest.process = childProcess.execFile("%windir%\\system32\\cmd.exe"); + } else { + this.httprequest.process = childProcess.execFile("/bin/sh", ["sh"], { type: childProcess.SpawnTypes.TERM }); + } + + this.httprequest.process.tunnel = this; + this.httprequest.process.on('exit', function (ecode, sig) { this.tunnel.end(); }); + this.httprequest.process.stderr.on('data', function (chunk) { this.parent.tunnel.write(chunk); }); + this.httprequest.process.stdout.pipe(this, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text. + this.pipe(this.httprequest.process.stdin, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. + this.prependListener('end', function () { this.httprequest.process.kill(); }); + this.removeAllListeners('data'); + this.on('data', onTunnelControlData); + //this.write('MeshCore Terminal Hello'); + if (process.platform == 'linux') { this.httprequest.process.stdin.write("stty erase ^H\nalias ls='ls --color=auto'\nclear\n"); } + } else if (this.httprequest.protocol == 2) + { + // Check user access rights + if (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) && ((this.httprequest.rights & MESHRIGHT_REMOTEVIEW) == 0)) { + // Disengage this tunnel, user does not have the rights to do this!! + this.httprequest.protocol = 999999; + sendConsoleText('Error: No Remote Control Rights.'); + return; + } + + // Remote desktop using native pipes + this.httprequest.desktop = { state: 0, kvm: mesh.getRemoteDesktopStream(), tunnel: this }; + this.httprequest.desktop.kvm.parent = this.httprequest.desktop; + this.desktop = this.httprequest.desktop; + + // Display a toast message + //require('toaster').Toast('MeshCentral', 'Remote Desktop Control Started.'); + + this.end = function () { + --this.desktop.kvm.connectionCount; + this.unpipe(this.httprequest.desktop.kvm); + this.httprequest.desktop.kvm.unpipe(this); + if (this.desktop.kvm.connectionCount == 0) { + // Display a toast message + //require('toaster').Toast('MeshCentral', 'Remote Desktop Control Ended.'); + this.httprequest.desktop.kvm.end(); + } + }; + if (this.httprequest.desktop.kvm.hasOwnProperty("connectionCount")) { this.httprequest.desktop.kvm.connectionCount++; } else { this.httprequest.desktop.kvm.connectionCount = 1; } + + //sendConsoleText('KVM Rights: ' + this.httprequest.rights); + if ((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) != 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. + // TODO!!! + } + + this.httprequest.desktop.kvm.pipe(this, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text. Pipe the KVM --> Browser images. + this.removeAllListeners('data'); + this.on('data', onTunnelControlData); + //this.write('MeshCore KVM Hello!1'); + } else if (this.httprequest.protocol == 5) { + // Check user access rights + if ((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) { + // Disengage this tunnel, user does not have the rights to do this!! + this.httprequest.protocol = 999999; + sendConsoleText('Error: No Remote Control Rights.'); + return; + } + + // 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) + this.httprequest.process.write(data); + } else if (this.httprequest.protocol == 2) { + // Send data into remote desktop + if (this.httprequest.desktop.state == 0) { + this.write(new Buffer(String.fromCharCode(0x11, 0xFE, 0x00, 0x00, 0x4D, 0x45, 0x53, 0x48, 0x00, 0x00, 0x00, 0x00, 0x02))); + this.httprequest.desktop.state = 1; + } else { + this.httprequest.desktop.write(data); + } + } else if (this.httprequest.protocol == 5) { + // Process files commands + var cmd = null; + try { cmd = JSON.parse(data); } catch (e) { }; + if (cmd == null) { return; } + if ((cmd.ctrlChannel == '102938') || ((cmd.type == 'offer') && (cmd.sdp != null))) { onTunnelControlData(cmd, this); return; } // If this is control data, handle it now. + if (cmd.action == undefined) { return; } + //sendConsoleText('CMD: ' + JSON.stringify(cmd)); + + if ((cmd.path != null) && (process.platform != 'win32') && (cmd.path[0] != '/')) { cmd.path = '/' + cmd.path; } // Add '/' to paths on non-windows + //console.log(objToString(cmd, 0, ' ')); + switch (cmd.action) { + case 'ls': { + /* + // Close the watcher if required + var samepath = ((this.httprequest.watcher != undefined) && (cmd.path == this.httprequest.watcher.path)); + if ((this.httprequest.watcher != undefined) && (samepath == false)) { + //console.log('Closing watcher: ' + this.httprequest.watcher.path); + //this.httprequest.watcher.close(); // TODO: This line causes the agent to crash!!!! + delete this.httprequest.watcher; + } + */ + + // Send the folder content to the browser + var response = getDirectoryInfo(cmd.path); + if (cmd.reqid != undefined) { response.reqid = cmd.reqid; } + this.write(new Buffer(JSON.stringify(response))); + + /* + // Start the directory watcher + if ((cmd.path != '') && (samepath == false)) { + var watcher = fs.watch(cmd.path, onFileWatcher); + watcher.tunnel = this.httprequest; + watcher.path = cmd.path; + this.httprequest.watcher = watcher; + //console.log('Starting watcher: ' + this.httprequest.watcher.path); + } + */ + break; + } + case 'mkdir': { + // Create a new empty folder + fs.mkdirSync(cmd.path); + break; + } + case 'rm': { + // Delete, possibly recursive delete + for (var i in cmd.delfiles) { + try { deleteFolderRecursive(obj.path.join(cmd.path, cmd.delfiles[i]), cmd.rec); } catch (e) { } + } + 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); + try { fs.renameSync(oldfullpath, newfullpath); } catch (e) { console.log(e); } + break; + } + case 'download': { + // Download a file + var sendNextBlock = 0; + if (cmd.sub == 'start') { // Setup the download + if (this.filedownload != null) { this.write({ action: 'download', sub: 'cancel', id: this.filedownload.id }); delete this.filedownload; } + this.filedownload = { id: cmd.id, path: cmd.path, ptr: 0 } + try { this.filedownload.f = fs.openSync(this.filedownload.path, 'rbN'); } catch (e) { this.write({ action: 'download', sub: 'cancel', id: this.filedownload.id }); delete this.filedownload; } + if (this.filedownload) { this.write({ action: 'download', sub: 'start', id: cmd.id }); } + } else if ((this.filedownload != null) && (cmd.id == this.filedownload.id)) { // Download commands + if (cmd.sub == 'startack') { sendNextBlock = 8; } else if (cmd.sub == 'stop') { delete this.filedownload; } else if (cmd.sub == 'ack') { sendNextBlock = 1; } + } + // Send the next download block(s) + while (sendNextBlock > 0) { + sendNextBlock--; + var buf = new Buffer(4096); + var len = fs.readSync(this.filedownload.f, buf, 4, 4092, null); + this.filedownload.ptr += len; + if (len < 4092) { buf.writeInt32BE(0x01000001, 0); fs.closeSync(this.filedownload.f); delete this.filedownload; sendNextBlock = 0; } else { buf.writeInt32BE(0x01000000, 0); } + this.write(buf.slice(0, len + 4)); // Write as binary + } + break; + } + /* + case 'download': { + // Packet download of a file, agent to browser + if (cmd.path == undefined) break; + var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path; + //console.log('Download: ' + filepath); + try { this.httprequest.downloadFile = fs.openSync(filepath, 'rbN'); } catch (e) { this.write(new Buffer(JSON.stringify({ action: 'downloaderror', reqid: cmd.reqid }))); break; } + this.httprequest.downloadFileId = cmd.reqid; + this.httprequest.downloadFilePtr = 0; + if (this.httprequest.downloadFile) { this.write(new Buffer(JSON.stringify({ action: 'downloadstart', reqid: this.httprequest.downloadFileId }))); } + break; + } + case 'download2': { + // Stream download of a file, agent to browser + if (cmd.path == undefined) break; + var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path; + try { this.httprequest.downloadFile = fs.createReadStream(filepath, { flags: 'rbN' }); } catch (e) { console.log(e); } + this.httprequest.downloadFile.pipe(this); + this.httprequest.downloadFile.end = function () { } + break; + } + */ + case 'upload': { + // Upload a file, browser to agent + if (this.httprequest.uploadFile != undefined) { fs.closeSync(this.httprequest.uploadFile); this.httprequest.uploadFile = undefined; } + if (cmd.path == undefined) break; + var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path; + try { this.httprequest.uploadFile = fs.openSync(filepath, 'wbN'); } catch (e) { this.write(new Buffer(JSON.stringify({ action: 'uploaderror', reqid: cmd.reqid }))); break; } + this.httprequest.uploadFileid = cmd.reqid; + if (this.httprequest.uploadFile) { this.write(new Buffer(JSON.stringify({ action: 'uploadstart', reqid: this.httprequest.uploadFileid }))); } + break; + } + case 'copy': { + // Copy a bunch of files from scpath to dspath + for (var i in cmd.names) { + var sc = obj.path.join(cmd.scpath, cmd.names[i]), ds = obj.path.join(cmd.dspath, cmd.names[i]); + 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]); + if (sc != ds) { try { fs.copyFileSync(sc, ds); fs.unlinkSync(sc); } catch (e) { } } + } + break; + } + } + } + //sendConsoleText("Got tunnel #" + this.httprequest.index + " data: " + data, this.httprequest.sessionid); + } + } + + // 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') { + var child = require('child_process'); + child.execFile(process.env['windir'] + '\\system32\\cmd.exe', ['/c', 'RunDll32.exe user32.dll,LockWorkStation'], { type: 1 }); + } + } catch (e) { } + break; + } + } + return; + } + + if (obj.type == 'close') { + // We received the close on the websocket + //sendConsoleText('Tunnel #' + ws.tunnel.index + ' WebSocket control close'); + try { ws.close(); } catch (e) { } + } else if (obj.type == '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 + ws.httprequest.process.stdout.unpipe(ws); + ws.httprequest.process.stderr.unpipe(ws); + } else if (ws.httprequest.protocol == 2) { // Desktop + // This is a KVM data stream, unpipe the KVM now and indicate to the other side that KVM data will no longer be received over WebSocket + ws.httprequest.desktop.kvm.unpipe(ws); + } else { + // Switch things around so all WebRTC data goes to onTunnelData(). + ws.rtcchannel.httprequest = ws.httprequest; + ws.rtcchannel.removeAllListeners('data'); + ws.rtcchannel.on('data', onTunnelData); + } + ws.write("{\"ctrlChannel\":\"102938\",\"type\":\"webrtc1\"}"); // End of data marker + } else if (obj.type == 'webrtc1') { + if (ws.httprequest.protocol == 1) { // Terminal + // Switch the user input from websocket to webrtc at this point. + 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. + } else if (obj.type == 'webrtc2') { + // Other side received websocket end of data marker, start sending data on WebRTC channel + if (ws.httprequest.protocol == 1) { // Terminal + 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. + } + } else if (obj.type == 'offer') { + // This is a WebRTC offer. + 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.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 () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC data channel closed');*/ }); + this.websocket.write("{\"ctrlChannel\":\"102938\",\"type\":\"webrtc0\"}"); // Indicate we are ready for WebRTC switch-over. + }); + var sdp = null; + try { sdp = ws.webrtc.setOffer(obj.sdp); } catch (ex) { } + if (sdp != null) { ws.write({ type: 'answer', ctrlChannel: '102938', sdp: sdp }); } + } + } + + // Console state + var consoleWebSockets = {}; + var consoleHttpRequest = null; + + // Console HTTP response + function consoleHttpResponse(response) { + response.data = function (data) { sendConsoleText(rstr2hex(buf2rstr(data)), this.sessionid); consoleHttpRequest = null; } + response.close = function () { sendConsoleText('httprequest.response.close', this.sessionid); consoleHttpRequest = null; } + }; + + // Open a web browser to a specified URL on current user's desktop + function openUserDesktopUrl(url) { + var child = null; + try { + switch (process.platform) { + case 'win32': + child = require('child_process').execFile(process.env['windir'] + '\\system32\\cmd.exe', ["/c", "start", url], { type: childProcess.SpawnTypes.USER }); + break; + case 'linux': + child = require('child_process').execFile('/usr/bin/xdg-open', ['xdg-open', url], { type: require('child_process').SpawnTypes.DETACHED, uid: require('user-sessions').consoleUid() }); + break; + case 'darwin': + child = require('child_process').execFile('/usr/bin/open', ['open', url], { uid: require('user-sessions').consoleUid() }); + break; + } + } catch (ex) { } + return child; + } + + // Process a mesh agent console command + function processConsoleCommand(cmd, args, rights, sessionid) { + try { + var response = null; + switch (cmd) { + case 'help': { // Displays available commands + response = 'Available commands: help, info, osinfo,args, print, type, dbget, dbset, dbcompact, eval, parseuri, httpget,\r\nwslist, wsconnect, wssend, wsclose, notify, ls, ps, kill, amt, netinfo, location, power, wakeonlan, scanwifi,\r\nscanamt, setdebug, smbios, rawsmbios, toast, lock, users, sendcaps, openurl.'; + break; + } + /* + case 'border': + { + if ((args['_'].length == 1) && (args['_'][0] == 'on')) { + if (meshCoreObj.users.length > 0) { + obj.borderManager.Start(meshCoreObj.users[0]); + response = 'Border blinking is on.'; + } else { + response = 'Cannot turn on border blinking, no logged in users.'; + } + } else if ((args['_'].length == 1) && (args['_'][0] == 'off')) { + obj.borderManager.Stop(); + response = 'Border blinking is off.'; + } else { + response = 'Proper usage: border "on|off"'; // Display correct command usage + } + } + break; + */ + case '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(', ') + '.'; } + break; + } + case 'toast': { + if (process.platform == 'win32') { + if (args['_'].length < 1) { response = 'Proper usage: toast "message"'; } else { + require('toaster').Toast('MeshCentral', args['_'][0]); + response = 'ok'; + } + } else { + response = 'Only supported on Windows.'; + } + 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].cmd + ((plist[i].user) ? (', ' + plist[i].user):'') + '\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])); + } + 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 '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 (amtLmsState >= 0) { response += '\r\nBuilt-in LMS: ' + ['Disabled', 'Connecting..', 'Connected'][amtLmsState] + '.'; } + if (meshCoreObj.osdesc) { response += '\r\nOS: ' + meshCoreObj.osdesc + '.'; } + response += '\r\nModules: ' + addedModules.join(', ') + '.'; + response += '\r\nServer Connection: ' + mesh.isControlChannelConnected + ', State: ' + meshServerConnectionState + '.'; + response += '\r\lastMeInfo: ' + lastMeInfo + '.'; + var oldNodeId = db.Get('OldNodeId'); + if (oldNodeId != null) { response += '\r\nOldNodeID: ' + oldNodeId + '.'; } + if (process.platform != 'win32') { response += '\r\nX11 support: ' + require('monitor-info').kvm_x11_support + '.'; } + break; + } + case 'osinfo': { // Return the operating system information + var i = 1; + if (args['_'].length > 0) { i = parseInt(args['_'][0]); if (i > 8) { i = 8; } response = 'Calling ' + i + ' times.'; } + for (var j = 0; j < i; j++) { + var pr = require('os').name(); + pr.sessionid = sessionid; + pr.then(function (v) { sendConsoleText("OS: " + v, this.sessionid); }); + } + break; + } + case 'sendcaps': { // Send capability flags to the server + if (args['_'].length == 0) { + response = 'Proper usage: sendcaps (number)'; // Display correct command usage + } else { + meshCoreObj.caps = parseInt(args['_'][0]); + mesh.SendCommand(meshCoreObj); + response = JSON.stringify(meshCoreObj); + } + break; + } + case 'sendosdesc': { // Send OS description + if (args['_'].length > 0) { + meshCoreObj.osdesc = args['_'][0]; + mesh.SendCommand(meshCoreObj); + response = JSON.stringify(meshCoreObj); + } else { + response = 'Proper usage: sendosdesc [os description]'; // Display correct command usage + } + break; + } + case 'args': { // Displays parsed command arguments + response = 'args ' + objToString(args, 0, ' ', true); + break; + } + case 'print': { // Print a message on the mesh agent console, does nothing when running in the background + var r = []; + for (var i in args['_']) { r.push(args['_'][i]); } + console.log(r.join(' ')); + response = 'Message printed on agent console.'; + break; + } + case 'type': { // Returns the content of a file + if (args['_'].length == 0) { + response = 'Proper usage: type (filepath) [maxlength]'; // Display correct command usage + } else { + var max = 4096; + if ((args['_'].length > 1) && (typeof args['_'][1] == 'number')) { max = args['_'][1]; } + if (max > 4096) max = 4096; + var buf = new Buffer(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]); + options.rejectUnauthorized = 0; + httprequest = http.request(options); + } catch (e) { response = 'Invalid HTTP websocket request'; } + if (httprequest != null) { + httprequest.upgrade = onWebSocketUpgrade; + httprequest.onerror = function (e) { sendConsoleText('ERROR: ' + 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 + getAmtInfo(function (state) { + var resp = 'Intel AMT not detected.'; + if (state != null) { resp = objToString(state, 0, ' ', true); } + sendConsoleText(resp, sessionid); + }); + break; + } + case 'netinfo': { // Show network interface information + //response = objToString(mesh.NetInfo, 0, ' '); + var interfaces = require('os').networkInterfaces(); + response = objToString(interfaces, 0, ' ', true); + break; + } + case 'netinfo2': { // Show network interface information + response = objToString(mesh.NetInfo, 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) || (typeof args['_'][0] != 'number')) { + 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(args['_'][0], args['_'][1]); + response = 'Power action executed with return code: ' + r + '.'; + } + } + break; + } + case 'location': { + getIpLocationData(function (location) { + sendConsoleText(objToString({ "action": "iplocation", "type": "publicip", "value": location }, 0, ' ')); + }); + break; + } + case 'parseuri': { + response = JSON.stringify(http.parseUri(args['_'][0])); + break; + } + case 'scanwifi': { + if (wifiScanner != null) { + var wifiPresent = wifiScanner.hasWireless; + if (wifiPresent) { response = "Perfoming Wifi scan..."; wifiScanner.Scan(); } else { response = "Wifi absent."; } + } else { response = "Wifi module not present."; } + break; + } + case 'scanamt': { + if (amtscanner != null) { + if (args['_'].length != 1) { + response = 'Usage examples:\r\n scanamt 1.2.3.4\r\n scanamt 1.2.3.0-1.2.3.255\r\n scanamt 1.2.3.0/24\r\n'; // Display correct command usage + } else { + response = 'Scanning: ' + args['_'][0] + '...'; + amtscanner.scan(args['_'][0], 2000, function (data) { + if (data.length > 0) { + var r = '', pstates = ['NotActivated', 'InActivation', 'Activated']; + for (var i in data) { + var x = data[i]; + if (r != '') { r += '\r\n'; } + r += x.address + ' - Intel AMT v' + x.majorVersion + '.' + x.minorVersion; + if (x.provisioningState < 3) { r += (', ' + pstates[x.provisioningState]); } + if (x.provisioningState == 2) { r += (', ' + x.openPorts.join(', ')); } + r += '.'; + } + } else { + r = 'No Intel AMT found.'; + } + sendConsoleText(r); + }); + } + } else { response = "Intel AMT scanner module not present."; } + break; + } + case 'modules': { + response = JSON.stringify(addedModules); + break; + } + default: { // This is an unknown command, return an error message + response = 'Unknown command \"' + cmd + '\", type \"help\" for list of avaialble commands.'; + break; + } + } + } catch (e) { response = 'Command returned an exception error: ' + e; console.log(e); } + if (response != null) { sendConsoleText(response, sessionid); } + } + + // Send a mesh agent console command + function sendConsoleText(text, sessionid) { + if (typeof text == 'object') { text = JSON.stringify(text); } + mesh.SendCommand({ "action": "msg", "type": "console", "value": text, "sessionid": sessionid }); + } + + // Called before the process exits + //process.exit = function (code) { console.log("Exit with code: " + code.toString()); } + + // Called when the server connection state changes + function handleServerConnection(state) { + meshServerConnectionState = state; + if (meshServerConnectionState == 0) { + // Server disconnected + if (selfInfoUpdateTimer != null) { clearInterval(selfInfoUpdateTimer); selfInfoUpdateTimer = null; } + lastSelfInfo = null; + } else { + // Server connected, send mesh core information + var oldNodeId = db.Get('OldNodeId'); + if (oldNodeId != null) { mesh.SendCommand({ action: 'mc1migration', oldnodeid: oldNodeId }); } + + // Update the server with basic info, logged in users and more. + mesh.SendCommand(meshCoreObj); + + // Send SMBios tables if present + if (SMBiosTablesRaw != null) { mesh.SendCommand({ "action": "smbios", "value": SMBiosTablesRaw }); } + + // Update the server on more advanced stuff, like Intel ME and Network Settings + meInfoStr = null; + sendPeriodicServerUpdate(); + //if (selfInfoUpdateTimer == null) { selfInfoUpdateTimer = setInterval(sendPeriodicServerUpdate, 1200000); } // 20 minutes + } + } + + // Update the server with the latest network interface information + var sendNetworkUpdateNagleTimer = null; + function sendNetworkUpdateNagle() { if (sendNetworkUpdateNagleTimer != null) { clearTimeout(sendNetworkUpdateNagleTimer); sendNetworkUpdateNagleTimer = null; } sendNetworkUpdateNagleTimer = setTimeout(sendNetworkUpdate, 5000); } + function sendNetworkUpdate(force) { + sendNetworkUpdateNagleTimer = null; + + // Update the network interfaces information data + var netInfo = mesh.NetInfo; + netInfo.action = 'netinfo'; + var netInfoStr = JSON.stringify(netInfo); + if ((force == true) || (clearGatewayMac(netInfoStr) != clearGatewayMac(lastNetworkInfo))) { mesh.SendCommand(netInfo); lastNetworkInfo = netInfoStr; } + } + + // Called periodically to check if we need to send updates to the server + function sendPeriodicServerUpdate(flags) { + if (meshServerConnectionState == 0) return; // Not connected to server, do nothing. + if (!flags) { flags = 0xFFFFFFFF; } + + if (flags & 1) { + // If we have a connected MEI, get Intel ME information + getAmtInfo(function (meinfo) { + try { + if (meinfo == null) return; + var intelamt = {}, p = false; + if (meinfo.Versions && meinfo.Versions.AMT) { intelamt.ver = meinfo.Versions.AMT; p = true; } + if (meinfo.ProvisioningState) { intelamt.state = meinfo.ProvisioningState; p = true; } + if (meinfo.Flags) { intelamt.flags = meinfo.Flags; p = true; } + if (meinfo.OsHostname) { intelamt.host = meinfo.OsHostname; p = true; } + if (meinfo.UUID) { intelamt.uuid = meinfo.UUID; p = true; } + if (p == true) { + var meInfoStr = JSON.stringify(intelamt); + if (meInfoStr != lastMeInfo) { + meshCoreObj.intelamt = intelamt; + mesh.SendCommand(meshCoreObj); + lastMeInfo = meInfoStr; + } + } + } catch (ex) { } + }); + } + + if (flags & 2) { + // Update network information + sendNetworkUpdateNagle(false); + } + } + + // Get Intel AMT information using MEI + function getAmtInfo(func) { + if (amtMei == null || amtMeiConnected != 2) { if (func != null) { func(null); } return; } + try { + amtMeiTmpState = { Flags: 0 }; // Flags: 1=EHBC, 2=CCM, 4=ACM + amtMei.getProtocolVersion(function (result) { if (result != null) { amtMeiTmpState.MeiVersion = result; } }); + amtMei.getVersion(function (result) { if (result) { amtMeiTmpState.Versions = {}; for (var version in result.Versions) { amtMeiTmpState.Versions[result.Versions[version].Description] = result.Versions[version].Version; } } }); + amtMei.getProvisioningMode(function (result) { if (result) { amtMeiTmpState.ProvisioningMode = result.mode; } }); + amtMei.getProvisioningState(function (result) { if (result) { amtMeiTmpState.ProvisioningState = result.state; } }); + amtMei.getEHBCState(function (result) { if ((result != null) && (result.EHBC == true)) { amtMeiTmpState.Flags += 1; } }); + amtMei.getControlMode(function (result) { if (result != null) { if (result.controlMode == 1) { amtMeiTmpState.Flags += 2; } if (result.controlMode == 2) { amtMeiTmpState.Flags += 4; } } }); + amtMei.getUuid(function (result) { if ((result != null) && (result.uuid != null)) { amtMeiTmpState.UUID = result.uuid; } }); + //amtMei.getMACAddresses(function (result) { amtMeiTmpState.mac = result; }); + amtMei.getDnsSuffix(function (result) { if (result != null) { amtMeiTmpState.dns = result; } if (func != null) { func(amtMeiTmpState); } }); + } catch (e) { if (func != null) { func(null); } return; } + } + + // Called on MicroLMS Intel AMT user notification + function handleAmtNotification(notifyMsg) { + if ((notifyMsg == null) || (notifyMsg.Body == null) || (notifyMsg.Body.MessageID == null) || (notifyMsg.Body.MessageArguments == null)) return null; + var amtMessage = notifyMsg.Body.MessageID, amtMessageArg = notifyMsg.Body.MessageArguments[0], notify = null; + + switch (amtMessage) { + case 'iAMT0050': { if (amtMessageArg == '48') { notify = 'Intel® AMT Serial-over-LAN connected'; } else if (amtMessageArg == '49') { notify = 'Intel® AMT Serial-over-LAN disconnected'; } break; } // SOL + case 'iAMT0052': { if (amtMessageArg == '1') { notify = 'Intel® AMT KVM connected'; } else if (amtMessageArg == '2') { notify = 'Intel® AMT KVM disconnected'; } break; } // KVM + } + + // Send to the entire mesh, no sessionid or userid specified. + if (notify != null) { mesh.SendCommand({ "action": "msg", "type": "notify", "value": notify, "tag": "general" }); } + } + + // Starting function + obj.start = function () { + // Setup the mesh agent event handlers + mesh.AddCommandHandler(handleServerCommand); + mesh.AddConnectHandler(handleServerConnection); + + // Parse input arguments + //var args = parseArgs(process.argv); + //console.log(args); + + // Launch LMS + try { + var lme_heci = require('amt-lme'); + amtLmsState = 1; + amtLms = new lme_heci(); + amtLms.on('error', function (e) { amtLmsState = 0; amtLms = null; obj.setupMeiOsAdmin(null, 1); }); + amtLms.on('connect', function () { amtLmsState = 2; obj.setupMeiOsAdmin(null, 2); }); + //amtLms.on('bind', function (map) { }); + amtLms.on('notify', function (data, options, str, code) { + if (code == 'iAMT0052-3') { + obj.kvmGetData(); + } else { + //if (str != null) { sendConsoleText('Intel AMT LMS: ' + str); } + handleAmtNotification(data); + } + }); + } catch (e) { amtLmsState = -1; amtLms = null; } + + // Setup logged in user monitoring + try { + var userSession = require('user-sessions'); + userSession.on('changed', function onUserSessionChanged() { + userSession.enumerateUsers().then(function (users) { + var u = [], a = users.Active; + for (var i = 0; i < a.length; i++) { + var un = a[i].Domain ? (a[i].Domain + '\\' + a[i].Username) : (a[i].Username); + if (u.indexOf(un) == -1) { u.push(un); } // Only push users in the list once. + } + meshCoreObj.users = u; + mesh.SendCommand(meshCoreObj); + }); + }); + userSession.emit('changed'); + //userSession.on('locked', function (user) { sendConsoleText('[' + (user.Domain ? user.Domain + '\\' : '') + user.Username + '] has LOCKED the desktop'); }); + //userSession.on('unlocked', function (user) { sendConsoleText('[' + (user.Domain ? user.Domain + '\\' : '') + user.Username + '] has UNLOCKED the desktop'); }); + } catch (ex) { } + } + + obj.stop = function () { + mesh.AddCommandHandler(null); + mesh.AddConnectHandler(null); + } + + function onWebSocketClosed() { sendConsoleText("WebSocket #" + this.httprequest.index + " closed.", this.httprequest.sessionid); delete consoleWebSockets[this.httprequest.index]; } + function onWebSocketData(data) { sendConsoleText("Got WebSocket #" + this.httprequest.index + " data: " + data, this.httprequest.sessionid); } + function onWebSocketSendOk() { sendConsoleText("WebSocket #" + this.index + " SendOK.", this.sessionid); } + + function onWebSocketUpgrade(response, s, head) { + sendConsoleText("WebSocket #" + this.index + " connected.", this.sessionid); + this.s = s; + s.httprequest = this; + s.end = onWebSocketClosed; + s.data = onWebSocketData; + } + + + // + // KVM Data Channel + // + + obj.setupMeiOsAdmin = function (func, state) { + if ((amtMei == null) || (amtMeiConnected != 2)) { return; } // If there is no MEI, don't bother with this. + amtMei.getLocalSystemAccount(function (x) { + if (x == null) return; + var transport = require('amt-wsman-duk'); + var wsman = require('amt-wsman'); + var amt = require('amt'); + oswsstack = new wsman(transport, '127.0.0.1', 16992, x.user, x.pass, false); + obj.osamtstack = new amt(oswsstack); + if (func) { func(state); } + //var AllWsman = "CIM_SoftwareIdentity,IPS_SecIOService,IPS_ScreenSettingData,IPS_ProvisioningRecordLog,IPS_HostBasedSetupService,IPS_HostIPSettings,IPS_IPv6PortSettings".split(','); + //obj.osamtstack.BatchEnum(null, AllWsman, startLmsWsmanResponse, null, true); + //************************************* + // Setup KVM data channel if this is Intel AMT 12 or above + amtMei.getVersion(function (x) { + if (x == null) return; + var amtver = null; + try { for (var i in x.Versions) { if (x.Versions[i].Description == 'AMT') amtver = parseInt(x.Versions[i].Version.split('.')[0]); } } catch (e) { } + if ((amtver != null) && (amtver >= 12)) { + obj.kvmGetData('skip'); // Clear any previous data, this is a dummy read to about handling old data. + obj.kvmTempTimer = setInterval(function () { obj.kvmGetData(); }, 2000); // Start polling for KVM data. + obj.kvmSetData(JSON.stringify({ action: 'restart', ver: 1 })); // Send a restart command to advise the console if present that MicroLMS just started. + } + }); + }); + } + + obj.kvmGetData = function (tag) { + obj.osamtstack.IPS_KVMRedirectionSettingData_DataChannelRead(obj.kvmDataGetResponse, tag); + } + + obj.kvmDataGetResponse = function (stack, name, response, status, tag) { + if ((tag != 'skip') && (status == 200) && (response.Body.ReturnValue == 0)) { + var val = null; + try { val = Buffer.from(response.Body.DataMessage, 'base64').toString(); } catch (e) { return } + if (val != null) { obj.kvmProcessData(response.Body.RealmsBitmap, response.Body.MessageId, val); } + } + } + + var webRtcDesktop = null; + obj.kvmProcessData = function (realms, messageId, val) { + var data = null; + try { data = JSON.parse(val) } catch (e) { } + if ((data != null) && (data.action)) { + if (data.action == 'present') { obj.kvmSetData(JSON.stringify({ action: 'present', ver: 1, platform: process.platform })); } + if (data.action == 'offer') { + webRtcDesktop = {}; + var rtc = require('ILibWebRTC'); + webRtcDesktop.webrtc = rtc.createConnection(); + webRtcDesktop.webrtc.on('connected', function () { }); + webRtcDesktop.webrtc.on('disconnected', function () { webRtcCleanUp(); }); + webRtcDesktop.webrtc.on('dataChannel', function (rtcchannel) { + webRtcDesktop.rtcchannel = rtcchannel; + webRtcDesktop.kvm = mesh.getRemoteDesktopStream(); + webRtcDesktop.kvm.pipe(webRtcDesktop.rtcchannel, { dataTypeSkip: 1, end: false }); + webRtcDesktop.rtcchannel.on('end', function () { obj.webRtcCleanUp(); }); + webRtcDesktop.rtcchannel.on('data', function (x) { obj.kvmCtrlData(this, x); }); + webRtcDesktop.rtcchannel.pipe(webRtcDesktop.kvm, { dataTypeSkip: 1, end: false }); + //webRtcDesktop.kvm.on('end', function () { console.log('WebRTC DataChannel closed2'); webRtcCleanUp(); }); + //webRtcDesktop.rtcchannel.on('data', function (data) { console.log('WebRTC data: ' + data); }); + }); + obj.kvmSetData(JSON.stringify({ action: 'answer', ver: 1, sdp: webRtcDesktop.webrtc.setOffer(data.sdp) })); + } + } + } + + // Polyfill path.join + var path = { + join: function () { + var x = []; + for (var i in arguments) { + var w = arguments[i]; + if (w != null) { + while (w.endsWith('/') || w.endsWith('\\')) { w = w.substring(0, w.length - 1); } + if (i != 0) { while (w.startsWith('/') || w.startsWith('\\')) { w = w.substring(1); } } + x.push(w); + } + } + if (x.length == 0) return '/'; + return x.join('/'); + } + }; + + // Process KVM control channel data + obj.kvmCtrlData = function(channel, cmd) { + if (cmd.length > 0 && cmd.charCodeAt(0) != 123) { + // This is upload data + if (this.fileupload != null) { + cmd = Buffer.from(cmd, 'base64'); + var header = cmd.readUInt32BE(0); + if ((header == 0x01000000) || (header == 0x01000001)) { + fs.writeSync(this.fileupload.fp, cmd.slice(4)); + channel.write({ action: 'upload', sub: 'ack', reqid: this.fileupload.reqid }); + if (header == 0x01000001) { fs.closeSync(this.fileupload.fp); this.fileupload = null; } // Close the file + } + } + return; + } + //console.log('KVM Ctrl Data', cmd); + //sendConsoleText('KVM Ctrl Data: ' + cmd); + + try { cmd = JSON.parse(cmd); } catch (ex) { console.error('Invalid JSON: ' + cmd); return; } + if ((cmd.path != null) && (process.platform != 'win32') && (cmd.path[0] != '/')) { cmd.path = '/' + cmd.path; } // Add '/' to paths on non-windows + switch (cmd.action) { + case 'ping': { + // This is a keep alive + channel.write({ action: 'pong' }); + 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 }); } + break; + } + 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; } + channel.write(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); + break; + } + case 'rm': { + // Remove many files or folders + for (var i in cmd.delfiles) { + var fullpath = path.join(cmd.path, cmd.delfiles[i]); + try { fs.unlinkSync(fullpath); } catch (e) { console.log(e); } + } + break; + } + case 'rename': { + // Rename a file or folder + try { fs.renameSync(path.join(cmd.path, cmd.oldname), path.join(cmd.path, cmd.newname)); } catch (e) { console.log(e); } + break; + } + case 'download': { + // Download a file, to browser + var sendNextBlock = 0; + if (cmd.sub == 'start') { // Setup the download + if (this.filedownload != null) { channel.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) { channel.write({ action: 'download', sub: 'cancel', id: this.filedownload.id }); delete this.filedownload; } + if (this.filedownload) { channel.write({ action: 'download', sub: 'start', id: cmd.id }); } + } else if ((this.filedownload != null) && (cmd.id == this.filedownload.id)) { // Download commands + if (cmd.sub == 'startack') { sendNextBlock = 8; } else if (cmd.sub == 'stop') { delete this.filedownload; } else if (cmd.sub == 'ack') { sendNextBlock = 1; } + } + // Send the next download block(s) + while (sendNextBlock > 0) { + sendNextBlock--; + var buf = new Buffer(4096); + var len = fs.readSync(this.filedownload.f, buf, 4, 4092, null); + this.filedownload.ptr += len; + if (len < 4092) { buf.writeInt32BE(0x01000001, 0); fs.closeSync(this.filedownload.f); delete this.filedownload; sendNextBlock = 0; } else { buf.writeInt32BE(0x01000000, 0); } + channel.write(buf.slice(0, len + 4).toString('base64')); // Write as Base64 + } + break; + } + case 'upload': { + // Upload a file, from browser + if (cmd.sub == 'start') { // Start the upload + if (this.fileupload != null) { fs.closeSync(this.fileupload.fp); } + if (!cmd.path || !cmd.name) break; + this.fileupload = { reqid: cmd.reqid }; + var filepath = path.join(cmd.path, cmd.name); + try { this.fileupload.fp = fs.openSync(filepath, 'wbN'); } catch (e) { } + if (this.fileupload.fp) { channel.write({ action: 'upload', sub: 'start', reqid: this.fileupload.reqid }); } else { this.fileupload = null; channel.write({ action: 'upload', sub: 'error', reqid: this.fileupload.reqid }); } + } + else if (cmd.sub == 'cancel') { // Stop the upload + if (this.fileupload != null) { fs.closeSync(this.fileupload.fp); this.fileupload = null; } + } + break; + } + case 'copy': { + // Copy a bunch of files from scpath to dspath + for (var i in cmd.names) { + var sc = path.join(cmd.scpath, cmd.names[i]), ds = path.join(cmd.dspath, cmd.names[i]); + 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 = path.join(cmd.scpath, cmd.names[i]), ds = path.join(cmd.dspath, cmd.names[i]); + if (sc != ds) { try { fs.copyFileSync(sc, ds); fs.unlinkSync(sc); } catch (e) { } } + } + break; + } + } + } + + obj.webRtcCleanUp = function() { + if (webRtcDesktop == null) return; + if (webRtcDesktop.rtcchannel) { + try { webRtcDesktop.rtcchannel.close(); } catch (e) { } + try { webRtcDesktop.rtcchannel.removeAllListeners('data'); } catch (e) { } + try { webRtcDesktop.rtcchannel.removeAllListeners('end'); } catch (e) { } + delete webRtcDesktop.rtcchannel; + } + if (webRtcDesktop.webrtc) { + try { webRtcDesktop.webrtc.close(); } catch (e) { } + try { webRtcDesktop.webrtc.removeAllListeners('connected'); } catch (e) { } + try { webRtcDesktop.webrtc.removeAllListeners('disconnected'); } catch (e) { } + try { webRtcDesktop.webrtc.removeAllListeners('dataChannel'); } catch (e) { } + delete webRtcDesktop.webrtc; + } + if (webRtcDesktop.kvm) { + try { webRtcDesktop.kvm.end(); } catch (e) { } + delete webRtcDesktop.kvm; + } + webRtcDesktop = null; + } + + obj.kvmSetData = function(x) { + obj.osamtstack.IPS_KVMRedirectionSettingData_DataChannelWrite(Buffer.from(x).toString('base64'), function () { }); + } + + // Delete a directory with a files and directories within it + function deleteFolderRecursive(path, rec) { + 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 + deleteFolderRecursive(curPath, true); + } else { // delete file + fs.unlinkSync(curPath); + } + }); + } + fs.unlinkSync(path); + } + }; + + return obj; +} + +// +// Module startup +// + +try { + var xexports = null, mainMeshCore = null; + try { xexports = module.exports; } catch (e) { } + + if (xexports != null) { + // If we are running within NodeJS, export the core + module.exports.createMeshCore = createMeshCore; + } else { + // If we are not running in NodeJS, launch the core + mainMeshCore = createMeshCore(); + mainMeshCore.start(null); + } +} catch (ex) { + require('MeshAgent').SendCommand({ "action": "msg", "type": "console", "value": "uncaughtException2: " + ex }); +} \ No newline at end of file diff --git a/agents/meshcore.js b/agents/meshcore.js index 7dd688db..e1e95363 100644 --- a/agents/meshcore.js +++ b/agents/meshcore.js @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ - process.on('uncaughtException', function (ex) { require('MeshAgent').SendCommand({ "action": "msg", "type": "console", "value": "uncaughtException1: " + ex }); }); @@ -652,18 +651,24 @@ function createMeshCore(agent) { } // Remote terminal using native pipes - if (process.platform == "win32") { - this.httprequest.process = childProcess.execFile("%windir%\\system32\\cmd.exe"); - } else { + if (process.platform == "win32") + { + this.httprequest._term = require('win-terminal').Start(80, 25); + this.httprequest._term.pipe(this, { dataTypeSkip: 1 }); + this.pipe(this.httprequest._term, { dataTypeSkip: 1, end: false }); + this.prependListener('end', function () { this.httprequest._term.end(function () { console.log('Terminal was closed');}); }); + //this.httprequest.process = childProcess.execFile("%windir%\\system32\\cmd.exe"); + } else + { this.httprequest.process = childProcess.execFile("/bin/sh", ["sh"], { type: childProcess.SpawnTypes.TERM }); + this.httprequest.process.tunnel = this; + this.httprequest.process.on('exit', function (ecode, sig) { this.tunnel.end(); }); + this.httprequest.process.stderr.on('data', function (chunk) { this.parent.tunnel.write(chunk); }); + this.httprequest.process.stdout.pipe(this, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text. + this.pipe(this.httprequest.process.stdin, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. + this.prependListener('end', function () { this.httprequest.process.kill(); }); } - this.httprequest.process.tunnel = this; - this.httprequest.process.on('exit', function (ecode, sig) { this.tunnel.end(); }); - this.httprequest.process.stderr.on('data', function (chunk) { this.parent.tunnel.write(chunk); }); - this.httprequest.process.stdout.pipe(this, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text. - this.pipe(this.httprequest.process.stdin, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. - this.prependListener('end', function () { this.httprequest.process.kill(); }); this.removeAllListeners('data'); this.on('data', onTunnelControlData); //this.write('MeshCore Terminal Hello'); @@ -914,8 +919,15 @@ function createMeshCore(agent) { } else if (obj.type == '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 - ws.httprequest.process.stdout.unpipe(ws); - ws.httprequest.process.stderr.unpipe(ws); + if (process.platform == 'win32') + { + ws.httprequest._term.unpipe(ws); + } + else + { + ws.httprequest.process.stdout.unpipe(ws); + ws.httprequest.process.stderr.unpipe(ws); + } } else if (ws.httprequest.protocol == 2) { // Desktop // This is a KVM data stream, unpipe the KVM now and indicate to the other side that KVM data will no longer be received over WebSocket ws.httprequest.desktop.kvm.unpipe(ws); @@ -929,8 +941,16 @@ function createMeshCore(agent) { } else if (obj.type == 'webrtc1') { if (ws.httprequest.protocol == 1) { // Terminal // Switch the user input from websocket to webrtc at this point. - ws.unpipe(ws.httprequest.process.stdin); - ws.rtcchannel.pipe(ws.httprequest.process.stdin, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text. + 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. @@ -942,8 +962,15 @@ function createMeshCore(agent) { } else if (obj.type == 'webrtc2') { // Other side received websocket end of data marker, start sending data on WebRTC channel if (ws.httprequest.protocol == 1) { // Terminal - 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. + 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. } diff --git a/agents/modules_meshcore/monitor-border.js b/agents/modules_meshcore/monitor-border.js index f6173eca..01b9a0d5 100644 --- a/agents/modules_meshcore/monitor-border.js +++ b/agents/modules_meshcore/monitor-border.js @@ -1,3 +1,19 @@ +/* +Copyright 2018 Intel Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + var red = 0xFF; var yellow = 0xFFFF; var GXxor = 0x6; // src XOR dst diff --git a/agents/modules_meshcore/monitor-info.js b/agents/modules_meshcore/monitor-info.js index b89509a8..01929c2b 100644 --- a/agents/modules_meshcore/monitor-info.js +++ b/agents/modules_meshcore/monitor-info.js @@ -1,3 +1,18 @@ +/* +Copyright 2018 Intel Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ var promise = require('promise'); var PPosition = 4; diff --git a/agents/modules_meshcore/promise.js b/agents/modules_meshcore/promise.js deleted file mode 100644 index 227b23ac..00000000 --- a/agents/modules_meshcore/promise.js +++ /dev/null @@ -1,207 +0,0 @@ -/* -Copyright 2018 Intel Corporation - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -var refTable = {}; - -function event_switcher_helper(desired_callee, target) -{ - this._ObjectID = 'event_switcher'; - this.func = function func() - { - var args = []; - for(var i in arguments) - { - args.push(arguments[i]); - } - return (func.target.apply(func.desired, args)); - }; - this.func.desired = desired_callee; - this.func.target = target; - this.func.self = this; -} -function event_switcher(desired_callee, target) -{ - return (new event_switcher_helper(desired_callee, target)); -} - -function Promise(promiseFunc) -{ - this._ObjectID = 'promise'; - this.promise = this; - this._internal = { _ObjectID: 'promise.internal', promise: this, func: promiseFunc, completed: false, errors: false, completedArgs: [] }; - require('events').EventEmitter.call(this._internal); - this._internal.on('_eventHook', function (eventName, eventCallback) - { - //console.log('hook', eventName, 'errors/' + this.errors + ' completed/' + this.completed); - var r = null; - - if (eventName == 'resolved' && !this.errors && this.completed) - { - r = eventCallback.apply(this, this.completedArgs); - if(r!=null) - { - this.emit_returnValue('resolved', r); - } - } - if (eventName == 'rejected' && this.errors && this.completed) - { - eventCallback.apply(this, this.completedArgs); - } - if (eventName == 'settled' && this.completed) - { - eventCallback.apply(this, []); - } - }); - this._internal.resolver = function _resolver() - { - _resolver._self.errors = false; - _resolver._self.completed = true; - _resolver._self.completedArgs = []; - var args = ['resolved']; - if (this.emit_returnValue && this.emit_returnValue('resolved') != null) - { - _resolver._self.completedArgs.push(this.emit_returnValue('resolved')); - args.push(this.emit_returnValue('resolved')); - } - else - { - for (var a in arguments) - { - _resolver._self.completedArgs.push(arguments[a]); - args.push(arguments[a]); - } - } - _resolver._self.emit.apply(_resolver._self, args); - _resolver._self.emit('settled'); - }; - this._internal.rejector = function _rejector() - { - _rejector._self.errors = true; - _rejector._self.completed = true; - _rejector._self.completedArgs = []; - var args = ['rejected']; - for (var a in arguments) - { - _rejector._self.completedArgs.push(arguments[a]); - args.push(arguments[a]); - } - - _rejector._self.emit.apply(_rejector._self, args); - _rejector._self.emit('settled'); - }; - this.catch = function(func) - { - this._internal.once('rejected', event_switcher(this, func).func); - } - this.finally = function (func) - { - this._internal.once('settled', event_switcher(this, func).func); - }; - this.then = function (resolved, rejected) - { - if (resolved) { this._internal.once('resolved', event_switcher(this, resolved).func); } - if (rejected) { this._internal.once('rejected', event_switcher(this, rejected).func); } - - var retVal = new Promise(function (r, j) { }); - this._internal.once('resolved', retVal._internal.resolver); - this._internal.once('rejected', retVal._internal.rejector); - retVal.parentPromise = this; - return (retVal); - }; - - this._internal.resolver._self = this._internal; - this._internal.rejector._self = this._internal;; - - try - { - promiseFunc.call(this, this._internal.resolver, this._internal.rejector); - } - catch(e) - { - this._internal.errors = true; - this._internal.completed = true; - this._internal.completedArgs = [e]; - this._internal.emit('rejected', e); - this._internal.emit('settled'); - } - - if(!this._internal.completed) - { - // Save reference of this object - refTable[this._internal._hashCode()] = this._internal; - this._internal.once('settled', function () { refTable[this._hashCode()] = null; }); - } -} - -Promise.resolve = function resolve() -{ - var retVal = new Promise(function (r, j) { }); - var args = []; - for (var i in arguments) - { - args.push(arguments[i]); - } - retVal._internal.resolver.apply(retVal._internal, args); - return (retVal); -}; -Promise.reject = function reject() { - var retVal = new Promise(function (r, j) { }); - var args = []; - for (var i in arguments) { - args.push(arguments[i]); - } - retVal._internal.rejector.apply(retVal._internal, args); - return (retVal); -}; -Promise.all = function all(promiseList) -{ - var ret = new Promise(function (res, rej) - { - this.__rejector = rej; - this.__resolver = res; - this.__promiseList = promiseList; - this.__done = false; - this.__count = 0; - }); - - for (var i in promiseList) - { - promiseList[i].then(function () - { - // Success - if(++ret.__count == ret.__promiseList.length) - { - ret.__done = true; - ret.__resolver(ret.__promiseList); - } - }, function (arg) - { - // Failure - if(!ret.__done) - { - ret.__done = true; - ret.__rejector(arg); - } - }); - } - if (promiseList.length == 0) - { - ret.__resolver(promiseList); - } - return (ret); -}; - -module.exports = Promise; \ No newline at end of file diff --git a/agents/modules_meshcore/service-manager.js b/agents/modules_meshcore/service-manager.js index ee6689ed..7c3092b9 100644 --- a/agents/modules_meshcore/service-manager.js +++ b/agents/modules_meshcore/service-manager.js @@ -214,6 +214,13 @@ function serviceManager() throw ('could not find service: ' + name); } } + else + { + this.isAdmin = function isAdmin() + { + return (require('user-sessions').isRoot()); + } + } this.installService = function installService(options) { if (process.platform == 'win32') @@ -273,6 +280,8 @@ function serviceManager() } if(process.platform == 'linux') { + if (!this.isAdmin()) { throw ('Installing as Service, requires root'); } + switch (this.getServiceType()) { case 'init': @@ -311,14 +320,70 @@ function serviceManager() break; } } + if(process.platform == 'darwin') + { + if (!this.isAdmin()) { throw ('Installing as Service, requires root'); } + + // Mac OS + var stdoutpath = (options.stdout ? ('StandardOutPath\n' + options.stdout + '') : ''); + var autoStart = (options.startType == 'AUTO_START' ? '' : ''); + var params = ' ProgramArguments\n'; + params += ' \n'; + params += (' /usr/local/mesh_services/' + options.name + '/' + options.name + '\n'); + if(options.parameters) + { + for(var itm in options.parameters) + { + params += (' ' + options.parameters[itm] + '\n'); + } + } + params += ' \n'; + + var plist = '\n'; + plist += '\n'; + plist += '\n'; + plist += ' \n'; + plist += ' Label\n'; + plist += (' ' + options.name + '\n'); + plist += (params + '\n'); + plist += ' WorkingDirectory\n'; + plist += (' /usr/local/mesh_services/' + options.name + '\n'); + plist += (stdoutpath + '\n'); + plist += ' RunAtLoad\n'; + plist += (autoStart + '\n'); + plist += ' \n'; + plist += ''; + + if (!require('fs').existsSync('/usr/local/mesh_services')) { require('fs').mkdirSync('/usr/local/mesh_services'); } + if (!require('fs').existsSync('/Library/LaunchDaemons/' + options.name + '.plist')) + { + if (!require('fs').existsSync('/usr/local/mesh_services/' + options.name)) { require('fs').mkdirSync('/usr/local/mesh_services/' + options.name); } + if (options.binary) + { + require('fs').writeFileSync('/usr/local/mesh_services/' + options.name + '/' + options.name, options.binary); + } + else + { + require('fs').copyFileSync(options.servicePath, '/usr/local/mesh_services/' + options.name + '/' + options.name); + } + require('fs').writeFileSync('/Library/LaunchDaemons/' + options.name + '.plist', plist); + var m = require('fs').statSync('/usr/local/mesh_services/' + options.name + '/' + options.name).mode; + m |= (require('fs').CHMOD_MODES.S_IXUSR | require('fs').CHMOD_MODES.S_IXGRP); + require('fs').chmodSync('/usr/local/mesh_services/' + options.name + '/' + options.name, m); + } + else + { + throw ('Service: ' + options.name + ' already exists'); + } + } } this.uninstallService = function uninstallService(name) { + if (!this.isAdmin()) { throw ('Uninstalling a service, requires admin'); } + if (typeof (name) == 'object') { name = name.name; } if (process.platform == 'win32') { - if (!this.isAdmin()) { throw ('Uninstalling a service, requires admin'); } - var service = this.getService(name); if (service.status.state == undefined || service.status.state == 'STOPPED') { @@ -388,6 +453,39 @@ function serviceManager() break; } } + else if(process.platform == 'darwin') + { + if (require('fs').existsSync('/Library/LaunchDaemons/' + name + '.plist')) + { + var child = require('child_process').execFile('/bin/sh', ['sh']); + child.stdout.on('data', function (chunk) { }); + child.stdin.write('launchctl stop ' + name + '\n'); + child.stdin.write('launchctl unload /Library/LaunchDaemons/' + name + '.plist\n'); + child.stdin.write('exit\n'); + child.waitExit(); + + try + { + require('fs').unlinkSync('/usr/local/mesh_services/' + name + '/' + name); + require('fs').unlinkSync('/Library/LaunchDaemons/' + name + '.plist'); + } + catch(e) + { + throw ('Error uninstalling service: ' + name + ' => ' + e); + } + + try + { + require('fs').rmdirSync('/usr/local/mesh_services/' + name); + } + catch(e) + {} + } + else + { + throw ('Service: ' + name + ' does not exist'); + } + } } if(process.platform == 'linux') { diff --git a/agents/modules_meshcore/wifi-scanner-windows.js b/agents/modules_meshcore/wifi-scanner-windows.js index bb794954..afd922c1 100644 --- a/agents/modules_meshcore/wifi-scanner-windows.js +++ b/agents/modules_meshcore/wifi-scanner-windows.js @@ -1,3 +1,18 @@ +/* +Copyright 2018 Intel Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ function _Scan() { diff --git a/agents/modules_meshcore/wifi-scanner.js b/agents/modules_meshcore/wifi-scanner.js index 2dfbb031..ee810488 100644 --- a/agents/modules_meshcore/wifi-scanner.js +++ b/agents/modules_meshcore/wifi-scanner.js @@ -1,3 +1,19 @@ +/* +Copyright 2018 Intel Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + var MemoryStream = require('MemoryStream'); var WindowsChildScript = 'var parent = require("ScriptContainer");var Wireless = require("wifi-scanner-windows");Wireless.on("Scan", function (ap) { parent.send(ap); });Wireless.Scan();'; diff --git a/agents/modules_meshcore/win-message-pump.js b/agents/modules_meshcore/win-message-pump.js index a243d45e..a371d901 100644 --- a/agents/modules_meshcore/win-message-pump.js +++ b/agents/modules_meshcore/win-message-pump.js @@ -17,6 +17,8 @@ limitations under the License. var WH_CALLWNDPROC = 4; var WM_QUIT = 0x0012; +var GM = require('_GenericMarshal'); + function WindowsMessagePump(options) { this._ObjectID = 'win-message-pump'; @@ -27,92 +29,94 @@ function WindowsMessagePump(options) emitterUtils.createEvent('message'); emitterUtils.createEvent('exit'); - this._child = require('ScriptContainer').Create({ processIsolation: 0 }); - this._child.MessagePump = this; - this._child.prependListener('~', function _childFinalizer() { this.MessagePump.emit('exit', 0); this.MessagePump.stop(); }); - this._child.once('exit', function onExit(code) { this.MessagePump.emit('exit', code); }); - this._child.once('ready', function onReady() - { - var execString = - "var m = require('_GenericMarshal');\ - var h = null;\ - var k = m.CreateNativeProxy('Kernel32.dll');\ - k.CreateMethod('GetLastError');\ - k.CreateMethod('GetModuleHandleA');\ - var u = m.CreateNativeProxy('User32.dll');\ - u.CreateMethod('GetMessageA');\ - u.CreateMethod('CreateWindowExA');\ - u.CreateMethod('TranslateMessage');\ - u.CreateMethod('DispatchMessageA');\ - u.CreateMethod('RegisterClassExA');\ - u.CreateMethod('DefWindowProcA');\ - var wndclass = m.CreateVariable(m.PointerSize == 4 ? 48 : 80);\ - wndclass.hinstance = k.GetModuleHandleA(0);\ - wndclass.cname = m.CreateVariable('MainWWWClass');\ - wndclass.wndproc = m.GetGenericGlobalCallback(4);\ - wndclass.toBuffer().writeUInt32LE(wndclass._size);\ - wndclass.cname.pointerBuffer().copy(wndclass.Deref(m.PointerSize == 4 ? 40 : 64, m.PointerSize).toBuffer());\ - wndclass.wndproc.pointerBuffer().copy(wndclass.Deref(8, m.PointerSize).toBuffer());\ - wndclass.hinstance.pointerBuffer().copy(wndclass.Deref(m.PointerSize == 4 ? 20 : 24, m.PointerSize).toBuffer());\ - wndclass.wndproc.on('GlobalCallback', function onWndProc(xhwnd, xmsg, wparam, lparam)\ - {\ - if(h==null || h.Val == xhwnd.Val)\ - {\ - require('ScriptContainer').send({message: xmsg.Val, wparam: wparam.Val, lparam: lparam.Val, lparam_hex: lparam.pointerBuffer().toString('hex')});\ - var retVal = u.DefWindowProcA(xhwnd, xmsg, wparam, lparam);\ - return(retVal);\ - }\ - });\ - u.RegisterClassExA(wndclass);\ - h = u.CreateWindowExA(0x00000088, wndclass.cname, 0, 0x00800000, 0, 0, 100, 100, 0, 0, 0, 0);\ - if(h.Val == 0)\ - {\ - require('ScriptContainer').send({error: 'Error Creating Hidden Window'});\ - process.exit();\ - }\ - require('ScriptContainer').send({hwnd: h.pointerBuffer().toString('hex')});\ - require('ScriptContainer').on('data', function onData(jmsg)\ - {\ - if(jmsg.listen)\ - {\ - var msg = m.CreateVariable(m.PointerSize == 4 ? 28 : 48);\ - while(u.GetMessageA(msg, h, 0, 0).Val>0)\ - {\ - u.TranslateMessage(msg);\ - u.DispatchMessageA(msg);\ - }\ - process.exit();\ - }\ - });"; + this._msg = GM.CreateVariable(GM.PointerSize == 4 ? 28 : 48); + this._kernel32 = GM.CreateNativeProxy('Kernel32.dll'); + this._kernel32.mp = this; + this._kernel32.CreateMethod('GetLastError'); + this._kernel32.CreateMethod('GetModuleHandleA'); - this.ExecuteString(execString); - }); - this._child.on('data', function onChildData(msg) + this._user32 = GM.CreateNativeProxy('User32.dll'); + this._user32.mp = this; + this._user32.CreateMethod('GetMessageA'); + this._user32.CreateMethod('CreateWindowExA'); + this._user32.CreateMethod('TranslateMessage'); + this._user32.CreateMethod('DispatchMessageA'); + this._user32.CreateMethod('RegisterClassExA'); + this._user32.CreateMethod('DefWindowProcA'); + this._user32.CreateMethod('PostMessageA'); + + + this.wndclass = GM.CreateVariable(GM.PointerSize == 4 ? 48 : 80); + this.wndclass.mp = this; + this.wndclass.hinstance = this._kernel32.GetModuleHandleA(0); + this.wndclass.cname = GM.CreateVariable('MainWWWClass'); + this.wndclass.wndproc = GM.GetGenericGlobalCallback(4); + this.wndclass.wndproc.mp = this; + this.wndclass.toBuffer().writeUInt32LE(this.wndclass._size); + this.wndclass.cname.pointerBuffer().copy(this.wndclass.Deref(GM.PointerSize == 4 ? 40 : 64, GM.PointerSize).toBuffer()); + this.wndclass.wndproc.pointerBuffer().copy(this.wndclass.Deref(8, GM.PointerSize).toBuffer()); + this.wndclass.hinstance.pointerBuffer().copy(this.wndclass.Deref(GM.PointerSize == 4 ? 20 : 24, GM.PointerSize).toBuffer()); + this.wndclass.wndproc.on('GlobalCallback', function onWndProc(xhwnd, xmsg, wparam, lparam) { - if (msg.hwnd) + if (this.mp._hwnd != null && this.mp._hwnd.Val == xhwnd.Val) { - var m = require('_GenericMarshal'); - this._hwnd = m.CreatePointer(Buffer.from(msg.hwnd, 'hex')); - this.MessagePump.emit('hwnd', this._hwnd); - this.send({ listen: this.MessagePump._options.filter }); + // This is for us + this.mp.emit('message', { message: xmsg.Val, wparam: wparam.Val, lparam: lparam.Val, lparam_hex: lparam.pointerBuffer().toString('hex') }); + return (this.mp._user32.DefWindowProcA(xhwnd, xmsg, wparam, lparam)); } - else if(msg.message) + else if(this.mp._hwnd == null && this.CallingThread() == this.mp._user32.RegisterClassExA.async.threadId()) { - this.MessagePump.emit('message', msg); - } - else - { - console.log('Received: ', msg); + // This message was generated from our CreateWindowExA method + return (this.mp._user32.DefWindowProcA(xhwnd, xmsg, wparam, lparam)); } }); + + this._user32.RegisterClassExA.async(this.wndclass).then(function () + { + this.nativeProxy.CreateWindowExA.async(this.nativeProxy.RegisterClassExA.async, 0x00000088, this.nativeProxy.mp.wndclass.cname, 0, 0x00800000, 0, 0, 100, 100, 0, 0, 0, 0) + .then(function(h) + { + if (h.Val == 0) + { + // Error creating hidden window + this.nativeProxy.mp.emit('error', 'Error creating hidden window'); + } + else + { + this.nativeProxy.mp._hwnd = h; + this.nativeProxy.mp.emit('hwnd', h); + this.nativeProxy.mp._startPump(); + } + }); + }); + this._startPump = function _startPump() + { + this._user32.GetMessageA.async(this._user32.RegisterClassExA.async, this._msg, this._hwnd, 0, 0).then(function (r) + { + if(r.Val > 0) + { + this.nativeProxy.TranslateMessage.async(this.nativeProxy.RegisterClassExA.async, this.nativeProxy.mp._msg).then(function () + { + this.nativeProxy.DispatchMessageA.async(this.nativeProxy.RegisterClassExA.async, this.nativeProxy.mp._msg).then(function () + { + this.nativeProxy.mp._startPump(); + }); + }); + } + else + { + // We got a 'QUIT' message + delete this.nativeProxy.mp._hwnd; + this.nativeProxy.mp.emit('exit', 0); + } + }, function (err) { this.nativeProxy.mp.stop(); }); + } + this.stop = function stop() { - if(this._child && this._child._hwnd) + if (this._hwnd) { - var marshal = require('_GenericMarshal'); - var User32 = marshal.CreateNativeProxy('User32.dll'); - User32.CreateMethod('PostMessageA'); - User32.PostMessageA(this._child._hwnd, WM_QUIT, 0, 0); + this._user32.PostMessageA(this._hwnd, WM_QUIT, 0, 0); } }; } diff --git a/agents/modules_meshcore/win-terminal.js b/agents/modules_meshcore/win-terminal.js new file mode 100644 index 00000000..74fc42d0 --- /dev/null +++ b/agents/modules_meshcore/win-terminal.js @@ -0,0 +1,555 @@ +/* +Copyright 2018 Intel Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +var promise = require('promise'); +var duplex = require('stream').Duplex; + +var SW_HIDE = 0; +var SW_MINIMIZE = 6; +var STARTF_USESHOWWINDOW = 0x1; +var STD_INPUT_HANDLE = -10; +var STD_OUTPUT_HANDLE = -11; +var EVENT_CONSOLE_CARET = 0x4001; +var EVENT_CONSOLE_END_APPLICATION = 0x4007; +var WINEVENT_OUTOFCONTEXT = 0x000; +var WINEVENT_SKIPOWNPROCESS = 0x0002; +var CREATE_NEW_PROCESS_GROUP = 0x200; +var EVENT_CONSOLE_UPDATE_REGION = 0x4002; +var EVENT_CONSOLE_UPDATE_SIMPLE = 0x4003; +var EVENT_CONSOLE_UPDATE_SCROLL = 0x4004; +var EVENT_CONSOLE_LAYOUT = 0x4005; +var EVENT_CONSOLE_START_APPLICATION = 0x4006; +var KEY_EVENT = 0x1; +var MAPVK_VK_TO_VSC = 0; +var WM_QUIT = 0x12; + +var GM = require('_GenericMarshal'); +var si = GM.CreateVariable(GM.PointerSize == 4 ? 68 : 104); +var pi = GM.CreateVariable(GM.PointerSize == 4 ? 16 : 24); + +si.Deref(0, 4).toBuffer().writeUInt32LE(GM.PointerSize == 4 ? 68 : 104); // si.cb +si.Deref(GM.PointerSize == 4 ? 48 : 64, 2).toBuffer().writeUInt16LE(SW_HIDE | SW_MINIMIZE); // si.wShowWindow +si.Deref(GM.PointerSize == 4 ? 44 : 60, 4).toBuffer().writeUInt32LE(STARTF_USESHOWWINDOW); // si.dwFlags; + +var MSG = GM.CreateVariable(GM.PointerSize == 4 ? 28 : 48); + +function windows_terminal() +{ + this._ObjectID = 'windows_terminal'; + this._user32 = GM.CreateNativeProxy('User32.dll'); + this._user32.CreateMethod('DispatchMessageA'); + this._user32.CreateMethod('GetMessageA'); + this._user32.CreateMethod('MapVirtualKeyA'); + this._user32.CreateMethod('PostThreadMessageA'); + this._user32.CreateMethod('SetWinEventHook'); + this._user32.CreateMethod('ShowWindow'); + this._user32.CreateMethod('TranslateMessage'); + this._user32.CreateMethod('UnhookWinEvent'); + this._user32.CreateMethod('VkKeyScanA'); + this._user32.terminal = this; + + this._kernel32 = GM.CreateNativeProxy('Kernel32.dll'); + this._kernel32.CreateMethod('AllocConsole'); + this._kernel32.CreateMethod('CreateProcessA'); + this._kernel32.CreateMethod('CloseHandle'); + this._kernel32.CreateMethod('FillConsoleOutputAttribute'); + this._kernel32.CreateMethod('FillConsoleOutputCharacterA'); + this._kernel32.CreateMethod('GetConsoleScreenBufferInfo'); + this._kernel32.CreateMethod('GetConsoleWindow'); + this._kernel32.CreateMethod('GetLastError'); + this._kernel32.CreateMethod('GetStdHandle'); + this._kernel32.CreateMethod('GetThreadId'); + this._kernel32.CreateMethod('ReadConsoleOutputA'); + this._kernel32.CreateMethod('SetConsoleCursorPosition'); + this._kernel32.CreateMethod('SetConsoleScreenBufferSize'); + this._kernel32.CreateMethod('SetConsoleWindowInfo'); + this._kernel32.CreateMethod('TerminateProcess'); + this._kernel32.CreateMethod('WaitForSingleObject'); + this._kernel32.CreateMethod('WriteConsoleInputA'); + + var currentX = 0; + var currentY = 0; + + this._scrx = 0; + this._scry = 0; + + this.SendCursorUpdate = function() + { + var newCsbi = GM.CreateVariable(22); + + if (this._kernel32.GetConsoleScreenBufferInfo(this._stdoutput, newCsbi).Val == 0) { return; } + if (newCsbi.Deref(4,2).toBuffer().readUInt16LE() != this.currentX || newCsbi.Deref(6,2).toBuffer().readUInt16LE() != this.currentY) + { + //wchar_t mywbuf[512]; + //swprintf(mywbuf, 512, TEXT("csbi.dwCursorPosition.X = %d, csbi.dwCursorPosition.Y = %d, newCsbi.dwCursorPosition.X = %d, newCsbi.dwCursorPosition.Y = %d\r\n"), csbi.dwCursorPosition.X, csbi.dwCursorPosition.Y, newCsbi.dwCursorPosition.X, newCsbi.dwCursorPosition.Y); + //OutputDebugString(mywbuf); + + //m_viewOffset = newCsbi.srWindow.Top; + //WriteMoveCursor((SerialAgent *)this->sa, (char)(newCsbi.dwCursorPosition.Y - m_viewOffset), (char)(newCsbi.dwCursorPosition.X - m_viewOffset)); + //LowStackSendData((SerialAgent *)(this->sa), "", 0); + + this.currentX = newCsbi.Deref(4,2).toBuffer().readUInt16LE(); + this.currentY = newCsbi.Deref(6,2).toBuffer().readUInt16LE(); + } + } + this.ClearScreen = function() + { + var CONSOLE_SCREEN_BUFFER_INFO = GM.CreateVariable(22); + if (this._kernel32.GetConsoleScreenBufferInfo(this._stdoutput, CONSOLE_SCREEN_BUFFER_INFO).Val == 0) { return; } + + var coordScreen = GM.CreateVariable(4); + var dwConSize = CONSOLE_SCREEN_BUFFER_INFO.Deref(0,2).toBuffer().readUInt16LE(0) * CONSOLE_SCREEN_BUFFER_INFO.Deref(2,2).toBuffer().readUInt16LE(0); + var cCharsWritten = GM.CreateVariable(4); + + // Fill the entire screen with blanks. + if (this._kernel32.FillConsoleOutputCharacterA(this._stdoutput, 32, dwConSize, coordScreen.Deref(0,4).toBuffer().readUInt32LE(), cCharsWritten).Val == 0) { return; } + + // Get the current text attribute. + if (this._kernel32.GetConsoleScreenBufferInfo(this._stdoutput, CONSOLE_SCREEN_BUFFER_INFO).Val == 0) { return; } + + // Set the buffer's attributes accordingly. + if (this._kernel32.FillConsoleOutputAttribute(this._stdoutput, CONSOLE_SCREEN_BUFFER_INFO.Deref(8, 2).toBuffer().readUInt16LE(0), dwConSize, coordScreen.Deref(0, 4).toBuffer().readUInt32LE(), cCharsWritten).Val == 0) { return; } + + // Put the cursor at its home coordinates. + this._kernel32.SetConsoleCursorPosition(this._stdoutput, coordScreen.Deref(0, 4).toBuffer().readUInt32LE()); + + // Put the window to top-left. + var rect = GM.CreateVariable(8); + var srWindow = CONSOLE_SCREEN_BUFFER_INFO.Deref(10,8).toBuffer(); + rect.Deref(4,2).toBuffer().writeUInt16LE(srWindow.readUInt16LE(4) - srWindow.readUInt16LE(0)); + rect.Deref(6,2).toBuffer().writeUInt16LE(srWindow.readUInt16LE(6) - srWindow.readUInt16LE(2)); + + this._kernel32.SetConsoleWindowInfo(this._stdoutput, 1, rect); + } + + this.Start = function Start(CONSOLE_SCREEN_WIDTH, CONSOLE_SCREEN_HEIGHT) + { + if(this._kernel32.GetConsoleWindow().Val == 0) + { + if(this._kernel32.AllocConsole().Val == 0) + { + throw ('AllocConsole failed with: ' + this._kernel32.GetLastError().Val); + } + } + + this._stdinput = this._kernel32.GetStdHandle(STD_INPUT_HANDLE); + this._stdoutput = this._kernel32.GetStdHandle(STD_OUTPUT_HANDLE); + this._connected = false; + var coordScreen = GM.CreateVariable(4); + coordScreen.Deref(0, 2).toBuffer().writeUInt16LE(CONSOLE_SCREEN_WIDTH); + coordScreen.Deref(2, 2).toBuffer().writeUInt16LE(CONSOLE_SCREEN_HEIGHT); + + var rect = GM.CreateVariable(8); + rect.Deref(4, 2).toBuffer().writeUInt16LE(CONSOLE_SCREEN_WIDTH - 1); + rect.Deref(6, 2).toBuffer().writeUInt16LE(CONSOLE_SCREEN_HEIGHT - 1); + + if(this._kernel32.SetConsoleWindowInfo(this._stdoutput, 1, rect).Val == 0) + { + throw ('Failed to set Console Screen Size'); + } + if(this._kernel32.SetConsoleScreenBufferSize(this._stdoutput, coordScreen.Deref(0,4).toBuffer().readUInt32LE()).Val == 0) + { + throw ('Failed to set Console Buffer Size'); + } + this.ClearScreen(); + this._hookThread().then(function () + { + // Hook Ready + this.terminal.StartCommand(); + }, console.log); + this._stream = new duplex({ + 'write': function (chunk, flush) + { + if (!this.terminal.connected) + { + //console.log('_write: ' + chunk); + if (!this._promise.chunk) + { + this._promise.chunk = []; + } + if (typeof (chunk) == 'string') + { + this._promise.chunk.push(chunk); + } + else + { + this._promise.chunk.push(Buffer.alloc(chunk.length)); + chunk.copy(this._promise.chunk.peek()); + } + this._promise.chunk.peek().flush = flush; + this._promise.then(function () + { + var buf; + while(this.chunk.length > 0) + { + buf = this.chunk.shift(); + this.terminal._WriteBuffer(buf); + buf.flush(); + } + }); + } + else + { + //console.log('writeNOW: ' + chunk); + this.terminal._WriteBuffer(chunk); + flush(); + } + }, + 'final': function (flush) + { + var p = this.terminal._stop(); + p.__flush = flush; + p.then(function () { this.__flush(); }); + } + }); + this._stream.terminal = this; + this._stream._promise = new promise(function (res, rej) { this._res = res; this._rej = rej; }); + this._stream._promise.terminal = this; + return (this._stream); + }; + this._stop = function() + { + if (this.stopping) { return (this.stopping); } + console.log('Stopping Terminal...'); + this.stopping = new promise(function (res, rej) { this._res = res; this._rej = rej; }); + + var threadID = this._kernel32.GetThreadId(this._user32.SetWinEventHook.async.thread()).Val; + this._user32.PostThreadMessageA(threadID, WM_QUIT, 0, 0); + return (this.stopping); + } + + this._hookThread = function () + { + var ret = new promise(function (res, rej) { this._res = res; this._rej = rej; }); + ret.terminal = this; + this._ConsoleWinEventProc = GM.GetGenericGlobalCallback(7); + this._ConsoleWinEventProc.terminal = this; + var p = this._user32.SetWinEventHook.async(EVENT_CONSOLE_CARET, EVENT_CONSOLE_END_APPLICATION, 0, this._ConsoleWinEventProc, 0, 0, WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS); + p.ready = ret; + p.terminal = this; + p.then(function (hwinEventHook) + { + if (hwinEventHook.Val == 0) + { + this.ready._rej('Error calling SetWinEventHook'); + } + else + { + this.terminal.hwinEventHook = hwinEventHook; + this.ready._res(); + this.terminal._GetMessage(); + } + }); + this._ConsoleWinEventProc.on('GlobalCallback', function (hhook, dwEvent, hwnd, idObject, idChild, idEventThread, swmsEventTime) + { + if (!this.terminal.hwinEventHook || this.terminal.hwinEventHook.Val != hhook.Val) { return; } + var buffer = null; + + switch (dwEvent.Val) + { + case EVENT_CONSOLE_CARET: + break; + case EVENT_CONSOLE_UPDATE_REGION: + if (!this.terminal.connected) + { + this.terminal.connected = true; this.terminal._stream._promise._res(); + } + if (this.terminal._scrollTimer == null) + { + buffer = this.terminal._GetScreenBuffer(LOWORD(idObject.Val), HIWORD(idObject.Val), LOWORD(idChild.Val), HIWORD(idChild.Val)); + //console.log('UPDATE REGION: [Left: ' + LOWORD(idObject.Val) + ' Top: ' + HIWORD(idObject.Val) + ' Right: ' + LOWORD(idChild.Val) + ' Bottom: ' + HIWORD(idChild.Val) + ']'); + + this.terminal._SendDataBuffer(buffer); + } + break; + case EVENT_CONSOLE_UPDATE_SIMPLE: + //console.log('UPDATE SIMPLE: [X: ' + LOWORD(idObject.Val) + ' Y: ' + HIWORD(idObject.Val) + ' Char: ' + LOWORD(idChild.Val) + ' Attr: ' + HIWORD(idChild.Val) + ']'); + var simplebuffer = { data: [Buffer.alloc(1, LOWORD(idChild.Val))], attributes: [HIWORD(idChild.Val)], width: 1, height: 1, x: LOWORD(idObject.Val)+1, y: HIWORD(idObject.Val) }; + this.terminal._SendDataBuffer(simplebuffer); + break; + case EVENT_CONSOLE_UPDATE_SCROLL: + //console.log('UPDATE SCROLL: [dx: ' + idObject.Val + ' dy: ' + idChild.Val + ']'); + this.terminal._SendScroll(idObject.Val, idChild.Val); + break; + case EVENT_CONSOLE_LAYOUT: + //console.log('CONSOLE_LAYOUT'); + //snprintf( Buf, 512, "Event Console LAYOUT!\r\n"); + //SendLayout(); + break; + case EVENT_CONSOLE_START_APPLICATION: + //console.log('START APPLICATION: [PID: ' + idObject.Val + ' CID: ' + idChild.Val + ']'); + //snprintf( Buf, 512, "Event Console START APPLICATION!\r\nProcess ID: %d - Child ID: %d\r\n\r\n", (int)idObject, (int)idChild); + //SendConsoleEvent(dwEvent, idObject, idChild); + break; + case EVENT_CONSOLE_END_APPLICATION: + if(idObject.Val == this.terminal._hProcessID) + { + //console.log('END APPLICATION: [PID: ' + idObject.Val + ' CID: ' + idChild.Val + ']'); + this.terminal._stop().then(function () { console.log('STOPPED'); }); + } + break; + default: + //snprintf(Buf, 512, "unknown console event.\r\n"); + console.log('Unknown event: ' + dwEvent.Val); + break; + } + + //mbstowcs_s(&l, wBuf, Buf, 512); + //OutputDebugString(wBuf); + + }); + return (ret); + } + + this._GetMessage = function() + { + if (this._user32.abort) { console.log('aborting loop'); return; } + this._user32.GetMessageA.async(this._user32.SetWinEventHook.async, MSG, 0, 0, 0).then(function (ret) + { + //console.log('GetMessage Response'); + if(ret.Val != 0) + { + if (ret.Val == -1) + { + // handle the error and possibly exit + } + else + { + //console.log('TranslateMessage'); + this.nativeProxy._user32.TranslateMessage.async(this.nativeProxy.user32.SetWinEventHook.async, MSG).then(function () + { + //console.log('DispatchMessage'); + this.nativeProxy._user32.DispatchMessageA.async(this.nativeProxy.user32.SetWinEventHook.async, MSG).then(function () + { + this.nativeProxy.terminal._GetMessage(); + }, console.log); + }, console.log); + } + } + else + { + this.nativeProxy.UnhookWinEvent.async(this.nativeProxy.terminal._user32.SetWinEventHook.async, this.nativeProxy.terminal.hwinEventHook) + .then(function () + { + this.nativeProxy.terminal.stopping._res(); + if(this.nativeProxy.terminal._kernel32.TerminateProcess(this.nativeProxy.terminal._hProcess, 1067).Val == 0) + { + var e = this.nativeProxy.terminal._kernel32.GetLastError().Val; + console.log('Unable to kill Terminal Process, error: ' + e); + } + this.nativeProxy.terminal.stopping = null; + }, function (err) + { + console.log('REJECTED_UnhookWinEvent: ' + err); + }); + } + }, function (err) + { + // Get Message Failed + console.log('REJECTED_GETMessage: ' + err); + }); + } + this._WriteBuffer = function(buf) + { + for (var i = 0; i < buf.length; ++i) + { + if (typeof (buf) == 'string') + { + this._WriteCharacter(buf.charCodeAt(i), false); + } + else + { + this._WriteCharacter(buf[i], false); + } + } + } + this._WriteCharacter = function(key, bControlKey) + { + var rec = GM.CreateVariable(20); + rec.Deref(0,2).toBuffer().writeUInt16LE(KEY_EVENT); // rec.EventType + rec.Deref(4,4).toBuffer().writeUInt16LE(1); // rec.Event.KeyEvent.bKeyDown + rec.Deref(16, 4).toBuffer().writeUInt32LE(bControlKey); // rec.Event.KeyEvent.dwControlKeyState + rec.Deref(14, 1).toBuffer()[0] = key; // rec.Event.KeyEvent.uChar.AsciiChar + rec.Deref(8, 2).toBuffer().writeUInt16LE(1); // rec.Event.KeyEvent.wRepeatCount + rec.Deref(10, 2).toBuffer().writeUInt16LE(this._user32.VkKeyScanA(key).Val); // rec.Event.KeyEvent.wVirtualKeyCode + rec.Deref(12, 2).toBuffer().writeUInt16LE(this._user32.MapVirtualKeyA(this._user32.VkKeyScanA(key).Val, MAPVK_VK_TO_VSC).Val); + + var dwWritten = GM.CreateVariable(4); + if(this._kernel32.WriteConsoleInputA(this._stdinput, rec, 1, dwWritten).Val == 0) { return(false); } + + rec.Deref(4,4).toBuffer().writeUInt16LE(0); // rec.Event.KeyEvent.bKeyDown + return(this._kernel32.WriteConsoleInputA(this._stdinput, rec, 1, dwWritten).Val != 0); + } + + this._GetScreenBuffer = function(sx, sy, ex, ey) + { + // get the current visible screen buffer + + var info = GM.CreateVariable(22); + if (this._kernel32.GetConsoleScreenBufferInfo(this._stdoutput, info).Val == 0) { throw('Error getting screen buffer info'); } + + var nWidth = info.Deref(14,2).toBuffer().readUInt16LE() - info.Deref(10,2).toBuffer().readUInt16LE() + 1; + var nHeight = info.Deref(16,2).toBuffer().readUInt16LE() - info.Deref(12,2).toBuffer().readUInt16LE() + 1; + + if (arguments[3] == null) + { + // Use Default Parameters + sx = 0; + sy = 0; + ex = nWidth-1; + ey = nHeight-1; + } + else + { + if(this._scrx != 0) + { + sx += this._scrx; + ex += this._scrx; + } + if(this._scry != 0) + { + sy += this._scry; + ey += this._scry; + } + this._scrx = this._scry = 0; + } + + + var nBuffer = GM.CreateVariable((ex-sx+1) * (ey-sy+1) * 4); + var size = GM.CreateVariable(4); + size.Deref(0,2).toBuffer().writeUInt16LE(ex-sx+1, 0); + size.Deref(2, 2).toBuffer().writeUInt16LE(ey-sy+1, 0); + + var startCoord = GM.CreateVariable(4); + startCoord.Deref(0, 2).toBuffer().writeUInt16LE(0, 0); + startCoord.Deref(2, 2).toBuffer().writeUInt16LE(0, 0); + + var region = GM.CreateVariable(8); + region.buffer = region.toBuffer(); + region.buffer.writeUInt16LE(sx, 0); + region.buffer.writeUInt16LE(sy, 2); + region.buffer.writeUInt16LE(ex, 4); + region.buffer.writeUInt16LE(ey, 6); + + if (this._kernel32.ReadConsoleOutputA(this._stdoutput, nBuffer, size.Deref(0, 4).toBuffer().readUInt32LE(), startCoord.Deref(0, 4).toBuffer().readUInt32LE(), region).Val == 0) + { + throw('Unable to read Console Output'); + } + + // Lets convert the buffer into something simpler + //var retVal = { data: Buffer.alloc((dw - dx + 1) * (dh - dy + 1)), attributes: Buffer.alloc((dw - dx + 1) * (dh - dy + 1)), width: dw - dx + 1, height: dh - dy + 1, x: dx, y: dy }; + + var retVal = { data: [], attributes: [], width: ex - sx + 1, height: ey - sy + 1, x: sx, y: sy }; + var x, y, line, ifo; + var tmp; + var lineWidth = ex - sx + 1; + + for (y = 0; y <= (ey - sy) ; ++y) + { + retVal.data.push(Buffer.alloc(lineWidth)); + retVal.attributes.push(Buffer.alloc(lineWidth)); + + line = nBuffer.Deref(y * lineWidth * 4, lineWidth * 4).toBuffer(); + for(x = 0; x < lineWidth; ++x) + { + retVal.data.peek()[x] = line[x * 4]; + retVal.attributes.peek()[x] = line[2 + (x * 4)]; + } + } + + return (retVal); + } + + this._SendDataBuffer = function(data) + { + // { data, attributes, width, height, x, y } + + var dy, line, attr; + for(dy = 0; dy < data.height; ++dy) + { + line = data.data[dy]; + attr = data.attributes[dy]; + line.s = line.toString(); + //line = data.data.slice(data.width * dy, (data.width * dy) + data.width); + //attr = data.attributes.slice(data.width * dy, (data.width * dy) + data.width); + this._stream.push(TranslateLine(data.x, data.y + dy, line, attr)); + } + } + this._SendScroll = function _SendScroll(dx, dy) + { + if (this._scrollTimer) + { + return; + } + + var info = GM.CreateVariable(22); + if (this._kernel32.GetConsoleScreenBufferInfo(this._stdoutput, info).Val == 0) { throw ('Error getting screen buffer info'); } + + var nWidth = info.Deref(14, 2).toBuffer().readUInt16LE() - info.Deref(10, 2).toBuffer().readUInt16LE() + 1; + var nHeight = info.Deref(16, 2).toBuffer().readUInt16LE() - info.Deref(12, 2).toBuffer().readUInt16LE() + 1; + + this._stream.push(GetEsc('H', nHeight-1, 0)); + for (var i = 0; i > nHeight; ++i) + { + this._stream.push(Buffer.from('\r\n')); + } + + var buffer = this._GetScreenBuffer(0, 0, nWidth - 1, nHeight - 1); + this._SendDataBuffer(buffer); + + this._scrollTimer = setTimeout(function (self, nw, nh) + { + var buffer = self._GetScreenBuffer(0, 0, nw - 1, nh - 1); + self._SendDataBuffer(buffer); + self._scrollTimer = null; + }, 250, this, nWidth, nHeight); + } + + this.StartCommand = function StartCommand() + { + if(this._kernel32.CreateProcessA(GM.CreateVariable(process.env['windir'] + '\\system32\\cmd.exe'), 0, 0, 0, 1, CREATE_NEW_PROCESS_GROUP, 0, 0, si, pi).Val == 0) + { + console.log('Error Spawning CMD'); + return; + } + + this._kernel32.CloseHandle(pi.Deref(GM.PointerSize, GM.PointerSize).Deref()); // pi.hThread + this._hProcess = pi.Deref(0, GM.PointerSize).Deref(); // pi.hProcess + this._hProcessID = pi.Deref(GM.PointerSize == 4 ? 8 : 16, 4).toBuffer().readUInt32LE(); // pi.dwProcessId + //console.log('Ready => hProcess: ' + this._hProcess._ptr + ' PID: ' + this._hProcessID); + } +} + +function LOWORD(val) +{ + return (val & 0xFFFF); +} +function HIWORD(val) +{ + return ((val >> 16) & 0xFFFF); +} + +function GetEsc(CodeCharStr, arg1, arg2) +{ + return (Buffer.from('\x1B[' + arg1 + ';' + arg2 + CodeCharStr)); + //return (Buffer.from('*[' + arg1 + ';' + arg2 + CodeCharStr)); +} + +function TranslateLine(x, y, data, attributes) +{ + return (Buffer.concat([GetEsc('H', y, x), data])); +} +module.exports = new windows_terminal(); \ No newline at end of file diff --git a/package.json b/package.json index 071f6541..ab485d23 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.2.4-g", + "version": "0.2.4-h", "keywords": [ "Remote Management", "Intel AMT",