Improved Windows Terminal

Ylian Saint-Hilaire committed Dec 12, 2018 at 15:34 UTC 210f867960fb5551c4223c22331c33d1c66d0651
17 files changed +2733 -302
agents/MeshCmd-signed.exe
Binary files a/agents/MeshCmd-signed.exe and b/agents/MeshCmd-signed.exe differ
agents/MeshCmd64-signed.exe
Binary files a/agents/MeshCmd64-signed.exe and b/agents/MeshCmd64-signed.exe differ
agents/MeshService-signed.exe
Binary files a/agents/MeshService-signed.exe and b/agents/MeshService-signed.exe differ
agents/MeshService.exe
Binary files a/agents/MeshService.exe and b/agents/MeshService.exe differ
agents/MeshService64-signed.exe
Binary files a/agents/MeshService64-signed.exe and b/agents/MeshService64-signed.exe differ
agents/MeshService64.exe
Binary files a/agents/MeshService64.exe and b/agents/MeshService64.exe differ
agents/meshcore-01.js new
+1892
@@ -0,0 +1,1892 @@
1 +/*
2 +Copyright 2018 Intel Corporation
3 +
4 +Licensed under the Apache License, Version 2.0 (the "License");
5 +you may not use this file except in compliance with the License.
6 +You may obtain a copy of the License at
7 +
8 + http://www.apache.org/licenses/LICENSE-2.0
9 +
10 +Unless required by applicable law or agreed to in writing, software
11 +distributed under the License is distributed on an "AS IS" BASIS,
12 +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 +See the License for the specific language governing permissions and
14 +limitations under the License.
15 +*/
16 +
17 +
18 +process.on('uncaughtException', function (ex) {
19 + require('MeshAgent').SendCommand({ "action": "msg", "type": "console", "value": "uncaughtException1: " + ex });
20 +});
21 +
22 +// NOTE: This seems to cause big problems, don't enable the debugger in the server's meshcore.
23 +//attachDebugger({ webport: 9999, wait: 1 }).then(function (prt) { console.log('Point Browser for Debug to port: ' + prt); });
24 +
25 +// Mesh Rights
26 +const MESHRIGHT_EDITMESH = 1;
27 +const MESHRIGHT_MANAGEUSERS = 2;
28 +const MESHRIGHT_MANAGECOMPUTERS = 4;
29 +const MESHRIGHT_REMOTECONTROL = 8;
30 +const MESHRIGHT_AGENTCONSOLE = 16;
31 +const MESHRIGHT_SERVERFILES = 32;
32 +const MESHRIGHT_WAKEDEVICE = 64;
33 +const MESHRIGHT_SETNOTES = 128;
34 +const MESHRIGHT_REMOTEVIEW = 256;
35 +
36 +function createMeshCore(agent) {
37 + var obj = {};
38 +
39 + /*
40 + function borderController() {
41 + this.container = null;
42 + this.Start = function Start(user) {
43 + if (this.container == null) {
44 + if (process.platform == 'win32') {
45 + try {
46 + this.container = require('ScriptContainer').Create({ processIsolation: 1, sessionId: user.SessionId });
47 + } catch (ex) {
48 + this.container = require('ScriptContainer').Create({ processIsolation: 1 });
49 + }
50 + } else {
51 + this.container = require('ScriptContainer').Create({ processIsolation: 1, sessionId: user.uid });
52 + }
53 + this.container.parent = this;
54 + this.container.addModule('monitor-info', getJSModule('monitor-info'));
55 + this.container.addModule('monitor-border', getJSModule('monitor-border'));
56 + this.container.addModule('promise', getJSModule('promise'));
57 + this.container.once('exit', function (code) { sendConsoleText('Border Process Exited with code: ' + code); this.parent.container = this.parent._container = null; });
58 + this.container.ExecuteString("var border = require('monitor-border'); border.Start();");
59 + }
60 + }
61 + this.Stop = function Stop() {
62 + if (this.container != null) {
63 + this._container = this.container;
64 + this._container.parent = this;
65 + this.container = null;
66 + this._container.exit();
67 + }
68 + }
69 + }
70 + obj.borderManager = new borderController();
71 + */
72 +
73 + // MeshAgent JavaScript Core Module. This code is sent to and running on the mesh agent.
74 + var meshCoreObj = { "action": "coreinfo", "value": "MeshCore v6", "caps": 14 }; // Capability bitmask: 1 = Desktop, 2 = Terminal, 4 = Files, 8 = Console, 16 = JavaScript
75 +
76 + // Get the operating system description string
77 + try { require('os').name().then(function (v) { meshCoreObj.osdesc = v; }); } catch (ex) { }
78 +
79 + var meshServerConnectionState = 0;
80 + var tunnels = {};
81 + var lastMeInfo = null;
82 + var lastNetworkInfo = null;
83 + var lastPublicLocationInfo = null;
84 + var selfInfoUpdateTimer = null;
85 + var http = require('http');
86 + var net = require('net');
87 + var fs = require('fs');
88 + var rtc = require('ILibWebRTC');
89 + var processManager = require('process-manager');
90 + var amtMei = null, amtLms = null, amtLmsState = 0;
91 + var amtMeiConnected = 0, amtMeiTmpState = null;
92 + var wifiScannerLib = null;
93 + var wifiScanner = null;
94 + var networkMonitor = null;
95 + var amtscanner = null;
96 + var nextTunnelIndex = 1;
97 +
98 + // If we are running in Duktape, agent will be null
99 + if (agent == null) {
100 + // Running in native agent, Import libraries
101 + db = require('SimpleDataStore').Shared();
102 + sha = require('SHA256Stream');
103 + mesh = require('MeshAgent');
104 + childProcess = require('child_process');
105 + if (mesh.hasKVM == 1) { // if the agent is compiled with KVM support
106 + // Check if this computer supports a desktop
107 + try { if ((process.platform == 'win32') || (process.platform == 'darwin') || (require('monitor-info').kvm_x11_support)) { meshCoreObj.caps |= 1; } } catch (ex) { }
108 + }
109 + } else {
110 + // Running in nodejs
111 + meshCoreObj.value += '-NodeJS';
112 + meshCoreObj.caps = 8;
113 + mesh = agent.getMeshApi();
114 + }
115 +
116 + /*
117 + var AMTScanner = require("AMTScanner");
118 + var scan = new AMTScanner();
119 +
120 + scan.on("found", function (data) {
121 + if (typeof data === 'string') {
122 + console.log(data);
123 + } else {
124 + console.log(JSON.stringify(data, null, " "));
125 + }
126 + });
127 + scan.scan("10.2.55.140", 1000);
128 + scan.scan("10.2.55.139-10.2.55.145", 1000);
129 + scan.scan("10.2.55.128/25", 2000);
130 + */
131 +
132 + /*
133 + // Try to load up the network monitor
134 + try {
135 + networkMonitor = require('NetworkMonitor');
136 + networkMonitor.on('change', function () { sendNetworkUpdateNagle(); });
137 + networkMonitor.on('add', function (addr) { sendNetworkUpdateNagle(); });
138 + networkMonitor.on('remove', function (addr) { sendNetworkUpdateNagle(); });
139 + } catch (e) { networkMonitor = null; }
140 + */
141 +
142 + // Try to load up the Intel AMT scanner
143 + try {
144 + var AMTScannerModule = require('amt-scanner');
145 + amtscanner = new AMTScannerModule();
146 + //amtscanner.on('found', function (data) { if (typeof data != 'string') { data = JSON.stringify(data, null, " "); } sendConsoleText(data); });
147 + } catch (ex) { amtscanner = null; }
148 +
149 + // Fetch the SMBios Tables
150 + var SMBiosTables = null;
151 + var SMBiosTablesRaw = null;
152 + try {
153 + require('smbios').get(function (data) {
154 + if (data != null) {
155 + SMBiosTablesRaw = data;
156 + SMBiosTables = require('smbios').parse(data)
157 + if (mesh.isControlChannelConnected) { mesh.SendCommand({ "action": "smbios", "value": SMBiosTablesRaw }); }
158 +
159 + // If SMBios tables say that AMT is present, try to connect MEI
160 + if (SMBiosTables.amtInfo && (SMBiosTables.amtInfo.AMT == true)) {
161 + // Try to load up the MEI module
162 + try {
163 + var amtMeiLib = require('amt-mei');
164 + amtMei = new amtMeiLib();
165 + amtMei.on('error', function (e) { amtMeiLib = null; amtMei = null; amtMeiConnected = -1; });
166 + amtMeiConnected = 2;
167 + sendPeriodicServerUpdate(1);
168 + } catch (ex) { amtMeiLib = null; amtMei = null; amtMeiConnected = -1; }
169 + }
170 + }
171 + });
172 + } catch (ex) { sendConsoleText(ex); }
173 +
174 + // Try to load up the WIFI scanner
175 + try {
176 + var wifiScannerLib = require('wifi-scanner');
177 + wifiScanner = new wifiScannerLib();
178 + wifiScanner.on('accessPoint', function (data) { sendConsoleText(data); });
179 + } catch (ex) { wifiScannerLib = null; wifiScanner = null; }
180 +
181 + // Get our location (lat/long) using our public IP address
182 + var getIpLocationDataExInProgress = false;
183 + var getIpLocationDataExCounts = [0, 0];
184 + function getIpLocationDataEx(func) {
185 + if (getIpLocationDataExInProgress == true) { return false; }
186 + try {
187 + getIpLocationDataExInProgress = true;
188 + getIpLocationDataExCounts[0]++;
189 + var options = http.parseUri("http://ipinfo.io/json");
190 + options.method = 'GET';
191 + http.request(options, function (resp) {
192 + if (resp.statusCode == 200) {
193 + var geoData = '';
194 + resp.data = function (geoipdata) { geoData += geoipdata; };
195 + resp.end = function () {
196 + var location = null;
197 + try {
198 + if (typeof geoData == 'string') {
199 + var result = JSON.parse(geoData);
200 + if (result.ip && result.loc) { location = result; }
201 + }
202 + } catch (e) { }
203 + if (func) { getIpLocationDataExCounts[1]++; func(location); }
204 + }
205 + } else { func(null); }
206 + getIpLocationDataExInProgress = false;
207 + }).end();
208 + return true;
209 + }
210 + catch (e) { return false; }
211 + }
212 +
213 + // Remove all Gateway MAC addresses for interface list. This is useful because the gateway MAC is not always populated reliably.
214 + function clearGatewayMac(str) {
215 + if (str == null) return null;
216 + var x = JSON.parse(str);
217 + for (var i in x.netif) { if (x.netif[i].gatewaymac) { delete x.netif[i].gatewaymac } }
218 + return JSON.stringify(x);
219 + }
220 +
221 + function getIpLocationData(func) {
222 + // Get the location information for the cache if possible
223 + var publicLocationInfo = db.Get('publicLocationInfo');
224 + if (publicLocationInfo != null) { publicLocationInfo = JSON.parse(publicLocationInfo); }
225 + if (publicLocationInfo == null) {
226 + // Nothing in the cache, fetch the data
227 + getIpLocationDataEx(function (locationData) {
228 + if (locationData != null) {
229 + publicLocationInfo = {};
230 + publicLocationInfo.netInfoStr = lastNetworkInfo;
231 + publicLocationInfo.locationData = locationData;
232 + var x = db.Put('publicLocationInfo', JSON.stringify(publicLocationInfo)); // Save to database
233 + if (func) func(locationData); // Report the new location
234 + } else {
235 + if (func) func(null); // Report no location
236 + }
237 + });
238 + } else {
239 + // Check the cache
240 + if (clearGatewayMac(publicLocationInfo.netInfoStr) == clearGatewayMac(lastNetworkInfo)) {
241 + // Cache match
242 + if (func) func(publicLocationInfo.locationData);
243 + } else {
244 + // Cache mismatch
245 + getIpLocationDataEx(function (locationData) {
246 + if (locationData != null) {
247 + publicLocationInfo = {};
248 + publicLocationInfo.netInfoStr = lastNetworkInfo;
249 + publicLocationInfo.locationData = locationData;
250 + var x = db.Put('publicLocationInfo', JSON.stringify(publicLocationInfo)); // Save to database
251 + if (func) func(locationData); // Report the new location
252 + } else {
253 + if (func) func(publicLocationInfo.locationData); // Can't get new location, report the old location
254 + }
255 + });
256 + }
257 + }
258 + }
259 +
260 + // Polyfill String.endsWith
261 + if (!String.prototype.endsWith) {
262 + String.prototype.endsWith = function (searchString, position) {
263 + var subjectString = this.toString();
264 + if (typeof position !== 'number' || !isFinite(position) || Math.floor(position) !== position || position > subjectString.length) { position = subjectString.length; }
265 + position -= searchString.length;
266 + var lastIndex = subjectString.lastIndexOf(searchString, position);
267 + return lastIndex !== -1 && lastIndex === position;
268 + };
269 + }
270 +
271 + // Polyfill path.join
272 + obj.path = {
273 + join: function () {
274 + var x = [];
275 + for (var i in arguments) {
276 + var w = arguments[i];
277 + if (w != null) {
278 + while (w.endsWith('/') || w.endsWith('\\')) { w = w.substring(0, w.length - 1); }
279 + if (i != 0) {
280 + while (w.startsWith('/') || w.startsWith('\\')) { w = w.substring(1); }
281 + }
282 + x.push(w);
283 + }
284 + }
285 + if (x.length == 0) return '/';
286 + return x.join('/');
287 + }
288 + };
289 +
290 + // Replace a string with a number if the string is an exact number
291 + function toNumberIfNumber(x) { if ((typeof x == 'string') && (+parseInt(x) === x)) { x = parseInt(x); } return x; }
292 +
293 + // Convert decimal to hex
294 + function char2hex(i) { return (i + 0x100).toString(16).substr(-2).toUpperCase(); }
295 +
296 + // Convert a raw string to a hex string
297 + function rstr2hex(input) { var r = '', i; for (i = 0; i < input.length; i++) { r += char2hex(input.charCodeAt(i)); } return r; }
298 +
299 + // Convert a buffer into a string
300 + function buf2rstr(buf) { var r = ''; for (var i = 0; i < buf.length; i++) { r += String.fromCharCode(buf[i]); } return r; }
301 +
302 + // Convert a hex string to a raw string // TODO: Do this using Buffer(), will be MUCH faster
303 + function hex2rstr(d) {
304 + if (typeof d != "string" || d.length == 0) return '';
305 + var r = '', m = ('' + d).match(/../g), t;
306 + while (t = m.shift()) r += String.fromCharCode('0x' + t);
307 + return r
308 + }
309 +
310 + // Convert an object to string with all functions
311 + function objToString(x, p, pad, ret) {
312 + if (ret == undefined) ret = '';
313 + if (p == undefined) p = 0;
314 + if (x == null) { return '[null]'; }
315 + if (p > 8) { return '[...]'; }
316 + if (x == undefined) { return '[undefined]'; }
317 + if (typeof x == 'string') { if (p == 0) return x; return '"' + x + '"'; }
318 + if (typeof x == 'buffer') { return '[buffer]'; }
319 + if (typeof x != 'object') { return x; }
320 + var r = '{' + (ret ? '\r\n' : ' ');
321 + for (var i in x) { if (i != '_ObjectID') { r += (addPad(p + 2, pad) + i + ': ' + objToString(x[i], p + 2, pad, ret) + (ret ? '\r\n' : ' ')); } }
322 + return r + addPad(p, pad) + '}';
323 + }
324 +
325 + // Return p number of spaces
326 + function addPad(p, ret) { var r = ''; for (var i = 0; i < p; i++) { r += ret; } return r; }
327 +
328 + // Split a string taking into account the quoats. Used for command line parsing
329 + function splitArgs(str) {
330 + var myArray = [], myRegexp = /[^\s"]+|"([^"]*)"/gi;
331 + do { var match = myRegexp.exec(str); if (match != null) { myArray.push(match[1] ? match[1] : match[0]); } } while (match != null);
332 + return myArray;
333 + }
334 +
335 + // Parse arguments string array into an object
336 + function parseArgs(argv) {
337 + var results = { '_': [] }, current = null;
338 + for (var i = 1, len = argv.length; i < len; i++) {
339 + var x = argv[i];
340 + if (x.length > 2 && x[0] == '-' && x[1] == '-') {
341 + if (current != null) { results[current] = true; }
342 + current = x.substring(2);
343 + } else {
344 + if (current != null) { results[current] = toNumberIfNumber(x); current = null; } else { results['_'].push(toNumberIfNumber(x)); }
345 + }
346 + }
347 + if (current != null) { results[current] = true; }
348 + return results;
349 + }
350 +
351 + // Get server target url with a custom path
352 + function getServerTargetUrl(path) {
353 + var x = mesh.ServerUrl;
354 + //sendConsoleText("mesh.ServerUrl: " + mesh.ServerUrl);
355 + if (x == null) { return null; }
356 + if (path == null) { path = ''; }
357 + x = http.parseUri(x);
358 + if (x == null) return null;
359 + return x.protocol + '//' + x.host + ':' + x.port + '/' + path;
360 + }
361 +
362 + // Get server url. If the url starts with "*/..." change it, it not use the url as is.
363 + function getServerTargetUrlEx(url) {
364 + if (url.substring(0, 2) == '*/') { return getServerTargetUrl(url.substring(2)); }
365 + return url;
366 + }
367 +
368 + // Send a wake-on-lan packet
369 + function sendWakeOnLan(hexMac) {
370 + var count = 0;
371 + try {
372 + var interfaces = require('os').networkInterfaces();
373 + var magic = 'FFFFFFFFFFFF';
374 + for (var x = 1; x <= 16; ++x) { magic += hexMac; }
375 + var magicbin = Buffer.from(magic, 'hex');
376 +
377 + for (var adapter in interfaces) {
378 + if (interfaces.hasOwnProperty(adapter)) {
379 + for (var i = 0; i < interfaces[adapter].length; ++i) {
380 + var addr = interfaces[adapter][i];
381 + if ((addr.family == 'IPv4') && (addr.mac != '00:00:00:00:00:00')) {
382 + var socket = require('dgram').createSocket({ type: "udp4" });
383 + socket.bind({ address: addr.address });
384 + socket.setBroadcast(true);
385 + socket.send(magicbin, 7, "255.255.255.255");
386 + count++;
387 + }
388 + }
389 + }
390 + }
391 + } catch (e) { }
392 + return count;
393 + }
394 +
395 + // Handle a mesh agent command
396 + function handleServerCommand(data) {
397 + if (typeof data == 'object') {
398 + // If this is a console command, parse it and call the console handler
399 + switch (data.action) {
400 + case 'msg': {
401 + switch (data.type) {
402 + case 'console': { // Process a console command
403 + if (data.value && data.sessionid) {
404 + var args = splitArgs(data.value);
405 + processConsoleCommand(args[0].toLowerCase(), parseArgs(args), data.rights, data.sessionid);
406 + }
407 + break;
408 + }
409 + case 'tunnel': {
410 + if (data.value != null) { // Process a new tunnel connection request
411 + // Create a new tunnel object
412 + var xurl = getServerTargetUrlEx(data.value);
413 + if (xurl != null) {
414 + var woptions = http.parseUri(xurl);
415 + woptions.rejectUnauthorized = 0;
416 + //sendConsoleText(JSON.stringify(woptions));
417 + var tunnel = http.request(woptions);
418 + tunnel.upgrade = onTunnelUpgrade;
419 + tunnel.onerror = function (e) { sendConsoleText('ERROR: ' + JSON.stringify(e)); }
420 + tunnel.sessionid = data.sessionid;
421 + tunnel.rights = data.rights;
422 + tunnel.state = 0;
423 + tunnel.url = xurl;
424 + tunnel.protocol = 0;
425 + tunnel.tcpaddr = data.tcpaddr;
426 + tunnel.tcpport = data.tcpport;
427 + tunnel.end();
428 + // Put the tunnel in the tunnels list
429 + var index = nextTunnelIndex++;
430 + tunnel.index = index;
431 + tunnels[index] = tunnel;
432 +
433 + //sendConsoleText('New tunnel connection #' + index + ': ' + tunnel.url + ', rights: ' + tunnel.rights, data.sessionid);
434 + }
435 + }
436 + break;
437 + }
438 + case 'ps': {
439 + // Return the list of running processes
440 + if (data.sessionid) {
441 + processManager.getProcesses(function (plist) { mesh.SendCommand({ "action": "msg", "type": "ps", "value": JSON.stringify(plist), "sessionid": data.sessionid }); });
442 + }
443 + break;
444 + }
445 + case 'pskill': {
446 + // Kill a process
447 + if (data.value) {
448 + try { process.kill(data.value); } catch (e) { sendConsoleText(JSON.stringify(e)); }
449 + }
450 + break;
451 + }
452 + case 'openUrl': {
453 + // Open a local web browser and return success/fail
454 + sendConsoleText('OpenURL: ' + data.url);
455 + if (data.url) { mesh.SendCommand({ "action": "msg", "type":"openUrl", "url": data.url, "sessionid": data.sessionid, "success": (openUserDesktopUrl(data.url) != null) }); }
456 + break;
457 + }
458 + }
459 + break;
460 + }
461 + case 'wakeonlan': {
462 + // Send wake-on-lan on all interfaces for all MAC addresses in data.macs array. The array is a list of HEX MAC addresses.
463 + sendConsoleText('Server requesting wake-on-lan for: ' + data.macs.join(', '));
464 + for (var i in data.macs) { sendWakeOnLan(data.macs[i]); }
465 + break;
466 + }
467 + case 'poweraction': {
468 + // Server telling us to execute a power action
469 + if ((mesh.ExecPowerState != undefined) && (data.actiontype)) {
470 + var forced = 0;
471 + if (data.forced == 1) { forced = 1; }
472 + data.actiontype = parseInt(data.actiontype);
473 + sendConsoleText('Performing power action=' + data.actiontype + ', forced=' + forced + '.');
474 + var r = mesh.ExecPowerState(data.actiontype, forced);
475 + sendConsoleText('ExecPowerState returned code: ' + r);
476 + }
477 + break;
478 + }
479 + case 'iplocation': {
480 + // 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
481 + getIpLocationData(function (location) { mesh.SendCommand({ "action": "iplocation", "type": "publicip", "value": location }); });
482 + break;
483 + }
484 + case 'toast': {
485 + // Display a toast message
486 + if (data.title && data.msg) { require('toaster').Toast(data.title, data.msg); }
487 + break;
488 + }
489 + case 'openUrl': {
490 + // Open a local web browser and return success/fail
491 + sendConsoleText('OpenURL: ' + data.url);
492 + if (data.url) { mesh.SendCommand({ "action": "openUrl", "url": data.url, "sessionid": data.sessionid, "success": (openUserDesktopUrl(data.url) != null) }); }
493 + break;
494 + }
495 + }
496 + }
497 + }
498 +
499 + // Called when a file changed in the file system
500 + /*
501 + function onFileWatcher(a, b) {
502 + console.log('onFileWatcher', a, b, this.path);
503 + var response = getDirectoryInfo(this.path);
504 + if ((response != undefined) && (response != null)) { this.tunnel.s.write(JSON.stringify(response)); }
505 + }
506 + */
507 +
508 + // Get a formated response for a given directory path
509 + function getDirectoryInfo(reqpath) {
510 + var response = { path: reqpath, dir: [] };
511 + if (((reqpath == undefined) || (reqpath == '')) && (process.platform == 'win32')) {
512 + // List all the drives in the root, or the root itself
513 + var results = null;
514 + try { results = fs.readDrivesSync(); } catch (e) { } // TODO: Anyway to get drive total size and free space? Could draw a progress bar.
515 + if (results != null) {
516 + for (var i = 0; i < results.length; ++i) {
517 + var drive = { n: results[i].name, t: 1 };
518 + if (results[i].type == 'REMOVABLE') { drive.dt = 'removable'; } // TODO: See if this is USB/CDROM or something else, we can draw icons.
519 + response.dir.push(drive);
520 + }
521 + }
522 + } else {
523 + // List all the files and folders in this path
524 + if (reqpath == '') { reqpath = '/'; }
525 + var results = null, xpath = obj.path.join(reqpath, '*');
526 + //if (process.platform == "win32") { xpath = xpath.split('/').join('\\'); }
527 + try { results = fs.readdirSync(xpath); } catch (e) { }
528 + if (results != null) {
529 + for (var i = 0; i < results.length; ++i) {
530 + if ((results[i] != '.') && (results[i] != '..')) {
531 + var stat = null, p = obj.path.join(reqpath, results[i]);
532 + //if (process.platform == "win32") { p = p.split('/').join('\\'); }
533 + try { stat = fs.statSync(p); } catch (e) { } // TODO: Get file size/date
534 + if ((stat != null) && (stat != undefined)) {
535 + if (stat.isDirectory() == true) {
536 + response.dir.push({ n: results[i], t: 2, d: stat.mtime });
537 + } else {
538 + response.dir.push({ n: results[i], t: 3, s: stat.size, d: stat.mtime });
539 + }
540 + }
541 + }
542 + }
543 + }
544 + }
545 + return response;
546 + }
547 +
548 + // Tunnel callback operations
549 + function onTunnelUpgrade(response, s, head) {
550 + this.s = s;
551 + s.httprequest = this;
552 + s.end = onTunnelClosed;
553 + s.tunnel = this;
554 +
555 + if (this.tcpport != null) {
556 + // This is a TCP relay connection, pause now and try to connect to the target.
557 + s.pause();
558 + s.data = onTcpRelayServerTunnelData;
559 + var connectionOptions = { port: parseInt(this.tcpport) };
560 + if (this.tcpaddr != null) { connectionOptions.host = this.tcpaddr; } else { connectionOptions.host = '127.0.0.1'; }
561 + s.tcprelay = net.createConnection(connectionOptions, onTcpRelayTargetTunnelConnect);
562 + s.tcprelay.peerindex = this.index;
563 + } else {
564 + // This is a normal connect for KVM/Terminal/Files
565 + s.data = onTunnelData;
566 + }
567 + }
568 +
569 + // Called when the TCP relay target is connected
570 + function onTcpRelayTargetTunnelConnect() {
571 + var peerTunnel = tunnels[this.peerindex];
572 + this.pipe(peerTunnel.s); // Pipe Target --> Server
573 + peerTunnel.s.first = true;
574 + peerTunnel.s.resume();
575 + }
576 +
577 + // Called when we get data from the server for a TCP relay (We have to skip the first received 'c' and pipe the rest)
578 + function onTcpRelayServerTunnelData(data) {
579 + if (this.first == true) { this.first = false; this.pipe(this.tcprelay); } // Pipe Server --> Target
580 + }
581 +
582 + function onTunnelClosed() {
583 + if (tunnels[this.httprequest.index] == null) return; // Stop duplicate calls.
584 + //sendConsoleText("Tunnel #" + this.httprequest.index + " closed.", this.httprequest.sessionid);
585 + delete tunnels[this.httprequest.index];
586 +
587 + /*
588 + // Close the watcher if required
589 + if (this.httprequest.watcher != undefined) {
590 + //console.log('Closing watcher: ' + this.httprequest.watcher.path);
591 + //this.httprequest.watcher.close(); // TODO: This line causes the agent to crash!!!!
592 + delete this.httprequest.watcher;
593 + }
594 + */
595 +
596 + // If there is a upload or download active on this connection, close the file
597 + if (this.httprequest.uploadFile) { fs.closeSync(this.httprequest.uploadFile); this.httprequest.uploadFile = undefined; }
598 + if (this.httprequest.downloadFile) { fs.closeSync(this.httprequest.downloadFile); this.httprequest.downloadFile = undefined; }
599 +
600 + // Clean up WebRTC
601 + if (this.webrtc != null) {
602 + 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; }
603 + if (this.webrtc.websocket) { delete this.webrtc.websocket; }
604 + try { this.webrtc.close(); } catch (e) { }
605 + this.webrtc.removeAllListeners('connected');
606 + this.webrtc.removeAllListeners('disconnected');
607 + this.webrtc.removeAllListeners('dataChannel');
608 + delete this.webrtc;
609 + }
610 +
611 + // Clean up WebSocket
612 + this.removeAllListeners('data');
613 + }
614 + function onTunnelSendOk() { /*sendConsoleText("Tunnel #" + this.index + " SendOK.", this.sessionid);*/ }
615 + function onTunnelData(data) {
616 + //console.log("OnTunnelData");
617 + //sendConsoleText('OnTunnelData, ' + data.length + ', ' + typeof data + ', ' + data);
618 +
619 + // If this is upload data, save it to file
620 + if (this.httprequest.uploadFile) {
621 + 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.
622 + this.write(new Buffer(JSON.stringify({ action: 'uploadack', reqid: this.httprequest.uploadFileid }))); // Ask for more data
623 + return;
624 + }
625 + /*
626 + // If this is a download, send more of the file
627 + if (this.httprequest.downloadFile) {
628 + var buf = new Buffer(4096);
629 + var len = fs.readSync(this.httprequest.downloadFile, buf, 0, 4096, null);
630 + this.httprequest.downloadFilePtr += len;
631 + if (len > 0) { this.write(buf.slice(0, len)); } else { fs.closeSync(this.httprequest.downloadFile); this.httprequest.downloadFile = undefined; this.end(); }
632 + return;
633 + }
634 + */
635 +
636 + if (this.httprequest.state == 0) {
637 + // Check if this is a relay connection
638 + if (data == 'c') { this.httprequest.state = 1; /*sendConsoleText("Tunnel #" + this.httprequest.index + " now active", this.httprequest.sessionid);*/ }
639 + } else {
640 + // Handle tunnel data
641 + if (this.httprequest.protocol == 0) { // 1 = SOL, 2 = KVM, 3 = IDER, 4 = Files, 5 = FileTransfer
642 + // Take a look at the protocol
643 + this.httprequest.protocol = parseInt(data);
644 + if (typeof this.httprequest.protocol != 'number') { this.httprequest.protocol = 0; }
645 + if (this.httprequest.protocol == 1) {
646 + // Check user access rights
647 + if ((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) {
648 + // Disengage this tunnel, user does not have the rights to do this!!
649 + this.httprequest.protocol = 999999;
650 + sendConsoleText('Error: No Remote Control Rights.');
651 + return;
652 + }
653 +
654 + // Remote terminal using native pipes
655 + if (process.platform == "win32") {
656 + this.httprequest.process = childProcess.execFile("%windir%\\system32\\cmd.exe");
657 + } else {
658 + this.httprequest.process = childProcess.execFile("/bin/sh", ["sh"], { type: childProcess.SpawnTypes.TERM });
659 + }
660 +
661 + this.httprequest.process.tunnel = this;
662 + this.httprequest.process.on('exit', function (ecode, sig) { this.tunnel.end(); });
663 + this.httprequest.process.stderr.on('data', function (chunk) { this.parent.tunnel.write(chunk); });
664 + this.httprequest.process.stdout.pipe(this, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
665 + this.pipe(this.httprequest.process.stdin, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
666 + this.prependListener('end', function () { this.httprequest.process.kill(); });
667 + this.removeAllListeners('data');
668 + this.on('data', onTunnelControlData);
669 + //this.write('MeshCore Terminal Hello');
670 + if (process.platform == 'linux') { this.httprequest.process.stdin.write("stty erase ^H\nalias ls='ls --color=auto'\nclear\n"); }
671 + } else if (this.httprequest.protocol == 2)
672 + {
673 + // Check user access rights
674 + if (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) && ((this.httprequest.rights & MESHRIGHT_REMOTEVIEW) == 0)) {
675 + // Disengage this tunnel, user does not have the rights to do this!!
676 + this.httprequest.protocol = 999999;
677 + sendConsoleText('Error: No Remote Control Rights.');
678 + return;
679 + }
680 +
681 + // Remote desktop using native pipes
682 + this.httprequest.desktop = { state: 0, kvm: mesh.getRemoteDesktopStream(), tunnel: this };
683 + this.httprequest.desktop.kvm.parent = this.httprequest.desktop;
684 + this.desktop = this.httprequest.desktop;
685 +
686 + // Display a toast message
687 + //require('toaster').Toast('MeshCentral', 'Remote Desktop Control Started.');
688 +
689 + this.end = function () {
690 + --this.desktop.kvm.connectionCount;
691 + this.unpipe(this.httprequest.desktop.kvm);
692 + this.httprequest.desktop.kvm.unpipe(this);
693 + if (this.desktop.kvm.connectionCount == 0) {
694 + // Display a toast message
695 + //require('toaster').Toast('MeshCentral', 'Remote Desktop Control Ended.');
696 + this.httprequest.desktop.kvm.end();
697 + }
698 + };
699 + if (this.httprequest.desktop.kvm.hasOwnProperty("connectionCount")) { this.httprequest.desktop.kvm.connectionCount++; } else { this.httprequest.desktop.kvm.connectionCount = 1; }
700 +
701 + //sendConsoleText('KVM Rights: ' + this.httprequest.rights);
702 + if ((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) != 0) {
703 + // If we have remote control rights, pipe the KVM input
704 + this.pipe(this.httprequest.desktop.kvm, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. Pipe the Browser --> KVM input.
705 + } else {
706 + // We need to only pipe non-mouse & non-keyboard inputs.
707 + // TODO!!!
708 + }
709 +
710 + this.httprequest.desktop.kvm.pipe(this, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text. Pipe the KVM --> Browser images.
711 + this.removeAllListeners('data');
712 + this.on('data', onTunnelControlData);
713 + //this.write('MeshCore KVM Hello!1');
714 + } else if (this.httprequest.protocol == 5) {
715 + // Check user access rights
716 + if ((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) {
717 + // Disengage this tunnel, user does not have the rights to do this!!
718 + this.httprequest.protocol = 999999;
719 + sendConsoleText('Error: No Remote Control Rights.');
720 + return;
721 + }
722 +
723 + // Setup files
724 + // NOP
725 + }
726 + } else if (this.httprequest.protocol == 1) {
727 + // Send data into terminal stdin
728 + //this.write(data); // Echo back the keys (Does not seem to be a good idea)
729 + this.httprequest.process.write(data);
730 + } else if (this.httprequest.protocol == 2) {
731 + // Send data into remote desktop
732 + if (this.httprequest.desktop.state == 0) {
733 + this.write(new Buffer(String.fromCharCode(0x11, 0xFE, 0x00, 0x00, 0x4D, 0x45, 0x53, 0x48, 0x00, 0x00, 0x00, 0x00, 0x02)));
734 + this.httprequest.desktop.state = 1;
735 + } else {
736 + this.httprequest.desktop.write(data);
737 + }
738 + } else if (this.httprequest.protocol == 5) {
739 + // Process files commands
740 + var cmd = null;
741 + try { cmd = JSON.parse(data); } catch (e) { };
742 + if (cmd == null) { return; }
743 + if ((cmd.ctrlChannel == '102938') || ((cmd.type == 'offer') && (cmd.sdp != null))) { onTunnelControlData(cmd, this); return; } // If this is control data, handle it now.
744 + if (cmd.action == undefined) { return; }
745 + //sendConsoleText('CMD: ' + JSON.stringify(cmd));
746 +
747 + if ((cmd.path != null) && (process.platform != 'win32') && (cmd.path[0] != '/')) { cmd.path = '/' + cmd.path; } // Add '/' to paths on non-windows
748 + //console.log(objToString(cmd, 0, ' '));
749 + switch (cmd.action) {
750 + case 'ls': {
751 + /*
752 + // Close the watcher if required
753 + var samepath = ((this.httprequest.watcher != undefined) && (cmd.path == this.httprequest.watcher.path));
754 + if ((this.httprequest.watcher != undefined) && (samepath == false)) {
755 + //console.log('Closing watcher: ' + this.httprequest.watcher.path);
756 + //this.httprequest.watcher.close(); // TODO: This line causes the agent to crash!!!!
757 + delete this.httprequest.watcher;
758 + }
759 + */
760 +
761 + // Send the folder content to the browser
762 + var response = getDirectoryInfo(cmd.path);
763 + if (cmd.reqid != undefined) { response.reqid = cmd.reqid; }
764 + this.write(new Buffer(JSON.stringify(response)));
765 +
766 + /*
767 + // Start the directory watcher
768 + if ((cmd.path != '') && (samepath == false)) {
769 + var watcher = fs.watch(cmd.path, onFileWatcher);
770 + watcher.tunnel = this.httprequest;
771 + watcher.path = cmd.path;
772 + this.httprequest.watcher = watcher;
773 + //console.log('Starting watcher: ' + this.httprequest.watcher.path);
774 + }
775 + */
776 + break;
777 + }
778 + case 'mkdir': {
779 + // Create a new empty folder
780 + fs.mkdirSync(cmd.path);
781 + break;
782 + }
783 + case 'rm': {
784 + // Delete, possibly recursive delete
785 + for (var i in cmd.delfiles) {
786 + try { deleteFolderRecursive(obj.path.join(cmd.path, cmd.delfiles[i]), cmd.rec); } catch (e) { }
787 + }
788 + break;
789 + }
790 + case 'rename': {
791 + // Rename a file or folder
792 + var oldfullpath = obj.path.join(cmd.path, cmd.oldname);
793 + var newfullpath = obj.path.join(cmd.path, cmd.newname);
794 + try { fs.renameSync(oldfullpath, newfullpath); } catch (e) { console.log(e); }
795 + break;
796 + }
797 + case 'download': {
798 + // Download a file
799 + var sendNextBlock = 0;
800 + if (cmd.sub == 'start') { // Setup the download
801 + if (this.filedownload != null) { this.write({ action: 'download', sub: 'cancel', id: this.filedownload.id }); delete this.filedownload; }
802 + this.filedownload = { id: cmd.id, path: cmd.path, ptr: 0 }
803 + 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; }
804 + if (this.filedownload) { this.write({ action: 'download', sub: 'start', id: cmd.id }); }
805 + } else if ((this.filedownload != null) && (cmd.id == this.filedownload.id)) { // Download commands
806 + if (cmd.sub == 'startack') { sendNextBlock = 8; } else if (cmd.sub == 'stop') { delete this.filedownload; } else if (cmd.sub == 'ack') { sendNextBlock = 1; }
807 + }
808 + // Send the next download block(s)
809 + while (sendNextBlock > 0) {
810 + sendNextBlock--;
811 + var buf = new Buffer(4096);
812 + var len = fs.readSync(this.filedownload.f, buf, 4, 4092, null);
813 + this.filedownload.ptr += len;
814 + if (len < 4092) { buf.writeInt32BE(0x01000001, 0); fs.closeSync(this.filedownload.f); delete this.filedownload; sendNextBlock = 0; } else { buf.writeInt32BE(0x01000000, 0); }
815 + this.write(buf.slice(0, len + 4)); // Write as binary
816 + }
817 + break;
818 + }
819 + /*
820 + case 'download': {
821 + // Packet download of a file, agent to browser
822 + if (cmd.path == undefined) break;
823 + var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path;
824 + //console.log('Download: ' + filepath);
825 + try { this.httprequest.downloadFile = fs.openSync(filepath, 'rbN'); } catch (e) { this.write(new Buffer(JSON.stringify({ action: 'downloaderror', reqid: cmd.reqid }))); break; }
826 + this.httprequest.downloadFileId = cmd.reqid;
827 + this.httprequest.downloadFilePtr = 0;
828 + if (this.httprequest.downloadFile) { this.write(new Buffer(JSON.stringify({ action: 'downloadstart', reqid: this.httprequest.downloadFileId }))); }
829 + break;
830 + }
831 + case 'download2': {
832 + // Stream download of a file, agent to browser
833 + if (cmd.path == undefined) break;
834 + var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path;
835 + try { this.httprequest.downloadFile = fs.createReadStream(filepath, { flags: 'rbN' }); } catch (e) { console.log(e); }
836 + this.httprequest.downloadFile.pipe(this);
837 + this.httprequest.downloadFile.end = function () { }
838 + break;
839 + }
840 + */
841 + case 'upload': {
842 + // Upload a file, browser to agent
843 + if (this.httprequest.uploadFile != undefined) { fs.closeSync(this.httprequest.uploadFile); this.httprequest.uploadFile = undefined; }
844 + if (cmd.path == undefined) break;
845 + var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path;
846 + try { this.httprequest.uploadFile = fs.openSync(filepath, 'wbN'); } catch (e) { this.write(new Buffer(JSON.stringify({ action: 'uploaderror', reqid: cmd.reqid }))); break; }
847 + this.httprequest.uploadFileid = cmd.reqid;
848 + if (this.httprequest.uploadFile) { this.write(new Buffer(JSON.stringify({ action: 'uploadstart', reqid: this.httprequest.uploadFileid }))); }
849 + break;
850 + }
851 + case 'copy': {
852 + // Copy a bunch of files from scpath to dspath
853 + for (var i in cmd.names) {
854 + var sc = obj.path.join(cmd.scpath, cmd.names[i]), ds = obj.path.join(cmd.dspath, cmd.names[i]);
855 + if (sc != ds) { try { fs.copyFileSync(sc, ds); } catch (e) { } }
856 + }
857 + break;
858 + }
859 + case 'move': {
860 + // Move a bunch of files from scpath to dspath
861 + for (var i in cmd.names) {
862 + var sc = obj.path.join(cmd.scpath, cmd.names[i]), ds = obj.path.join(cmd.dspath, cmd.names[i]);
863 + if (sc != ds) { try { fs.copyFileSync(sc, ds); fs.unlinkSync(sc); } catch (e) { } }
864 + }
865 + break;
866 + }
867 + }
868 + }
869 + //sendConsoleText("Got tunnel #" + this.httprequest.index + " data: " + data, this.httprequest.sessionid);
870 + }
871 + }
872 +
873 + // Called when receiving control data on WebRTC
874 + function onTunnelWebRTCControlData(data) {
875 + if (typeof data != 'string') return;
876 + var obj;
877 + try { obj = JSON.parse(data); } catch (e) { sendConsoleText('Invalid control JSON on WebRTC: ' + data); return; }
878 + if (obj.type == 'close') {
879 + //sendConsoleText('Tunnel #' + this.xrtc.websocket.tunnel.index + ' WebRTC control close');
880 + try { this.close(); } catch (e) { }
881 + try { this.xrtc.close(); } catch (e) { }
882 + }
883 + }
884 +
885 + // Called when receiving control data on websocket
886 + function onTunnelControlData(data, ws) {
887 + var obj;
888 + if (ws == null) { ws = this; }
889 + if (typeof data == 'string') { try { obj = JSON.parse(data); } catch (e) { sendConsoleText('Invalid control JSON: ' + data); return; } }
890 + else if (typeof data == 'object') { obj = data; } else { return; }
891 + //sendConsoleText('onTunnelControlData(' + ws.httprequest.protocol + '): ' + JSON.stringify(data));
892 + //console.log('onTunnelControlData: ' + JSON.stringify(data));
893 +
894 + if (obj.action) {
895 + switch (obj.action) {
896 + case 'lock': {
897 + // Lock the current user out of the desktop
898 + try {
899 + if (process.platform == 'win32') {
900 + var child = require('child_process');
901 + child.execFile(process.env['windir'] + '\\system32\\cmd.exe', ['/c', 'RunDll32.exe user32.dll,LockWorkStation'], { type: 1 });
902 + }
903 + } catch (e) { }
904 + break;
905 + }
906 + }
907 + return;
908 + }
909 +
910 + if (obj.type == 'close') {
911 + // We received the close on the websocket
912 + //sendConsoleText('Tunnel #' + ws.tunnel.index + ' WebSocket control close');
913 + try { ws.close(); } catch (e) { }
914 + } else if (obj.type == 'webrtc0') { // Browser indicates we can start WebRTC switch-over.
915 + if (ws.httprequest.protocol == 1) { // Terminal
916 + // 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
917 + ws.httprequest.process.stdout.unpipe(ws);
918 + ws.httprequest.process.stderr.unpipe(ws);
919 + } else if (ws.httprequest.protocol == 2) { // Desktop
920 + // 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
921 + ws.httprequest.desktop.kvm.unpipe(ws);
922 + } else {
923 + // Switch things around so all WebRTC data goes to onTunnelData().
924 + ws.rtcchannel.httprequest = ws.httprequest;
925 + ws.rtcchannel.removeAllListeners('data');
926 + ws.rtcchannel.on('data', onTunnelData);
927 + }
928 + ws.write("{\"ctrlChannel\":\"102938\",\"type\":\"webrtc1\"}"); // End of data marker
929 + } else if (obj.type == 'webrtc1') {
930 + if (ws.httprequest.protocol == 1) { // Terminal
931 + // Switch the user input from websocket to webrtc at this point.
932 + ws.unpipe(ws.httprequest.process.stdin);
933 + ws.rtcchannel.pipe(ws.httprequest.process.stdin, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
934 + ws.resume(); // Resume the websocket to keep receiving control data
935 + } else if (ws.httprequest.protocol == 2) { // Desktop
936 + // Switch the user input from websocket to webrtc at this point.
937 + ws.unpipe(ws.httprequest.desktop.kvm);
938 + try { ws.webrtc.rtcchannel.pipe(ws.httprequest.desktop.kvm, { dataTypeSkip: 1, end: false }); } catch (e) { sendConsoleText('EX2'); } // 0 = Binary, 1 = Text.
939 + ws.resume(); // Resume the websocket to keep receiving control data
940 + }
941 + ws.write("{\"ctrlChannel\":\"102938\",\"type\":\"webrtc2\"}"); // Indicates we will no longer get any data on websocket, switching to WebRTC at this point.
942 + } else if (obj.type == 'webrtc2') {
943 + // Other side received websocket end of data marker, start sending data on WebRTC channel
944 + if (ws.httprequest.protocol == 1) { // Terminal
945 + ws.httprequest.process.stdout.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
946 + ws.httprequest.process.stderr.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
947 + } else if (ws.httprequest.protocol == 2) { // Desktop
948 + ws.httprequest.desktop.kvm.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
949 + }
950 + } else if (obj.type == 'offer') {
951 + // This is a WebRTC offer.
952 + ws.webrtc = rtc.createConnection();
953 + ws.webrtc.websocket = ws;
954 + ws.webrtc.on('connected', function () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC connected');*/ });
955 + ws.webrtc.on('disconnected', function () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC disconnected');*/ });
956 + ws.webrtc.on('dataChannel', function (rtcchannel) {
957 + //sendConsoleText('WebRTC Datachannel open, protocol: ' + this.websocket.httprequest.protocol);
958 + rtcchannel.xrtc = this;
959 + rtcchannel.websocket = this.websocket;
960 + this.rtcchannel = rtcchannel;
961 + this.websocket.rtcchannel = rtcchannel;
962 + this.websocket.rtcchannel.on('data', onTunnelWebRTCControlData);
963 + this.websocket.rtcchannel.on('end', function () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC data channel closed');*/ });
964 + this.websocket.write("{\"ctrlChannel\":\"102938\",\"type\":\"webrtc0\"}"); // Indicate we are ready for WebRTC switch-over.
965 + });
966 + var sdp = null;
967 + try { sdp = ws.webrtc.setOffer(obj.sdp); } catch (ex) { }
968 + if (sdp != null) { ws.write({ type: 'answer', ctrlChannel: '102938', sdp: sdp }); }
969 + }
970 + }
971 +
972 + // Console state
973 + var consoleWebSockets = {};
974 + var consoleHttpRequest = null;
975 +
976 + // Console HTTP response
977 + function consoleHttpResponse(response) {
978 + response.data = function (data) { sendConsoleText(rstr2hex(buf2rstr(data)), this.sessionid); consoleHttpRequest = null; }
979 + response.close = function () { sendConsoleText('httprequest.response.close', this.sessionid); consoleHttpRequest = null; }
980 + };
981 +
982 + // Open a web browser to a specified URL on current user's desktop
983 + function openUserDesktopUrl(url) {
984 + var child = null;
985 + try {
986 + switch (process.platform) {
987 + case 'win32':
988 + child = require('child_process').execFile(process.env['windir'] + '\\system32\\cmd.exe', ["/c", "start", url], { type: childProcess.SpawnTypes.USER });
989 + break;
990 + case 'linux':
991 + child = require('child_process').execFile('/usr/bin/xdg-open', ['xdg-open', url], { type: require('child_process').SpawnTypes.DETACHED, uid: require('user-sessions').consoleUid() });
992 + break;
993 + case 'darwin':
994 + child = require('child_process').execFile('/usr/bin/open', ['open', url], { uid: require('user-sessions').consoleUid() });
995 + break;
996 + }
997 + } catch (ex) { }
998 + return child;
999 + }
1000 +
1001 + // Process a mesh agent console command
1002 + function processConsoleCommand(cmd, args, rights, sessionid) {
1003 + try {
1004 + var response = null;
1005 + switch (cmd) {
1006 + case 'help': { // Displays available commands
1007 + 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.';
1008 + break;
1009 + }
1010 + /*
1011 + case 'border':
1012 + {
1013 + if ((args['_'].length == 1) && (args['_'][0] == 'on')) {
1014 + if (meshCoreObj.users.length > 0) {
1015 + obj.borderManager.Start(meshCoreObj.users[0]);
1016 + response = 'Border blinking is on.';
1017 + } else {
1018 + response = 'Cannot turn on border blinking, no logged in users.';
1019 + }
1020 + } else if ((args['_'].length == 1) && (args['_'][0] == 'off')) {
1021 + obj.borderManager.Stop();
1022 + response = 'Border blinking is off.';
1023 + } else {
1024 + response = 'Proper usage: border "on|off"'; // Display correct command usage
1025 + }
1026 + }
1027 + break;
1028 + */
1029 + case 'openurl': {
1030 + if (args['_'].length != 1) { response = 'Proper usage: openurl (url)'; } // Display usage
1031 + else { if (openUserDesktopUrl(args['_'][0]) == null) { response = 'Failed.'; } else { response = 'Success.'; } }
1032 + break;
1033 + }
1034 + case 'users': {
1035 + if (meshCoreObj.users == null) { response = 'Active users are unknown.'; } else { response = 'Active Users: ' + meshCoreObj.users.join(', ') + '.'; }
1036 + break;
1037 + }
1038 + case 'toast': {
1039 + if (process.platform == 'win32') {
1040 + if (args['_'].length < 1) { response = 'Proper usage: toast "message"'; } else {
1041 + require('toaster').Toast('MeshCentral', args['_'][0]);
1042 + response = 'ok';
1043 + }
1044 + } else {
1045 + response = 'Only supported on Windows.';
1046 + }
1047 + break;
1048 + }
1049 + case 'setdebug': {
1050 + if (args['_'].length < 1) { response = 'Proper usage: setdebug (target), 0 = Disabled, 1 = StdOut, 2 = This Console, * = All Consoles, 4 = WebLog, 8 = Logfile'; } // Display usage
1051 + else { if (args['_'][0] == '*') { console.setDestination(2); } else { console.setDestination(parseInt(args['_'][0]), sessionid); } }
1052 + break;
1053 + }
1054 + case 'ps': {
1055 + processManager.getProcesses(function (plist) {
1056 + var x = '';
1057 + for (var i in plist) { x += i + ', ' + plist[i].cmd + ((plist[i].user) ? (', ' + plist[i].user):'') + '\r\n'; }
1058 + sendConsoleText(x, sessionid);
1059 + });
1060 + break;
1061 + }
1062 + case 'kill': {
1063 + if ((args['_'].length < 1)) {
1064 + response = 'Proper usage: kill [pid]'; // Display correct command usage
1065 + } else {
1066 + process.kill(parseInt(args['_'][0]));
1067 + response = 'Killed process ' + args['_'][0] + '.';
1068 + }
1069 + break;
1070 + }
1071 + case 'smbios': {
1072 + if (SMBiosTables == null) { response = 'SMBios tables not available.'; } else { response = objToString(SMBiosTables, 0, ' ', true); }
1073 + break;
1074 + }
1075 + case 'rawsmbios': {
1076 + if (SMBiosTablesRaw == null) { response = 'SMBios tables not available.'; } else {
1077 + response = '';
1078 + for (var i in SMBiosTablesRaw) {
1079 + var header = false;
1080 + for (var j in SMBiosTablesRaw[i]) {
1081 + if (SMBiosTablesRaw[i][j].length > 0) {
1082 + if (header == false) { response += ('Table type #' + i + ((require('smbios').smTableTypes[i] == null) ? '' : (', ' + require('smbios').smTableTypes[i]))) + '\r\n'; header = true; }
1083 + response += (' ' + SMBiosTablesRaw[i][j].toString('hex')) + '\r\n';
1084 + }
1085 + }
1086 + }
1087 + }
1088 + break;
1089 + }
1090 + case 'eval': { // Eval JavaScript
1091 + if (args['_'].length < 1) {
1092 + response = 'Proper usage: eval "JavaScript code"'; // Display correct command usage
1093 + } else {
1094 + response = JSON.stringify(mesh.eval(args['_'][0]));
1095 + }
1096 + break;
1097 + }
1098 + case 'notify': { // Send a notification message to the mesh
1099 + if (args['_'].length != 1) {
1100 + response = 'Proper usage: notify "message" [--session]'; // Display correct command usage
1101 + } else {
1102 + var notification = { "action": "msg", "type": "notify", "value": args['_'][0], "tag": "console" };
1103 + if (args.session) { notification.sessionid = sessionid; } // If "--session" is specified, notify only this session, if not, the server will notify the mesh
1104 + mesh.SendCommand(notification); // no sessionid or userid specified, notification will go to the entire mesh
1105 + response = 'ok';
1106 + }
1107 + break;
1108 + }
1109 + case 'info': { // Return information about the agent and agent core module
1110 + 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 + '.';
1111 + if (amtLmsState >= 0) { response += '\r\nBuilt-in LMS: ' + ['Disabled', 'Connecting..', 'Connected'][amtLmsState] + '.'; }
1112 + if (meshCoreObj.osdesc) { response += '\r\nOS: ' + meshCoreObj.osdesc + '.'; }
1113 + response += '\r\nModules: ' + addedModules.join(', ') + '.';
1114 + response += '\r\nServer Connection: ' + mesh.isControlChannelConnected + ', State: ' + meshServerConnectionState + '.';
1115 + response += '\r\lastMeInfo: ' + lastMeInfo + '.';
1116 + var oldNodeId = db.Get('OldNodeId');
1117 + if (oldNodeId != null) { response += '\r\nOldNodeID: ' + oldNodeId + '.'; }
1118 + if (process.platform != 'win32') { response += '\r\nX11 support: ' + require('monitor-info').kvm_x11_support + '.'; }
1119 + break;
1120 + }
1121 + case 'osinfo': { // Return the operating system information
1122 + var i = 1;
1123 + if (args['_'].length > 0) { i = parseInt(args['_'][0]); if (i > 8) { i = 8; } response = 'Calling ' + i + ' times.'; }
1124 + for (var j = 0; j < i; j++) {
1125 + var pr = require('os').name();
1126 + pr.sessionid = sessionid;
1127 + pr.then(function (v) { sendConsoleText("OS: " + v, this.sessionid); });
1128 + }
1129 + break;
1130 + }
1131 + case 'sendcaps': { // Send capability flags to the server
1132 + if (args['_'].length == 0) {
1133 + response = 'Proper usage: sendcaps (number)'; // Display correct command usage
1134 + } else {
1135 + meshCoreObj.caps = parseInt(args['_'][0]);
1136 + mesh.SendCommand(meshCoreObj);
1137 + response = JSON.stringify(meshCoreObj);
1138 + }
1139 + break;
1140 + }
1141 + case 'sendosdesc': { // Send OS description
1142 + if (args['_'].length > 0) {
1143 + meshCoreObj.osdesc = args['_'][0];
1144 + mesh.SendCommand(meshCoreObj);
1145 + response = JSON.stringify(meshCoreObj);
1146 + } else {
1147 + response = 'Proper usage: sendosdesc [os description]'; // Display correct command usage
1148 + }
1149 + break;
1150 + }
1151 + case 'args': { // Displays parsed command arguments
1152 + response = 'args ' + objToString(args, 0, ' ', true);
1153 + break;
1154 + }
1155 + case 'print': { // Print a message on the mesh agent console, does nothing when running in the background
1156 + var r = [];
1157 + for (var i in args['_']) { r.push(args['_'][i]); }
1158 + console.log(r.join(' '));
1159 + response = 'Message printed on agent console.';
1160 + break;
1161 + }
1162 + case 'type': { // Returns the content of a file
1163 + if (args['_'].length == 0) {
1164 + response = 'Proper usage: type (filepath) [maxlength]'; // Display correct command usage
1165 + } else {
1166 + var max = 4096;
1167 + if ((args['_'].length > 1) && (typeof args['_'][1] == 'number')) { max = args['_'][1]; }
1168 + if (max > 4096) max = 4096;
1169 + var buf = new Buffer(max), fd = fs.openSync(args['_'][0], "r"), r = fs.readSync(fd, buf, 0, max); // Read the file content
1170 + response = buf.toString();
1171 + var i = response.indexOf('\n');
1172 + if ((i > 0) && (response[i - 1] != '\r')) { response = response.split('\n').join('\r\n'); }
1173 + if (r == max) response += '...';
1174 + fs.closeSync(fd);
1175 + }
1176 + break;
1177 + }
1178 + case 'dbkeys': { // Return all data store keys
1179 + response = JSON.stringify(db.Keys);
1180 + break;
1181 + }
1182 + case 'dbget': { // Return the data store value for a given key
1183 + if (db == null) { response = 'Database not accessible.'; break; }
1184 + if (args['_'].length != 1) {
1185 + response = 'Proper usage: dbget (key)'; // Display the value for a given database key
1186 + } else {
1187 + response = db.Get(args['_'][0]);
1188 + }
1189 + break;
1190 + }
1191 + case 'dbset': { // Set a data store key and value pair
1192 + if (db == null) { response = 'Database not accessible.'; break; }
1193 + if (args['_'].length != 2) {
1194 + response = 'Proper usage: dbset (key) (value)'; // Set a database key
1195 + } else {
1196 + var r = db.Put(args['_'][0], args['_'][1]);
1197 + response = 'Key set: ' + r;
1198 + }
1199 + break;
1200 + }
1201 + case 'dbcompact': { // Compact the data store
1202 + if (db == null) { response = 'Database not accessible.'; break; }
1203 + var r = db.Compact();
1204 + response = 'Database compacted: ' + r;
1205 + break;
1206 + }
1207 + case 'httpget': {
1208 + if (consoleHttpRequest != null) {
1209 + response = 'HTTP operation already in progress.';
1210 + } else {
1211 + if (args['_'].length != 1) {
1212 + response = 'Proper usage: httpget (url)';
1213 + } else {
1214 + var options = http.parseUri(args['_'][0]);
1215 + options.method = 'GET';
1216 + if (options == null) {
1217 + response = 'Invalid url.';
1218 + } else {
1219 + try { consoleHttpRequest = http.request(options, consoleHttpResponse); } catch (e) { response = 'Invalid HTTP GET request'; }
1220 + consoleHttpRequest.sessionid = sessionid;
1221 + if (consoleHttpRequest != null) {
1222 + consoleHttpRequest.end();
1223 + response = 'HTTPGET ' + options.protocol + '//' + options.host + ':' + options.port + options.path;
1224 + }
1225 + }
1226 + }
1227 + }
1228 + break;
1229 + }
1230 + case 'wslist': { // List all web sockets
1231 + response = '';
1232 + for (var i in consoleWebSockets) {
1233 + var httprequest = consoleWebSockets[i];
1234 + response += 'Websocket #' + i + ', ' + httprequest.url + '\r\n';
1235 + }
1236 + if (response == '') { response = 'no websocket sessions.'; }
1237 + break;
1238 + }
1239 + case 'wsconnect': { // Setup a web socket
1240 + if (args['_'].length == 0) {
1241 + response = 'Proper usage: wsconnect (url)\r\nFor example: wsconnect wss://localhost:443/meshrelay.ashx?id=abc'; // Display correct command usage
1242 + } else {
1243 + var httprequest = null;
1244 + try {
1245 + var options = http.parseUri(args['_'][0]);
1246 + options.rejectUnauthorized = 0;
1247 + httprequest = http.request(options);
1248 + } catch (e) { response = 'Invalid HTTP websocket request'; }
1249 + if (httprequest != null) {
1250 + httprequest.upgrade = onWebSocketUpgrade;
1251 + httprequest.onerror = function (e) { sendConsoleText('ERROR: ' + JSON.stringify(e)); }
1252 +
1253 + var index = 1;
1254 + while (consoleWebSockets[index]) { index++; }
1255 + httprequest.sessionid = sessionid;
1256 + httprequest.index = index;
1257 + httprequest.url = args['_'][0];
1258 + consoleWebSockets[index] = httprequest;
1259 + response = 'New websocket session #' + index;
1260 + }
1261 + }
1262 + break;
1263 + }
1264 + case 'wssend': { // Send data on a web socket
1265 + if (args['_'].length == 0) {
1266 + response = 'Proper usage: wssend (socketnumber)\r\n'; // Display correct command usage
1267 + for (var i in consoleWebSockets) {
1268 + var httprequest = consoleWebSockets[i];
1269 + response += 'Websocket #' + i + ', ' + httprequest.url + '\r\n';
1270 + }
1271 + } else {
1272 + var i = parseInt(args['_'][0]);
1273 + var httprequest = consoleWebSockets[i];
1274 + if (httprequest != undefined) {
1275 + httprequest.s.write(args['_'][1]);
1276 + response = 'ok';
1277 + } else {
1278 + response = 'Invalid web socket number';
1279 + }
1280 + }
1281 + break;
1282 + }
1283 + case 'wsclose': { // Close a websocket
1284 + if (args['_'].length == 0) {
1285 + response = 'Proper usage: wsclose (socketnumber)'; // Display correct command usage
1286 + } else {
1287 + var i = parseInt(args['_'][0]);
1288 + var httprequest = consoleWebSockets[i];
1289 + if (httprequest != undefined) {
1290 + if (httprequest.s != null) { httprequest.s.end(); } else { httprequest.end(); }
1291 + response = 'ok';
1292 + } else {
1293 + response = 'Invalid web socket number';
1294 + }
1295 + }
1296 + break;
1297 + }
1298 + case 'tunnels': { // Show the list of current tunnels
1299 + response = '';
1300 + for (var i in tunnels) { response += 'Tunnel #' + i + ', ' + tunnels[i].url + '\r\n'; }
1301 + if (response == '') { response = 'No websocket sessions.'; }
1302 + break;
1303 + }
1304 + case 'ls': { // Show list of files and folders
1305 + response = '';
1306 + var xpath = '*';
1307 + if (args['_'].length > 0) { xpath = obj.path.join(args['_'][0], '*'); }
1308 + response = 'List of ' + xpath + '\r\n';
1309 + var results = fs.readdirSync(xpath);
1310 + for (var i = 0; i < results.length; ++i) {
1311 + var stat = null, p = obj.path.join(args['_'][0], results[i]);
1312 + try { stat = fs.statSync(p); } catch (e) { }
1313 + if ((stat == null) || (stat == undefined)) {
1314 + response += (results[i] + "\r\n");
1315 + } else {
1316 + response += (results[i] + " " + ((stat.isDirectory()) ? "(Folder)" : "(File)") + "\r\n");
1317 + }
1318 + }
1319 + break;
1320 + }
1321 + case 'lsx': { // Show list of files and folders
1322 + response = objToString(getDirectoryInfo(args['_'][0]), 0, ' ', true);
1323 + break;
1324 + }
1325 + case 'lock': { // Lock the current user out of the desktop
1326 + 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'; }
1327 + else { response = 'Not supported on the platform'; }
1328 + break;
1329 + }
1330 + case 'amt': { // Show Intel AMT status
1331 + getAmtInfo(function (state) {
1332 + var resp = 'Intel AMT not detected.';
1333 + if (state != null) { resp = objToString(state, 0, ' ', true); }
1334 + sendConsoleText(resp, sessionid);
1335 + });
1336 + break;
1337 + }
1338 + case 'netinfo': { // Show network interface information
1339 + //response = objToString(mesh.NetInfo, 0, ' ');
1340 + var interfaces = require('os').networkInterfaces();
1341 + response = objToString(interfaces, 0, ' ', true);
1342 + break;
1343 + }
1344 + case 'netinfo2': { // Show network interface information
1345 + response = objToString(mesh.NetInfo, 0, ' ', true);
1346 + break;
1347 + }
1348 + case 'wakeonlan': { // Send wake-on-lan
1349 + if ((args['_'].length != 1) || (args['_'][0].length != 12)) {
1350 + response = 'Proper usage: wakeonlan [mac], for example "wakeonlan 010203040506".';
1351 + } else {
1352 + var count = sendWakeOnLan(args['_'][0]);
1353 + response = 'Sent wake-on-lan on ' + count + ' interface(s).';
1354 + }
1355 + break;
1356 + }
1357 + case 'sendall': { // Send a message to all consoles on this mesh
1358 + sendConsoleText(args['_'].join(' '));
1359 + break;
1360 + }
1361 + case 'power': { // Execute a power action on this computer
1362 + if (mesh.ExecPowerState == undefined) {
1363 + response = 'Power command not supported on this agent.';
1364 + } else {
1365 + if ((args['_'].length == 0) || (typeof args['_'][0] != 'number')) {
1366 + 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
1367 + } else {
1368 + var r = mesh.ExecPowerState(args['_'][0], args['_'][1]);
1369 + response = 'Power action executed with return code: ' + r + '.';
1370 + }
1371 + }
1372 + break;
1373 + }
1374 + case 'location': {
1375 + getIpLocationData(function (location) {
1376 + sendConsoleText(objToString({ "action": "iplocation", "type": "publicip", "value": location }, 0, ' '));
1377 + });
1378 + break;
1379 + }
1380 + case 'parseuri': {
1381 + response = JSON.stringify(http.parseUri(args['_'][0]));
1382 + break;
1383 + }
1384 + case 'scanwifi': {
1385 + if (wifiScanner != null) {
1386 + var wifiPresent = wifiScanner.hasWireless;
1387 + if (wifiPresent) { response = "Perfoming Wifi scan..."; wifiScanner.Scan(); } else { response = "Wifi absent."; }
1388 + } else { response = "Wifi module not present."; }
1389 + break;
1390 + }
1391 + case 'scanamt': {
1392 + if (amtscanner != null) {
1393 + if (args['_'].length != 1) {
1394 + 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
1395 + } else {
1396 + response = 'Scanning: ' + args['_'][0] + '...';
1397 + amtscanner.scan(args['_'][0], 2000, function (data) {
1398 + if (data.length > 0) {
1399 + var r = '', pstates = ['NotActivated', 'InActivation', 'Activated'];
1400 + for (var i in data) {
1401 + var x = data[i];
1402 + if (r != '') { r += '\r\n'; }
1403 + r += x.address + ' - Intel AMT v' + x.majorVersion + '.' + x.minorVersion;
1404 + if (x.provisioningState < 3) { r += (', ' + pstates[x.provisioningState]); }
1405 + if (x.provisioningState == 2) { r += (', ' + x.openPorts.join(', ')); }
1406 + r += '.';
1407 + }
1408 + } else {
1409 + r = 'No Intel AMT found.';
1410 + }
1411 + sendConsoleText(r);
1412 + });
1413 + }
1414 + } else { response = "Intel AMT scanner module not present."; }
1415 + break;
1416 + }
1417 + case 'modules': {
1418 + response = JSON.stringify(addedModules);
1419 + break;
1420 + }
1421 + default: { // This is an unknown command, return an error message
1422 + response = 'Unknown command \"' + cmd + '\", type \"help\" for list of avaialble commands.';
1423 + break;
1424 + }
1425 + }
1426 + } catch (e) { response = 'Command returned an exception error: ' + e; console.log(e); }
1427 + if (response != null) { sendConsoleText(response, sessionid); }
1428 + }
1429 +
1430 + // Send a mesh agent console command
1431 + function sendConsoleText(text, sessionid) {
1432 + if (typeof text == 'object') { text = JSON.stringify(text); }
1433 + mesh.SendCommand({ "action": "msg", "type": "console", "value": text, "sessionid": sessionid });
1434 + }
1435 +
1436 + // Called before the process exits
1437 + //process.exit = function (code) { console.log("Exit with code: " + code.toString()); }
1438 +
1439 + // Called when the server connection state changes
1440 + function handleServerConnection(state) {
1441 + meshServerConnectionState = state;
1442 + if (meshServerConnectionState == 0) {
1443 + // Server disconnected
1444 + if (selfInfoUpdateTimer != null) { clearInterval(selfInfoUpdateTimer); selfInfoUpdateTimer = null; }
1445 + lastSelfInfo = null;
1446 + } else {
1447 + // Server connected, send mesh core information
1448 + var oldNodeId = db.Get('OldNodeId');
1449 + if (oldNodeId != null) { mesh.SendCommand({ action: 'mc1migration', oldnodeid: oldNodeId }); }
1450 +
1451 + // Update the server with basic info, logged in users and more.
1452 + mesh.SendCommand(meshCoreObj);
1453 +
1454 + // Send SMBios tables if present
1455 + if (SMBiosTablesRaw != null) { mesh.SendCommand({ "action": "smbios", "value": SMBiosTablesRaw }); }
1456 +
1457 + // Update the server on more advanced stuff, like Intel ME and Network Settings
1458 + meInfoStr = null;
1459 + sendPeriodicServerUpdate();
1460 + //if (selfInfoUpdateTimer == null) { selfInfoUpdateTimer = setInterval(sendPeriodicServerUpdate, 1200000); } // 20 minutes
1461 + }
1462 + }
1463 +
1464 + // Update the server with the latest network interface information
1465 + var sendNetworkUpdateNagleTimer = null;
1466 + function sendNetworkUpdateNagle() { if (sendNetworkUpdateNagleTimer != null) { clearTimeout(sendNetworkUpdateNagleTimer); sendNetworkUpdateNagleTimer = null; } sendNetworkUpdateNagleTimer = setTimeout(sendNetworkUpdate, 5000); }
1467 + function sendNetworkUpdate(force) {
1468 + sendNetworkUpdateNagleTimer = null;
1469 +
1470 + // Update the network interfaces information data
1471 + var netInfo = mesh.NetInfo;
1472 + netInfo.action = 'netinfo';
1473 + var netInfoStr = JSON.stringify(netInfo);
1474 + if ((force == true) || (clearGatewayMac(netInfoStr) != clearGatewayMac(lastNetworkInfo))) { mesh.SendCommand(netInfo); lastNetworkInfo = netInfoStr; }
1475 + }
1476 +
1477 + // Called periodically to check if we need to send updates to the server
1478 + function sendPeriodicServerUpdate(flags) {
1479 + if (meshServerConnectionState == 0) return; // Not connected to server, do nothing.
1480 + if (!flags) { flags = 0xFFFFFFFF; }
1481 +
1482 + if (flags & 1) {
1483 + // If we have a connected MEI, get Intel ME information
1484 + getAmtInfo(function (meinfo) {
1485 + try {
1486 + if (meinfo == null) return;
1487 + var intelamt = {}, p = false;
1488 + if (meinfo.Versions && meinfo.Versions.AMT) { intelamt.ver = meinfo.Versions.AMT; p = true; }
1489 + if (meinfo.ProvisioningState) { intelamt.state = meinfo.ProvisioningState; p = true; }
1490 + if (meinfo.Flags) { intelamt.flags = meinfo.Flags; p = true; }
1491 + if (meinfo.OsHostname) { intelamt.host = meinfo.OsHostname; p = true; }
1492 + if (meinfo.UUID) { intelamt.uuid = meinfo.UUID; p = true; }
1493 + if (p == true) {
1494 + var meInfoStr = JSON.stringify(intelamt);
1495 + if (meInfoStr != lastMeInfo) {
1496 + meshCoreObj.intelamt = intelamt;
1497 + mesh.SendCommand(meshCoreObj);
1498 + lastMeInfo = meInfoStr;
1499 + }
1500 + }
1501 + } catch (ex) { }
1502 + });
1503 + }
1504 +
1505 + if (flags & 2) {
1506 + // Update network information
1507 + sendNetworkUpdateNagle(false);
1508 + }
1509 + }
1510 +
1511 + // Get Intel AMT information using MEI
1512 + function getAmtInfo(func) {
1513 + if (amtMei == null || amtMeiConnected != 2) { if (func != null) { func(null); } return; }
1514 + try {
1515 + amtMeiTmpState = { Flags: 0 }; // Flags: 1=EHBC, 2=CCM, 4=ACM
1516 + amtMei.getProtocolVersion(function (result) { if (result != null) { amtMeiTmpState.MeiVersion = result; } });
1517 + amtMei.getVersion(function (result) { if (result) { amtMeiTmpState.Versions = {}; for (var version in result.Versions) { amtMeiTmpState.Versions[result.Versions[version].Description] = result.Versions[version].Version; } } });
1518 + amtMei.getProvisioningMode(function (result) { if (result) { amtMeiTmpState.ProvisioningMode = result.mode; } });
1519 + amtMei.getProvisioningState(function (result) { if (result) { amtMeiTmpState.ProvisioningState = result.state; } });
1520 + amtMei.getEHBCState(function (result) { if ((result != null) && (result.EHBC == true)) { amtMeiTmpState.Flags += 1; } });
1521 + amtMei.getControlMode(function (result) { if (result != null) { if (result.controlMode == 1) { amtMeiTmpState.Flags += 2; } if (result.controlMode == 2) { amtMeiTmpState.Flags += 4; } } });
1522 + amtMei.getUuid(function (result) { if ((result != null) && (result.uuid != null)) { amtMeiTmpState.UUID = result.uuid; } });
1523 + //amtMei.getMACAddresses(function (result) { amtMeiTmpState.mac = result; });
1524 + amtMei.getDnsSuffix(function (result) { if (result != null) { amtMeiTmpState.dns = result; } if (func != null) { func(amtMeiTmpState); } });
1525 + } catch (e) { if (func != null) { func(null); } return; }
1526 + }
1527 +
1528 + // Called on MicroLMS Intel AMT user notification
1529 + function handleAmtNotification(notifyMsg) {
1530 + if ((notifyMsg == null) || (notifyMsg.Body == null) || (notifyMsg.Body.MessageID == null) || (notifyMsg.Body.MessageArguments == null)) return null;
1531 + var amtMessage = notifyMsg.Body.MessageID, amtMessageArg = notifyMsg.Body.MessageArguments[0], notify = null;
1532 +
1533 + switch (amtMessage) {
1534 + 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
1535 + case 'iAMT0052': { if (amtMessageArg == '1') { notify = 'Intel&reg; AMT KVM connected'; } else if (amtMessageArg == '2') { notify = 'Intel&reg; AMT KVM disconnected'; } break; } // KVM
1536 + }
1537 +
1538 + // Send to the entire mesh, no sessionid or userid specified.
1539 + if (notify != null) { mesh.SendCommand({ "action": "msg", "type": "notify", "value": notify, "tag": "general" }); }
1540 + }
1541 +
1542 + // Starting function
1543 + obj.start = function () {
1544 + // Setup the mesh agent event handlers
1545 + mesh.AddCommandHandler(handleServerCommand);
1546 + mesh.AddConnectHandler(handleServerConnection);
1547 +
1548 + // Parse input arguments
1549 + //var args = parseArgs(process.argv);
1550 + //console.log(args);
1551 +
1552 + // Launch LMS
1553 + try {
1554 + var lme_heci = require('amt-lme');
1555 + amtLmsState = 1;
1556 + amtLms = new lme_heci();
1557 + amtLms.on('error', function (e) { amtLmsState = 0; amtLms = null; obj.setupMeiOsAdmin(null, 1); });
1558 + amtLms.on('connect', function () { amtLmsState = 2; obj.setupMeiOsAdmin(null, 2); });
1559 + //amtLms.on('bind', function (map) { });
1560 + amtLms.on('notify', function (data, options, str, code) {
1561 + if (code == 'iAMT0052-3') {
1562 + obj.kvmGetData();
1563 + } else {
1564 + //if (str != null) { sendConsoleText('Intel AMT LMS: ' + str); }
1565 + handleAmtNotification(data);
1566 + }
1567 + });
1568 + } catch (e) { amtLmsState = -1; amtLms = null; }
1569 +
1570 + // Setup logged in user monitoring
1571 + try {
1572 + var userSession = require('user-sessions');
1573 + userSession.on('changed', function onUserSessionChanged() {
1574 + userSession.enumerateUsers().then(function (users) {
1575 + var u = [], a = users.Active;
1576 + for (var i = 0; i < a.length; i++) {
1577 + var un = a[i].Domain ? (a[i].Domain + '\\' + a[i].Username) : (a[i].Username);
1578 + if (u.indexOf(un) == -1) { u.push(un); } // Only push users in the list once.
1579 + }
1580 + meshCoreObj.users = u;
1581 + mesh.SendCommand(meshCoreObj);
1582 + });
1583 + });
1584 + userSession.emit('changed');
1585 + //userSession.on('locked', function (user) { sendConsoleText('[' + (user.Domain ? user.Domain + '\\' : '') + user.Username + '] has LOCKED the desktop'); });
1586 + //userSession.on('unlocked', function (user) { sendConsoleText('[' + (user.Domain ? user.Domain + '\\' : '') + user.Username + '] has UNLOCKED the desktop'); });
1587 + } catch (ex) { }
1588 + }
1589 +
1590 + obj.stop = function () {
1591 + mesh.AddCommandHandler(null);
1592 + mesh.AddConnectHandler(null);
1593 + }
1594 +
1595 + function onWebSocketClosed() { sendConsoleText("WebSocket #" + this.httprequest.index + " closed.", this.httprequest.sessionid); delete consoleWebSockets[this.httprequest.index]; }
1596 + function onWebSocketData(data) { sendConsoleText("Got WebSocket #" + this.httprequest.index + " data: " + data, this.httprequest.sessionid); }
1597 + function onWebSocketSendOk() { sendConsoleText("WebSocket #" + this.index + " SendOK.", this.sessionid); }
1598 +
1599 + function onWebSocketUpgrade(response, s, head) {
1600 + sendConsoleText("WebSocket #" + this.index + " connected.", this.sessionid);
1601 + this.s = s;
1602 + s.httprequest = this;
1603 + s.end = onWebSocketClosed;
1604 + s.data = onWebSocketData;
1605 + }
1606 +
1607 +
1608 + //
1609 + // KVM Data Channel
1610 + //
1611 +
1612 + obj.setupMeiOsAdmin = function (func, state) {
1613 + if ((amtMei == null) || (amtMeiConnected != 2)) { return; } // If there is no MEI, don't bother with this.
1614 + amtMei.getLocalSystemAccount(function (x) {
1615 + if (x == null) return;
1616 + var transport = require('amt-wsman-duk');
1617 + var wsman = require('amt-wsman');
1618 + var amt = require('amt');
1619 + oswsstack = new wsman(transport, '127.0.0.1', 16992, x.user, x.pass, false);
1620 + obj.osamtstack = new amt(oswsstack);
1621 + if (func) { func(state); }
1622 + //var AllWsman = "CIM_SoftwareIdentity,IPS_SecIOService,IPS_ScreenSettingData,IPS_ProvisioningRecordLog,IPS_HostBasedSetupService,IPS_HostIPSettings,IPS_IPv6PortSettings".split(',');
1623 + //obj.osamtstack.BatchEnum(null, AllWsman, startLmsWsmanResponse, null, true);
1624 + //*************************************
1625 + // Setup KVM data channel if this is Intel AMT 12 or above
1626 + amtMei.getVersion(function (x) {
1627 + if (x == null) return;
1628 + var amtver = null;
1629 + try { for (var i in x.Versions) { if (x.Versions[i].Description == 'AMT') amtver = parseInt(x.Versions[i].Version.split('.')[0]); } } catch (e) { }
1630 + if ((amtver != null) && (amtver >= 12)) {
1631 + obj.kvmGetData('skip'); // Clear any previous data, this is a dummy read to about handling old data.
1632 + obj.kvmTempTimer = setInterval(function () { obj.kvmGetData(); }, 2000); // Start polling for KVM data.
1633 + obj.kvmSetData(JSON.stringify({ action: 'restart', ver: 1 })); // Send a restart command to advise the console if present that MicroLMS just started.
1634 + }
1635 + });
1636 + });
1637 + }
1638 +
1639 + obj.kvmGetData = function (tag) {
1640 + obj.osamtstack.IPS_KVMRedirectionSettingData_DataChannelRead(obj.kvmDataGetResponse, tag);
1641 + }
1642 +
1643 + obj.kvmDataGetResponse = function (stack, name, response, status, tag) {
1644 + if ((tag != 'skip') && (status == 200) && (response.Body.ReturnValue == 0)) {
1645 + var val = null;
1646 + try { val = Buffer.from(response.Body.DataMessage, 'base64').toString(); } catch (e) { return }
1647 + if (val != null) { obj.kvmProcessData(response.Body.RealmsBitmap, response.Body.MessageId, val); }
1648 + }
1649 + }
1650 +
1651 + var webRtcDesktop = null;
1652 + obj.kvmProcessData = function (realms, messageId, val) {
1653 + var data = null;
1654 + try { data = JSON.parse(val) } catch (e) { }
1655 + if ((data != null) && (data.action)) {
1656 + if (data.action == 'present') { obj.kvmSetData(JSON.stringify({ action: 'present', ver: 1, platform: process.platform })); }
1657 + if (data.action == 'offer') {
1658 + webRtcDesktop = {};
1659 + var rtc = require('ILibWebRTC');
1660 + webRtcDesktop.webrtc = rtc.createConnection();
1661 + webRtcDesktop.webrtc.on('connected', function () { });
1662 + webRtcDesktop.webrtc.on('disconnected', function () { webRtcCleanUp(); });
1663 + webRtcDesktop.webrtc.on('dataChannel', function (rtcchannel) {
1664 + webRtcDesktop.rtcchannel = rtcchannel;
1665 + webRtcDesktop.kvm = mesh.getRemoteDesktopStream();
1666 + webRtcDesktop.kvm.pipe(webRtcDesktop.rtcchannel, { dataTypeSkip: 1, end: false });
1667 + webRtcDesktop.rtcchannel.on('end', function () { obj.webRtcCleanUp(); });
1668 + webRtcDesktop.rtcchannel.on('data', function (x) { obj.kvmCtrlData(this, x); });
1669 + webRtcDesktop.rtcchannel.pipe(webRtcDesktop.kvm, { dataTypeSkip: 1, end: false });
1670 + //webRtcDesktop.kvm.on('end', function () { console.log('WebRTC DataChannel closed2'); webRtcCleanUp(); });
1671 + //webRtcDesktop.rtcchannel.on('data', function (data) { console.log('WebRTC data: ' + data); });
1672 + });
1673 + obj.kvmSetData(JSON.stringify({ action: 'answer', ver: 1, sdp: webRtcDesktop.webrtc.setOffer(data.sdp) }));
1674 + }
1675 + }
1676 + }
1677 +
1678 + // Polyfill path.join
1679 + var path = {
1680 + join: function () {
1681 + var x = [];
1682 + for (var i in arguments) {
1683 + var w = arguments[i];
1684 + if (w != null) {
1685 + while (w.endsWith('/') || w.endsWith('\\')) { w = w.substring(0, w.length - 1); }
1686 + if (i != 0) { while (w.startsWith('/') || w.startsWith('\\')) { w = w.substring(1); } }
1687 + x.push(w);
1688 + }
1689 + }
1690 + if (x.length == 0) return '/';
1691 + return x.join('/');
1692 + }
1693 + };
1694 +
1695 + // Process KVM control channel data
1696 + obj.kvmCtrlData = function(channel, cmd) {
1697 + if (cmd.length > 0 && cmd.charCodeAt(0) != 123) {
1698 + // This is upload data
1699 + if (this.fileupload != null) {
1700 + cmd = Buffer.from(cmd, 'base64');
1701 + var header = cmd.readUInt32BE(0);
1702 + if ((header == 0x01000000) || (header == 0x01000001)) {
1703 + fs.writeSync(this.fileupload.fp, cmd.slice(4));
1704 + channel.write({ action: 'upload', sub: 'ack', reqid: this.fileupload.reqid });
1705 + if (header == 0x01000001) { fs.closeSync(this.fileupload.fp); this.fileupload = null; } // Close the file
1706 + }
1707 + }
1708 + return;
1709 + }
1710 + //console.log('KVM Ctrl Data', cmd);
1711 + //sendConsoleText('KVM Ctrl Data: ' + cmd);
1712 +
1713 + try { cmd = JSON.parse(cmd); } catch (ex) { console.error('Invalid JSON: ' + cmd); return; }
1714 + if ((cmd.path != null) && (process.platform != 'win32') && (cmd.path[0] != '/')) { cmd.path = '/' + cmd.path; } // Add '/' to paths on non-windows
1715 + switch (cmd.action) {
1716 + case 'ping': {
1717 + // This is a keep alive
1718 + channel.write({ action: 'pong' });
1719 + break;
1720 + }
1721 + case 'lock': {
1722 + // Lock the current user out of the desktop
1723 + 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 }); }
1724 + break;
1725 + }
1726 + case 'ls': {
1727 + /*
1728 + // Close the watcher if required
1729 + var samepath = ((this.httprequest.watcher != undefined) && (cmd.path == this.httprequest.watcher.path));
1730 + if ((this.httprequest.watcher != undefined) && (samepath == false)) {
1731 + //console.log('Closing watcher: ' + this.httprequest.watcher.path);
1732 + //this.httprequest.watcher.close(); // TODO: This line causes the agent to crash!!!!
1733 + delete this.httprequest.watcher;
1734 + }
1735 + */
1736 +
1737 + // Send the folder content to the browser
1738 + var response = getDirectoryInfo(cmd.path);
1739 + if (cmd.reqid != undefined) { response.reqid = cmd.reqid; }
1740 + channel.write(response);
1741 +
1742 + /*
1743 + // Start the directory watcher
1744 + if ((cmd.path != '') && (samepath == false)) {
1745 + var watcher = fs.watch(cmd.path, onFileWatcher);
1746 + watcher.tunnel = this.httprequest;
1747 + watcher.path = cmd.path;
1748 + this.httprequest.watcher = watcher;
1749 + //console.log('Starting watcher: ' + this.httprequest.watcher.path);
1750 + }
1751 + */
1752 + break;
1753 + }
1754 + case 'mkdir': {
1755 + // Create a new empty folder
1756 + fs.mkdirSync(cmd.path);
1757 + break;
1758 + }
1759 + case 'rm': {
1760 + // Remove many files or folders
1761 + for (var i in cmd.delfiles) {
1762 + var fullpath = path.join(cmd.path, cmd.delfiles[i]);
1763 + try { fs.unlinkSync(fullpath); } catch (e) { console.log(e); }
1764 + }
1765 + break;
1766 + }
1767 + case 'rename': {
1768 + // Rename a file or folder
1769 + try { fs.renameSync(path.join(cmd.path, cmd.oldname), path.join(cmd.path, cmd.newname)); } catch (e) { console.log(e); }
1770 + break;
1771 + }
1772 + case 'download': {
1773 + // Download a file, to browser
1774 + var sendNextBlock = 0;
1775 + if (cmd.sub == 'start') { // Setup the download
1776 + if (this.filedownload != null) { channel.write({ action: 'download', sub: 'cancel', id: this.filedownload.id }); delete this.filedownload; }
1777 + this.filedownload = { id: cmd.id, path: cmd.path, ptr: 0 }
1778 + 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; }
1779 + if (this.filedownload) { channel.write({ action: 'download', sub: 'start', id: cmd.id }); }
1780 + } else if ((this.filedownload != null) && (cmd.id == this.filedownload.id)) { // Download commands
1781 + if (cmd.sub == 'startack') { sendNextBlock = 8; } else if (cmd.sub == 'stop') { delete this.filedownload; } else if (cmd.sub == 'ack') { sendNextBlock = 1; }
1782 + }
1783 + // Send the next download block(s)
1784 + while (sendNextBlock > 0) {
1785 + sendNextBlock--;
1786 + var buf = new Buffer(4096);
1787 + var len = fs.readSync(this.filedownload.f, buf, 4, 4092, null);
1788 + this.filedownload.ptr += len;
1789 + if (len < 4092) { buf.writeInt32BE(0x01000001, 0); fs.closeSync(this.filedownload.f); delete this.filedownload; sendNextBlock = 0; } else { buf.writeInt32BE(0x01000000, 0); }
1790 + channel.write(buf.slice(0, len + 4).toString('base64')); // Write as Base64
1791 + }
1792 + break;
1793 + }
1794 + case 'upload': {
1795 + // Upload a file, from browser
1796 + if (cmd.sub == 'start') { // Start the upload
1797 + if (this.fileupload != null) { fs.closeSync(this.fileupload.fp); }
1798 + if (!cmd.path || !cmd.name) break;
1799 + this.fileupload = { reqid: cmd.reqid };
1800 + var filepath = path.join(cmd.path, cmd.name);
1801 + try { this.fileupload.fp = fs.openSync(filepath, 'wbN'); } catch (e) { }
1802 + 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 }); }
1803 + }
1804 + else if (cmd.sub == 'cancel') { // Stop the upload
1805 + if (this.fileupload != null) { fs.closeSync(this.fileupload.fp); this.fileupload = null; }
1806 + }
1807 + break;
1808 + }
1809 + case 'copy': {
1810 + // Copy a bunch of files from scpath to dspath
1811 + for (var i in cmd.names) {
1812 + var sc = path.join(cmd.scpath, cmd.names[i]), ds = path.join(cmd.dspath, cmd.names[i]);
1813 + if (sc != ds) { try { fs.copyFileSync(sc, ds); } catch (e) { } }
1814 + }
1815 + break;
1816 + }
1817 + case 'move': {
1818 + // Move a bunch of files from scpath to dspath
1819 + for (var i in cmd.names) {
1820 + var sc = path.join(cmd.scpath, cmd.names[i]), ds = path.join(cmd.dspath, cmd.names[i]);
1821 + if (sc != ds) { try { fs.copyFileSync(sc, ds); fs.unlinkSync(sc); } catch (e) { } }
1822 + }
1823 + break;
1824 + }
1825 + }
1826 + }
1827 +
1828 + obj.webRtcCleanUp = function() {
1829 + if (webRtcDesktop == null) return;
1830 + if (webRtcDesktop.rtcchannel) {
1831 + try { webRtcDesktop.rtcchannel.close(); } catch (e) { }
1832 + try { webRtcDesktop.rtcchannel.removeAllListeners('data'); } catch (e) { }
1833 + try { webRtcDesktop.rtcchannel.removeAllListeners('end'); } catch (e) { }
1834 + delete webRtcDesktop.rtcchannel;
1835 + }
1836 + if (webRtcDesktop.webrtc) {
1837 + try { webRtcDesktop.webrtc.close(); } catch (e) { }
1838 + try { webRtcDesktop.webrtc.removeAllListeners('connected'); } catch (e) { }
1839 + try { webRtcDesktop.webrtc.removeAllListeners('disconnected'); } catch (e) { }
1840 + try { webRtcDesktop.webrtc.removeAllListeners('dataChannel'); } catch (e) { }
1841 + delete webRtcDesktop.webrtc;
1842 + }
1843 + if (webRtcDesktop.kvm) {
1844 + try { webRtcDesktop.kvm.end(); } catch (e) { }
1845 + delete webRtcDesktop.kvm;
1846 + }
1847 + webRtcDesktop = null;
1848 + }
1849 +
1850 + obj.kvmSetData = function(x) {
1851 + obj.osamtstack.IPS_KVMRedirectionSettingData_DataChannelWrite(Buffer.from(x).toString('base64'), function () { });
1852 + }
1853 +
1854 + // Delete a directory with a files and directories within it
1855 + function deleteFolderRecursive(path, rec) {
1856 + if (fs.existsSync(path)) {
1857 + if (rec == true) {
1858 + fs.readdirSync(obj.path.join(path, '*')).forEach(function (file, index) {
1859 + var curPath = obj.path.join(path, file);
1860 + if (fs.statSync(curPath).isDirectory()) { // recurse
1861 + deleteFolderRecursive(curPath, true);
1862 + } else { // delete file
1863 + fs.unlinkSync(curPath);
1864 + }
1865 + });
1866 + }
1867 + fs.unlinkSync(path);
1868 + }
1869 + };
1870 +
1871 + return obj;
1872 +}
1873 +
1874 +//
1875 +// Module startup
1876 +//
1877 +
1878 +try {
1879 + var xexports = null, mainMeshCore = null;
1880 + try { xexports = module.exports; } catch (e) { }
1881 +
1882 + if (xexports != null) {
1883 + // If we are running within NodeJS, export the core
1884 + module.exports.createMeshCore = createMeshCore;
1885 + } else {
1886 + // If we are not running in NodeJS, launch the core
1887 + mainMeshCore = createMeshCore();
1888 + mainMeshCore.start(null);
1889 + }
1890 +} catch (ex) {
1891 + require('MeshAgent').SendCommand({ "action": "msg", "type": "console", "value": "uncaughtException2: " + ex });
1892 +}
\ No newline at end of file
agents/meshcore.js
+43 -16
@@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
14 limitations under the License.
15 */
16
17 -
17 process.on('uncaughtException', function (ex) {
18 require('MeshAgent').SendCommand({ "action": "msg", "type": "console", "value": "uncaughtException1: " + ex });
19 });
@@ -652,18 +651,24 @@ function createMeshCore(agent) {
651 }
652
653 // Remote terminal using native pipes
655 - if (process.platform == "win32") {
656 - this.httprequest.process = childProcess.execFile("%windir%\\system32\\cmd.exe");
657 - } else {
654 + if (process.platform == "win32")
655 + {
656 + this.httprequest._term = require('win-terminal').Start(80, 25);
657 + this.httprequest._term.pipe(this, { dataTypeSkip: 1 });
658 + this.pipe(this.httprequest._term, { dataTypeSkip: 1, end: false });
659 + this.prependListener('end', function () { this.httprequest._term.end(function () { console.log('Terminal was closed');}); });
660 + //this.httprequest.process = childProcess.execFile("%windir%\\system32\\cmd.exe");
661 + } else
662 + {
663 this.httprequest.process = childProcess.execFile("/bin/sh", ["sh"], { type: childProcess.SpawnTypes.TERM });
664 + this.httprequest.process.tunnel = this;
665 + this.httprequest.process.on('exit', function (ecode, sig) { this.tunnel.end(); });
666 + this.httprequest.process.stderr.on('data', function (chunk) { this.parent.tunnel.write(chunk); });
667 + this.httprequest.process.stdout.pipe(this, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
668 + this.pipe(this.httprequest.process.stdin, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
669 + this.prependListener('end', function () { this.httprequest.process.kill(); });
670 }
671
661 - this.httprequest.process.tunnel = this;
662 - this.httprequest.process.on('exit', function (ecode, sig) { this.tunnel.end(); });
663 - this.httprequest.process.stderr.on('data', function (chunk) { this.parent.tunnel.write(chunk); });
664 - this.httprequest.process.stdout.pipe(this, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
665 - this.pipe(this.httprequest.process.stdin, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
666 - this.prependListener('end', function () { this.httprequest.process.kill(); });
672 this.removeAllListeners('data');
673 this.on('data', onTunnelControlData);
674 //this.write('MeshCore Terminal Hello');
@@ -914,8 +919,15 @@ function createMeshCore(agent) {
919 } else if (obj.type == 'webrtc0') { // Browser indicates we can start WebRTC switch-over.
920 if (ws.httprequest.protocol == 1) { // Terminal
921 // 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
917 - ws.httprequest.process.stdout.unpipe(ws);
918 - ws.httprequest.process.stderr.unpipe(ws);
922 + if (process.platform == 'win32')
923 + {
924 + ws.httprequest._term.unpipe(ws);
925 + }
926 + else
927 + {
928 + ws.httprequest.process.stdout.unpipe(ws);
929 + ws.httprequest.process.stderr.unpipe(ws);
930 + }
931 } else if (ws.httprequest.protocol == 2) { // Desktop
932 // 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
933 ws.httprequest.desktop.kvm.unpipe(ws);
@@ -929,8 +941,16 @@ function createMeshCore(agent) {
941 } else if (obj.type == 'webrtc1') {
942 if (ws.httprequest.protocol == 1) { // Terminal
943 // Switch the user input from websocket to webrtc at this point.
932 - ws.unpipe(ws.httprequest.process.stdin);
933 - ws.rtcchannel.pipe(ws.httprequest.process.stdin, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
944 + if (process.platform == 'win32')
945 + {
946 + ws.unpipe(ws.httprequest._term);
947 + ws.rtcchannel.pipe(ws.httprequest._term, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
948 + }
949 + else
950 + {
951 + ws.unpipe(ws.httprequest.process.stdin);
952 + ws.rtcchannel.pipe(ws.httprequest.process.stdin, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
953 + }
954 ws.resume(); // Resume the websocket to keep receiving control data
955 } else if (ws.httprequest.protocol == 2) { // Desktop
956 // Switch the user input from websocket to webrtc at this point.
@@ -942,8 +962,15 @@ function createMeshCore(agent) {
962 } else if (obj.type == 'webrtc2') {
963 // Other side received websocket end of data marker, start sending data on WebRTC channel
964 if (ws.httprequest.protocol == 1) { // Terminal
945 - ws.httprequest.process.stdout.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
946 - ws.httprequest.process.stderr.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
965 + if (process.platform == 'win32')
966 + {
967 + ws.httprequest._term.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
968 + }
969 + else
970 + {
971 + ws.httprequest.process.stdout.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
972 + ws.httprequest.process.stderr.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
973 + }
974 } else if (ws.httprequest.protocol == 2) { // Desktop
975 ws.httprequest.desktop.kvm.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
976 }
agents/modules_meshcore/monitor-border.js
+16
@@ -1,3 +1,19 @@
1 +/*
2 +Copyright 2018 Intel Corporation
3 +
4 +Licensed under the Apache License, Version 2.0 (the "License");
5 +you may not use this file except in compliance with the License.
6 +You may obtain a copy of the License at
7 +
8 + http://www.apache.org/licenses/LICENSE-2.0
9 +
10 +Unless required by applicable law or agreed to in writing, software
11 +distributed under the License is distributed on an "AS IS" BASIS,
12 +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 +See the License for the specific language governing permissions and
14 +limitations under the License.
15 +*/
16 +
17 var red = 0xFF;
18 var yellow = 0xFFFF;
19 var GXxor = 0x6; // src XOR dst
agents/modules_meshcore/monitor-info.js
+15
@@ -1,3 +1,18 @@
1 +/*
2 +Copyright 2018 Intel Corporation
3 +
4 +Licensed under the Apache License, Version 2.0 (the "License");
5 +you may not use this file except in compliance with the License.
6 +You may obtain a copy of the License at
7 +
8 + http://www.apache.org/licenses/LICENSE-2.0
9 +
10 +Unless required by applicable law or agreed to in writing, software
11 +distributed under the License is distributed on an "AS IS" BASIS,
12 +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 +See the License for the specific language governing permissions and
14 +limitations under the License.
15 +*/
16
17 var promise = require('promise');
18 var PPosition = 4;
agents/modules_meshcore/promise.js deleted
-207
@@ -1,207 +0,0 @@
1 -/*
2 -Copyright 2018 Intel Corporation
3 -
4 -Licensed under the Apache License, Version 2.0 (the "License");
5 -you may not use this file except in compliance with the License.
6 -You may obtain a copy of the License at
7 -
8 - http://www.apache.org/licenses/LICENSE-2.0
9 -
10 -Unless required by applicable law or agreed to in writing, software
11 -distributed under the License is distributed on an "AS IS" BASIS,
12 -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 -See the License for the specific language governing permissions and
14 -limitations under the License.
15 -*/
16 -
17 -var refTable = {};
18 -
19 -function event_switcher_helper(desired_callee, target)
20 -{
21 - this._ObjectID = 'event_switcher';
22 - this.func = function func()
23 - {
24 - var args = [];
25 - for(var i in arguments)
26 - {
27 - args.push(arguments[i]);
28 - }
29 - return (func.target.apply(func.desired, args));
30 - };
31 - this.func.desired = desired_callee;
32 - this.func.target = target;
33 - this.func.self = this;
34 -}
35 -function event_switcher(desired_callee, target)
36 -{
37 - return (new event_switcher_helper(desired_callee, target));
38 -}
39 -
40 -function Promise(promiseFunc)
41 -{
42 - this._ObjectID = 'promise';
43 - this.promise = this;
44 - this._internal = { _ObjectID: 'promise.internal', promise: this, func: promiseFunc, completed: false, errors: false, completedArgs: [] };
45 - require('events').EventEmitter.call(this._internal);
46 - this._internal.on('_eventHook', function (eventName, eventCallback)
47 - {
48 - //console.log('hook', eventName, 'errors/' + this.errors + ' completed/' + this.completed);
49 - var r = null;
50 -
51 - if (eventName == 'resolved' && !this.errors && this.completed)
52 - {
53 - r = eventCallback.apply(this, this.completedArgs);
54 - if(r!=null)
55 - {
56 - this.emit_returnValue('resolved', r);
57 - }
58 - }
59 - if (eventName == 'rejected' && this.errors && this.completed)
60 - {
61 - eventCallback.apply(this, this.completedArgs);
62 - }
63 - if (eventName == 'settled' && this.completed)
64 - {
65 - eventCallback.apply(this, []);
66 - }
67 - });
68 - this._internal.resolver = function _resolver()
69 - {
70 - _resolver._self.errors = false;
71 - _resolver._self.completed = true;
72 - _resolver._self.completedArgs = [];
73 - var args = ['resolved'];
74 - if (this.emit_returnValue && this.emit_returnValue('resolved') != null)
75 - {
76 - _resolver._self.completedArgs.push(this.emit_returnValue('resolved'));
77 - args.push(this.emit_returnValue('resolved'));
78 - }
79 - else
80 - {
81 - for (var a in arguments)
82 - {
83 - _resolver._self.completedArgs.push(arguments[a]);
84 - args.push(arguments[a]);
85 - }
86 - }
87 - _resolver._self.emit.apply(_resolver._self, args);
88 - _resolver._self.emit('settled');
89 - };
90 - this._internal.rejector = function _rejector()
91 - {
92 - _rejector._self.errors = true;
93 - _rejector._self.completed = true;
94 - _rejector._self.completedArgs = [];
95 - var args = ['rejected'];
96 - for (var a in arguments)
97 - {
98 - _rejector._self.completedArgs.push(arguments[a]);
99 - args.push(arguments[a]);
100 - }
101 -
102 - _rejector._self.emit.apply(_rejector._self, args);
103 - _rejector._self.emit('settled');
104 - };
105 - this.catch = function(func)
106 - {
107 - this._internal.once('rejected', event_switcher(this, func).func);
108 - }
109 - this.finally = function (func)
110 - {
111 - this._internal.once('settled', event_switcher(this, func).func);
112 - };
113 - this.then = function (resolved, rejected)
114 - {
115 - if (resolved) { this._internal.once('resolved', event_switcher(this, resolved).func); }
116 - if (rejected) { this._internal.once('rejected', event_switcher(this, rejected).func); }
117 -
118 - var retVal = new Promise(function (r, j) { });
119 - this._internal.once('resolved', retVal._internal.resolver);
120 - this._internal.once('rejected', retVal._internal.rejector);
121 - retVal.parentPromise = this;
122 - return (retVal);
123 - };
124 -
125 - this._internal.resolver._self = this._internal;
126 - this._internal.rejector._self = this._internal;;
127 -
128 - try
129 - {
130 - promiseFunc.call(this, this._internal.resolver, this._internal.rejector);
131 - }
132 - catch(e)
133 - {
134 - this._internal.errors = true;
135 - this._internal.completed = true;
136 - this._internal.completedArgs = [e];
137 - this._internal.emit('rejected', e);
138 - this._internal.emit('settled');
139 - }
140 -
141 - if(!this._internal.completed)
142 - {
143 - // Save reference of this object
144 - refTable[this._internal._hashCode()] = this._internal;
145 - this._internal.once('settled', function () { refTable[this._hashCode()] = null; });
146 - }
147 -}
148 -
149 -Promise.resolve = function resolve()
150 -{
151 - var retVal = new Promise(function (r, j) { });
152 - var args = [];
153 - for (var i in arguments)
154 - {
155 - args.push(arguments[i]);
156 - }
157 - retVal._internal.resolver.apply(retVal._internal, args);
158 - return (retVal);
159 -};
160 -Promise.reject = function reject() {
161 - var retVal = new Promise(function (r, j) { });
162 - var args = [];
163 - for (var i in arguments) {
164 - args.push(arguments[i]);
165 - }
166 - retVal._internal.rejector.apply(retVal._internal, args);
167 - return (retVal);
168 -};
169 -Promise.all = function all(promiseList)
170 -{
171 - var ret = new Promise(function (res, rej)
172 - {
173 - this.__rejector = rej;
174 - this.__resolver = res;
175 - this.__promiseList = promiseList;
176 - this.__done = false;
177 - this.__count = 0;
178 - });
179 -
180 - for (var i in promiseList)
181 - {
182 - promiseList[i].then(function ()
183 - {
184 - // Success
185 - if(++ret.__count == ret.__promiseList.length)
186 - {
187 - ret.__done = true;
188 - ret.__resolver(ret.__promiseList);
189 - }
190 - }, function (arg)
191 - {
192 - // Failure
193 - if(!ret.__done)
194 - {
195 - ret.__done = true;
196 - ret.__rejector(arg);
197 - }
198 - });
199 - }
200 - if (promiseList.length == 0)
201 - {
202 - ret.__resolver(promiseList);
203 - }
204 - return (ret);
205 -};
206 -
207 -module.exports = Promise;
\ No newline at end of file
agents/modules_meshcore/service-manager.js
+100 -2
@@ -214,6 +214,13 @@ function serviceManager()
214 throw ('could not find service: ' + name);
215 }
216 }
217 + else
218 + {
219 + this.isAdmin = function isAdmin()
220 + {
221 + return (require('user-sessions').isRoot());
222 + }
223 + }
224 this.installService = function installService(options)
225 {
226 if (process.platform == 'win32')
@@ -273,6 +280,8 @@ function serviceManager()
280 }
281 if(process.platform == 'linux')
282 {
283 + if (!this.isAdmin()) { throw ('Installing as Service, requires root'); }
284 +
285 switch (this.getServiceType())
286 {
287 case 'init':
@@ -311,14 +320,70 @@ function serviceManager()
320 break;
321 }
322 }
323 + if(process.platform == 'darwin')
324 + {
325 + if (!this.isAdmin()) { throw ('Installing as Service, requires root'); }
326 +
327 + // Mac OS
328 + var stdoutpath = (options.stdout ? ('<key>StandardOutPath</key>\n<string>' + options.stdout + '</string>') : '');
329 + var autoStart = (options.startType == 'AUTO_START' ? '<true/>' : '<false/>');
330 + var params = ' <key>ProgramArguments</key>\n';
331 + params += ' <array>\n';
332 + params += (' <string>/usr/local/mesh_services/' + options.name + '/' + options.name + '</string>\n');
333 + if(options.parameters)
334 + {
335 + for(var itm in options.parameters)
336 + {
337 + params += (' <string>' + options.parameters[itm] + '</string>\n');
338 + }
339 + }
340 + params += ' </array>\n';
341 +
342 + var plist = '<?xml version="1.0" encoding="UTF-8"?>\n';
343 + plist += '<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n';
344 + plist += '<plist version="1.0">\n';
345 + plist += ' <dict>\n';
346 + plist += ' <key>Label</key>\n';
347 + plist += (' <string>' + options.name + '</string>\n');
348 + plist += (params + '\n');
349 + plist += ' <key>WorkingDirectory</key>\n';
350 + plist += (' <string>/usr/local/mesh_services/' + options.name + '</string>\n');
351 + plist += (stdoutpath + '\n');
352 + plist += ' <key>RunAtLoad</key>\n';
353 + plist += (autoStart + '\n');
354 + plist += ' </dict>\n';
355 + plist += '</plist>';
356 +
357 + if (!require('fs').existsSync('/usr/local/mesh_services')) { require('fs').mkdirSync('/usr/local/mesh_services'); }
358 + if (!require('fs').existsSync('/Library/LaunchDaemons/' + options.name + '.plist'))
359 + {
360 + if (!require('fs').existsSync('/usr/local/mesh_services/' + options.name)) { require('fs').mkdirSync('/usr/local/mesh_services/' + options.name); }
361 + if (options.binary)
362 + {
363 + require('fs').writeFileSync('/usr/local/mesh_services/' + options.name + '/' + options.name, options.binary);
364 + }
365 + else
366 + {
367 + require('fs').copyFileSync(options.servicePath, '/usr/local/mesh_services/' + options.name + '/' + options.name);
368 + }
369 + require('fs').writeFileSync('/Library/LaunchDaemons/' + options.name + '.plist', plist);
370 + var m = require('fs').statSync('/usr/local/mesh_services/' + options.name + '/' + options.name).mode;
371 + m |= (require('fs').CHMOD_MODES.S_IXUSR | require('fs').CHMOD_MODES.S_IXGRP);
372 + require('fs').chmodSync('/usr/local/mesh_services/' + options.name + '/' + options.name, m);
373 + }
374 + else
375 + {
376 + throw ('Service: ' + options.name + ' already exists');
377 + }
378 + }
379 }
380 this.uninstallService = function uninstallService(name)
381 {
382 + if (!this.isAdmin()) { throw ('Uninstalling a service, requires admin'); }
383 +
384 if (typeof (name) == 'object') { name = name.name; }
385 if (process.platform == 'win32')
386 {
320 - if (!this.isAdmin()) { throw ('Uninstalling a service, requires admin'); }
321 -
387 var service = this.getService(name);
388 if (service.status.state == undefined || service.status.state == 'STOPPED')
389 {
@@ -388,6 +453,39 @@ function serviceManager()
453 break;
454 }
455 }
456 + else if(process.platform == 'darwin')
457 + {
458 + if (require('fs').existsSync('/Library/LaunchDaemons/' + name + '.plist'))
459 + {
460 + var child = require('child_process').execFile('/bin/sh', ['sh']);
461 + child.stdout.on('data', function (chunk) { });
462 + child.stdin.write('launchctl stop ' + name + '\n');
463 + child.stdin.write('launchctl unload /Library/LaunchDaemons/' + name + '.plist\n');
464 + child.stdin.write('exit\n');
465 + child.waitExit();
466 +
467 + try
468 + {
469 + require('fs').unlinkSync('/usr/local/mesh_services/' + name + '/' + name);
470 + require('fs').unlinkSync('/Library/LaunchDaemons/' + name + '.plist');
471 + }
472 + catch(e)
473 + {
474 + throw ('Error uninstalling service: ' + name + ' => ' + e);
475 + }
476 +
477 + try
478 + {
479 + require('fs').rmdirSync('/usr/local/mesh_services/' + name);
480 + }
481 + catch(e)
482 + {}
483 + }
484 + else
485 + {
486 + throw ('Service: ' + name + ' does not exist');
487 + }
488 + }
489 }
490 if(process.platform == 'linux')
491 {
agents/modules_meshcore/wifi-scanner-windows.js
+15
@@ -1,3 +1,18 @@
1 +/*
2 +Copyright 2018 Intel Corporation
3 +
4 +Licensed under the Apache License, Version 2.0 (the "License");
5 +you may not use this file except in compliance with the License.
6 +You may obtain a copy of the License at
7 +
8 + http://www.apache.org/licenses/LICENSE-2.0
9 +
10 +Unless required by applicable law or agreed to in writing, software
11 +distributed under the License is distributed on an "AS IS" BASIS,
12 +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 +See the License for the specific language governing permissions and
14 +limitations under the License.
15 +*/
16
17 function _Scan()
18 {
agents/modules_meshcore/wifi-scanner.js
+16
@@ -1,3 +1,19 @@
1 +/*
2 +Copyright 2018 Intel Corporation
3 +
4 +Licensed under the Apache License, Version 2.0 (the "License");
5 +you may not use this file except in compliance with the License.
6 +You may obtain a copy of the License at
7 +
8 + http://www.apache.org/licenses/LICENSE-2.0
9 +
10 +Unless required by applicable law or agreed to in writing, software
11 +distributed under the License is distributed on an "AS IS" BASIS,
12 +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 +See the License for the specific language governing permissions and
14 +limitations under the License.
15 +*/
16 +
17 var MemoryStream = require('MemoryStream');
18 var WindowsChildScript = 'var parent = require("ScriptContainer");var Wireless = require("wifi-scanner-windows");Wireless.on("Scan", function (ap) { parent.send(ap); });Wireless.Scan();';
19
agents/modules_meshcore/win-message-pump.js
+80 -76
@@ -17,6 +17,8 @@ limitations under the License.
17 var WH_CALLWNDPROC = 4;
18 var WM_QUIT = 0x0012;
19
20 +var GM = require('_GenericMarshal');
21 +
22 function WindowsMessagePump(options)
23 {
24 this._ObjectID = 'win-message-pump';
@@ -27,92 +29,94 @@ function WindowsMessagePump(options)
29 emitterUtils.createEvent('message');
30 emitterUtils.createEvent('exit');
31
30 - this._child = require('ScriptContainer').Create({ processIsolation: 0 });
31 - this._child.MessagePump = this;
32 - this._child.prependListener('~', function _childFinalizer() { this.MessagePump.emit('exit', 0); this.MessagePump.stop(); });
33 - this._child.once('exit', function onExit(code) { this.MessagePump.emit('exit', code); });
34 - this._child.once('ready', function onReady()
35 - {
36 - var execString =
37 - "var m = require('_GenericMarshal');\
38 - var h = null;\
39 - var k = m.CreateNativeProxy('Kernel32.dll');\
40 - k.CreateMethod('GetLastError');\
41 - k.CreateMethod('GetModuleHandleA');\
42 - var u = m.CreateNativeProxy('User32.dll');\
43 - u.CreateMethod('GetMessageA');\
44 - u.CreateMethod('CreateWindowExA');\
45 - u.CreateMethod('TranslateMessage');\
46 - u.CreateMethod('DispatchMessageA');\
47 - u.CreateMethod('RegisterClassExA');\
48 - u.CreateMethod('DefWindowProcA');\
49 - var wndclass = m.CreateVariable(m.PointerSize == 4 ? 48 : 80);\
50 - wndclass.hinstance = k.GetModuleHandleA(0);\
51 - wndclass.cname = m.CreateVariable('MainWWWClass');\
52 - wndclass.wndproc = m.GetGenericGlobalCallback(4);\
53 - wndclass.toBuffer().writeUInt32LE(wndclass._size);\
54 - wndclass.cname.pointerBuffer().copy(wndclass.Deref(m.PointerSize == 4 ? 40 : 64, m.PointerSize).toBuffer());\
55 - wndclass.wndproc.pointerBuffer().copy(wndclass.Deref(8, m.PointerSize).toBuffer());\
56 - wndclass.hinstance.pointerBuffer().copy(wndclass.Deref(m.PointerSize == 4 ? 20 : 24, m.PointerSize).toBuffer());\
57 - wndclass.wndproc.on('GlobalCallback', function onWndProc(xhwnd, xmsg, wparam, lparam)\
58 - {\
59 - if(h==null || h.Val == xhwnd.Val)\
60 - {\
61 - require('ScriptContainer').send({message: xmsg.Val, wparam: wparam.Val, lparam: lparam.Val, lparam_hex: lparam.pointerBuffer().toString('hex')});\
62 - var retVal = u.DefWindowProcA(xhwnd, xmsg, wparam, lparam);\
63 - return(retVal);\
64 - }\
65 - });\
66 - u.RegisterClassExA(wndclass);\
67 - h = u.CreateWindowExA(0x00000088, wndclass.cname, 0, 0x00800000, 0, 0, 100, 100, 0, 0, 0, 0);\
68 - if(h.Val == 0)\
69 - {\
70 - require('ScriptContainer').send({error: 'Error Creating Hidden Window'});\
71 - process.exit();\
72 - }\
73 - require('ScriptContainer').send({hwnd: h.pointerBuffer().toString('hex')});\
74 - require('ScriptContainer').on('data', function onData(jmsg)\
75 - {\
76 - if(jmsg.listen)\
77 - {\
78 - var msg = m.CreateVariable(m.PointerSize == 4 ? 28 : 48);\
79 - while(u.GetMessageA(msg, h, 0, 0).Val>0)\
80 - {\
81 - u.TranslateMessage(msg);\
82 - u.DispatchMessageA(msg);\
83 - }\
84 - process.exit();\
85 - }\
86 - });";
32 + this._msg = GM.CreateVariable(GM.PointerSize == 4 ? 28 : 48);
33 + this._kernel32 = GM.CreateNativeProxy('Kernel32.dll');
34 + this._kernel32.mp = this;
35 + this._kernel32.CreateMethod('GetLastError');
36 + this._kernel32.CreateMethod('GetModuleHandleA');
37
88 - this.ExecuteString(execString);
89 - });
90 - this._child.on('data', function onChildData(msg)
38 + this._user32 = GM.CreateNativeProxy('User32.dll');
39 + this._user32.mp = this;
40 + this._user32.CreateMethod('GetMessageA');
41 + this._user32.CreateMethod('CreateWindowExA');
42 + this._user32.CreateMethod('TranslateMessage');
43 + this._user32.CreateMethod('DispatchMessageA');
44 + this._user32.CreateMethod('RegisterClassExA');
45 + this._user32.CreateMethod('DefWindowProcA');
46 + this._user32.CreateMethod('PostMessageA');
47 +
48 +
49 + this.wndclass = GM.CreateVariable(GM.PointerSize == 4 ? 48 : 80);
50 + this.wndclass.mp = this;
51 + this.wndclass.hinstance = this._kernel32.GetModuleHandleA(0);
52 + this.wndclass.cname = GM.CreateVariable('MainWWWClass');
53 + this.wndclass.wndproc = GM.GetGenericGlobalCallback(4);
54 + this.wndclass.wndproc.mp = this;
55 + this.wndclass.toBuffer().writeUInt32LE(this.wndclass._size);
56 + this.wndclass.cname.pointerBuffer().copy(this.wndclass.Deref(GM.PointerSize == 4 ? 40 : 64, GM.PointerSize).toBuffer());
57 + this.wndclass.wndproc.pointerBuffer().copy(this.wndclass.Deref(8, GM.PointerSize).toBuffer());
58 + this.wndclass.hinstance.pointerBuffer().copy(this.wndclass.Deref(GM.PointerSize == 4 ? 20 : 24, GM.PointerSize).toBuffer());
59 + this.wndclass.wndproc.on('GlobalCallback', function onWndProc(xhwnd, xmsg, wparam, lparam)
60 {
92 - if (msg.hwnd)
93 - {
94 - var m = require('_GenericMarshal');
95 - this._hwnd = m.CreatePointer(Buffer.from(msg.hwnd, 'hex'));
96 - this.MessagePump.emit('hwnd', this._hwnd);
97 - this.send({ listen: this.MessagePump._options.filter });
98 - }
99 - else if(msg.message)
61 + if (this.mp._hwnd != null && this.mp._hwnd.Val == xhwnd.Val)
62 {
101 - this.MessagePump.emit('message', msg);
63 + // This is for us
64 + this.mp.emit('message', { message: xmsg.Val, wparam: wparam.Val, lparam: lparam.Val, lparam_hex: lparam.pointerBuffer().toString('hex') });
65 + return (this.mp._user32.DefWindowProcA(xhwnd, xmsg, wparam, lparam));
66 }
103 - else
67 + else if(this.mp._hwnd == null && this.CallingThread() == this.mp._user32.RegisterClassExA.async.threadId())
68 {
105 - console.log('Received: ', msg);
69 + // This message was generated from our CreateWindowExA method
70 + return (this.mp._user32.DefWindowProcA(xhwnd, xmsg, wparam, lparam));
71 }
72 });
73 +
74 + this._user32.RegisterClassExA.async(this.wndclass).then(function ()
75 + {
76 + this.nativeProxy.CreateWindowExA.async(this.nativeProxy.RegisterClassExA.async, 0x00000088, this.nativeProxy.mp.wndclass.cname, 0, 0x00800000, 0, 0, 100, 100, 0, 0, 0, 0)
77 + .then(function(h)
78 + {
79 + if (h.Val == 0)
80 + {
81 + // Error creating hidden window
82 + this.nativeProxy.mp.emit('error', 'Error creating hidden window');
83 + }
84 + else
85 + {
86 + this.nativeProxy.mp._hwnd = h;
87 + this.nativeProxy.mp.emit('hwnd', h);
88 + this.nativeProxy.mp._startPump();
89 + }
90 + });
91 + });
92 + this._startPump = function _startPump()
93 + {
94 + this._user32.GetMessageA.async(this._user32.RegisterClassExA.async, this._msg, this._hwnd, 0, 0).then(function (r)
95 + {
96 + if(r.Val > 0)
97 + {
98 + this.nativeProxy.TranslateMessage.async(this.nativeProxy.RegisterClassExA.async, this.nativeProxy.mp._msg).then(function ()
99 + {
100 + this.nativeProxy.DispatchMessageA.async(this.nativeProxy.RegisterClassExA.async, this.nativeProxy.mp._msg).then(function ()
101 + {
102 + this.nativeProxy.mp._startPump();
103 + });
104 + });
105 + }
106 + else
107 + {
108 + // We got a 'QUIT' message
109 + delete this.nativeProxy.mp._hwnd;
110 + this.nativeProxy.mp.emit('exit', 0);
111 + }
112 + }, function (err) { this.nativeProxy.mp.stop(); });
113 + }
114 +
115 this.stop = function stop()
116 {
110 - if(this._child && this._child._hwnd)
117 + if (this._hwnd)
118 {
112 - var marshal = require('_GenericMarshal');
113 - var User32 = marshal.CreateNativeProxy('User32.dll');
114 - User32.CreateMethod('PostMessageA');
115 - User32.PostMessageA(this._child._hwnd, WM_QUIT, 0, 0);
119 + this._user32.PostMessageA(this._hwnd, WM_QUIT, 0, 0);
120 }
121 };
122 }
agents/modules_meshcore/win-terminal.js new
+555
@@ -0,0 +1,555 @@
1 +/*
2 +Copyright 2018 Intel Corporation
3 +
4 +Licensed under the Apache License, Version 2.0 (the "License");
5 +you may not use this file except in compliance with the License.
6 +You may obtain a copy of the License at
7 +
8 + http://www.apache.org/licenses/LICENSE-2.0
9 +
10 +Unless required by applicable law or agreed to in writing, software
11 +distributed under the License is distributed on an "AS IS" BASIS,
12 +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 +See the License for the specific language governing permissions and
14 +limitations under the License.
15 +*/
16 +
17 +var promise = require('promise');
18 +var duplex = require('stream').Duplex;
19 +
20 +var SW_HIDE = 0;
21 +var SW_MINIMIZE = 6;
22 +var STARTF_USESHOWWINDOW = 0x1;
23 +var STD_INPUT_HANDLE = -10;
24 +var STD_OUTPUT_HANDLE = -11;
25 +var EVENT_CONSOLE_CARET = 0x4001;
26 +var EVENT_CONSOLE_END_APPLICATION = 0x4007;
27 +var WINEVENT_OUTOFCONTEXT = 0x000;
28 +var WINEVENT_SKIPOWNPROCESS = 0x0002;
29 +var CREATE_NEW_PROCESS_GROUP = 0x200;
30 +var EVENT_CONSOLE_UPDATE_REGION = 0x4002;
31 +var EVENT_CONSOLE_UPDATE_SIMPLE = 0x4003;
32 +var EVENT_CONSOLE_UPDATE_SCROLL = 0x4004;
33 +var EVENT_CONSOLE_LAYOUT = 0x4005;
34 +var EVENT_CONSOLE_START_APPLICATION = 0x4006;
35 +var KEY_EVENT = 0x1;
36 +var MAPVK_VK_TO_VSC = 0;
37 +var WM_QUIT = 0x12;
38 +
39 +var GM = require('_GenericMarshal');
40 +var si = GM.CreateVariable(GM.PointerSize == 4 ? 68 : 104);
41 +var pi = GM.CreateVariable(GM.PointerSize == 4 ? 16 : 24);
42 +
43 +si.Deref(0, 4).toBuffer().writeUInt32LE(GM.PointerSize == 4 ? 68 : 104); // si.cb
44 +si.Deref(GM.PointerSize == 4 ? 48 : 64, 2).toBuffer().writeUInt16LE(SW_HIDE | SW_MINIMIZE); // si.wShowWindow
45 +si.Deref(GM.PointerSize == 4 ? 44 : 60, 4).toBuffer().writeUInt32LE(STARTF_USESHOWWINDOW); // si.dwFlags;
46 +
47 +var MSG = GM.CreateVariable(GM.PointerSize == 4 ? 28 : 48);
48 +
49 +function windows_terminal()
50 +{
51 + this._ObjectID = 'windows_terminal';
52 + this._user32 = GM.CreateNativeProxy('User32.dll');
53 + this._user32.CreateMethod('DispatchMessageA');
54 + this._user32.CreateMethod('GetMessageA');
55 + this._user32.CreateMethod('MapVirtualKeyA');
56 + this._user32.CreateMethod('PostThreadMessageA');
57 + this._user32.CreateMethod('SetWinEventHook');
58 + this._user32.CreateMethod('ShowWindow');
59 + this._user32.CreateMethod('TranslateMessage');
60 + this._user32.CreateMethod('UnhookWinEvent');
61 + this._user32.CreateMethod('VkKeyScanA');
62 + this._user32.terminal = this;
63 +
64 + this._kernel32 = GM.CreateNativeProxy('Kernel32.dll');
65 + this._kernel32.CreateMethod('AllocConsole');
66 + this._kernel32.CreateMethod('CreateProcessA');
67 + this._kernel32.CreateMethod('CloseHandle');
68 + this._kernel32.CreateMethod('FillConsoleOutputAttribute');
69 + this._kernel32.CreateMethod('FillConsoleOutputCharacterA');
70 + this._kernel32.CreateMethod('GetConsoleScreenBufferInfo');
71 + this._kernel32.CreateMethod('GetConsoleWindow');
72 + this._kernel32.CreateMethod('GetLastError');
73 + this._kernel32.CreateMethod('GetStdHandle');
74 + this._kernel32.CreateMethod('GetThreadId');
75 + this._kernel32.CreateMethod('ReadConsoleOutputA');
76 + this._kernel32.CreateMethod('SetConsoleCursorPosition');
77 + this._kernel32.CreateMethod('SetConsoleScreenBufferSize');
78 + this._kernel32.CreateMethod('SetConsoleWindowInfo');
79 + this._kernel32.CreateMethod('TerminateProcess');
80 + this._kernel32.CreateMethod('WaitForSingleObject');
81 + this._kernel32.CreateMethod('WriteConsoleInputA');
82 +
83 + var currentX = 0;
84 + var currentY = 0;
85 +
86 + this._scrx = 0;
87 + this._scry = 0;
88 +
89 + this.SendCursorUpdate = function()
90 + {
91 + var newCsbi = GM.CreateVariable(22);
92 +
93 + if (this._kernel32.GetConsoleScreenBufferInfo(this._stdoutput, newCsbi).Val == 0) { return; }
94 + if (newCsbi.Deref(4,2).toBuffer().readUInt16LE() != this.currentX || newCsbi.Deref(6,2).toBuffer().readUInt16LE() != this.currentY)
95 + {
96 + //wchar_t mywbuf[512];
97 + //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);
98 + //OutputDebugString(mywbuf);
99 +
100 + //m_viewOffset = newCsbi.srWindow.Top;
101 + //WriteMoveCursor((SerialAgent *)this->sa, (char)(newCsbi.dwCursorPosition.Y - m_viewOffset), (char)(newCsbi.dwCursorPosition.X - m_viewOffset));
102 + //LowStackSendData((SerialAgent *)(this->sa), "", 0);
103 +
104 + this.currentX = newCsbi.Deref(4,2).toBuffer().readUInt16LE();
105 + this.currentY = newCsbi.Deref(6,2).toBuffer().readUInt16LE();
106 + }
107 + }
108 + this.ClearScreen = function()
109 + {
110 + var CONSOLE_SCREEN_BUFFER_INFO = GM.CreateVariable(22);
111 + if (this._kernel32.GetConsoleScreenBufferInfo(this._stdoutput, CONSOLE_SCREEN_BUFFER_INFO).Val == 0) { return; }
112 +
113 + var coordScreen = GM.CreateVariable(4);
114 + var dwConSize = CONSOLE_SCREEN_BUFFER_INFO.Deref(0,2).toBuffer().readUInt16LE(0) * CONSOLE_SCREEN_BUFFER_INFO.Deref(2,2).toBuffer().readUInt16LE(0);
115 + var cCharsWritten = GM.CreateVariable(4);
116 +
117 + // Fill the entire screen with blanks.
118 + if (this._kernel32.FillConsoleOutputCharacterA(this._stdoutput, 32, dwConSize, coordScreen.Deref(0,4).toBuffer().readUInt32LE(), cCharsWritten).Val == 0) { return; }
119 +
120 + // Get the current text attribute.
121 + if (this._kernel32.GetConsoleScreenBufferInfo(this._stdoutput, CONSOLE_SCREEN_BUFFER_INFO).Val == 0) { return; }
122 +
123 + // Set the buffer's attributes accordingly.
124 + 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; }
125 +
126 + // Put the cursor at its home coordinates.
127 + this._kernel32.SetConsoleCursorPosition(this._stdoutput, coordScreen.Deref(0, 4).toBuffer().readUInt32LE());
128 +
129 + // Put the window to top-left.
130 + var rect = GM.CreateVariable(8);
131 + var srWindow = CONSOLE_SCREEN_BUFFER_INFO.Deref(10,8).toBuffer();
132 + rect.Deref(4,2).toBuffer().writeUInt16LE(srWindow.readUInt16LE(4) - srWindow.readUInt16LE(0));
133 + rect.Deref(6,2).toBuffer().writeUInt16LE(srWindow.readUInt16LE(6) - srWindow.readUInt16LE(2));
134 +
135 + this._kernel32.SetConsoleWindowInfo(this._stdoutput, 1, rect);
136 + }
137 +
138 + this.Start = function Start(CONSOLE_SCREEN_WIDTH, CONSOLE_SCREEN_HEIGHT)
139 + {
140 + if(this._kernel32.GetConsoleWindow().Val == 0)
141 + {
142 + if(this._kernel32.AllocConsole().Val == 0)
143 + {
144 + throw ('AllocConsole failed with: ' + this._kernel32.GetLastError().Val);
145 + }
146 + }
147 +
148 + this._stdinput = this._kernel32.GetStdHandle(STD_INPUT_HANDLE);
149 + this._stdoutput = this._kernel32.GetStdHandle(STD_OUTPUT_HANDLE);
150 + this._connected = false;
151 + var coordScreen = GM.CreateVariable(4);
152 + coordScreen.Deref(0, 2).toBuffer().writeUInt16LE(CONSOLE_SCREEN_WIDTH);
153 + coordScreen.Deref(2, 2).toBuffer().writeUInt16LE(CONSOLE_SCREEN_HEIGHT);
154 +
155 + var rect = GM.CreateVariable(8);
156 + rect.Deref(4, 2).toBuffer().writeUInt16LE(CONSOLE_SCREEN_WIDTH - 1);
157 + rect.Deref(6, 2).toBuffer().writeUInt16LE(CONSOLE_SCREEN_HEIGHT - 1);
158 +
159 + if(this._kernel32.SetConsoleWindowInfo(this._stdoutput, 1, rect).Val == 0)
160 + {
161 + throw ('Failed to set Console Screen Size');
162 + }
163 + if(this._kernel32.SetConsoleScreenBufferSize(this._stdoutput, coordScreen.Deref(0,4).toBuffer().readUInt32LE()).Val == 0)
164 + {
165 + throw ('Failed to set Console Buffer Size');
166 + }
167 + this.ClearScreen();
168 + this._hookThread().then(function ()
169 + {
170 + // Hook Ready
171 + this.terminal.StartCommand();
172 + }, console.log);
173 + this._stream = new duplex({
174 + 'write': function (chunk, flush)
175 + {
176 + if (!this.terminal.connected)
177 + {
178 + //console.log('_write: ' + chunk);
179 + if (!this._promise.chunk)
180 + {
181 + this._promise.chunk = [];
182 + }
183 + if (typeof (chunk) == 'string')
184 + {
185 + this._promise.chunk.push(chunk);
186 + }
187 + else
188 + {
189 + this._promise.chunk.push(Buffer.alloc(chunk.length));
190 + chunk.copy(this._promise.chunk.peek());
191 + }
192 + this._promise.chunk.peek().flush = flush;
193 + this._promise.then(function ()
194 + {
195 + var buf;
196 + while(this.chunk.length > 0)
197 + {
198 + buf = this.chunk.shift();
199 + this.terminal._WriteBuffer(buf);
200 + buf.flush();
201 + }
202 + });
203 + }
204 + else
205 + {
206 + //console.log('writeNOW: ' + chunk);
207 + this.terminal._WriteBuffer(chunk);
208 + flush();
209 + }
210 + },
211 + 'final': function (flush)
212 + {
213 + var p = this.terminal._stop();
214 + p.__flush = flush;
215 + p.then(function () { this.__flush(); });
216 + }
217 + });
218 + this._stream.terminal = this;
219 + this._stream._promise = new promise(function (res, rej) { this._res = res; this._rej = rej; });
220 + this._stream._promise.terminal = this;
221 + return (this._stream);
222 + };
223 + this._stop = function()
224 + {
225 + if (this.stopping) { return (this.stopping); }
226 + console.log('Stopping Terminal...');
227 + this.stopping = new promise(function (res, rej) { this._res = res; this._rej = rej; });
228 +
229 + var threadID = this._kernel32.GetThreadId(this._user32.SetWinEventHook.async.thread()).Val;
230 + this._user32.PostThreadMessageA(threadID, WM_QUIT, 0, 0);
231 + return (this.stopping);
232 + }
233 +
234 + this._hookThread = function ()
235 + {
236 + var ret = new promise(function (res, rej) { this._res = res; this._rej = rej; });
237 + ret.terminal = this;
238 + this._ConsoleWinEventProc = GM.GetGenericGlobalCallback(7);
239 + this._ConsoleWinEventProc.terminal = this;
240 + var p = this._user32.SetWinEventHook.async(EVENT_CONSOLE_CARET, EVENT_CONSOLE_END_APPLICATION, 0, this._ConsoleWinEventProc, 0, 0, WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS);
241 + p.ready = ret;
242 + p.terminal = this;
243 + p.then(function (hwinEventHook)
244 + {
245 + if (hwinEventHook.Val == 0)
246 + {
247 + this.ready._rej('Error calling SetWinEventHook');
248 + }
249 + else
250 + {
251 + this.terminal.hwinEventHook = hwinEventHook;
252 + this.ready._res();
253 + this.terminal._GetMessage();
254 + }
255 + });
256 + this._ConsoleWinEventProc.on('GlobalCallback', function (hhook, dwEvent, hwnd, idObject, idChild, idEventThread, swmsEventTime)
257 + {
258 + if (!this.terminal.hwinEventHook || this.terminal.hwinEventHook.Val != hhook.Val) { return; }
259 + var buffer = null;
260 +
261 + switch (dwEvent.Val)
262 + {
263 + case EVENT_CONSOLE_CARET:
264 + break;
265 + case EVENT_CONSOLE_UPDATE_REGION:
266 + if (!this.terminal.connected)
267 + {
268 + this.terminal.connected = true; this.terminal._stream._promise._res();
269 + }
270 + if (this.terminal._scrollTimer == null)
271 + {
272 + buffer = this.terminal._GetScreenBuffer(LOWORD(idObject.Val), HIWORD(idObject.Val), LOWORD(idChild.Val), HIWORD(idChild.Val));
273 + //console.log('UPDATE REGION: [Left: ' + LOWORD(idObject.Val) + ' Top: ' + HIWORD(idObject.Val) + ' Right: ' + LOWORD(idChild.Val) + ' Bottom: ' + HIWORD(idChild.Val) + ']');
274 +
275 + this.terminal._SendDataBuffer(buffer);
276 + }
277 + break;
278 + case EVENT_CONSOLE_UPDATE_SIMPLE:
279 + //console.log('UPDATE SIMPLE: [X: ' + LOWORD(idObject.Val) + ' Y: ' + HIWORD(idObject.Val) + ' Char: ' + LOWORD(idChild.Val) + ' Attr: ' + HIWORD(idChild.Val) + ']');
280 + 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) };
281 + this.terminal._SendDataBuffer(simplebuffer);
282 + break;
283 + case EVENT_CONSOLE_UPDATE_SCROLL:
284 + //console.log('UPDATE SCROLL: [dx: ' + idObject.Val + ' dy: ' + idChild.Val + ']');
285 + this.terminal._SendScroll(idObject.Val, idChild.Val);
286 + break;
287 + case EVENT_CONSOLE_LAYOUT:
288 + //console.log('CONSOLE_LAYOUT');
289 + //snprintf( Buf, 512, "Event Console LAYOUT!\r\n");
290 + //SendLayout();
291 + break;
292 + case EVENT_CONSOLE_START_APPLICATION:
293 + //console.log('START APPLICATION: [PID: ' + idObject.Val + ' CID: ' + idChild.Val + ']');
294 + //snprintf( Buf, 512, "Event Console START APPLICATION!\r\nProcess ID: %d - Child ID: %d\r\n\r\n", (int)idObject, (int)idChild);
295 + //SendConsoleEvent(dwEvent, idObject, idChild);
296 + break;
297 + case EVENT_CONSOLE_END_APPLICATION:
298 + if(idObject.Val == this.terminal._hProcessID)
299 + {
300 + //console.log('END APPLICATION: [PID: ' + idObject.Val + ' CID: ' + idChild.Val + ']');
301 + this.terminal._stop().then(function () { console.log('STOPPED'); });
302 + }
303 + break;
304 + default:
305 + //snprintf(Buf, 512, "unknown console event.\r\n");
306 + console.log('Unknown event: ' + dwEvent.Val);
307 + break;
308 + }
309 +
310 + //mbstowcs_s(&l, wBuf, Buf, 512);
311 + //OutputDebugString(wBuf);
312 +
313 + });
314 + return (ret);
315 + }
316 +
317 + this._GetMessage = function()
318 + {
319 + if (this._user32.abort) { console.log('aborting loop'); return; }
320 + this._user32.GetMessageA.async(this._user32.SetWinEventHook.async, MSG, 0, 0, 0).then(function (ret)
321 + {
322 + //console.log('GetMessage Response');
323 + if(ret.Val != 0)
324 + {
325 + if (ret.Val == -1)
326 + {
327 + // handle the error and possibly exit
328 + }
329 + else
330 + {
331 + //console.log('TranslateMessage');
332 + this.nativeProxy._user32.TranslateMessage.async(this.nativeProxy.user32.SetWinEventHook.async, MSG).then(function ()
333 + {
334 + //console.log('DispatchMessage');
335 + this.nativeProxy._user32.DispatchMessageA.async(this.nativeProxy.user32.SetWinEventHook.async, MSG).then(function ()
336 + {
337 + this.nativeProxy.terminal._GetMessage();
338 + }, console.log);
339 + }, console.log);
340 + }
341 + }
342 + else
343 + {
344 + this.nativeProxy.UnhookWinEvent.async(this.nativeProxy.terminal._user32.SetWinEventHook.async, this.nativeProxy.terminal.hwinEventHook)
345 + .then(function ()
346 + {
347 + this.nativeProxy.terminal.stopping._res();
348 + if(this.nativeProxy.terminal._kernel32.TerminateProcess(this.nativeProxy.terminal._hProcess, 1067).Val == 0)
349 + {
350 + var e = this.nativeProxy.terminal._kernel32.GetLastError().Val;
351 + console.log('Unable to kill Terminal Process, error: ' + e);
352 + }
353 + this.nativeProxy.terminal.stopping = null;
354 + }, function (err)
355 + {
356 + console.log('REJECTED_UnhookWinEvent: ' + err);
357 + });
358 + }
359 + }, function (err)
360 + {
361 + // Get Message Failed
362 + console.log('REJECTED_GETMessage: ' + err);
363 + });
364 + }
365 + this._WriteBuffer = function(buf)
366 + {
367 + for (var i = 0; i < buf.length; ++i)
368 + {
369 + if (typeof (buf) == 'string')
370 + {
371 + this._WriteCharacter(buf.charCodeAt(i), false);
372 + }
373 + else
374 + {
375 + this._WriteCharacter(buf[i], false);
376 + }
377 + }
378 + }
379 + this._WriteCharacter = function(key, bControlKey)
380 + {
381 + var rec = GM.CreateVariable(20);
382 + rec.Deref(0,2).toBuffer().writeUInt16LE(KEY_EVENT); // rec.EventType
383 + rec.Deref(4,4).toBuffer().writeUInt16LE(1); // rec.Event.KeyEvent.bKeyDown
384 + rec.Deref(16, 4).toBuffer().writeUInt32LE(bControlKey); // rec.Event.KeyEvent.dwControlKeyState
385 + rec.Deref(14, 1).toBuffer()[0] = key; // rec.Event.KeyEvent.uChar.AsciiChar
386 + rec.Deref(8, 2).toBuffer().writeUInt16LE(1); // rec.Event.KeyEvent.wRepeatCount
387 + rec.Deref(10, 2).toBuffer().writeUInt16LE(this._user32.VkKeyScanA(key).Val); // rec.Event.KeyEvent.wVirtualKeyCode
388 + rec.Deref(12, 2).toBuffer().writeUInt16LE(this._user32.MapVirtualKeyA(this._user32.VkKeyScanA(key).Val, MAPVK_VK_TO_VSC).Val);
389 +
390 + var dwWritten = GM.CreateVariable(4);
391 + if(this._kernel32.WriteConsoleInputA(this._stdinput, rec, 1, dwWritten).Val == 0) { return(false); }
392 +
393 + rec.Deref(4,4).toBuffer().writeUInt16LE(0); // rec.Event.KeyEvent.bKeyDown
394 + return(this._kernel32.WriteConsoleInputA(this._stdinput, rec, 1, dwWritten).Val != 0);
395 + }
396 +
397 + this._GetScreenBuffer = function(sx, sy, ex, ey)
398 + {
399 + // get the current visible screen buffer
400 +
401 + var info = GM.CreateVariable(22);
402 + if (this._kernel32.GetConsoleScreenBufferInfo(this._stdoutput, info).Val == 0) { throw('Error getting screen buffer info'); }
403 +
404 + var nWidth = info.Deref(14,2).toBuffer().readUInt16LE() - info.Deref(10,2).toBuffer().readUInt16LE() + 1;
405 + var nHeight = info.Deref(16,2).toBuffer().readUInt16LE() - info.Deref(12,2).toBuffer().readUInt16LE() + 1;
406 +
407 + if (arguments[3] == null)
408 + {
409 + // Use Default Parameters
410 + sx = 0;
411 + sy = 0;
412 + ex = nWidth-1;
413 + ey = nHeight-1;
414 + }
415 + else
416 + {
417 + if(this._scrx != 0)
418 + {
419 + sx += this._scrx;
420 + ex += this._scrx;
421 + }
422 + if(this._scry != 0)
423 + {
424 + sy += this._scry;
425 + ey += this._scry;
426 + }
427 + this._scrx = this._scry = 0;
428 + }
429 +
430 +
431 + var nBuffer = GM.CreateVariable((ex-sx+1) * (ey-sy+1) * 4);
432 + var size = GM.CreateVariable(4);
433 + size.Deref(0,2).toBuffer().writeUInt16LE(ex-sx+1, 0);
434 + size.Deref(2, 2).toBuffer().writeUInt16LE(ey-sy+1, 0);
435 +
436 + var startCoord = GM.CreateVariable(4);
437 + startCoord.Deref(0, 2).toBuffer().writeUInt16LE(0, 0);
438 + startCoord.Deref(2, 2).toBuffer().writeUInt16LE(0, 0);
439 +
440 + var region = GM.CreateVariable(8);
441 + region.buffer = region.toBuffer();
442 + region.buffer.writeUInt16LE(sx, 0);
443 + region.buffer.writeUInt16LE(sy, 2);
444 + region.buffer.writeUInt16LE(ex, 4);
445 + region.buffer.writeUInt16LE(ey, 6);
446 +
447 + if (this._kernel32.ReadConsoleOutputA(this._stdoutput, nBuffer, size.Deref(0, 4).toBuffer().readUInt32LE(), startCoord.Deref(0, 4).toBuffer().readUInt32LE(), region).Val == 0)
448 + {
449 + throw('Unable to read Console Output');
450 + }
451 +
452 + // Lets convert the buffer into something simpler
453 + //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 };
454 +
455 + var retVal = { data: [], attributes: [], width: ex - sx + 1, height: ey - sy + 1, x: sx, y: sy };
456 + var x, y, line, ifo;
457 + var tmp;
458 + var lineWidth = ex - sx + 1;
459 +
460 + for (y = 0; y <= (ey - sy) ; ++y)
461 + {
462 + retVal.data.push(Buffer.alloc(lineWidth));
463 + retVal.attributes.push(Buffer.alloc(lineWidth));
464 +
465 + line = nBuffer.Deref(y * lineWidth * 4, lineWidth * 4).toBuffer();
466 + for(x = 0; x < lineWidth; ++x)
467 + {
468 + retVal.data.peek()[x] = line[x * 4];
469 + retVal.attributes.peek()[x] = line[2 + (x * 4)];
470 + }
471 + }
472 +
473 + return (retVal);
474 + }
475 +
476 + this._SendDataBuffer = function(data)
477 + {
478 + // { data, attributes, width, height, x, y }
479 +
480 + var dy, line, attr;
481 + for(dy = 0; dy < data.height; ++dy)
482 + {
483 + line = data.data[dy];
484 + attr = data.attributes[dy];
485 + line.s = line.toString();
486 + //line = data.data.slice(data.width * dy, (data.width * dy) + data.width);
487 + //attr = data.attributes.slice(data.width * dy, (data.width * dy) + data.width);
488 + this._stream.push(TranslateLine(data.x, data.y + dy, line, attr));
489 + }
490 + }
491 + this._SendScroll = function _SendScroll(dx, dy)
492 + {
493 + if (this._scrollTimer)
494 + {
495 + return;
496 + }
497 +
498 + var info = GM.CreateVariable(22);
499 + if (this._kernel32.GetConsoleScreenBufferInfo(this._stdoutput, info).Val == 0) { throw ('Error getting screen buffer info'); }
500 +
501 + var nWidth = info.Deref(14, 2).toBuffer().readUInt16LE() - info.Deref(10, 2).toBuffer().readUInt16LE() + 1;
502 + var nHeight = info.Deref(16, 2).toBuffer().readUInt16LE() - info.Deref(12, 2).toBuffer().readUInt16LE() + 1;
503 +
504 + this._stream.push(GetEsc('H', nHeight-1, 0));
505 + for (var i = 0; i > nHeight; ++i)
506 + {
507 + this._stream.push(Buffer.from('\r\n'));
508 + }
509 +
510 + var buffer = this._GetScreenBuffer(0, 0, nWidth - 1, nHeight - 1);
511 + this._SendDataBuffer(buffer);
512 +
513 + this._scrollTimer = setTimeout(function (self, nw, nh)
514 + {
515 + var buffer = self._GetScreenBuffer(0, 0, nw - 1, nh - 1);
516 + self._SendDataBuffer(buffer);
517 + self._scrollTimer = null;
518 + }, 250, this, nWidth, nHeight);
519 + }
520 +
521 + this.StartCommand = function StartCommand()
522 + {
523 + 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)
524 + {
525 + console.log('Error Spawning CMD');
526 + return;
527 + }
528 +
529 + this._kernel32.CloseHandle(pi.Deref(GM.PointerSize, GM.PointerSize).Deref()); // pi.hThread
530 + this._hProcess = pi.Deref(0, GM.PointerSize).Deref(); // pi.hProcess
531 + this._hProcessID = pi.Deref(GM.PointerSize == 4 ? 8 : 16, 4).toBuffer().readUInt32LE(); // pi.dwProcessId
532 + //console.log('Ready => hProcess: ' + this._hProcess._ptr + ' PID: ' + this._hProcessID);
533 + }
534 +}
535 +
536 +function LOWORD(val)
537 +{
538 + return (val & 0xFFFF);
539 +}
540 +function HIWORD(val)
541 +{
542 + return ((val >> 16) & 0xFFFF);
543 +}
544 +
545 +function GetEsc(CodeCharStr, arg1, arg2)
546 +{
547 + return (Buffer.from('\x1B[' + arg1 + ';' + arg2 + CodeCharStr));
548 + //return (Buffer.from('*[' + arg1 + ';' + arg2 + CodeCharStr));
549 +}
550 +
551 +function TranslateLine(x, y, data, attributes)
552 +{
553 + return (Buffer.concat([GetEsc('H', y, x), data]));
554 +}
555 +module.exports = new windows_terminal();
\ No newline at end of file
package.json
+1 -1
@@ -1,6 +1,6 @@
1 {
2 "name": "meshcentral",
3 - "version": "0.2.4-g",
3 + "version": "0.2.4-h",
4 "keywords": [
5 "Remote Management",
6 "Intel AMT",