MeshCentral/agents/meshcore.js

1992 lines
104 KiB
JavaScript
Raw Normal View History

2017-08-28 19:27:45 +03:00
/*
2018-01-04 23:15:21 +03:00
Copyright 2018 Intel Corporation
2017-08-28 19:27:45 +03:00
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
2018-01-17 04:30:34 +03:00
http://www.apache.org/licenses/LICENSE-2.0
2017-08-28 19:27:45 +03:00
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.
*/
2018-09-20 21:45:12 +03:00
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.
2018-11-27 04:12:27 +03:00
//attachDebugger({ webport: 9999, wait: 1 }).then(function (prt) { console.log('Point Browser for Debug to port: ' + prt); });
2018-09-20 21:45:12 +03:00
// Mesh Rights
2018-12-30 02:24:33 +03:00
var MESHRIGHT_EDITMESH = 1;
var MESHRIGHT_MANAGEUSERS = 2;
var MESHRIGHT_MANAGECOMPUTERS = 4;
var MESHRIGHT_REMOTECONTROL = 8;
var MESHRIGHT_AGENTCONSOLE = 16;
var MESHRIGHT_SERVERFILES = 32;
var MESHRIGHT_WAKEDEVICE = 64;
var MESHRIGHT_SETNOTES = 128;
var MESHRIGHT_REMOTEVIEW = 256;
2018-09-01 01:23:42 +03:00
function createMeshCore(agent) {
var obj = {};
2018-09-15 03:42:39 +03:00
2018-09-20 21:45:12 +03:00
/*
2018-09-01 01:23:42 +03:00
function borderController() {
this.container = null;
this.Start = function Start(user) {
if (this.container == null) {
if (process.platform == 'win32') {
2018-09-22 02:34:35 +03:00
try {
this.container = require('ScriptContainer').Create({ processIsolation: 1, sessionId: user.SessionId });
} catch (ex) {
this.container = require('ScriptContainer').Create({ processIsolation: 1 });
}
} else {
2018-09-01 01:23:42 +03:00
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();");
}
}
2018-09-01 01:23:42 +03:00
this.Stop = function Stop() {
if (this.container != null) {
this._container = this.container;
this._container.parent = this;
this.container = null;
this._container.exit();
}
}
}
2018-09-22 02:34:35 +03:00
obj.borderManager = new borderController();
2018-09-20 21:45:12 +03:00
*/
2017-08-28 19:27:45 +03:00
// 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) { }
2017-08-28 19:27:45 +03:00
var meshServerConnectionState = 0;
var tunnels = {};
2018-09-22 02:34:35 +03:00
var lastMeInfo = null;
2017-08-28 19:27:45 +03:00
var lastNetworkInfo = null;
var lastPublicLocationInfo = null;
2017-08-28 19:27:45 +03:00
var selfInfoUpdateTimer = null;
var http = require('http');
2017-10-24 00:09:58 +03:00
var net = require('net');
2017-08-28 19:27:45 +03:00
var fs = require('fs');
var rtc = require('ILibWebRTC');
2018-04-11 23:49:05 +03:00
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;
2018-01-10 07:13:41 +03:00
var amtscanner = null;
2018-01-19 02:43:43 +03:00
var nextTunnelIndex = 1;
2018-12-28 08:31:20 +03:00
var oswsstack = null;
var osamtstack = null;
2018-09-28 02:17:05 +03:00
// 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) { }
2018-09-28 02:17:05 +03:00
}
} else {
// Running in nodejs
meshCoreObj.value += '-NodeJS';
meshCoreObj.caps = 8;
2018-09-28 02:17:05 +03:00
mesh = agent.getMeshApi();
}
2018-01-10 07:13:41 +03:00
/*
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);
*/
2018-01-17 04:30:34 +03:00
/*
// 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; }
2018-01-17 04:30:34 +03:00
*/
2018-01-10 07:13:41 +03:00
// 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); });
2018-09-22 02:34:35 +03:00
} catch (ex) { amtscanner = null; }
2018-09-28 02:17:05 +03:00
// 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 }); }
2018-09-28 02:17:05 +03:00
// If SMBios tables say that AMT is present, try to connect MEI
2018-12-28 08:31:20 +03:00
if (SMBiosTables.amtInfo && (SMBiosTables.amtInfo.AMT == true)) { resetMei(); }
2018-09-28 02:17:05 +03:00
}
});
} catch (ex) { sendConsoleText(ex); }
2018-01-04 23:15:21 +03:00
// Try to load up the WIFI scanner
try {
2018-01-31 05:23:57 +03:00
var wifiScannerLib = require('wifi-scanner');
wifiScanner = new wifiScannerLib();
wifiScanner.on('accessPoint', function (data) { sendConsoleText(data); });
2018-09-22 02:34:35 +03:00
} catch (ex) { wifiScannerLib = null; wifiScanner = null; }
2018-12-28 08:31:20 +03:00
// Try to load up the MEI module
function resetMei() {
try {
var amtMeiLib = require('amt-mei');
amtMei = new amtMeiLib();
amtMei.on('error', function (e) { amtMeiLib = null; amtMei = null; amtMeiConnected = -1; sendConsoleText('MEI Error.'); });
amtMeiConnected = 2;
sendPeriodicServerUpdate(1);
} catch (ex) { amtMeiLib = null; amtMei = null; amtMeiConnected = -1; }
}
// Get our location (lat/long) using our public IP address
2017-09-01 21:23:22 +03:00
var getIpLocationDataExInProgress = false;
2017-10-24 00:09:58 +03:00
var getIpLocationDataExCounts = [0, 0];
function getIpLocationDataEx(func) {
2017-09-01 21:23:22 +03:00
if (getIpLocationDataExInProgress == true) { return false; }
try {
2017-09-01 21:23:22 +03:00
getIpLocationDataExInProgress = true;
getIpLocationDataExCounts[0]++;
var options = http.parseUri("http://ipinfo.io/json");
options.method = 'GET';
http.request(options, function (resp) {
2018-01-04 23:15:21 +03:00
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();
2017-09-01 21:23:22 +03:00
return true;
}
2017-09-01 21:23:22 +03:00
catch (e) { return false; }
}
2018-01-04 23:15:21 +03:00
// 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);
}
2018-01-04 23:15:21 +03:00
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) {
2017-09-02 03:34:02 +03:00
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) {
2017-09-02 03:34:02 +03:00
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
}
});
}
}
}
2018-01-04 23:15:21 +03:00
2017-08-28 19:27:45 +03:00
// 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;
};
}
2018-01-04 23:15:21 +03:00
2017-08-28 19:27:45 +03:00
// 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); }
2017-10-24 00:09:58 +03:00
if (i != 0) {
while (w.startsWith('/') || w.startsWith('\\')) { w = w.substring(1); }
}
2017-08-28 19:27:45 +03:00
x.push(w);
}
}
if (x.length == 0) return '/';
return x.join('/');
}
};
2018-01-04 23:15:21 +03:00
2017-08-28 19:27:45 +03:00
// 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; }
2018-01-04 23:15:21 +03:00
2017-08-28 19:27:45 +03:00
// Convert decimal to hex
function char2hex(i) { return (i + 0x100).toString(16).substr(-2).toUpperCase(); }
2018-01-04 23:15:21 +03:00
2017-08-28 19:27:45 +03:00
// 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; }
2018-01-04 23:15:21 +03:00
2017-08-28 19:27:45 +03:00
// 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; }
2018-01-04 23:15:21 +03:00
2017-08-28 19:27:45 +03:00
// 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
}
2018-01-04 23:15:21 +03:00
2017-08-28 19:27:45 +03:00
// Convert an object to string with all functions
function objToString(x, p, pad, ret) {
2017-08-28 19:27:45 +03:00
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) + '}';
2017-08-28 19:27:45 +03:00
}
2018-01-04 23:15:21 +03:00
2017-08-28 19:27:45 +03:00
// Return p number of spaces
function addPad(p, ret) { var r = ''; for (var i = 0; i < p; i++) { r += ret; } return r; }
2018-01-04 23:15:21 +03:00
2017-08-28 19:27:45 +03:00
// 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;
}
2018-01-04 23:15:21 +03:00
2017-08-28 19:27:45 +03:00
// 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;
}
2018-01-04 23:15:21 +03:00
// 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;
}
2018-01-04 23:15:21 +03:00
// 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;
}
2018-01-04 23:15:21 +03:00
// 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');
2018-01-04 23:15:21 +03:00
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;
2017-08-28 19:27:45 +03:00
}
2018-01-04 23:15:21 +03:00
2017-08-28 19:27:45 +03:00
// 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': {
2018-04-11 23:49:05 +03:00
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;
}
2018-04-11 23:49:05 +03:00
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;
2018-12-01 08:23:10 +03:00
//sendConsoleText(JSON.stringify(woptions));
2018-04-11 23:49:05 +03:00
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++;
2018-04-11 23:49:05 +03:00
tunnel.index = index;
tunnels[index] = tunnel;
//sendConsoleText('New tunnel connection #' + index + ': ' + tunnel.url + ', rights: ' + tunnel.rights, data.sessionid);
2018-04-11 23:49:05 +03:00
}
}
break;
}
case 'ps': {
// Return the list of running processes
2018-04-11 23:49:05 +03:00
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) }); }
2018-04-11 23:49:05 +03:00
break;
}
2017-08-28 19:27:45 +03:00
}
2018-04-12 21:15:01 +03:00
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;
}
2017-09-01 21:23:22 +03:00
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;
}
2017-09-06 03:19:28 +03:00
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;
2017-08-28 19:27:45 +03:00
}
2018-04-20 04:19:15 +03:00
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;
}
2017-08-28 19:27:45 +03:00
}
}
}
2018-01-04 23:15:21 +03:00
2017-08-28 19:27:45 +03:00
// Called when a file changed in the file system
2017-12-15 01:57:52 +03:00
/*
2017-08-28 19:27:45 +03:00
function onFileWatcher(a, b) {
2017-10-24 00:09:58 +03:00
console.log('onFileWatcher', a, b, this.path);
2017-08-28 19:27:45 +03:00
var response = getDirectoryInfo(this.path);
if ((response != undefined) && (response != null)) { this.tunnel.s.write(JSON.stringify(response)); }
}
2017-12-15 01:57:52 +03:00
*/
2017-08-28 19:27:45 +03:00
// 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 = '/'; }
2018-07-06 20:13:19 +03:00
var results = null, xpath = obj.path.join(reqpath, '*');
2018-07-27 02:31:43 +03:00
//if (process.platform == "win32") { xpath = xpath.split('/').join('\\'); }
2017-08-28 19:27:45 +03:00
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]);
2018-07-27 02:31:43 +03:00
//if (process.platform == "win32") { p = p.split('/').join('\\'); }
2017-08-28 19:27:45 +03:00
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;
}
2018-01-04 23:15:21 +03:00
2017-08-28 19:27:45 +03:00
// Tunnel callback operations
2017-10-24 00:09:58 +03:00
function onTunnelUpgrade(response, s, head) {
this.s = s;
s.httprequest = this;
s.end = onTunnelClosed;
2018-01-19 02:43:43 +03:00
s.tunnel = this;
2018-01-04 23:15:21 +03:00
2017-10-24 00:09:58 +03:00
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;
}
}
2018-01-04 23:15:21 +03:00
2017-10-24 00:09:58 +03:00
// 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();
}
2018-01-04 23:15:21 +03:00
2017-10-24 00:09:58 +03:00
// 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
}
2018-01-04 23:15:21 +03:00
2017-08-28 19:27:45 +03:00
function onTunnelClosed() {
2018-01-19 02:43:43 +03:00
if (tunnels[this.httprequest.index] == null) return; // Stop duplicate calls.
//sendConsoleText("Tunnel #" + this.httprequest.index + " closed.", this.httprequest.sessionid);
2017-08-28 19:27:45 +03:00
delete tunnels[this.httprequest.index];
2018-01-04 23:15:21 +03:00
2017-12-15 01:57:52 +03:00
/*
2017-08-28 19:27:45 +03:00
// 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!!!!
2017-10-24 00:09:58 +03:00
delete this.httprequest.watcher;
2017-08-28 19:27:45 +03:00
}
2017-12-15 01:57:52 +03:00
*/
2017-08-28 19:27:45 +03:00
// 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; }
2018-01-19 02:43:43 +03:00
// 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');
2017-08-28 19:27:45 +03:00
}
function onTunnelSendOk() { /*sendConsoleText("Tunnel #" + this.index + " SendOK.", this.sessionid);*/ }
2017-08-28 19:27:45 +03:00
function onTunnelData(data) {
2017-12-19 19:50:19 +03:00
//console.log("OnTunnelData");
2018-07-06 20:13:19 +03:00
//sendConsoleText('OnTunnelData, ' + data.length + ', ' + typeof data + ', ' + data);
2018-01-04 23:15:21 +03:00
2017-08-28 19:27:45 +03:00
// If this is upload data, save it to file
if (this.httprequest.uploadFile) {
2018-01-03 03:52:49 +03:00
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
2017-08-28 19:27:45 +03:00
return;
}
/*
2017-08-28 19:27:45 +03:00
// 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;
}
*/
2017-08-28 19:27:45 +03:00
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);*/ }
2017-08-28 19:27:45 +03:00
} 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
2017-08-28 19:27:45 +03:00
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;
}
2018-01-17 04:30:34 +03:00
// Remote terminal using native pipes
2018-12-13 02:34:42 +03:00
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
{
2018-01-17 04:30:34 +03:00
this.httprequest.process = childProcess.execFile("/bin/sh", ["sh"], { type: childProcess.SpawnTypes.TERM });
2018-12-13 02:34:42 +03:00
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(); });
2017-08-28 19:27:45 +03:00
}
2018-01-17 04:30:34 +03:00
this.removeAllListeners('data');
this.on('data', onTunnelControlData);
2018-02-08 05:45:14 +03:00
//this.write('MeshCore Terminal Hello');
2018-11-15 02:44:28 +03:00
if (process.platform == 'linux') { this.httprequest.process.stdin.write("stty erase ^H\nalias ls='ls --color=auto'\nclear\n"); }
2018-04-20 04:19:15 +03:00
} 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;
}
2018-01-17 04:30:34 +03:00
// 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;
2018-04-20 04:19:15 +03:00
// Display a toast message
//require('toaster').Toast('MeshCentral', 'Remote Desktop Control Started.');
2018-01-17 04:30:34 +03:00
this.end = function () {
--this.desktop.kvm.connectionCount;
this.unpipe(this.httprequest.desktop.kvm);
this.httprequest.desktop.kvm.unpipe(this);
2018-04-20 04:19:15 +03:00
if (this.desktop.kvm.connectionCount == 0) {
// Display a toast message
//require('toaster').Toast('MeshCentral', 'Remote Desktop Control Ended.');
this.httprequest.desktop.kvm.end();
}
2018-01-17 04:30:34 +03:00
};
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.
2018-01-17 04:30:34 +03:00
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;
}
2017-08-28 19:27:45 +03:00
// 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));
2017-10-17 06:11:03 +03:00
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, ' '));
2017-08-28 19:27:45 +03:00
switch (cmd.action) {
case 'ls': {
2017-12-15 01:57:52 +03:00
/*
2017-08-28 19:27:45 +03:00
// 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;
}
2017-12-15 01:57:52 +03:00
*/
2017-08-28 19:27:45 +03:00
// Send the folder content to the browser
var response = getDirectoryInfo(cmd.path);
if (cmd.reqid != undefined) { response.reqid = cmd.reqid; }
2018-01-03 03:52:49 +03:00
this.write(new Buffer(JSON.stringify(response)));
2018-01-04 23:15:21 +03:00
2017-12-15 01:57:52 +03:00
/*
2017-08-28 19:27:45 +03:00
// 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);
}
2017-12-15 01:57:52 +03:00
*/
2017-08-28 19:27:45 +03:00
break;
}
case 'mkdir': {
// Create a new empty folder
fs.mkdirSync(cmd.path);
break;
}
case 'rm': {
2018-09-25 21:51:40 +03:00
// Delete, possibly recursive delete
2017-08-28 19:27:45 +03:00
for (var i in cmd.delfiles) {
2018-09-25 21:51:40 +03:00
try { deleteFolderRecursive(obj.path.join(cmd.path, cmd.delfiles[i]), cmd.rec); } catch (e) { }
2017-08-28 19:27:45 +03:00
}
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;
2018-02-13 23:28:11 +03:00
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;
}
/*
2017-08-28 19:27:45 +03:00
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);
2018-01-03 03:52:49 +03:00
try { this.httprequest.downloadFile = fs.openSync(filepath, 'rbN'); } catch (e) { this.write(new Buffer(JSON.stringify({ action: 'downloaderror', reqid: cmd.reqid }))); break; }
2017-08-28 19:27:45 +03:00
this.httprequest.downloadFileId = cmd.reqid;
this.httprequest.downloadFilePtr = 0;
2018-01-03 03:52:49 +03:00
if (this.httprequest.downloadFile) { this.write(new Buffer(JSON.stringify({ action: 'downloadstart', reqid: this.httprequest.downloadFileId }))); }
2017-08-28 19:27:45 +03:00
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;
}
*/
2017-08-28 19:27:45 +03:00
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;
2018-01-03 03:52:49 +03:00
try { this.httprequest.uploadFile = fs.openSync(filepath, 'wbN'); } catch (e) { this.write(new Buffer(JSON.stringify({ action: 'uploaderror', reqid: cmd.reqid }))); break; }
2017-08-28 19:27:45 +03:00
this.httprequest.uploadFileid = cmd.reqid;
2018-01-03 03:52:49 +03:00
if (this.httprequest.uploadFile) { this.write(new Buffer(JSON.stringify({ action: 'uploadstart', reqid: this.httprequest.uploadFileid }))); }
2017-08-28 19:27:45 +03:00
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;
}
2017-08-28 19:27:45 +03:00
}
}
//sendConsoleText("Got tunnel #" + this.httprequest.index + " data: " + data, this.httprequest.sessionid);
}
}
2018-01-17 04:30:34 +03:00
2018-01-19 02:43:43 +03:00
// 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; }
2018-01-19 02:43:43 +03:00
if (obj.type == 'close') {
//sendConsoleText('Tunnel #' + this.xrtc.websocket.tunnel.index + ' WebRTC control close');
2018-01-19 02:43:43 +03:00
try { this.close(); } catch (e) { }
try { this.xrtc.close(); } catch (e) { }
}
}
// Called when receiving control data on websocket
function onTunnelControlData(data, ws) {
2018-01-19 02:43:43 +03:00
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));
2018-01-19 02:43:43 +03:00
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;
}
2018-01-19 02:43:43 +03:00
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
2018-12-13 02:34:42 +03:00
if (process.platform == 'win32')
{
ws.httprequest._term.unpipe(ws);
}
else
{
ws.httprequest.process.stdout.unpipe(ws);
ws.httprequest.process.stderr.unpipe(ws);
}
} else if (ws.httprequest.protocol == 2) { // Desktop
// This is a KVM data stream, unpipe the KVM now and indicate to the other side that KVM data will no longer be received over WebSocket
ws.httprequest.desktop.kvm.unpipe(ws);
} else {
// Switch things around so all WebRTC data goes to onTunnelData().
ws.rtcchannel.httprequest = ws.httprequest;
ws.rtcchannel.removeAllListeners('data');
ws.rtcchannel.on('data', onTunnelData);
}
ws.write("{\"ctrlChannel\":\"102938\",\"type\":\"webrtc1\"}"); // End of data marker
2018-01-19 02:43:43 +03:00
} else if (obj.type == 'webrtc1') {
if (ws.httprequest.protocol == 1) { // Terminal
2018-01-19 02:43:43 +03:00
// Switch the user input from websocket to webrtc at this point.
2018-12-13 02:34:42 +03:00
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
2018-01-19 02:43:43 +03:00
// Switch the user input from websocket to webrtc at this point.
ws.unpipe(ws.httprequest.desktop.kvm);
2018-04-18 05:00:31 +03:00
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
2018-01-19 02:43:43 +03:00
}
ws.write("{\"ctrlChannel\":\"102938\",\"type\":\"webrtc2\"}"); // Indicates we will no longer get any data on websocket, switching to WebRTC at this point.
2018-01-19 02:43:43 +03:00
} 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
2018-12-13 02:34:42 +03:00
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.
2018-01-19 02:43:43 +03:00
}
} else if (obj.type == 'offer') {
2018-01-17 04:30:34 +03:00
// 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);
2018-01-19 02:43:43 +03:00
rtcchannel.xrtc = this;
rtcchannel.websocket = this.websocket;
2018-01-17 04:30:34 +03:00
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.
2018-01-17 04:30:34 +03:00
});
2018-04-13 04:14:03 +03:00
var sdp = null;
try { sdp = ws.webrtc.setOffer(obj.sdp); } catch (ex) { }
if (sdp != null) { ws.write({ type: 'answer', ctrlChannel: '102938', sdp: sdp }); }
2018-01-17 04:30:34 +03:00
}
}
2017-08-28 19:27:45 +03:00
// Console state
var consoleWebSockets = {};
var consoleHttpRequest = null;
2018-01-04 23:15:21 +03:00
2017-08-28 19:27:45 +03:00
// 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':
2018-12-20 23:12:24 +03:00
//child = require('child_process').execFile(process.env['windir'] + '\\system32\\cmd.exe', ["/c", "start", url], { type: childProcess.SpawnTypes.USER, uid: require('user-sessions').Current().Active[0].SessionId });
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;
}
2017-08-28 19:27:45 +03:00
// Process a mesh agent console command
function processConsoleCommand(cmd, args, rights, sessionid) {
try {
var response = null;
switch (cmd) {
case 'help': { // Displays available commands
2018-12-28 08:31:20 +03:00
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, amtreset, amtccm, amtdeactivate.';
2018-04-20 04:19:15 +03:00
break;
}
2018-09-20 21:45:12 +03:00
/*
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;
2018-09-20 21:45:12 +03:00
*/
2018-12-28 08:31:20 +03:00
case 'amtreset': {
resetMei();
resetMicroLms();
response = 'Done.';
break;
}
case 'amtccm': {
if (amtMei == null) { response = 'Intel AMT not supported.'; } else {
if (args['_'].length != 1) { response = 'Proper usage: amtccm (adminPassword)'; } // Display usage
else { activeToCCM(args['_'][0]); }
}
break;
}
case 'amtdeactivate': {
if (amtMei == null) { response = 'Intel AMT not supported.'; } else { deactivateCCM(); }
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(', ') + '.'; }
2018-12-20 23:12:24 +03:00
require('user-sessions').enumerateUsers().then(function (u) { for (var i in u) { sendConsoleText(u[i]); } });
break;
}
2018-04-20 04:19:15 +03:00
case 'toast': {
2018-04-21 02:05:06 +03:00
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.';
}
2018-03-09 04:58:22 +03:00
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;
}
2018-04-11 23:49:05 +03:00
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': {
2018-09-28 02:17:05 +03:00
if (SMBiosTables == null) { response = 'SMBios tables not available.'; } else { response = objToString(SMBiosTables, 0, ' ', true); }
break;
}
case 'rawsmbios': {
2018-09-28 02:17:05 +03:00
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';
}
}
2018-09-28 02:17:05 +03:00
}
}
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]));
}
2017-08-28 19:27:45 +03:00
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(', ') + '.';
2018-09-20 21:45:12 +03:00
response += '\r\nServer Connection: ' + mesh.isControlChannelConnected + ', State: ' + meshServerConnectionState + '.';
2018-09-22 02:34:35 +03:00
response += '\r\lastMeInfo: ' + lastMeInfo + '.';
var oldNodeId = db.Get('OldNodeId');
if (oldNodeId != null) { response += '\r\nOldNodeID: ' + oldNodeId + '.'; }
2018-09-20 21:45:12 +03:00
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;
2018-09-22 02:34:35 +03:00
if (args['_'].length > 0) { i = parseInt(args['_'][0]); if (i > 8) { i = 8; } response = 'Calling ' + i + ' times.'; }
2018-09-20 21:45:12 +03:00
for (var j = 0; j < i; j++) {
var pr = require('os').name();
pr.sessionid = sessionid;
pr.then(function (v) { sendConsoleText("OS: " + v, this.sessionid); });
}
2017-08-28 19:27:45 +03:00
break;
}
2018-09-20 21:45:12 +03:00
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);
2018-09-20 21:45:12 +03:00
}
break;
}
2018-09-22 02:34:35 +03:00
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
}
2018-09-22 02:34:35 +03:00
break;
}
2017-08-28 19:27:45 +03:00
case 'args': { // Displays parsed command arguments
response = 'args ' + objToString(args, 0, ' ', true);
2017-08-28 19:27:45 +03:00
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;
}
2017-09-25 21:00:57 +03:00
case 'dbkeys': { // Return all data store keys
response = JSON.stringify(db.Keys);
break;
}
2017-08-28 19:27:45 +03:00
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]);
2017-08-28 19:27:45 +03:00
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;
2017-08-28 19:27:45 +03:00
}
}
}
}
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;
2017-09-02 03:34:02 +03:00
try {
2018-01-04 23:15:21 +03:00
var options = http.parseUri(args['_'][0]);
options.rejectUnauthorized = 0;
httprequest = http.request(options);
2017-09-02 03:34:02 +03:00
} catch (e) { response = 'Invalid HTTP websocket request'; }
2017-08-28 19:27:45 +03:00
if (httprequest != null) {
httprequest.upgrade = onWebSocketUpgrade;
httprequest.onerror = function (e) { sendConsoleText('ERROR: ' + JSON.stringify(e)); }
2018-01-04 23:15:21 +03:00
2017-08-28 19:27:45 +03:00
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) {
2017-09-02 03:34:02 +03:00
if (httprequest.s != null) { httprequest.s.end(); } else { httprequest.end(); }
2017-08-28 19:27:45 +03:00
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");
2017-10-24 00:09:58 +03:00
}
2017-08-28 19:27:45 +03:00
}
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;
}
2017-08-28 19:27:45 +03:00
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);
});
2017-08-28 19:27:45 +03:00
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).';
}
2017-08-28 19:27:45 +03:00
break;
}
case 'sendall': { // Send a message to all consoles on this mesh
sendConsoleText(args['_'].join(' '));
break;
}
2017-09-01 21:23:22 +03:00
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;
}
2018-01-10 07:13:41 +03:00
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;
}
2017-08-28 19:27:45 +03:00
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); }
}
2018-01-04 23:15:21 +03:00
2017-08-28 19:27:45 +03:00
// 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 });
}
2018-01-04 23:15:21 +03:00
2017-08-28 19:27:45 +03:00
// Called before the process exits
//process.exit = function (code) { console.log("Exit with code: " + code.toString()); }
2018-01-04 23:15:21 +03:00
2017-08-28 19:27:45 +03:00
// 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 }); }
2018-09-22 02:34:35 +03:00
// Update the server with basic info, logged in users and more.
mesh.SendCommand(meshCoreObj);
2018-09-22 02:34:35 +03:00
2018-09-28 02:17:05 +03:00
// Send SMBios tables if present
if (SMBiosTablesRaw != null) { mesh.SendCommand({ "action": "smbios", "value": SMBiosTablesRaw }); }
2018-09-22 02:34:35 +03:00
// 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
}
2017-08-28 19:27:45 +03:00
}
2018-09-22 02:34:35 +03:00
// 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;
2018-01-04 23:15:21 +03:00
2017-08-28 19:27:45 +03:00
// 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; }
2017-08-28 19:27:45 +03:00
}
2018-01-04 23:15:21 +03:00
// Called periodically to check if we need to send updates to the server
2018-09-22 02:34:35 +03:00
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;
}
2018-09-22 02:34:35 +03:00
}
} catch (ex) { }
});
}
2018-09-22 02:34:35 +03:00
if (flags & 2) {
// Update network information
sendNetworkUpdateNagle(false);
}
}
2018-01-04 23:15:21 +03:00
// 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; } });
2018-09-28 02:17:05 +03:00
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; } } });
2018-12-28 08:31:20 +03:00
//amtMei.getMACAddresses(function (result) { if (result) { amtMeiTmpState.mac = result; } });
amtMei.getLanInterfaceSettings(0, function (result) { if (result) { amtMeiTmpState.net0 = result; } });
amtMei.getLanInterfaceSettings(1, function (result) { if (result) { amtMeiTmpState.net1 = result; } });
amtMei.getUuid(function (result) { if ((result != null) && (result.uuid != null)) { amtMeiTmpState.UUID = result.uuid; } });
amtMei.getDnsSuffix(function (result) { if (result != null) { amtMeiTmpState.dns = result; } if (func != null) { func(amtMeiTmpState); } });
} catch (e) { if (func != null) { func(null); } return; }
}
2018-12-28 08:31:20 +03:00
2017-08-28 19:27:45 +03:00
// 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;
2017-08-28 19:27:45 +03:00
switch (amtMessage) {
case 'iAMT0050': { if (amtMessageArg == '48') { notify = 'Intel&reg; AMT Serial-over-LAN connected'; } else if (amtMessageArg == '49') { notify = 'Intel&reg; AMT Serial-over-LAN disconnected'; } break; } // SOL
case 'iAMT0052': { if (amtMessageArg == '1') { notify = 'Intel&reg; AMT KVM connected'; } else if (amtMessageArg == '2') { notify = 'Intel&reg; AMT KVM disconnected'; } break; } // KVM
2017-08-28 19:27:45 +03:00
}
// Send to the entire mesh, no sessionid or userid specified.
if (notify != null) { mesh.SendCommand({ "action": "msg", "type": "notify", "value": notify, "tag": "general" }); }
2017-08-28 19:27:45 +03:00
}
2018-12-28 08:31:20 +03:00
function resetMicroLms() {
// Launch LMS
try {
2018-01-31 05:23:57 +03:00
var lme_heci = require('amt-lme');
amtLmsState = 1;
amtLms = new lme_heci();
2018-12-28 08:31:20 +03:00
amtLms.on('error', function (e) { amtLmsState = 0; amtLms = null; sendConsoleText('LMS Error.'); obj.setupMeiOsAdmin(null, 1); });
amtLms.on('connect', function () { amtLmsState = 2; obj.setupMeiOsAdmin(null, 2); });
2018-04-12 21:15:01 +03:00
//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; }
2018-12-28 08:31:20 +03:00
}
// Starting function
obj.start = function () {
// Setup the mesh agent event handlers
mesh.AddCommandHandler(handleServerCommand);
mesh.AddConnectHandler(handleServerConnection);
// Parse input arguments
//var args = parseArgs(process.argv);
//console.log(args);
resetMicroLms();
// Setup logged in user monitoring (THIS IS BROKEN IN WIN7)
2018-09-22 02:34:35 +03:00
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);
2018-09-22 02:34:35 +03:00
});
});
2018-09-22 02:34:35 +03:00
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) { }
2017-08-28 19:27:45 +03:00
}
2018-01-04 23:15:21 +03:00
2017-08-28 19:27:45 +03:00
obj.stop = function () {
mesh.AddCommandHandler(null);
mesh.AddConnectHandler(null);
}
2018-01-04 23:15:21 +03:00
2017-08-28 19:27:45 +03:00
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); }
2018-01-04 23:15:21 +03:00
2017-08-28 19:27:45 +03:00
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
//
2018-09-22 02:34:35 +03:00
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) {
2018-11-27 04:12:27 +03:00
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) {
2018-11-27 04:12:27 +03:00
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) {
2018-07-14 05:18:43 +03:00
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;
2018-07-14 05:18:43 +03:00
}
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 () { });
}
2018-09-25 21:51:40 +03:00
// 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);
}
};
2018-12-28 08:31:20 +03:00
//
// Deactivate Intel AMT CCM
//
// When called, this will use MEI to deactivate Intel AMT when it's in CCM mode. Simply calls "unprovision" on MEI and checks the return code.
function deactivateCCM() {
amtMei.unprovision(1, function (status) { if (status == 0) { sendConsoleText('Success deactivating Intel AMT CCM.'); } else { sendConsoleText('Intel AMT CCM deactivation error: ' + status); } });
}
//
// Activate Intel AMT to CCM
//
function activeToCCM(adminpass) {
amtMei.getLocalSystemAccount(function (x) {
if (x.user && x.pass) {
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);
osamtstack = new amt(oswsstack);
osamtstack.BatchEnum(null, ['*AMT_GeneralSettings', '*IPS_HostBasedSetupService'], activeToCCMEx2, adminpass);
} else {
sendConsoleText('Unable to get $$OsAdmin password.');
}
});
}
function activeToCCMEx2(stack, name, responses, status, adminpass) {
if (status != 200) { sendConsoleText('Failed to fetch activation status, status ' + status); }
else if (responses['IPS_HostBasedSetupService'].response['AllowedControlModes'].length != 2) { sendConsoleText('Client control mode activation not allowed'); }
else { stack.IPS_HostBasedSetupService_Setup(2, md5hex('admin:' + responses['AMT_GeneralSettings'].response['DigestRealm'] + ':' + adminpass).substring(0, 32), null, null, null, null, activeToCCMEx3); }
}
function activeToCCMEx3(stack, name, responses, status) {
if (status != 200) { sendConsoleText('Failed to activate, status ' + status); }
else if (responses.Body.ReturnValue != 0) { sendConsoleText('Client control mode activation failed: ' + responses.Body.ReturnValueStr); }
else { sendConsoleText('Intel AMT CCM activation success'); }
}
function md5hex(str) { return require('MD5Stream').create().syncHash(str).toString('hex'); }
2017-08-28 19:27:45 +03:00
return obj;
}
//
// Module startup
//
2018-09-20 21:45:12 +03:00
try {
var xexports = null, mainMeshCore = null;
try { xexports = module.exports; } catch (e) { }
2017-08-28 19:27:45 +03:00
2018-09-20 21:45:12 +03:00
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 });
}