EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
recoverycore.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
9var needStreamFix = (new Date(process.versions.meshAgent) < new Date('2020-01-21 13:27:45.000-08:00'));
10try
11{
12 Object.defineProperty(Array.prototype, 'find', {
13 value: function (func)
14 {
15 var i = 0;
16 for(i=0;i<this.length;++i)
17 {
18 if(func(this[i]))
19 {
20 return (this[i]);
21 }
22 }
23 return (null);
24 }
25 });
26}
27catch(x)
28{
29
30}
31try
32{
33 Object.defineProperty(Array.prototype, 'findIndex', {
34 value: function (func)
35 {
36 var i = 0;
37 for (i = 0; i < this.length; ++i)
38 {
39 if (func(this[i], i, this))
40 {
41 return (i);
42 }
43 }
44 return (-1);
45 }
46 });
47}
48catch (x)
49{
50
51}
52
53if (process.platform != 'win32')
54{
55 var ch = require('child_process');
56 ch._execFile = ch.execFile;
57 ch.execFile = function execFile(path, args, options)
58 {
59 if (options && options.type && options.type == ch.SpawnTypes.TERM && options.env)
60 {
61 options.env['TERM'] = 'xterm-256color';
62 }
63 return (this._execFile(path, args, options));
64 };
65}
66
67function _getPotentialServiceNames()
68{
69 var registry = require('win-registry');
70 var ret = [];
71 var K = registry.QueryKey(registry.HKEY.LocalMachine, 'SYSTEM\\CurrentControlSet\\Services');
72 var service, s;
73 while (K.subkeys.length > 0)
74 {
75 service = K.subkeys.shift();
76 try
77 {
78 s = registry.QueryKey(registry.HKEY.LocalMachine, 'SYSTEM\\CurrentControlSet\\Services\\' + service, 'ImagePath');
79 if (s.startsWith(process.execPath) || s.startsWith('"' + process.execPath + '"'))
80 {
81 ret.push(service);
82 }
83 }
84 catch (x)
85 {
86 }
87 }
88 return (ret);
89}
90function _verifyServiceName(names)
91{
92 var i;
93 var s;
94 var ret = null;
95 for (i = 0; i < names.length; ++i)
96 {
97 try
98 {
99 s = require('service-manager').manager.getService(names[i]);
100 if (s.isMe())
101 {
102 ret = names[i];
103 s.close();
104 break;
105 }
106 s.close();
107 }
108 catch (z) { }
109 }
110 return (ret);
111}
112
113function windows_getCommandLine()
114{
115 var parms = [];
116 var GM = require('_GenericMarshal');
117 var k32 = GM.CreateNativeProxy('kernel32.dll');
118 var s32 = GM.CreateNativeProxy('shell32.dll');
119 k32.CreateMethod('GetCommandLineW');
120 k32.CreateMethod('LocalFree');
121 s32.CreateMethod('CommandLineToArgvW');
122 var v = k32.GetCommandLineW();
123 var i;
124 var len = GM.CreateVariable(4);
125 var val = s32.CommandLineToArgvW(v, len);
126 len = len.toBuffer().readInt32LE(0);
127 if (len > 0)
128 {
129 for (i = 0; i < len; ++i)
130 {
131 parms.push(val.Deref(i * GM.PointerSize, GM.PointerSize).Deref().Wide2UTF8);
132 }
133 }
134 k32.LocalFree(val);
135 return (parms);
136}
137
138if (require('MeshAgent').ARCHID == null)
139{
140 var id = null;
141 switch (process.platform)
142 {
143 case 'win32':
144 id = require('_GenericMarshal').PointerSize == 4 ? 3 : 4;
145 break;
146 case 'freebsd':
147 id = require('_GenericMarshal').PointerSize == 4 ? 31 : 30;
148 break;
149 case 'darwin':
150 try
151 {
152 id = require('os').arch() == 'x64' ? 16 : 29;
153 }
154 catch (xx)
155 {
156 id = 16;
157 }
158 break;
159 }
160 if (id != null) { Object.defineProperty(require('MeshAgent'), 'ARCHID', { value: id }); }
161}
162
163//attachDebugger({ webport: 9994, wait: 1 }).then(function (p) { console.log('Debug on port: ' + p); });
164
165function sendConsoleText(msg, sessionid)
166{
167 if (sessionid != null)
168 {
169 require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: msg, sessionid: sessionid });
170 }
171 else
172 {
173 require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: msg });
174 }
175}
176
177function sendAgentMessage(msg, icon)
178{
179 if (sendAgentMessage.messages == null)
180 {
181 sendAgentMessage.messages = {};
182 sendAgentMessage.nextid = 1;
183 }
184 sendAgentMessage.messages[sendAgentMessage.nextid++] = { msg: msg, icon: icon };
185 require('MeshAgent').SendCommand({ action: 'sessions', type: 'msg', value: sendAgentMessage.messages });
186}
187
188// Add to the server event log
189function MeshServerLog(msg, state)
190{
191 if (typeof msg == 'string') { msg = { action: 'log', msg: msg }; } else { msg.action = 'log'; }
192 if (state)
193 {
194 if (state.userid) { msg.userid = state.userid; }
195 if (state.username) { msg.username = state.username; }
196 if (state.sessionid) { msg.sessionid = state.sessionid; }
197 if (state.remoteaddr) { msg.remoteaddr = state.remoteaddr; }
198 }
199 require('MeshAgent').SendCommand(msg);
200}
201
202// Add to the server event log, use internationalized events
203function MeshServerLogEx(id, args, msg, state)
204{
205 var msg = { action: 'log', msgid: id, msgArgs: args, msg: msg };
206 if (state)
207 {
208 if (state.userid) { msg.userid = state.userid; }
209 if (state.username) { msg.username = state.username; }
210 if (state.sessionid) { msg.sessionid = state.sessionid; }
211 if (state.remoteaddr) { msg.remoteaddr = state.remoteaddr; }
212 }
213 require('MeshAgent').SendCommand(msg);
214}
215
216function getOpenDescriptors()
217{
218 switch(process.platform)
219 {
220 case "freebsd":
221 var child = require('child_process').execFile('/bin/sh', ['sh']);
222 child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
223 child.stderr.on('data', function (c) { });
224
225 child.stdin.write("procstat -f " + process.pid + " | tr '\\n' '`' | awk -F'`' '");
226 child.stdin.write('{');
227 child.stdin.write(' DEL="";');
228 child.stdin.write(' printf "[";');
229 child.stdin.write(' for(i=1;i<NF;++i)');
230 child.stdin.write(' {');
231 child.stdin.write(' A=split($i,B," ");');
232 child.stdin.write(' if(B[3] ~ /^[0-9]/)');
233 child.stdin.write(' {');
234 child.stdin.write(' printf "%s%s", DEL, B[3];');
235 child.stdin.write(' DEL=",";');
236 child.stdin.write(' }');
237 child.stdin.write(' }');
238 child.stdin.write(' printf "]";');
239 child.stdin.write("}'");
240
241 child.stdin.write('\nexit\n');
242 child.waitExit();
243
244 try
245 {
246 return(JSON.parse(child.stdout.str.trim()));
247 }
248 catch(e)
249 {
250 return ([]);
251 }
252 break;
253 case "linux":
254 var child = require('child_process').execFile('/bin/sh', ['sh']);
255 child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
256 child.stderr.on('data', function (c) { });
257
258 child.stdin.write("ls /proc/" + process.pid + "/fd | tr '\\n' '`' | awk -F'`' '");
259 child.stdin.write('{');
260 child.stdin.write(' printf "[";');
261 child.stdin.write(' DEL="";');
262 child.stdin.write(' for(i=1;i<NF;++i)');
263 child.stdin.write(' {');
264 child.stdin.write(' printf "%s%s",DEL,$i;');
265 child.stdin.write(' DEL=",";');
266 child.stdin.write(' }');
267 child.stdin.write(' printf "]";');
268 child.stdin.write("}'");
269 child.stdin.write('\nexit\n');
270 child.waitExit();
271
272 try
273 {
274 return (JSON.parse(child.stdout.str.trim()));
275 }
276 catch (e)
277 {
278 return ([]);
279 }
280 break;
281 default:
282 return ([]);
283 }
284}
285
286
287function pathjoin() {
288 var x = [];
289 for (var i in arguments) {
290 var w = arguments[i];
291 if (w != null) {
292 while (w.endsWith('/') || w.endsWith('\\')) { w = w.substring(0, w.length - 1); }
293 if (i != 0) {
294 while (w.startsWith('/') || w.startsWith('\\')) { w = w.substring(1); }
295 }
296 x.push(w);
297 }
298 }
299 if (x.length == 0) return '/';
300 return x.join('/');
301}
302// Replace a string with a number if the string is an exact number
303function toNumberIfNumber(x) { if ((typeof x == 'string') && (+parseInt(x) === x)) { x = parseInt(x); } return x; }
304
305
306function closeDescriptors(libc, descriptors)
307{
308 var fd = null;
309 while(descriptors.length>0)
310 {
311 fd = descriptors.pop();
312 if(fd > 2)
313 {
314 libc.close(fd);
315 }
316 }
317}
318
319function linux_execv(name, agentfilename, sessionid)
320{
321 var libs = require('monitor-info').getLibInfo('libc');
322 var libc = null;
323
324 if ((libs.length == 0 || libs.length == null) && require('MeshAgent').ARCHID == 33)
325 {
326 var child = require('child_process').execFile('/bin/sh', ['sh']);
327 child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
328 child.stderr.str = ''; child.stderr.on('data', function (c) { this.str += c.toString(); });
329 child.stdin.write("ls /lib/libc.* | tr '\\n' '`' | awk -F'`' '{ " + ' printf "["; DEL=""; for(i=1;i<NF;++i) { printf "%s{\\"path\\":\\"%s\\"}",DEL,$i; DEL=""; } printf "]"; }\'\nexit\n');
330 child.waitExit();
331
332 try
333 {
334 libs = JSON.parse(child.stdout.str.trim());
335 }
336 catch(e)
337 {
338 }
339 }
340
341 while (libs.length > 0)
342 {
343 try {
344 libc = require('_GenericMarshal').CreateNativeProxy(libs.pop().path);
345 break;
346 }
347 catch (e) {
348 libc = null;
349 continue;
350 }
351 }
352 if (libc != null) {
353 try
354 {
355 libc.CreateMethod('execv');
356 libc.CreateMethod('close');
357 }
358 catch (e) {
359 libc = null;
360 }
361 }
362
363 if (libc == null) {
364 // Couldn't find libc.so, fallback to using service manager to restart agent
365 if (sessionid != null) { sendConsoleText('Restarting service via service-manager...', sessionid) }
366 try {
367 // restart service
368 var s = require('service-manager').manager.getService(name);
369 s.restart();
370 }
371 catch (zz) {
372 sendConsoleText('Self Update encountered an error trying to restart service', sessionid);
373 sendAgentMessage('Self Update encountered an error trying to restart service', 3);
374 }
375 return;
376 }
377
378 if (sessionid != null) { sendConsoleText('Restarting service via execv()...', sessionid) }
379
380 var i;
381 var args;
382 var argtmp = [];
383 var argarr = [process.execPath];
384 var path = require('_GenericMarshal').CreateVariable(process.execPath);
385
386 if (require('MeshAgent').getStartupOptions != null) {
387 var options = require('MeshAgent').getStartupOptions();
388 for (i in options) {
389 argarr.push('--' + i + '="' + options[i] + '"');
390 }
391 }
392
393 args = require('_GenericMarshal').CreateVariable((1 + argarr.length) * require('_GenericMarshal').PointerSize);
394 for (i = 0; i < argarr.length; ++i) {
395 var arg = require('_GenericMarshal').CreateVariable(argarr[i]);
396 argtmp.push(arg);
397 arg.pointerBuffer().copy(args.toBuffer(), i * require('_GenericMarshal').PointerSize);
398 }
399
400 var descriptors = getOpenDescriptors();
401 closeDescriptors(libc, descriptors);
402
403 libc.execv(path, args);
404 if (sessionid != null) { sendConsoleText('Self Update failed because execv() failed', sessionid) }
405 sendAgentMessage('Self Update failed because execv() failed', 3);
406}
407
408function bsd_execv(name, agentfilename, sessionid) {
409 var child = require('child_process').execFile('/bin/sh', ['sh']);
410 child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
411 child.stderr.str = ''; child.stderr.on('data', function (c) { this.str += c.toString(); });
412 child.stdin.write("cat /usr/lib/libc.so | awk '");
413 child.stdin.write('{');
414 child.stdin.write(' a=split($0, tok, "(");');
415 child.stdin.write(' if(a>1)');
416 child.stdin.write(' {');
417 child.stdin.write(' split(tok[2], b, ")");');
418 child.stdin.write(' split(b[1], c, " ");');
419 child.stdin.write(' print c[1];');
420 child.stdin.write(' }');
421 child.stdin.write("}'\nexit\n");
422 child.waitExit();
423 if (child.stdout.str.trim() == '') {
424 if (sessionid != null) { sendConsoleText('Self Update failed because cannot find libc.so', sessionid) }
425 sendAgentMessage('Self Update failed because cannot find libc.so', 3);
426 return;
427 }
428
429 var libc = null;
430 try
431 {
432 libc = require('_GenericMarshal').CreateNativeProxy(child.stdout.str.trim());
433 libc.CreateMethod('execv');
434 libc.CreateMethod('close');
435 }
436 catch (e) {
437 if (sessionid != null) { sendConsoleText('Self Update failed: ' + e.toString(), sessionid) }
438 sendAgentMessage('Self Update failed: ' + e.toString(), 3);
439 return;
440 }
441
442 var i;
443 var path = require('_GenericMarshal').CreateVariable(process.execPath);
444 var argarr = [process.execPath];
445 var argtmp = [];
446 var args;
447 var options = require('MeshAgent').getStartupOptions();
448 for (i in options) {
449 argarr.push('--' + i + '="' + options[i] + '"');
450 }
451 args = require('_GenericMarshal').CreateVariable((1 + argarr.length) * require('_GenericMarshal').PointerSize);
452 for (i = 0; i < argarr.length; ++i) {
453 var arg = require('_GenericMarshal').CreateVariable(argarr[i]);
454 argtmp.push(arg);
455 arg.pointerBuffer().copy(args.toBuffer(), i * require('_GenericMarshal').PointerSize);
456 }
457
458 if (sessionid != null) { sendConsoleText('Restarting service via execv()', sessionid) }
459
460 var descriptors = getOpenDescriptors();
461 closeDescriptors(libc, descriptors);
462
463 libc.execv(path, args);
464 if (sessionid != null) { sendConsoleText('Self Update failed because execv() failed', sessionid) }
465 sendAgentMessage('Self Update failed because execv() failed', 3);
466}
467
468function windows_execve(name, agentfilename, sessionid) {
469 var libc;
470 try {
471 libc = require('_GenericMarshal').CreateNativeProxy('msvcrt.dll');
472 libc.CreateMethod('_wexecve');
473 }
474 catch (xx) {
475 sendConsoleText('Self Update failed because msvcrt.dll is missing', sessionid);
476 sendAgentMessage('Self Update failed because msvcrt.dll is missing', 3);
477 return;
478 }
479
480 var cwd = process.cwd();
481 if (!cwd.endsWith('\\'))
482 {
483 cwd += '\\';
484 }
485 var cmd = require('_GenericMarshal').CreateVariable(process.env['windir'] + '\\system32\\cmd.exe', { wide: true });
486 var args = require('_GenericMarshal').CreateVariable(3 * require('_GenericMarshal').PointerSize);
487 var arg1 = require('_GenericMarshal').CreateVariable('cmd.exe', { wide: true });
488 var arg2 = require('_GenericMarshal').CreateVariable('/C net stop "' + name + '" & "' + cwd + agentfilename + '.update.exe" -b64exec ' + 'dHJ5CnsKICAgIHZhciBzZXJ2aWNlTG9jYXRpb24gPSBwcm9jZXNzLmFyZ3YucG9wKCkudG9Mb3dlckNhc2UoKTsKICAgIHJlcXVpcmUoJ3Byb2Nlc3MtbWFuYWdlcicpLmVudW1lcmF0ZVByb2Nlc3NlcygpLnRoZW4oZnVuY3Rpb24gKHByb2MpCiAgICB7CiAgICAgICAgZm9yICh2YXIgcCBpbiBwcm9jKQogICAgICAgIHsKICAgICAgICAgICAgaWYgKHByb2NbcF0ucGF0aCAmJiAocHJvY1twXS5wYXRoLnRvTG93ZXJDYXNlKCkgPT0gc2VydmljZUxvY2F0aW9uKSkKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgcHJvY2Vzcy5raWxsKHByb2NbcF0ucGlkKTsKICAgICAgICAgICAgfQogICAgICAgIH0KICAgICAgICBwcm9jZXNzLmV4aXQoKTsKICAgIH0pOwp9CmNhdGNoIChlKQp7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQ==' +
489 ' "' + process.execPath + '" & copy "' + cwd + agentfilename + '.update.exe" "' + process.execPath + '" & net start "' + name + '" & erase "' + cwd + agentfilename + '.update.exe"', { wide: true });
490
491 if (name == null)
492 {
493 // We can continue with self update for Temp/Console Mode on Windows
494 var db = null;
495 var update = cwd + agentfilename + '.update.exe';
496 var updatedb = cwd + agentfilename + '.update.db';
497 var parms = windows_getCommandLine(); parms.shift();
498
499 var updatesource = parms.find(function (v) { return (v.startsWith('--updateSourcePath=')); });
500 if (updatesource == null)
501 {
502 parms.push('--updateSourcePath="' + cwd + agentfilename + '"');
503 updatesource = (cwd + agentfilename).split('.exe'); updatesource.pop(); updatesource = updatesource.join('.exe');
504 db = updatesource + '.db';
505 updatesource = (' & move "' + updatedb + '" "' + db + '"') + (' & erase "' + updatedb + '" & move "' + update + '" "' + updatesource + '.exe"');
506 }
507 else
508 {
509 updatesource = updatesource.substring(19).split('.exe');
510 updatesource.pop(); updatesource = updatesource.join('.exe');
511 db = updatesource + '.db';
512 updatesource = (' & move "' + update + '" "' + updatesource + '.exe" & move "' + updatedb + '" "' + db + '" & erase "' + updatedb + '"') + (' & echo move "' + update + '" "' + updatesource + '.exe" & echo move "' + updatedb + '" "' + db + '"');
513 }
514
515 var tmp = '/C echo copy "' + db + '" "' + updatedb + '" & copy "' + db + '" "' + updatedb + '"' + ' & "' + update + '" ' + parms.join(' ') + updatesource + ' & erase "' + update + '" & echo ERASE "' + update + '"';
516 arg2 = require('_GenericMarshal').CreateVariable(tmp, { wide: true });
517 }
518
519 arg1.pointerBuffer().copy(args.toBuffer());
520 arg2.pointerBuffer().copy(args.toBuffer(), require('_GenericMarshal').PointerSize);
521
522 libc._wexecve(cmd, args, 0);
523}
524
525// Start a JavaScript based Agent Self-Update
526function agentUpdate_Start(updateurl, updateoptions) {
527 // If this value is null
528 var sessionid = (updateoptions != null) ? updateoptions.sessionid : null; // If this is null, messages will be broadcast. Otherwise they will be unicasted
529
530 // If the url starts with *, switch it to use the same protoco, host and port as the control channel.
531 if (updateurl != null) {
532 updateurl = getServerTargetUrlEx(updateurl);
533 if (updateurl.startsWith("wss://")) { updateurl = "https://" + updateurl.substring(6); }
534 }
535
536 if (agentUpdate_Start._selfupdate != null)
537 {
538 // We were already called, so we will ignore this duplicate request
539 if (sessionid != null) { sendConsoleText('Self update already in progress...', sessionid); }
540 }
541 else {
542 if (agentUpdate_Start._retryCount == null) { agentUpdate_Start._retryCount = 0; }
543 if (require('MeshAgent').ARCHID == null && updateurl == null) {
544 // This agent doesn't have the ability to tell us which ARCHID it is, so we don't know which agent to pull
545 sendConsoleText('Unable to initiate update, agent ARCHID is not defined', sessionid);
546 }
547 else
548 {
549 var agentfilename = process.execPath.split(process.platform == 'win32' ? '\\' : '/').pop(); // Local File Name, ie: MeshAgent.exe
550 var name = require('MeshAgent').serviceName;
551 if (name == null) { name = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'; }
552 if (process.platform == 'win32')
553 {
554 // Special Processing for Temporary/Console Mode Agents on Windows
555 var parms = windows_getCommandLine(); // This uses FFI to fetch the command line parameters that the agent was started with
556 if (parms.findIndex(function (val) { return (val != null && (val.toUpperCase() == 'RUN' || val.toUpperCase() == 'CONNECT')); }) >= 0)
557 {
558 // This is a Temporary/Console Mode Agent
559 sendConsoleText('This is a temporary/console agent, checking for conflicts with background services...');
560
561 // Check to see if our binary conflicts with an installed agent
562 var agents = _getPotentialServiceNames();
563 if (_getPotentialServiceNames().length > 0)
564 {
565 sendConsoleText('Self update cannot continue because the installed agent (' + agents[0] + ') conflicts with the currently running Temp/Console agent...', sessionid);
566 return;
567 }
568
569
570 sendConsoleText('No conflicts detected...');
571 name = null;
572 }
573 else
574 {
575 // Not running in Temp/Console Mode... No Op here....
576 }
577 }
578 else
579 {
580 // Non Windows Self Update
581 try
582 {
583 var s = require('service-manager').manager.getService(name);
584 if (!s.isMe())
585 {
586 if (process.platform == 'win32') { s.close(); }
587 sendConsoleText('Self Update cannot continue, this agent is not an instance of background service (' + name + ')', sessionid);
588 return;
589 }
590 if (process.platform == 'win32') { s.close(); }
591 }
592 catch (zz)
593 {
594 sendConsoleText('Self Update Failed because this agent is not an instance of (' + name + ')', sessionid);
595 sendAgentMessage('Self Update Failed because this agent is not an instance of (' + name + ')', 3);
596 return;
597 }
598 }
599 if ((sessionid != null) && (updateurl != null)) { sendConsoleText('Downloading update from: ' + updateurl, sessionid); }
600 var options = require('http').parseUri(updateurl != null ? updateurl : require('MeshAgent').ServerUrl);
601 options.protocol = 'https:';
602 if (updateurl == null) { options.path = ('/meshagents?id=' + require('MeshAgent').ARCHID); sendConsoleText('Downloading update from: ' + options.path, sessionid); }
603 options.rejectUnauthorized = false;
604 options.checkServerIdentity = function checkServerIdentity(certs) {
605 // If the tunnel certificate matches the control channel certificate, accept the connection
606 try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.digest == certs[0].digest) return; } catch (ex) { }
607 try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.fingerprint == certs[0].fingerprint) return; } catch (ex) { }
608
609 // Check that the certificate is the one expected by the server, fail if not.
610 if (checkServerIdentity.servertlshash == null) {
611 if (require('MeshAgent').ServerInfo == null || require('MeshAgent').ServerInfo.ControlChannelCertificate == null) { return; }
612 sendConsoleText('Self Update failed, because the url cannot be verified: ' + updateurl, sessionid);
613 sendAgentMessage('Self Update failed, because the url cannot be verified: ' + updateurl, 3);
614 throw new Error('BadCert');
615 }
616 if (certs[0].digest == null) { return; }
617 if ((checkServerIdentity.servertlshash != null) && (checkServerIdentity.servertlshash.toLowerCase() != certs[0].digest.split(':').join('').toLowerCase())) {
618 sendConsoleText('Self Update failed, because the supplied certificate does not match', sessionid);
619 sendAgentMessage('Self Update failed, because the supplied certificate does not match', 3);
620 throw new Error('BadCert')
621 }
622 }
623 options.checkServerIdentity.servertlshash = (updateoptions != null ? updateoptions.tlshash : null);
624 agentUpdate_Start._selfupdate = require('https').get(options);
625 agentUpdate_Start._selfupdate.on('error', function (e) {
626 sendConsoleText('Self Update failed, because there was a problem trying to download the update from ' + updateurl, sessionid);
627 sendAgentMessage('Self Update failed, because there was a problem trying to download the update from ' + updateurl, 3);
628 agentUpdate_Start._selfupdate = null;
629 });
630 agentUpdate_Start._selfupdate.on('response', function (img)
631 {
632 var self = this;
633 this._file = require('fs').createWriteStream(agentfilename + (process.platform=='win32'?'.update.exe':'.update'), { flags: 'wb' });
634 this._filehash = require('SHA384Stream').create();
635 this._filehash.on('hash', function (h)
636 {
637 if (updateoptions != null && updateoptions.hash != null)
638 {
639 if (updateoptions.hash.toLowerCase() == h.toString('hex').toLowerCase())
640 {
641 if (sessionid != null) { sendConsoleText('Download complete. HASH verified.', sessionid); }
642 }
643 else
644 {
645 agentUpdate_Start._retryCount++;
646 sendConsoleText('Self Update FAILED because the downloaded agent FAILED hash check (' + agentUpdate_Start._retryCount + '), URL: ' + updateurl, sessionid);
647 sendAgentMessage('Self Update FAILED because the downloaded agent FAILED hash check (' + agentUpdate_Start._retryCount + '), URL: ' + updateurl, 3);
648 agentUpdate_Start._selfupdate = null;
649
650 try
651 {
652 // We are clearing these two properties, becuase some older agents may not cleanup correctly causing problems with the retry
653 require('https').globalAgent.sockets = {};
654 require('https').globalAgent.requests = {};
655 }
656 catch(z)
657 {}
658 if (needStreamFix)
659 {
660 sendConsoleText('This is an older agent that may have an httpstream bug. On next retry will try to fetch the update differently...');
661 needStreamFix = false;
662 }
663
664 if (agentUpdate_Start._retryCount < 4)
665 {
666 // Retry the download again
667 sendConsoleText('Self Update will try again in 20 seconds...', sessionid);
668 agentUpdate_Start._timeout = setTimeout(agentUpdate_Start, 20000, updateurl, updateoptions);
669 }
670 else
671 {
672 sendConsoleText('Self Update giving up, too many failures...', sessionid);
673 sendAgentMessage('Self Update giving up, too many failures...', 3);
674 }
675 return;
676 }
677 }
678 else
679 {
680 sendConsoleText('Download complete. HASH=' + h.toString('hex'), sessionid);
681 }
682
683 // Send an indication to the server that we got the update download correctly.
684 try { require('MeshAgent').SendCommand({ action: 'agentupdatedownloaded' }); } catch (e) { }
685
686 if (sessionid != null) { sendConsoleText('Updating and restarting agent...', sessionid); }
687 if (process.platform == 'win32')
688 {
689 // Use _wexecve() equivalent to perform the update
690 windows_execve(name, agentfilename, sessionid);
691 }
692 else
693 {
694 var m = require('fs').statSync(process.execPath).mode;
695 require('fs').chmodSync(process.cwd() + agentfilename + '.update', m);
696
697 // remove binary
698 require('fs').unlinkSync(process.execPath);
699
700 // copy update
701 require('fs').copyFileSync(process.cwd() + agentfilename + '.update', process.execPath);
702 require('fs').chmodSync(process.execPath, m);
703
704 // erase update
705 require('fs').unlinkSync(process.cwd() + agentfilename + '.update');
706
707 switch (process.platform)
708 {
709 case 'freebsd':
710 bsd_execv(name, agentfilename, sessionid);
711 break;
712 case 'linux':
713 linux_execv(name, agentfilename, sessionid);
714 break;
715 default:
716 try
717 {
718 // restart service
719 var s = require('service-manager').manager.getService(name);
720 s.restart();
721 }
722 catch (zz)
723 {
724 if (zz.toString() != 'waitExit() aborted because thread is exiting')
725 {
726 sendConsoleText('Self Update encountered an error trying to restart service', sessionid);
727 sendAgentMessage('Self Update encountered an error trying to restart service', 3);
728 }
729 }
730 break;
731 }
732 }
733 });
734
735 if (!needStreamFix)
736 {
737 img.pipe(this._file);
738 img.pipe(this._filehash);
739 }
740 else
741 {
742 img.once('data', function (buffer)
743 {
744 if(this.immediate)
745 {
746 clearImmediate(this.immediate);
747 this.immediate = null;
748
749 // No need to apply fix
750 self._file.write(buffer);
751 self._filehash.write(buffer);
752
753 this.pipe(self._file);
754 this.pipe(self._filehash);
755 }
756 else
757 {
758 // Need to apply fix
759 this.pipe(self._file);
760 this.pipe(self._filehash);
761 }
762 });
763 this.immediate = setImmediate(function (self)
764 {
765 self.immediate = null;
766 },this);
767 }
768 });
769 }
770 }
771}
772
773// Return p number of spaces
774function addPad(p, ret) { var r = ''; for (var i = 0; i < p; i++) { r += ret; } return r; }
775
776setInterval(function () { sendConsoleText('Timer!'); }, 2000);
777
778var path =
779 {
780 join: function () {
781 var x = [];
782 for (var i in arguments) {
783 var w = arguments[i];
784 if (w != null) {
785 while (w.endsWith('/') || w.endsWith('\\')) { w = w.substring(0, w.length - 1); }
786 if (i != 0) { while (w.startsWith('/') || w.startsWith('\\')) { w = w.substring(1); } }
787 x.push(w);
788 }
789 }
790 if (x.length == 0) return '/';
791 return x.join('/');
792 }
793 };
794// Convert an object to string with all functions
795function objToString(x, p, pad, ret) {
796 if (ret == undefined) ret = '';
797 if (p == undefined) p = 0;
798 if (x == null) { return '[null]'; }
799 if (p > 8) { return '[...]'; }
800 if (x == undefined) { return '[undefined]'; }
801 if (typeof x == 'string') { if (p == 0) return x; return '"' + x + '"'; }
802 if (typeof x == 'buffer') { return '[buffer]'; }
803 if (typeof x != 'object') { return x; }
804 var r = '{' + (ret ? '\r\n' : ' ');
805 for (var i in x) { if (i != '_ObjectID') { r += (addPad(p + 2, pad) + i + ': ' + objToString(x[i], p + 2, pad, ret) + (ret ? '\r\n' : ' ')); } }
806 return r + addPad(p, pad) + '}';
807}
808
809// Split a string taking into account the quoats. Used for command line parsing
810function splitArgs(str) {
811 var myArray = [], myRegexp = /[^\s"]+|"([^"]*)"/gi;
812 do { var match = myRegexp.exec(str); if (match != null) { myArray.push(match[1] ? match[1] : match[0]); } } while (match != null);
813 return myArray;
814}
815
816// Parse arguments string array into an object
817function parseArgs(argv) {
818 var results = { '_': [] }, current = null;
819 for (var i = 1, len = argv.length; i < len; i++) {
820 var x = argv[i];
821 if (x.length > 2 && x[0] == '-' && x[1] == '-') {
822 if (current != null) { results[current] = true; }
823 current = x.substring(2);
824 } else {
825 if (current != null) { results[current] = toNumberIfNumber(x); current = null; } else { results['_'].push(toNumberIfNumber(x)); }
826 }
827 }
828 if (current != null) { results[current] = true; }
829 return results;
830}
831
832// Get server target url with a custom path
833function getServerTargetUrl(path) {
834 var x = require('MeshAgent').ServerUrl;
835 //sendConsoleText("mesh.ServerUrl: " + mesh.ServerUrl);
836 if (x == null) { return null; }
837 if (path == null) { path = ''; }
838 x = http.parseUri(x);
839 if (x == null) return null;
840 return x.protocol + '//' + x.host + ':' + x.port + '/' + path;
841}
842
843// Get server url. If the url starts with "*/..." change it, it not use the url as is.
844function getServerTargetUrlEx(url) {
845 if (url.substring(0, 2) == '*/') { return getServerTargetUrl(url.substring(2)); }
846 return url;
847}
848
849require('MeshAgent').on('Connected', function () {
850 require('os').name().then(function (v) {
851 //sendConsoleText("Mesh Agent Recovery Console, OS: " + v);
852 require('MeshAgent').SendCommand(meshCoreObj);
853 });
854});
855
856// Called when receiving control data on websocket
857function onTunnelControlData(data, ws) {
858 var obj;
859 if (ws == null) { ws = this; }
860 if (typeof data == 'string') { try { obj = JSON.parse(data); } catch (e) { sendConsoleText('Invalid control JSON: ' + data); return; } }
861 else if (typeof data == 'object') { obj = data; } else { return; }
862 //sendConsoleText('onTunnelControlData(' + ws.httprequest.protocol + '): ' + JSON.stringify(data));
863 //console.log('onTunnelControlData: ' + JSON.stringify(data));
864
865 if (obj.action) {
866 switch (obj.action) {
867 case 'lock': {
868 // Lock the current user out of the desktop
869 try {
870 if (process.platform == 'win32') {
871 MeshServerLog("Locking remote user out of desktop", ws.httprequest);
872 var child = require('child_process');
873 child.execFile(process.env['windir'] + '\\system32\\cmd.exe', ['/c', 'RunDll32.exe user32.dll,LockWorkStation'], { type: 1 });
874 }
875 } catch (e) { }
876 break;
877 }
878 default:
879 // Unknown action, ignore it.
880 break;
881 }
882 return;
883 }
884
885 switch (obj.type) {
886 case 'options': {
887 // These are additional connection options passed in the control channel.
888 //sendConsoleText('options: ' + JSON.stringify(obj));
889 delete obj.type;
890 ws.httprequest.xoptions = obj;
891
892 // Set additional user consent options if present
893 if ((obj != null) && (typeof obj.consent == 'number')) { ws.httprequest.consent |= obj.consent; }
894
895 break;
896 }
897 case 'close': {
898 // We received the close on the websocket
899 //sendConsoleText('Tunnel #' + ws.tunnel.index + ' WebSocket control close');
900 try { ws.close(); } catch (e) { }
901 break;
902 }
903 case 'termsize': {
904 // Indicates a change in terminal size
905 if (process.platform == 'win32') {
906 if (ws.httprequest._dispatcher == null) return;
907 if (ws.httprequest._dispatcher.invoke) { ws.httprequest._dispatcher.invoke('resizeTerminal', [obj.cols, obj.rows]); }
908 }
909 else {
910 if (ws.httprequest.process == null || ws.httprequest.process.pty == 0) return;
911 if (ws.httprequest.process.tcsetsize) { ws.httprequest.process.tcsetsize(obj.rows, obj.cols); }
912 }
913 break;
914 }
915 }
916}
917
918
919require('MeshAgent').AddCommandHandler(function (data)
920{
921 if (typeof data == 'object') {
922 // If this is a console command, parse it and call the console handler
923 switch (data.action) {
924 case 'agentupdate':
925 agentUpdate_Start(data.url, { hash: data.hash, tlshash: data.servertlshash, sessionid: data.sessionid });
926 break;
927 case 'msg':
928 {
929 switch (data.type) {
930 case 'console': { // Process a console command
931 if ((typeof data.rights != 'number') || ((data.rights & 8) == 0) || ((data.rights & 16) == 0)) break; // Check console rights (Remote Control and Console)
932 if (data.value && data.sessionid) {
933 var args = splitArgs(data.value);
934 processConsoleCommand(args[0].toLowerCase(), parseArgs(args), data.rights, data.sessionid);
935 }
936 break;
937 }
938 case 'tunnel':
939 {
940 if (data.value != null) { // Process a new tunnel connection request
941 // Create a new tunnel object
942 if (data.rights != 4294967295) {
943 MeshServerLog('Tunnel Error: RecoveryCore requires admin rights for tunnels');
944 break;
945 }
946
947 var xurl = getServerTargetUrlEx(data.value);
948 if (xurl != null)
949 {
950 xurl = xurl.split('$').join('%24').split('@').join('%40'); // Escape the $ and @ characters
951 var woptions = http.parseUri(xurl);
952 woptions.rejectUnauthorized = 0;
953 woptions.perMessageDeflate = false;
954 woptions.checkServerIdentity = function checkServerIdentity(certs) {
955 // If the tunnel certificate matches the control channel certificate, accept the connection
956 try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.digest == certs[0].digest) return; } catch (ex) { }
957 try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.fingerprint == certs[0].fingerprint) return; } catch (ex) { }
958
959 // Check that the certificate is the one expected by the server, fail if not.
960 if ((checkServerIdentity.servertlshash != null) && (checkServerIdentity.servertlshash.toLowerCase() != certs[0].digest.split(':').join('').toLowerCase())) { throw new Error('BadCert') }
961 }
962 woptions.checkServerIdentity.servertlshash = data.servertlshash;
963
964
965 //sendConsoleText(JSON.stringify(woptions));
966 var tunnel = http.request(woptions);
967 tunnel.on('upgrade', function (response, s, head) {
968 if (require('MeshAgent').idleTimeout != null) {
969 s.setTimeout(require('MeshAgent').idleTimeout * 1000);
970 s.on('timeout', function () {
971 this.ping();
972 this.setTimeout(require('MeshAgent').idleTimeout * 1000);
973 });
974 }
975
976 this.s = s;
977 s.httprequest = this;
978 s.tunnel = this;
979 s.on('end', function () {
980 if (tunnels[this.httprequest.index] == null) return; // Stop duplicate calls.
981
982 // If there is a upload or download active on this connection, close the file
983 if (this.httprequest.uploadFile) { fs.closeSync(this.httprequest.uploadFile); delete this.httprequest.uploadFile; delete this.httprequest.uploadFileid; delete this.httprequest.uploadFilePath; }
984 if (this.httprequest.downloadFile) { delete this.httprequest.downloadFile; }
985
986 //sendConsoleText("Tunnel #" + this.httprequest.index + " closed.", this.httprequest.sessionid);
987 delete tunnels[this.httprequest.index];
988
989 // Clean up WebSocket
990 this.removeAllListeners('data');
991 });
992 s.on('data', function (data) {
993 // If this is upload data, save it to file
994 if ((this.httprequest.uploadFile) && (typeof data == 'object') && (data[0] != 123)) {
995 // Save the data to file being uploaded.
996 if (data[0] == 0) {
997 // If data starts with zero, skip the first byte. This is used to escape binary file data from JSON.
998 try { fs.writeSync(this.httprequest.uploadFile, data, 1, data.length - 1); } catch (e) { sendConsoleText('FileUpload Error'); this.write(Buffer.from(JSON.stringify({ action: 'uploaderror' }))); return; } // Write to the file, if there is a problem, error out.
999 } else {
1000 // If data does not start with zero, save as-is.
1001 try { fs.writeSync(this.httprequest.uploadFile, data); } catch (e) { sendConsoleText('FileUpload Error'); this.write(Buffer.from(JSON.stringify({ action: 'uploaderror' }))); return; } // Write to the file, if there is a problem, error out.
1002 }
1003 this.write(Buffer.from(JSON.stringify({ action: 'uploadack', reqid: this.httprequest.uploadFileid }))); // Ask for more data.
1004 return;
1005 }
1006
1007 if (this.httprequest.state == 0) {
1008 // Check if this is a relay connection
1009 if ((data == 'c') || (data == 'cr')) { this.httprequest.state = 1; /*sendConsoleText("Tunnel #" + this.httprequest.index + " now active", this.httprequest.sessionid);*/ }
1010 }
1011 else {
1012 // Handle tunnel data
1013 if (this.httprequest.protocol == 0) { // 1 = Terminal (admin), 2 = Desktop, 5 = Files, 6 = PowerShell (admin), 7 = Plugin Data Exchange, 8 = Terminal (user), 9 = PowerShell (user), 10 = FileTransfer
1014 // Take a look at the protocol
1015 if ((data.length > 3) && (data[0] == '{')) { onTunnelControlData(data, this); return; }
1016 this.httprequest.protocol = parseInt(data);
1017 if (typeof this.httprequest.protocol != 'number') { this.httprequest.protocol = 0; }
1018 if (this.httprequest.protocol == 10) {
1019 //
1020 // Basic file transfer
1021 //
1022 var stats = null;
1023 if ((process.platform != 'win32') && (this.httprequest.xoptions.file.startsWith('/') == false)) { this.httprequest.xoptions.file = '/' + this.httprequest.xoptions.file; }
1024 try { stats = require('fs').statSync(this.httprequest.xoptions.file) } catch (e) { }
1025 try { if (stats) { this.httprequest.downloadFile = fs.createReadStream(this.httprequest.xoptions.file, { flags: 'rbN' }); } } catch (e) { }
1026 if (this.httprequest.downloadFile) {
1027 //sendConsoleText('BasicFileTransfer, ok, ' + this.httprequest.xoptions.file + ', ' + JSON.stringify(stats));
1028 this.write(JSON.stringify({ op: 'ok', size: stats.size }));
1029 this.httprequest.downloadFile.pipe(this);
1030 this.httprequest.downloadFile.end = function () { }
1031 } else {
1032 //sendConsoleText('BasicFileTransfer, cancel, ' + this.httprequest.xoptions.file);
1033 this.write(JSON.stringify({ op: 'cancel' }));
1034 }
1035 }
1036 else if ((this.httprequest.protocol == 1) || (this.httprequest.protocol == 6) || (this.httprequest.protocol == 8) || (this.httprequest.protocol == 9)) {
1037 //
1038 // Remote Terminal
1039 //
1040 if (process.platform == "win32") {
1041 var cols = 80, rows = 25;
1042 if (this.httprequest.xoptions) {
1043 if (this.httprequest.xoptions.rows) { rows = this.httprequest.xoptions.rows; }
1044 if (this.httprequest.xoptions.cols) { cols = this.httprequest.xoptions.cols; }
1045 }
1046
1047 // Admin Terminal
1048 if (require('win-virtual-terminal').supported) {
1049 // ConPTY PseudoTerminal
1050 // this.httprequest._term = require('win-virtual-terminal')[this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'](80, 25);
1051
1052 // The above line is commented out, because there is a bug with ClosePseudoConsole() API, so this is the workaround
1053 this.httprequest._dispatcher = require('win-dispatcher').dispatch({ modules: [{ name: 'win-virtual-terminal', script: getJSModule('win-virtual-terminal') }], launch: { module: 'win-virtual-terminal', method: 'Start', args: [cols, rows] } });
1054 this.httprequest._dispatcher.ws = this;
1055 this.httprequest._dispatcher.on('connection', function (c) {
1056 this.ws._term = c;
1057 c.pipe(this.ws, { dataTypeSkip: 1 });
1058 this.ws.pipe(c, { dataTypeSkip: 1 });
1059 });
1060 }
1061 else {
1062 // Legacy Terminal
1063 this.httprequest._term = require('win-terminal').Start(80, 25);
1064 this.httprequest._term.pipe(this, { dataTypeSkip: 1 });
1065 this.pipe(this.httprequest._term, { dataTypeSkip: 1, end: false });
1066 this.prependListener('end', function () { this.httprequest._term.end(function () { sendConsoleText('Terminal was closed'); }); });
1067 }
1068 }
1069 else {
1070 var env = { HISTCONTROL: 'ignoreboth' };
1071 if (process.env['LANG']) { env['LANG'] = process.env['LANG']; }
1072 if (process.env['PATH']) { env['PATH'] = process.env['PATH']; }
1073 if (this.httprequest.xoptions)
1074 {
1075 if (this.httprequest.xoptions.rows) { env.LINES = ('' + this.httprequest.xoptions.rows); }
1076 if (this.httprequest.xoptions.cols) { env.COLUMNS = ('' + this.httprequest.xoptions.cols); }
1077 }
1078 var options = { type: childProcess.SpawnTypes.TERM, env: env };
1079
1080 if (require('fs').existsSync('/bin/bash')) {
1081 this.httprequest.process = childProcess.execFile('/bin/bash', ['bash'], options); // Start bash
1082 }
1083 else {
1084 this.httprequest.process = childProcess.execFile('/bin/sh', ['sh'], options); // Start sh
1085 }
1086
1087 // Spaces at the beginning of lines are needed to hide commands from the command history
1088 if (process.platform == 'linux') { this.httprequest.process.stdin.write(' alias ls=\'ls --color=auto\';clear\n'); }
1089 this.httprequest.process.tunnel = this;
1090 this.httprequest.process.on('exit', function (ecode, sig) { this.tunnel.end(); });
1091 this.httprequest.process.stderr.on('data', function (chunk) { this.parent.tunnel.write(chunk); });
1092 this.httprequest.process.stdout.pipe(this, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
1093 this.pipe(this.httprequest.process.stdin, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
1094 this.prependListener('end', function () { this.httprequest.process.kill(); });
1095 }
1096 }
1097 }
1098 else if (this.httprequest.protocol == 5) {
1099 // Process files commands
1100 var cmd = null;
1101 try { cmd = JSON.parse(data); } catch (e) { };
1102 if (cmd == null) { return; }
1103 if ((cmd.ctrlChannel == '102938') || ((cmd.type == 'offer') && (cmd.sdp != null))) { return; } // If this is control data, handle it now.
1104 if (cmd.action == undefined) { return; }
1105 console.log('action: ', cmd.action);
1106
1107 //sendConsoleText('CMD: ' + JSON.stringify(cmd));
1108
1109 if ((cmd.path != null) && (process.platform != 'win32') && (cmd.path[0] != '/')) { cmd.path = '/' + cmd.path; } // Add '/' to paths on non-windows
1110 //console.log(objToString(cmd, 0, ' '));
1111 switch (cmd.action) {
1112 case 'ls':
1113 // Send the folder content to the browser
1114 var response = getDirectoryInfo(cmd.path);
1115 if (cmd.reqid != undefined) { response.reqid = cmd.reqid; }
1116 this.write(Buffer.from(JSON.stringify(response)));
1117 break;
1118 case 'mkdir':
1119 {
1120 // Create a new empty folder
1121 fs.mkdirSync(cmd.path);
1122 break;
1123 }
1124 case 'rm':
1125 {
1126 // Delete, possibly recursive delete
1127 for (var i in cmd.delfiles) {
1128 try { deleteFolderRecursive(path.join(cmd.path, cmd.delfiles[i]), cmd.rec); } catch (e) { }
1129 }
1130 break;
1131 }
1132 case 'rename':
1133 {
1134 // Rename a file or folder
1135 var oldfullpath = path.join(cmd.path, cmd.oldname);
1136 var newfullpath = path.join(cmd.path, cmd.newname);
1137 try { fs.renameSync(oldfullpath, newfullpath); } catch (e) { console.log(e); }
1138 break;
1139 }
1140 case 'findfile':
1141 {
1142 // Search for files
1143 var r = require('file-search').find('"' + cmd.path + '"', cmd.filter);
1144 if (!r.cancel) { r.cancel = function cancel() { this.child.kill(); }; }
1145 this._search = r;
1146 r.socket = this;
1147 r.socket.reqid = cmd.reqid; // Search request id. This is used to send responses and cancel the request.
1148 r.socket.path = cmd.path; // Search path
1149 r.on('result', function (str) { try { this.socket.write(Buffer.from(JSON.stringify({ action: 'findfile', r: str.substring(this.socket.path.length), reqid: this.socket.reqid }))); } catch (ex) { } });
1150 r.then(function () { try { this.socket.write(Buffer.from(JSON.stringify({ action: 'findfile', r: null, reqid: this.socket.reqid }))); } catch (ex) { } });
1151 break;
1152 }
1153 case 'cancelfindfile':
1154 {
1155 if (this._search) { this._search.cancel(); this._search = null; }
1156 break;
1157 }
1158 case 'download':
1159 {
1160 // Download a file
1161 var sendNextBlock = 0;
1162 if (cmd.sub == 'start') { // Setup the download
1163 if ((cmd.path == null) && (cmd.ask == 'coredump')) { // If we are asking for the coredump file, set the right path.
1164 if (process.platform == 'win32') {
1165 if (fs.existsSync(process.coreDumpLocation)) { cmd.path = process.coreDumpLocation; }
1166 } else {
1167 if ((process.cwd() != '//') && fs.existsSync(process.cwd() + 'core')) { cmd.path = process.cwd() + 'core'; }
1168 }
1169 }
1170 MeshServerLogEx((cmd.ask == 'coredump') ? 104 : 49, [cmd.path], 'Download: \"' + cmd.path + '\"', this.httprequest);
1171 if ((cmd.path == null) || (this.filedownload != null)) { this.write({ action: 'download', sub: 'cancel', id: this.filedownload.id }); delete this.filedownload; }
1172 this.filedownload = { id: cmd.id, path: cmd.path, ptr: 0 }
1173 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; }
1174 if (this.filedownload) { this.write({ action: 'download', sub: 'start', id: cmd.id }); }
1175 } else if ((this.filedownload != null) && (cmd.id == this.filedownload.id)) { // Download commands
1176 if (cmd.sub == 'startack') { sendNextBlock = ((typeof cmd.ack == 'number') ? cmd.ack : 8); } else if (cmd.sub == 'stop') { delete this.filedownload; } else if (cmd.sub == 'ack') { sendNextBlock = 1; }
1177 }
1178 // Send the next download block(s)
1179 while (sendNextBlock > 0) {
1180 sendNextBlock--;
1181 var buf = Buffer.alloc(16384);
1182 var len = fs.readSync(this.filedownload.f, buf, 4, 16380, null);
1183 this.filedownload.ptr += len;
1184 if (len < 16380) { buf.writeInt32BE(0x01000001, 0); fs.closeSync(this.filedownload.f); delete this.filedownload; sendNextBlock = 0; } else { buf.writeInt32BE(0x01000000, 0); }
1185 this.write(buf.slice(0, len + 4)); // Write as binary
1186 }
1187 break;
1188 }
1189 case 'upload':
1190 {
1191 // Upload a file, browser to agent
1192 if (this.httprequest.uploadFile != null) { fs.closeSync(this.httprequest.uploadFile); delete this.httprequest.uploadFile; }
1193 if (cmd.path == undefined) break;
1194 var filepath = cmd.name ? pathjoin(cmd.path, cmd.name) : cmd.path;
1195 this.httprequest.uploadFilePath = filepath;
1196 MeshServerLogEx(50, [filepath], 'Upload: \"' + filepath + '\"', this.httprequest);
1197 try { this.httprequest.uploadFile = fs.openSync(filepath, 'wbN'); } catch (e) { this.write(Buffer.from(JSON.stringify({ action: 'uploaderror', reqid: cmd.reqid }))); break; }
1198 this.httprequest.uploadFileid = cmd.reqid;
1199 if (this.httprequest.uploadFile) { this.write(Buffer.from(JSON.stringify({ action: 'uploadstart', reqid: this.httprequest.uploadFileid }))); }
1200 break;
1201 }
1202 case 'uploaddone':
1203 {
1204 // Indicates that an upload is done
1205 if (this.httprequest.uploadFile) {
1206 fs.closeSync(this.httprequest.uploadFile);
1207 this.write(Buffer.from(JSON.stringify({ action: 'uploaddone', reqid: this.httprequest.uploadFileid }))); // Indicate that we closed the file.
1208 delete this.httprequest.uploadFile;
1209 delete this.httprequest.uploadFileid;
1210 delete this.httprequest.uploadFilePath;
1211 }
1212 break;
1213 }
1214 case 'uploadcancel':
1215 {
1216 // Indicates that an upload is canceled
1217 if (this.httprequest.uploadFile) {
1218 fs.closeSync(this.httprequest.uploadFile);
1219 fs.unlinkSync(this.httprequest.uploadFilePath);
1220 this.write(Buffer.from(JSON.stringify({ action: 'uploadcancel', reqid: this.httprequest.uploadFileid }))); // Indicate that we closed the file.
1221 delete this.httprequest.uploadFile;
1222 delete this.httprequest.uploadFileid;
1223 delete this.httprequest.uploadFilePath;
1224 }
1225 break;
1226 }
1227 case 'copy': {
1228 // Copy a bunch of files from scpath to dspath
1229 for (var i in cmd.names) {
1230 var sc = path.join(cmd.scpath, cmd.names[i]), ds = path.join(cmd.dspath, cmd.names[i]);
1231 if (sc != ds) { try { fs.copyFileSync(sc, ds); } catch (e) { } }
1232 }
1233 break;
1234 }
1235 case 'move': {
1236 // Move a bunch of files from scpath to dspath
1237 for (var i in cmd.names) {
1238 var sc = path.join(cmd.scpath, cmd.names[i]), ds = path.join(cmd.dspath, cmd.names[i]);
1239 if (sc != ds) { try { fs.copyFileSync(sc, ds); fs.unlinkSync(sc); } catch (e) { } }
1240 }
1241 break;
1242 }
1243 }
1244 }
1245 }
1246 });
1247 });
1248 tunnel.onerror = function (e) { sendConsoleText("ERROR: " + JSON.stringify(e)); }
1249 tunnel.sessionid = data.sessionid;
1250 tunnel.rights = data.rights;
1251 tunnel.state = 0;
1252 tunnel.url = xurl;
1253 tunnel.protocol = 0;
1254 tunnel.tcpaddr = data.tcpaddr;
1255 tunnel.tcpport = data.tcpport;
1256 tunnel.end();
1257 // Put the tunnel in the tunnels list
1258 var index = nextTunnelIndex++;
1259 tunnel.index = index;
1260 tunnels[index] = tunnel;
1261
1262 //sendConsoleText('New tunnel connection #' + index + ': ' + tunnel.url + ', rights: ' + tunnel.rights, data.sessionid);
1263 }
1264 }
1265 break;
1266 }
1267
1268 default:
1269 // Unknown action, ignore it.
1270 break;
1271 }
1272 break;
1273 }
1274 default:
1275 // Unknown action, ignore it.
1276 break;
1277 }
1278 }
1279});
1280
1281function processConsoleCommand(cmd, args, rights, sessionid) {
1282 try {
1283 var response = null;
1284 switch (cmd)
1285 {
1286 default:
1287 { // This is an unknown command, return an error message
1288 response = 'Unknown command \"' + cmd + '\", type \"help\" for list of available commands.';
1289 break;
1290 }
1291 case 'commandline':
1292 {
1293 if (process.platform == 'win32')
1294 {
1295 response = JSON.stringify(windows_getCommandLine(), null, 1);
1296 }
1297 else
1298 {
1299 response = 'Unknown command \"' + cmd + '\", type \"help\" for list of available commands.';
1300 }
1301 }
1302 break;
1303 case 'help':
1304 response = "Available commands are: agentupdate, agentupdateex, dbkeys, dbget, dbset, dbcompact, eval, netinfo, osinfo, setdebug, versions.";
1305 break;
1306 case '_descriptors':
1307 response = 'Open Descriptors: ' + JSON.stringify(getOpenDescriptors());
1308 break;
1309 case 'versions':
1310 response = JSON.stringify(process.versions, null, ' ');
1311 break;
1312 case 'agentupdate':
1313 // Request that the server send a agent update command
1314 require('MeshAgent').SendCommand({ action: 'agentupdate', sessionid: sessionid });
1315 break;
1316 case 'agentupdateex':
1317 // Perform an direct agent update without requesting any information from the server, this should not typically be used.
1318 if (args['_'].length == 1) {
1319 if (args['_'][0].startsWith('https://')) { agentUpdate_Start(args['_'][0], { sessionid: sessionid }); } else { response = "Usage: agentupdateex https://server/path"; }
1320 } else {
1321 agentUpdate_Start(null, { sessionid: sessionid });
1322 }
1323 break;
1324 case 'eval':
1325 { // Eval JavaScript
1326 if (args['_'].length < 1) {
1327 response = 'Proper usage: eval "JavaScript code"'; // Display correct command usage
1328 } else {
1329 response = JSON.stringify(require('MeshAgent').eval(args['_'][0])); // This can only be run by trusted administrator.
1330 }
1331 break;
1332 }
1333 case 'setdebug':
1334 {
1335 if (args['_'].length < 1) { response = 'Proper usage: setdebug (target), 0 = Disabled, 1 = StdOut, 2 = This Console, * = All Consoles, 4 = WebLog, 8 = Logfile'; } // Display usage
1336 else { if (args['_'][0] == '*') { console.setDestination(2); } else { console.setDestination(parseInt(args['_'][0]), sessionid); } }
1337 break;
1338 }
1339 case 'osinfo': { // Return the operating system information
1340 var i = 1;
1341 if (args['_'].length > 0) { i = parseInt(args['_'][0]); if (i > 8) { i = 8; } response = 'Calling ' + i + ' times.'; }
1342 for (var j = 0; j < i; j++) {
1343 var pr = require('os').name();
1344 pr.sessionid = sessionid;
1345 pr.then(function (v) {
1346 sendConsoleText("OS: " + v + (process.platform == 'win32' ? (require('win-virtual-terminal').supported ? ' [ConPTY: YES]' : ' [ConPTY: NO]') : ''), this.sessionid);
1347 });
1348 }
1349 break;
1350 }
1351 case 'dbkeys': { // Return all data store keys
1352 response = JSON.stringify(db.Keys);
1353 break;
1354 }
1355 case 'dbget': { // Return the data store value for a given key
1356 if (db == null) { response = "Database not accessible."; break; }
1357 if (args['_'].length != 1) {
1358 response = "Proper usage: dbget (key)"; // Display the value for a given database key
1359 } else {
1360 response = db.Get(args['_'][0]);
1361 }
1362 break;
1363 }
1364 case 'dbset': { // Set a data store key and value pair
1365 if (db == null) { response = "Database not accessible."; break; }
1366 if (args['_'].length != 2) {
1367 response = "Proper usage: dbset (key) (value)"; // Set a database key
1368 } else {
1369 var r = db.Put(args['_'][0], args['_'][1]);
1370 response = "Key set: " + r;
1371 }
1372 break;
1373 }
1374 case 'dbcompact': { // Compact the data store
1375 if (db == null) { response = "Database not accessible."; break; }
1376 var r = db.Compact();
1377 response = "Database compacted: " + r;
1378 break;
1379 }
1380 case 'tunnels': { // Show the list of current tunnels
1381 response = '';
1382 for (var i in tunnels) { response += "Tunnel #" + i + ", " + tunnels[i].url + '\r\n'; }
1383 if (response == '') { response = "No websocket sessions."; }
1384 break;
1385 }
1386 case 'netinfo': { // Show network interface information
1387 //response = objToString(mesh.NetInfo, 0, ' ');
1388 var interfaces = require('os').networkInterfaces();
1389 response = objToString(interfaces, 0, ' ', true);
1390 break;
1391 }
1392 case 'name':
1393 {
1394 response = 'Service Name = ' + require('MeshAgent').serviceName;
1395 }
1396 break;
1397 }
1398 } catch (e) { response = "Command returned an exception error: " + e; console.log(e); }
1399 if (response != null) { sendConsoleText(response, sessionid); }
1400}
1401
1402// Get a formated response for a given directory path
1403function getDirectoryInfo(reqpath) {
1404 var response = { path: reqpath, dir: [] };
1405 if (((reqpath == undefined) || (reqpath == '')) && (process.platform == 'win32')) {
1406 // List all the drives in the root, or the root itself
1407 var results = null;
1408 try { results = fs.readDrivesSync(); } catch (e) { } // TODO: Anyway to get drive total size and free space? Could draw a progress bar.
1409 if (results != null) {
1410 for (var i = 0; i < results.length; ++i) {
1411 var drive = { n: results[i].name, t: 1 };
1412 if (results[i].type == 'REMOVABLE') { drive.dt = 'removable'; } // TODO: See if this is USB/CDROM or something else, we can draw icons.
1413 response.dir.push(drive);
1414 }
1415 }
1416 } else {
1417 // List all the files and folders in this path
1418 if (reqpath == '') { reqpath = '/'; }
1419 var results = null, xpath = path.join(reqpath, '*');
1420 //if (process.platform == "win32") { xpath = xpath.split('/').join('\\'); }
1421 try { results = fs.readdirSync(xpath); } catch (e) { }
1422 if (results != null) {
1423 for (var i = 0; i < results.length; ++i) {
1424 if ((results[i] != '.') && (results[i] != '..')) {
1425 var stat = null, p = path.join(reqpath, results[i]);
1426 //if (process.platform == "win32") { p = p.split('/').join('\\'); }
1427 try { stat = fs.statSync(p); } catch (e) { } // TODO: Get file size/date
1428 if ((stat != null) && (stat != undefined)) {
1429 if (stat.isDirectory() == true) {
1430 response.dir.push({ n: results[i], t: 2, d: stat.mtime });
1431 } else {
1432 response.dir.push({ n: results[i], t: 3, s: stat.size, d: stat.mtime });
1433 }
1434 }
1435 }
1436 }
1437 }
1438 }
1439 return response;
1440}
1441// Delete a directory with a files and directories within it
1442function deleteFolderRecursive(path, rec) {
1443 if (fs.existsSync(path)) {
1444 if (rec == true) {
1445 fs.readdirSync(path.join(path, '*')).forEach(function (file, index) {
1446 var curPath = path.join(path, file);
1447 if (fs.statSync(curPath).isDirectory()) { // recurse
1448 deleteFolderRecursive(curPath, true);
1449 } else { // delete file
1450 fs.unlinkSync(curPath);
1451 }
1452 });
1453 }
1454 fs.unlinkSync(path);
1455 }
1456};