amt-ider.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. /*
  2. Copyright 2020-2021 Intel Corporation
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. @description MeshCentral Server IDER handler
  13. @author Ylian Saint-Hilaire
  14. @version v0.3.0
  15. */
  16. /*jslint node: true */
  17. /*jshint node: true */
  18. /*jshint strict:false */
  19. /*jshint -W097 */
  20. /*jshint esversion: 6 */
  21. "use strict";
  22. // Construct a MeshAgent object, called upon connection
  23. module.exports.CreateAmtIderSession = function (parent, db, ws, req, args, domain, user) {
  24. const fs = require('fs');
  25. const path = require('path');
  26. const common = parent.common;
  27. const amtMeshRedirModule = require('./amt-redir-mesh.js');
  28. const amtMeshIderModule = require('./amt-ider-module.js');
  29. //console.log('New Server IDER session from ' + user.name);
  30. var obj = {};
  31. obj.user = user;
  32. obj.domain = domain;
  33. obj.ider = null;
  34. // Disconnect this user
  35. obj.close = function (arg) {
  36. if ((arg == 1) || (arg == null)) { try { ws.close(); parent.parent.debug(1, 'Soft disconnect'); } catch (e) { console.log(e); } } // Soft close, close the websocket
  37. if (arg == 2) { try { ws._socket._parent.end(); parent.parent.debug(1, 'Hard disconnect'); } catch (e) { console.log(e); } } // Hard close, close the TCP socket
  38. };
  39. try {
  40. // Check if the user is logged in
  41. if (user == null) { try { ws.close(); } catch (e) { } return; }
  42. // When data is received from the web socket
  43. ws.on('message', processWebSocketData);
  44. // If error, do nothing
  45. ws.on('error', function (err) { console.log(err); obj.close(0); });
  46. // If the web socket is closed
  47. ws.on('close', function (req) {
  48. // Close the IDER session
  49. if (obj.ider) { obj.ider.Stop(); delete obj.ider; }
  50. });
  51. // We are all set, start receiving data
  52. ws._socket.resume();
  53. } catch (e) { console.log(e); }
  54. // Process incoming web socket data from the browser
  55. function processWebSocketData(msg) {
  56. var command, i = 0, mesh = null, meshid = null, nodeid = null, meshlinks = null, change = 0;
  57. try { command = JSON.parse(msg.toString('utf8')); } catch (e) { return; }
  58. if (common.validateString(command.action, 3, 32) == false) return; // Action must be a string between 3 and 32 chars
  59. switch (command.action) {
  60. case 'ping': { try { ws.send(JSON.stringify({ action: 'pong' })); } catch (ex) { } break; }
  61. case 'start': {
  62. // Get the list of disk images
  63. var domainx = 'domain' + ((domain.id == '') ? '' : ('-' + domain.id));
  64. var useridx = user._id.split('/')[2];
  65. var userPath = parent.parent.path.join(parent.parent.filespath, domainx, 'user-' + useridx);
  66. // Look for a list of disk images for the user to select.
  67. if (fs.existsSync(userPath)) {
  68. // Do something
  69. readFsRec(userPath, function (err, results) {
  70. var floppyImages = [], cdromImages = [];
  71. for (var i in results) {
  72. if (results[i].toLowerCase().endsWith('.img')) { floppyImages.push(results[i].substring(userPath.length + 1)); }
  73. else if (results[i].toLowerCase().endsWith('.iso')) { cdromImages.push(results[i].substring(userPath.length + 1)); }
  74. }
  75. var xx, sel = true, html = "<div style='margin:10px 5px 10px 5px'>Select disk images & start type.</div>";
  76. // Floppy image selection
  77. xx = "<select style=width:240px id=xxFloppyImagesSelect><option value=''>None</option>";
  78. for (var i in floppyImages) { xx += "<option value='" + encodeURIComponent(floppyImages[i]) + "'" + (sel?" selected":"") + ">" + floppyImages[i] + "</option>"; sel = false; }
  79. xx += "</select>";
  80. html += "<div style=margin:5px>" + addHtmlValue("Floppy Image", xx) + "</div>";
  81. // CDROM image selection
  82. sel = true;
  83. xx = "<select style=width:240px id=xxCdromImagesSelect><option value=''>None</option>";
  84. for (var i in cdromImages) { xx += "<option value='" + encodeURIComponent(cdromImages[i]) + "'" + (sel ? " selected" : "") + ">" + cdromImages[i] + "</option>"; sel = false; }
  85. xx += "</select>";
  86. html += "<div style=margin:5px>" + addHtmlValue("CDROM Image", xx) + "</div>";
  87. // Start type
  88. xx = "<select style=width:240px id=xxIderStartType><option value=0>On next boot<option value=1>Graceful<option value=2>Immediate</select>";
  89. html += "<div style=margin:5px>" + addHtmlValue("Session Start", xx) + "</div>";
  90. var js = "function iderServerCall() { return { ider: 1, floppyPath: Q('xxFloppyImagesSelect').value, cdromPath: Q('xxCdromImagesSelect').value, iderStart: Q('xxIderStartType').value }; }";
  91. try { ws.send(JSON.stringify({ action: 'dialog', args: { html: html, js: js }, buttons: 3 })); } catch (ex) { }
  92. });
  93. } else {
  94. // No user folder
  95. try { ws.send(JSON.stringify({ action: 'dialog', args: { html: 'No disk images found on remote server. Upload .img and .iso files to server "My Files" folder to enable this feature.' }, buttons: 2 })); } catch (ex) { }
  96. }
  97. break;
  98. }
  99. case 'dialogResponse': {
  100. if (command.args.ider == 1) { // Start IDER Session
  101. // Decode and validate file paths
  102. if ((command.args.floppyPath != null) && (typeof command.args.floppyPath != 'string')) { command.args.floppyPath = null; } else { command.args.floppyPath = decodeURIComponent(command.args.floppyPath); }
  103. if ((command.args.cdromPath != null) && (typeof command.args.cdromPath != 'string')) { command.args.cdromPath = null; } else { command.args.cdromPath = decodeURIComponent(command.args.cdromPath); }
  104. // TODO: Double check that "." or ".." are not used.
  105. if ((command.args.floppyPath != null) && (command.args.floppyPath.indexOf('..') >= 0)) { delete command.args.floppyPath; }
  106. if ((command.args.cdromPath != null) && (command.args.cdromPath.indexOf('..') >= 0)) { delete command.args.cdromPath; }
  107. // Get the disk image paths
  108. var domainx = 'domain' + ((domain.id == '') ? '' : ('-' + domain.id));
  109. var useridx = user._id.split('/')[2];
  110. var floppyPath = null, cdromPath = null;
  111. if (command.args.floppyPath) { floppyPath = parent.parent.path.join(parent.parent.filespath, domainx, 'user-' + useridx, command.args.floppyPath); }
  112. if (command.args.cdromPath) { cdromPath = parent.parent.path.join(parent.parent.filespath, domainx, 'user-' + useridx, command.args.cdromPath); }
  113. // Setup the IDER session
  114. obj.ider = amtMeshRedirModule.CreateAmtRedirect(amtMeshIderModule.CreateAmtRemoteIder(parent, parent.parent), domain, user, parent, parent.parent);
  115. obj.ider.onStateChanged = onIderStateChange;
  116. obj.ider.m.iderStart = command.args.iderStart;
  117. obj.ider.m.sectorStats = iderSectorStats;
  118. obj.ider.tlsv1only = req.query.tlsv1only;
  119. // Setup disk images
  120. var iderError = obj.ider.m.diskSetup(floppyPath, cdromPath);
  121. // Error with the disk images, unable to start IDER
  122. if (iderError != 0) { try { ws.send(JSON.stringify({ action: 'error', code: iderError })); } catch (ex) { } break; }
  123. // Start the IDER session
  124. obj.ider.Start(req.query.host, req.query.port, req.query.tls);
  125. }
  126. break;
  127. }
  128. default: {
  129. // Unknown user action
  130. console.log('Unknown IDER action from user ' + user.name + ': ' + command.action + '.');
  131. break;
  132. }
  133. }
  134. }
  135. function onIderStateChange(sender, state) {
  136. try { ws.send(JSON.stringify({ action: 'state', state: state })); } catch (ex) { }
  137. switch (state) {
  138. case 0:
  139. // Close the websocket connection and clean up.
  140. obj.ider.onStateChanged = null;
  141. obj.ider.m.sectorStats = null;
  142. obj.ider = null;
  143. obj.close();
  144. break;
  145. }
  146. }
  147. function iderSectorStats(mode, dev, total, start, len) {
  148. try { ws.send(JSON.stringify({ action: 'stats', mode: mode, dev: dev, total: total, start: start, len: len, toAmt: obj.ider.m.bytesToAmt, fromAmt: obj.ider.m.bytesFromAmt })); } catch (ex) { }
  149. }
  150. // Recursivly read all of the files in a fonder
  151. function readFsRec(dir, func) {
  152. var results = [];
  153. fs.readdir(dir, function (err, list) {
  154. if (err) return func(err);
  155. var pending = list.length;
  156. if (!pending) return func(null, results);
  157. list.forEach(function (file) {
  158. file = path.resolve(dir, file);
  159. fs.stat(file, function (err, stat) {
  160. if (stat && stat.isDirectory()) {
  161. readFsRec(file, function (err, res) { results = results.concat(res); if (!--pending) func(null, results); });
  162. } else {
  163. results.push(file); if (!--pending) func(null, results);
  164. }
  165. });
  166. });
  167. });
  168. };
  169. function addHtmlValue(t, v) { return '<div style=height:20px><div style=float:right;width:240px;overflow:hidden><b title="' + v + '">' + v + '</b></div><div>' + t + '</div></div>'; }
  170. return obj;
  171. };