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;
9var needStreamFix = (new Date(process.versions.meshAgent) < new Date('2020-01-21 13:27:45.000-08:00'));
12 Object.defineProperty(Array.prototype, 'find', {
13 value: function (func)
16 for(i=0;i<this.length;++i)
33 Object.defineProperty(Array.prototype, 'findIndex', {
34 value: function (func)
37 for (i = 0; i < this.length; ++i)
39 if (func(this[i], i, this))
53if (process.platform != 'win32')
55 var ch = require('child_process');
56 ch._execFile = ch.execFile;
57 ch.execFile = function execFile(path, args, options)
59 if (options && options.type && options.type == ch.SpawnTypes.TERM && options.env)
61 options.env['TERM'] = 'xterm-256color';
63 return (this._execFile(path, args, options));
67function _getPotentialServiceNames()
69 var registry = require('win-registry');
71 var K = registry.QueryKey(registry.HKEY.LocalMachine, 'SYSTEM\\CurrentControlSet\\Services');
73 while (K.subkeys.length > 0)
75 service = K.subkeys.shift();
78 s = registry.QueryKey(registry.HKEY.LocalMachine, 'SYSTEM\\CurrentControlSet\\Services\\' + service, 'ImagePath');
79 if (s.startsWith(process.execPath) || s.startsWith('"' + process.execPath + '"'))
90function _verifyServiceName(names)
95 for (i = 0; i < names.length; ++i)
99 s = require('service-manager').manager.getService(names[i]);
113function windows_getCommandLine()
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();
124 var len = GM.CreateVariable(4);
125 var val = s32.CommandLineToArgvW(v, len);
126 len = len.toBuffer().readInt32LE(0);
129 for (i = 0; i < len; ++i)
131 parms.push(val.Deref(i * GM.PointerSize, GM.PointerSize).Deref().Wide2UTF8);
138if (require('MeshAgent').ARCHID == null)
141 switch (process.platform)
144 id = require('_GenericMarshal').PointerSize == 4 ? 3 : 4;
147 id = require('_GenericMarshal').PointerSize == 4 ? 31 : 30;
152 id = require('os').arch() == 'x64' ? 16 : 29;
160 if (id != null) { Object.defineProperty(require('MeshAgent'), 'ARCHID', { value: id }); }
163//attachDebugger({ webport: 9994, wait: 1 }).then(function (p) { console.log('Debug on port: ' + p); });
165function sendConsoleText(msg, sessionid)
167 if (sessionid != null)
169 require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: msg, sessionid: sessionid });
173 require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: msg });
177function sendAgentMessage(msg, icon)
179 if (sendAgentMessage.messages == null)
181 sendAgentMessage.messages = {};
182 sendAgentMessage.nextid = 1;
184 sendAgentMessage.messages[sendAgentMessage.nextid++] = { msg: msg, icon: icon };
185 require('MeshAgent').SendCommand({ action: 'sessions', type: 'msg', value: sendAgentMessage.messages });
188// Add to the server event log
189function MeshServerLog(msg, state)
191 if (typeof msg == 'string') { msg = { action: 'log', msg: msg }; } else { msg.action = 'log'; }
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; }
199 require('MeshAgent').SendCommand(msg);
202// Add to the server event log, use internationalized events
203function MeshServerLogEx(id, args, msg, state)
205 var msg = { action: 'log', msgid: id, msgArgs: args, msg: msg };
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; }
213 require('MeshAgent').SendCommand(msg);
216function getOpenDescriptors()
218 switch(process.platform)
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) { });
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("}'");
241 child.stdin.write('\nexit\n');
246 return(JSON.parse(child.stdout.str.trim()));
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) { });
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');
274 return (JSON.parse(child.stdout.str.trim()));
289 for (var i in arguments) {
290 var w = arguments[i];
292 while (w.endsWith('/') || w.endsWith('\\')) { w = w.substring(0, w.length - 1); }
294 while (w.startsWith('/') || w.startsWith('\\')) { w = w.substring(1); }
299 if (x.length == 0) return '/';
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; }
306function closeDescriptors(libc, descriptors)
309 while(descriptors.length>0)
311 fd = descriptors.pop();
319function linux_execv(name, agentfilename, sessionid)
321 var libs = require('monitor-info').getLibInfo('libc');
324 if ((libs.length == 0 || libs.length == null) && require('MeshAgent').ARCHID == 33)
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');
334 libs = JSON.parse(child.stdout.str.trim());
341 while (libs.length > 0)
344 libc = require('_GenericMarshal').CreateNativeProxy(libs.pop().path);
355 libc.CreateMethod('execv');
356 libc.CreateMethod('close');
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) }
368 var s = require('service-manager').manager.getService(name);
372 sendConsoleText('Self Update encountered an error trying to restart service', sessionid);
373 sendAgentMessage('Self Update encountered an error trying to restart service', 3);
378 if (sessionid != null) { sendConsoleText('Restarting service via execv()...', sessionid) }
383 var argarr = [process.execPath];
384 var path = require('_GenericMarshal').CreateVariable(process.execPath);
386 if (require('MeshAgent').getStartupOptions != null) {
387 var options = require('MeshAgent').getStartupOptions();
389 argarr.push('--' + i + '="' + options[i] + '"');
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]);
397 arg.pointerBuffer().copy(args.toBuffer(), i * require('_GenericMarshal').PointerSize);
400 var descriptors = getOpenDescriptors();
401 closeDescriptors(libc, descriptors);
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);
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");
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);
432 libc = require('_GenericMarshal').CreateNativeProxy(child.stdout.str.trim());
433 libc.CreateMethod('execv');
434 libc.CreateMethod('close');
437 if (sessionid != null) { sendConsoleText('Self Update failed: ' + e.toString(), sessionid) }
438 sendAgentMessage('Self Update failed: ' + e.toString(), 3);
443 var path = require('_GenericMarshal').CreateVariable(process.execPath);
444 var argarr = [process.execPath];
447 var options = require('MeshAgent').getStartupOptions();
449 argarr.push('--' + i + '="' + options[i] + '"');
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]);
455 arg.pointerBuffer().copy(args.toBuffer(), i * require('_GenericMarshal').PointerSize);
458 if (sessionid != null) { sendConsoleText('Restarting service via execv()', sessionid) }
460 var descriptors = getOpenDescriptors();
461 closeDescriptors(libc, descriptors);
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);
468function windows_execve(name, agentfilename, sessionid) {
471 libc = require('_GenericMarshal').CreateNativeProxy('msvcrt.dll');
472 libc.CreateMethod('_wexecve');
475 sendConsoleText('Self Update failed because msvcrt.dll is missing', sessionid);
476 sendAgentMessage('Self Update failed because msvcrt.dll is missing', 3);
480 var cwd = process.cwd();
481 if (!cwd.endsWith('\\'))
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 });
493 // We can continue with self update for Temp/Console Mode on Windows
495 var update = cwd + agentfilename + '.update.exe';
496 var updatedb = cwd + agentfilename + '.update.db';
497 var parms = windows_getCommandLine(); parms.shift();
499 var updatesource = parms.find(function (v) { return (v.startsWith('--updateSourcePath=')); });
500 if (updatesource == null)
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"');
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 + '"');
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 });
519 arg1.pointerBuffer().copy(args.toBuffer());
520 arg2.pointerBuffer().copy(args.toBuffer(), require('_GenericMarshal').PointerSize);
522 libc._wexecve(cmd, args, 0);
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
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); }
536 if (agentUpdate_Start._selfupdate != null)
538 // We were already called, so we will ignore this duplicate request
539 if (sessionid != null) { sendConsoleText('Self update already in progress...', sessionid); }
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);
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')
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)
558 // This is a Temporary/Console Mode Agent
559 sendConsoleText('This is a temporary/console agent, checking for conflicts with background services...');
561 // Check to see if our binary conflicts with an installed agent
562 var agents = _getPotentialServiceNames();
563 if (_getPotentialServiceNames().length > 0)
565 sendConsoleText('Self update cannot continue because the installed agent (' + agents[0] + ') conflicts with the currently running Temp/Console agent...', sessionid);
570 sendConsoleText('No conflicts detected...');
575 // Not running in Temp/Console Mode... No Op here....
580 // Non Windows Self Update
583 var s = require('service-manager').manager.getService(name);
586 if (process.platform == 'win32') { s.close(); }
587 sendConsoleText('Self Update cannot continue, this agent is not an instance of background service (' + name + ')', sessionid);
590 if (process.platform == 'win32') { s.close(); }
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);
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) { }
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');
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')
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;
630 agentUpdate_Start._selfupdate.on('response', function (img)
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)
637 if (updateoptions != null && updateoptions.hash != null)
639 if (updateoptions.hash.toLowerCase() == h.toString('hex').toLowerCase())
641 if (sessionid != null) { sendConsoleText('Download complete. HASH verified.', sessionid); }
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;
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 = {};
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;
664 if (agentUpdate_Start._retryCount < 4)
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);
672 sendConsoleText('Self Update giving up, too many failures...', sessionid);
673 sendAgentMessage('Self Update giving up, too many failures...', 3);
680 sendConsoleText('Download complete. HASH=' + h.toString('hex'), sessionid);
683 // Send an indication to the server that we got the update download correctly.
684 try { require('MeshAgent').SendCommand({ action: 'agentupdatedownloaded' }); } catch (e) { }
686 if (sessionid != null) { sendConsoleText('Updating and restarting agent...', sessionid); }
687 if (process.platform == 'win32')
689 // Use _wexecve() equivalent to perform the update
690 windows_execve(name, agentfilename, sessionid);
694 var m = require('fs').statSync(process.execPath).mode;
695 require('fs').chmodSync(process.cwd() + agentfilename + '.update', m);
698 require('fs').unlinkSync(process.execPath);
701 require('fs').copyFileSync(process.cwd() + agentfilename + '.update', process.execPath);
702 require('fs').chmodSync(process.execPath, m);
705 require('fs').unlinkSync(process.cwd() + agentfilename + '.update');
707 switch (process.platform)
710 bsd_execv(name, agentfilename, sessionid);
713 linux_execv(name, agentfilename, sessionid);
719 var s = require('service-manager').manager.getService(name);
724 if (zz.toString() != 'waitExit() aborted because thread is exiting')
726 sendConsoleText('Self Update encountered an error trying to restart service', sessionid);
727 sendAgentMessage('Self Update encountered an error trying to restart service', 3);
737 img.pipe(this._file);
738 img.pipe(this._filehash);
742 img.once('data', function (buffer)
746 clearImmediate(this.immediate);
747 this.immediate = null;
749 // No need to apply fix
750 self._file.write(buffer);
751 self._filehash.write(buffer);
753 this.pipe(self._file);
754 this.pipe(self._filehash);
759 this.pipe(self._file);
760 this.pipe(self._filehash);
763 this.immediate = setImmediate(function (self)
765 self.immediate = null;
773// Return p number of spaces
774function addPad(p, ret) { var r = ''; for (var i = 0; i < p; i++) { r += ret; } return r; }
776setInterval(function () { sendConsoleText('Timer!'); }, 2000);
782 for (var i in arguments) {
783 var w = arguments[i];
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); } }
790 if (x.length == 0) return '/';
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) + '}';
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);
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++) {
821 if (x.length > 2 && x[0] == '-' && x[1] == '-') {
822 if (current != null) { results[current] = true; }
823 current = x.substring(2);
825 if (current != null) { results[current] = toNumberIfNumber(x); current = null; } else { results['_'].push(toNumberIfNumber(x)); }
828 if (current != null) { results[current] = true; }
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;
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)); }
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);
856// Called when receiving control data on websocket
857function onTunnelControlData(data, ws) {
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));
866 switch (obj.action) {
868 // Lock the current user out of the desktop
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 });
879 // Unknown action, ignore it.
887 // These are additional connection options passed in the control channel.
888 //sendConsoleText('options: ' + JSON.stringify(obj));
890 ws.httprequest.xoptions = obj;
892 // Set additional user consent options if present
893 if ((obj != null) && (typeof obj.consent == 'number')) { ws.httprequest.consent |= obj.consent; }
898 // We received the close on the websocket
899 //sendConsoleText('Tunnel #' + ws.tunnel.index + ' WebSocket control close');
900 try { ws.close(); } catch (e) { }
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]); }
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); }
919require('MeshAgent').AddCommandHandler(function (data)
921 if (typeof data == 'object') {
922 // If this is a console command, parse it and call the console handler
923 switch (data.action) {
925 agentUpdate_Start(data.url, { hash: data.hash, tlshash: data.servertlshash, sessionid: data.sessionid });
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);
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');
947 var xurl = getServerTargetUrlEx(data.value);
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) { }
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') }
962 woptions.checkServerIdentity.servertlshash = data.servertlshash;
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 () {
972 this.setTimeout(require('MeshAgent').idleTimeout * 1000);
977 s.httprequest = this;
979 s.on('end', function () {
980 if (tunnels[this.httprequest.index] == null) return; // Stop duplicate calls.
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; }
986 //sendConsoleText("Tunnel #" + this.httprequest.index + " closed.", this.httprequest.sessionid);
987 delete tunnels[this.httprequest.index];
989 // Clean up WebSocket
990 this.removeAllListeners('data');
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.
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.
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.
1003 this.write(Buffer.from(JSON.stringify({ action: 'uploadack', reqid: this.httprequest.uploadFileid }))); // Ask for more data.
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);*/ }
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) {
1020 // Basic file transfer
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 () { }
1032 //sendConsoleText('BasicFileTransfer, cancel, ' + this.httprequest.xoptions.file);
1033 this.write(JSON.stringify({ op: 'cancel' }));
1036 else if ((this.httprequest.protocol == 1) || (this.httprequest.protocol == 6) || (this.httprequest.protocol == 8) || (this.httprequest.protocol == 9)) {
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; }
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);
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) {
1057 c.pipe(this.ws, { dataTypeSkip: 1 });
1058 this.ws.pipe(c, { dataTypeSkip: 1 });
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'); }); });
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)
1075 if (this.httprequest.xoptions.rows) { env.LINES = ('' + this.httprequest.xoptions.rows); }
1076 if (this.httprequest.xoptions.cols) { env.COLUMNS = ('' + this.httprequest.xoptions.cols); }
1078 var options = { type: childProcess.SpawnTypes.TERM, env: env };
1080 if (require('fs').existsSync('/bin/bash')) {
1081 this.httprequest.process = childProcess.execFile('/bin/bash', ['bash'], options); // Start bash
1084 this.httprequest.process = childProcess.execFile('/bin/sh', ['sh'], options); // Start sh
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(); });
1098 else if (this.httprequest.protocol == 5) {
1099 // Process files commands
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);
1107 //sendConsoleText('CMD: ' + JSON.stringify(cmd));
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) {
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)));
1120 // Create a new empty folder
1121 fs.mkdirSync(cmd.path);
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) { }
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); }
1143 var r = require('file-search').find('"' + cmd.path + '"', cmd.filter);
1144 if (!r.cancel) { r.cancel = function cancel() { this.child.kill(); }; }
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) { } });
1153 case 'cancelfindfile':
1155 if (this._search) { this._search.cancel(); this._search = null; }
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; }
1167 if ((process.cwd() != '//') && fs.existsSync(process.cwd() + 'core')) { cmd.path = process.cwd() + 'core'; }
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; }
1178 // Send the next download block(s)
1179 while (sendNextBlock > 0) {
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
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 }))); }
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;
1214 case 'uploadcancel':
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;
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) { } }
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) { } }
1248 tunnel.onerror = function (e) { sendConsoleText("ERROR: " + JSON.stringify(e)); }
1249 tunnel.sessionid = data.sessionid;
1250 tunnel.rights = data.rights;
1253 tunnel.protocol = 0;
1254 tunnel.tcpaddr = data.tcpaddr;
1255 tunnel.tcpport = data.tcpport;
1257 // Put the tunnel in the tunnels list
1258 var index = nextTunnelIndex++;
1259 tunnel.index = index;
1260 tunnels[index] = tunnel;
1262 //sendConsoleText('New tunnel connection #' + index + ': ' + tunnel.url + ', rights: ' + tunnel.rights, data.sessionid);
1269 // Unknown action, ignore it.
1275 // Unknown action, ignore it.
1281function processConsoleCommand(cmd, args, rights, sessionid) {
1283 var response = null;
1287 { // This is an unknown command, return an error message
1288 response = 'Unknown command \"' + cmd + '\", type \"help\" for list of available commands.';
1293 if (process.platform == 'win32')
1295 response = JSON.stringify(windows_getCommandLine(), null, 1);
1299 response = 'Unknown command \"' + cmd + '\", type \"help\" for list of available commands.';
1304 response = "Available commands are: agentupdate, agentupdateex, dbkeys, dbget, dbset, dbcompact, eval, netinfo, osinfo, setdebug, versions.";
1306 case '_descriptors':
1307 response = 'Open Descriptors: ' + JSON.stringify(getOpenDescriptors());
1310 response = JSON.stringify(process.versions, null, ' ');
1313 // Request that the server send a agent update command
1314 require('MeshAgent').SendCommand({ action: 'agentupdate', sessionid: sessionid });
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"; }
1321 agentUpdate_Start(null, { sessionid: sessionid });
1325 { // Eval JavaScript
1326 if (args['_'].length < 1) {
1327 response = 'Proper usage: eval "JavaScript code"'; // Display correct command usage
1329 response = JSON.stringify(require('MeshAgent').eval(args['_'][0])); // This can only be run by trusted administrator.
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); } }
1339 case 'osinfo': { // Return the operating system information
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);
1351 case 'dbkeys': { // Return all data store keys
1352 response = JSON.stringify(db.Keys);
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
1360 response = db.Get(args['_'][0]);
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
1369 var r = db.Put(args['_'][0], args['_'][1]);
1370 response = "Key set: " + r;
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;
1380 case 'tunnels': { // Show the list of current tunnels
1382 for (var i in tunnels) { response += "Tunnel #" + i + ", " + tunnels[i].url + '\r\n'; }
1383 if (response == '') { response = "No websocket sessions."; }
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);
1394 response = 'Service Name = ' + require('MeshAgent').serviceName;
1398 } catch (e) { response = "Command returned an exception error: " + e; console.log(e); }
1399 if (response != null) { sendConsoleText(response, sessionid); }
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
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);
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 });
1432 response.dir.push({ n: results[i], t: 3, s: stat.size, d: stat.mtime });
1441// Delete a directory with a files and directories within it
1442function deleteFolderRecursive(path, rec) {
1443 if (fs.existsSync(path)) {
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);
1454 fs.unlinkSync(path);