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",