EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
agentrecoverycore.js
Go to the documentation of this file.
1
2var http = require('http');
3var childProcess = require('child_process');
4var meshCoreObj = { "action": "coreinfo", "value": "MeshCore Recovery", "caps": 14 }; // Capability bitmask: 1 = Desktop, 2 = Terminal, 4 = Files, 8 = Console, 16 = JavaScript
5var nextTunnelIndex = 1;
6var tunnels = {};
7var fs = require('fs');
8
9//attachDebugger({ webport: 9994, wait: 1 }).then(function (p) { console.log('Debug on port: ' + p); });
10
11function sendConsoleText(msg)
12{
13 require('MeshAgent').SendCommand({ "action": "msg", "type": "console", "value": msg });
14}
15// Return p number of spaces
16function addPad(p, ret) { var r = ''; for (var i = 0; i < p; i++) { r += ret; } return r; }
17
18var path =
19 {
20 join: function ()
21 {
22 var x = [];
23 for (var i in arguments)
24 {
25 var w = arguments[i];
26 if (w != null)
27 {
28 while (w.endsWith('/') || w.endsWith('\\')) { w = w.substring(0, w.length - 1); }
29 if (i != 0)
30 {
31 while (w.startsWith('/') || w.startsWith('\\')) { w = w.substring(1); }
32 }
33 x.push(w);
34 }
35 }
36 if (x.length == 0) return '/';
37 return x.join('/');
38 }
39 };
40// Convert an object to string with all functions
41function objToString(x, p, pad, ret) {
42 if (ret == undefined) ret = '';
43 if (p == undefined) p = 0;
44 if (x == null) { return '[null]'; }
45 if (p > 8) { return '[...]'; }
46 if (x == undefined) { return '[undefined]'; }
47 if (typeof x == 'string') { if (p == 0) return x; return '"' + x + '"'; }
48 if (typeof x == 'buffer') { return '[buffer]'; }
49 if (typeof x != 'object') { return x; }
50 var r = '{' + (ret ? '\r\n' : ' ');
51 for (var i in x) { if (i != '_ObjectID') { r += (addPad(p + 2, pad) + i + ': ' + objToString(x[i], p + 2, pad, ret) + (ret ? '\r\n' : ' ')); } }
52 return r + addPad(p, pad) + '}';
53}
54
55// Split a string taking into account the quoats. Used for command line parsing
56function splitArgs(str)
57{
58 var myArray = [], myRegexp = /[^\s"]+|"([^"]*)"/gi;
59 do { var match = myRegexp.exec(str); if (match != null) { myArray.push(match[1] ? match[1] : match[0]); } } while (match != null);
60 return myArray;
61}
62
63// Parse arguments string array into an object
64function parseArgs(argv)
65{
66 var results = { '_': [] }, current = null;
67 for (var i = 1, len = argv.length; i < len; i++) {
68 var x = argv[i];
69 if (x.length > 2 && x[0] == '-' && x[1] == '-') {
70 if (current != null) { results[current] = true; }
71 current = x.substring(2);
72 } else {
73 if (current != null) { results[current] = toNumberIfNumber(x); current = null; } else { results['_'].push(toNumberIfNumber(x)); }
74 }
75 }
76 if (current != null) { results[current] = true; }
77 return results;
78}
79// Get server target url with a custom path
80function getServerTargetUrl(path)
81{
82 var x = require('MeshAgent').ServerUrl;
83 //sendConsoleText("mesh.ServerUrl: " + mesh.ServerUrl);
84 if (x == null) { return null; }
85 if (path == null) { path = ''; }
86 x = http.parseUri(x);
87 if (x == null) return null;
88 return x.protocol + '//' + x.host + ':' + x.port + '/' + path;
89}
90
91// Get server url. If the url starts with "*/..." change it, it not use the url as is.
92function getServerTargetUrlEx(url)
93{
94 if (url.substring(0, 2) == '*/') { return getServerTargetUrl(url.substring(2)); }
95 return url;
96}
97
98require('MeshAgent').on('Connected', function ()
99{
100 require('os').name().then(function (v)
101 {
102 sendConsoleText("Mesh Agent Receovery Console, OS: " + v);
103 require('MeshAgent').SendCommand(meshCoreObj);
104 });
105});
106
107// Tunnel callback operations
108function onTunnelUpgrade(response, s, head) {
109 this.s = s;
110 s.httprequest = this;
111 s.end = onTunnelClosed;
112 s.tunnel = this;
113
114 //sendConsoleText('onTunnelUpgrade');
115
116 if (this.tcpport != null) {
117 // This is a TCP relay connection, pause now and try to connect to the target.
118 s.pause();
119 s.data = onTcpRelayServerTunnelData;
120 var connectionOptions = { port: parseInt(this.tcpport) };
121 if (this.tcpaddr != null) { connectionOptions.host = this.tcpaddr; } else { connectionOptions.host = '127.0.0.1'; }
122 s.tcprelay = net.createConnection(connectionOptions, onTcpRelayTargetTunnelConnect);
123 s.tcprelay.peerindex = this.index;
124 } else {
125 // This is a normal connect for KVM/Terminal/Files
126 s.data = onTunnelData;
127 }
128}
129
130require('MeshAgent').AddCommandHandler(function (data)
131{
132 if (typeof data == 'object')
133 {
134 // If this is a console command, parse it and call the console handler
135 switch (data.action)
136 {
137 case 'msg':
138 {
139 switch (data.type)
140 {
141 case 'console': { // Process a console command
142 if (data.value && data.sessionid)
143 {
144 var args = splitArgs(data.value);
145 processConsoleCommand(args[0].toLowerCase(), parseArgs(args), data.rights, data.sessionid);
146 }
147 break;
148 }
149 case 'tunnel':
150 {
151 if (data.value != null) { // Process a new tunnel connection request
152 // Create a new tunnel object
153 var xurl = getServerTargetUrlEx(data.value);
154 if (xurl != null) {
155 var woptions = http.parseUri(xurl);
156 woptions.rejectUnauthorized = 0;
157 //sendConsoleText(JSON.stringify(woptions));
158 var tunnel = http.request(woptions);
159 tunnel.on('upgrade', function (response, s, head)
160 {
161 this.s = s;
162 s.httprequest = this;
163 s.tunnel = this;
164 s.on('end', function ()
165 {
166 if (tunnels[this.httprequest.index] == null) return; // Stop duplicate calls.
167
168 // If there is a upload or download active on this connection, close the file
169 if (this.httprequest.uploadFile) { fs.closeSync(this.httprequest.uploadFile); this.httprequest.uploadFile = undefined; }
170 if (this.httprequest.downloadFile) { fs.closeSync(this.httprequest.downloadFile); this.httprequest.downloadFile = undefined; }
171
172
173 //sendConsoleText("Tunnel #" + this.httprequest.index + " closed.", this.httprequest.sessionid);
174 delete tunnels[this.httprequest.index];
175
176 // Clean up WebSocket
177 this.removeAllListeners('data');
178 });
179 s.on('data', function (data)
180 {
181 // If this is upload data, save it to file
182 if (this.httprequest.uploadFile)
183 {
184 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.
185 this.write(new Buffer(JSON.stringify({ action: 'uploadack', reqid: this.httprequest.uploadFileid }))); // Ask for more data
186 return;
187 }
188
189 if (this.httprequest.state == 0) {
190 // Check if this is a relay connection
191 if (data == 'c') { this.httprequest.state = 1; sendConsoleText("Tunnel #" + this.httprequest.index + " now active", this.httprequest.sessionid); }
192 } else {
193 // Handle tunnel data
194 if (this.httprequest.protocol == 0)
195 {
196 // Take a look at the protocol
197 this.httprequest.protocol = parseInt(data);
198 if (typeof this.httprequest.protocol != 'number') { this.httprequest.protocol = 0; }
199 if (this.httprequest.protocol == 1)
200 {
201 // Remote terminal using native pipes
202 if (process.platform == "win32")
203 {
204 this.httprequest._term = require('win-terminal').Start(80, 25);
205 this.httprequest._term.pipe(this, { dataTypeSkip: 1 });
206 this.pipe(this.httprequest._term, { dataTypeSkip: 1, end: false });
207 this.prependListener('end', function () { this.httprequest._term.end(function () { sendConsoleText('Terminal was closed'); }); });
208 }
209 else
210 {
211 this.httprequest.process = childProcess.execFile("/bin/sh", ["sh"], { type: childProcess.SpawnTypes.TERM });
212 this.httprequest.process.tunnel = this;
213 this.httprequest.process.on('exit', function (ecode, sig) { this.tunnel.end(); });
214 this.httprequest.process.stderr.on('data', function (chunk) { this.parent.tunnel.write(chunk); });
215 this.httprequest.process.stdout.pipe(this, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
216 this.pipe(this.httprequest.process.stdin, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
217 this.prependListener('end', function () { this.httprequest.process.kill(); });
218 }
219
220 this.on('end', function () {
221 if (process.platform == "win32")
222 {
223 // Unpipe the web socket
224 this.unpipe(this.httprequest._term);
225 this.httprequest._term.unpipe(this);
226
227 // Clean up
228 this.httprequest._term.end();
229 this.httprequest._term = null;
230 }
231 });
232 }
233 }
234 else if (this.httprequest.protocol == 5)
235 {
236 // Process files commands
237 var cmd = null;
238 try { cmd = JSON.parse(data); } catch (e) { };
239 if (cmd == null) { return; }
240 if ((cmd.ctrlChannel == '102938') || ((cmd.type == 'offer') && (cmd.sdp != null))) { return; } // If this is control data, handle it now.
241 if (cmd.action == undefined) { return; }
242 console.log('action: ', cmd.action);
243
244 //sendConsoleText('CMD: ' + JSON.stringify(cmd));
245
246 if ((cmd.path != null) && (process.platform != 'win32') && (cmd.path[0] != '/')) { cmd.path = '/' + cmd.path; } // Add '/' to paths on non-windows
247 //console.log(objToString(cmd, 0, ' '));
248 switch (cmd.action)
249 {
250 case 'ls':
251 // Send the folder content to the browser
252 var response = getDirectoryInfo(cmd.path);
253 if (cmd.reqid != undefined) { response.reqid = cmd.reqid; }
254 this.write(new Buffer(JSON.stringify(response)));
255 break;
256 case 'mkdir': {
257 // Create a new empty folder
258 fs.mkdirSync(cmd.path);
259 break;
260 }
261 case 'mkfile': {
262 // Create a new empty file
263 fs.closeSync(fs.openSync(cmd.path, 'w'));
264 break;
265 }
266 case 'rm': {
267 // Delete, possibly recursive delete
268 for (var i in cmd.delfiles)
269 {
270 try { deleteFolderRecursive(path.join(cmd.path, cmd.delfiles[i]), cmd.rec); } catch (e) { }
271 }
272 break;
273 }
274 case 'rename': {
275 // Rename a file or folder
276 var oldfullpath = path.join(cmd.path, cmd.oldname);
277 var newfullpath = path.join(cmd.path, cmd.newname);
278 try { fs.renameSync(oldfullpath, newfullpath); } catch (e) { console.log(e); }
279 break;
280 }
281 case 'upload': {
282 // Upload a file, browser to agent
283 if (this.httprequest.uploadFile != undefined) { fs.closeSync(this.httprequest.uploadFile); this.httprequest.uploadFile = undefined; }
284 if (cmd.path == undefined) break;
285 var filepath = cmd.name ? path.join(cmd.path, cmd.name) : cmd.path;
286 try { this.httprequest.uploadFile = fs.openSync(filepath, 'wbN'); } catch (e) { this.write(new Buffer(JSON.stringify({ action: 'uploaderror', reqid: cmd.reqid }))); break; }
287 this.httprequest.uploadFileid = cmd.reqid;
288 if (this.httprequest.uploadFile) { this.write(new Buffer(JSON.stringify({ action: 'uploadstart', reqid: this.httprequest.uploadFileid }))); }
289 break;
290 }
291 case 'copy': {
292 // Copy a bunch of files from scpath to dspath
293 for (var i in cmd.names) {
294 var sc = path.join(cmd.scpath, cmd.names[i]), ds = path.join(cmd.dspath, cmd.names[i]);
295 if (sc != ds) { try { fs.copyFileSync(sc, ds); } catch (e) { } }
296 }
297 break;
298 }
299 case 'move': {
300 // Move a bunch of files from scpath to dspath
301 for (var i in cmd.names) {
302 var sc = path.join(cmd.scpath, cmd.names[i]), ds = path.join(cmd.dspath, cmd.names[i]);
303 if (sc != ds) { try { fs.copyFileSync(sc, ds); fs.unlinkSync(sc); } catch (e) { } }
304 }
305 break;
306 }
307 }
308 }
309 }
310 });
311 });
312 tunnel.onerror = function (e) { sendConsoleText('ERROR: ' + JSON.stringify(e)); }
313 tunnel.sessionid = data.sessionid;
314 tunnel.rights = data.rights;
315 tunnel.state = 0;
316 tunnel.url = xurl;
317 tunnel.protocol = 0;
318 tunnel.tcpaddr = data.tcpaddr;
319 tunnel.tcpport = data.tcpport;
320 tunnel.end();
321 // Put the tunnel in the tunnels list
322 var index = nextTunnelIndex++;
323 tunnel.index = index;
324 tunnels[index] = tunnel;
325
326 //sendConsoleText('New tunnel connection #' + index + ': ' + tunnel.url + ', rights: ' + tunnel.rights, data.sessionid);
327 }
328 }
329 break;
330 }
331
332 default:
333 // Unknown action, ignore it.
334 break;
335 }
336 break;
337 }
338 default:
339 // Unknown action, ignore it.
340 break;
341 }
342 }
343});
344
345function processConsoleCommand(cmd, args, rights, sessionid)
346{
347 try
348 {
349 var response = null;
350 switch (cmd)
351 {
352 case 'help':
353 response = 'Available commands are: osinfo, dbkeys, dbget, dbset, dbcompact, netinfo.';
354 break;
355
356 case 'osinfo': { // Return the operating system information
357 var i = 1;
358 if (args['_'].length > 0) { i = parseInt(args['_'][0]); if (i > 8) { i = 8; } response = 'Calling ' + i + ' times.'; }
359 for (var j = 0; j < i; j++) {
360 var pr = require('os').name();
361 pr.sessionid = sessionid;
362 pr.then(function (v) { sendConsoleText("OS: " + v, this.sessionid); });
363 }
364 break;
365 }
366 case 'dbkeys': { // Return all data store keys
367 response = JSON.stringify(db.Keys);
368 break;
369 }
370 case 'dbget': { // Return the data store value for a given key
371 if (db == null) { response = 'Database not accessible.'; break; }
372 if (args['_'].length != 1) {
373 response = 'Proper usage: dbget (key)'; // Display the value for a given database key
374 } else {
375 response = db.Get(args['_'][0]);
376 }
377 break;
378 }
379 case 'dbset': { // Set a data store key and value pair
380 if (db == null) { response = 'Database not accessible.'; break; }
381 if (args['_'].length != 2) {
382 response = 'Proper usage: dbset (key) (value)'; // Set a database key
383 } else {
384 var r = db.Put(args['_'][0], args['_'][1]);
385 response = 'Key set: ' + r;
386 }
387 break;
388 }
389 case 'dbcompact': { // Compact the data store
390 if (db == null) { response = 'Database not accessible.'; break; }
391 var r = db.Compact();
392 response = 'Database compacted: ' + r;
393 break;
394 }
395 case 'tunnels': { // Show the list of current tunnels
396 response = '';
397 for (var i in tunnels) { response += 'Tunnel #' + i + ', ' + tunnels[i].url + '\r\n'; }
398 if (response == '') { response = 'No websocket sessions.'; }
399 break;
400 }
401 case 'netinfo': { // Show network interface information
402 //response = objToString(mesh.NetInfo, 0, ' ');
403 var interfaces = require('os').networkInterfaces();
404 response = objToString(interfaces, 0, ' ', true);
405 break;
406 }
407 default: { // This is an unknown command, return an error message
408 response = 'Unknown command \"' + cmd + '\", type \"help\" for list of available commands.';
409 break;
410 }
411 }
412 } catch (e) { response = 'Command returned an exception error: ' + e; console.log(e); }
413 if (response != null) { sendConsoleText(response, sessionid); }
414}
415
416// Get a formated response for a given directory path
417function getDirectoryInfo(reqpath)
418{
419 var response = { path: reqpath, dir: [] };
420 if (((reqpath == undefined) || (reqpath == '')) && (process.platform == 'win32')) {
421 // List all the drives in the root, or the root itself
422 var results = null;
423 try { results = fs.readDrivesSync(); } catch (e) { } // TODO: Anyway to get drive total size and free space? Could draw a progress bar.
424 if (results != null) {
425 for (var i = 0; i < results.length; ++i) {
426 var drive = { n: results[i].name, t: 1 };
427 if (results[i].type == 'REMOVABLE') { drive.dt = 'removable'; } // TODO: See if this is USB/CDROM or something else, we can draw icons.
428 response.dir.push(drive);
429 }
430 }
431 } else {
432 // List all the files and folders in this path
433 if (reqpath == '') { reqpath = '/'; }
434 var results = null, xpath = path.join(reqpath, '*');
435 //if (process.platform == "win32") { xpath = xpath.split('/').join('\\'); }
436 try { results = fs.readdirSync(xpath); } catch (e) { }
437 if (results != null) {
438 for (var i = 0; i < results.length; ++i) {
439 if ((results[i] != '.') && (results[i] != '..')) {
440 var stat = null, p = path.join(reqpath, results[i]);
441 //if (process.platform == "win32") { p = p.split('/').join('\\'); }
442 try { stat = fs.statSync(p); } catch (e) { } // TODO: Get file size/date
443 if ((stat != null) && (stat != undefined)) {
444 if (stat.isDirectory() == true) {
445 response.dir.push({ n: results[i], t: 2, d: stat.mtime });
446 } else {
447 response.dir.push({ n: results[i], t: 3, s: stat.size, d: stat.mtime });
448 }
449 }
450 }
451 }
452 }
453 }
454 return response;
455}
456// Delete a directory with a files and directories within it
457function deleteFolderRecursive(path, rec) {
458 if (fs.existsSync(path)) {
459 if (rec == true) {
460 fs.readdirSync(path.join(path, '*')).forEach(function (file, index) {
461 var curPath = path.join(path, file);
462 if (fs.statSync(curPath).isDirectory()) { // recurse
463 deleteFolderRecursive(curPath, true);
464 } else { // delete file
465 fs.unlinkSync(curPath);
466 }
467 });
468 }
469 fs.unlinkSync(path);
470 }
471};