recoverycore.js 79 KB

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