2Copyright 2020-2021 Intel Corporation
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
8 http://www.apache.org/licenses/LICENSE-2.0
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
16@description MeshCentral Server IDER handler
17@author Ylian Saint-Hilaire
23/*jshint strict:false */
25/*jshint esversion: 6 */
28// Construct a MeshAgent object, called upon connection
29module.exports.CreateAmtIderSession = function (parent, db, ws, req, args, domain, user) {
30 const fs = require('fs');
31 const path = require('path');
32 const common = parent.common;
33 const amtMeshRedirModule = require('./amt-redir-mesh.js');
34 const amtMeshIderModule = require('./amt-ider-module.js');
36 //console.log('New Server IDER session from ' + user.name);
43 // Disconnect this user
44 obj.close = function (arg) {
45 if ((arg == 1) || (arg == null)) { try { ws.close(); parent.parent.debug(1, 'Soft disconnect'); } catch (e) { console.log(e); } } // Soft close, close the websocket
46 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
51 // Check if the user is logged in
52 if (user == null) { try { ws.close(); } catch (e) { } return; }
54 // When data is received from the web socket
55 ws.on('message', processWebSocketData);
57 // If error, do nothing
58 ws.on('error', function (err) { console.log(err); obj.close(0); });
60 // If the web socket is closed
61 ws.on('close', function (req) {
62 // Close the IDER session
63 if (obj.ider) { obj.ider.Stop(); delete obj.ider; }
66 // We are all set, start receiving data
69 } catch (e) { console.log(e); }
71 // Process incoming web socket data from the browser
72 function processWebSocketData(msg) {
73 var command, i = 0, mesh = null, meshid = null, nodeid = null, meshlinks = null, change = 0;
74 try { command = JSON.parse(msg.toString('utf8')); } catch (e) { return; }
75 if (common.validateString(command.action, 3, 32) == false) return; // Action must be a string between 3 and 32 chars
77 switch (command.action) {
78 case 'ping': { try { ws.send(JSON.stringify({ action: 'pong' })); } catch (ex) { } break; }
80 // Get the list of disk images
81 var domainx = 'domain' + ((domain.id == '') ? '' : ('-' + domain.id));
82 var useridx = user._id.split('/')[2];
83 var userPath = parent.parent.path.join(parent.parent.filespath, domainx, 'user-' + useridx);
85 // Look for a list of disk images for the user to select.
86 if (fs.existsSync(userPath)) {
88 readFsRec(userPath, function (err, results) {
89 var floppyImages = [], cdromImages = [];
90 for (var i in results) {
91 if (results[i].toLowerCase().endsWith('.img')) { floppyImages.push(results[i].substring(userPath.length + 1)); }
92 else if (results[i].toLowerCase().endsWith('.iso')) { cdromImages.push(results[i].substring(userPath.length + 1)); }
94 var xx, sel = true, html = "<div style='margin:10px 5px 10px 5px'>Select disk images & start type.</div>";
96 // Floppy image selection
97 xx = "<select style=width:240px id=xxFloppyImagesSelect><option value=''>None</option>";
98 for (var i in floppyImages) { xx += "<option value='" + encodeURIComponent(floppyImages[i]) + "'" + (sel?" selected":"") + ">" + floppyImages[i] + "</option>"; sel = false; }
100 html += "<div style=margin:5px>" + addHtmlValue("Floppy Image", xx) + "</div>";
102 // CDROM image selection
104 xx = "<select style=width:240px id=xxCdromImagesSelect><option value=''>None</option>";
105 for (var i in cdromImages) { xx += "<option value='" + encodeURIComponent(cdromImages[i]) + "'" + (sel ? " selected" : "") + ">" + cdromImages[i] + "</option>"; sel = false; }
107 html += "<div style=margin:5px>" + addHtmlValue("CDROM Image", xx) + "</div>";
110 xx = "<select style=width:240px id=xxIderStartType><option value=0>On next boot<option value=1>Graceful<option value=2>Immediate</select>";
111 html += "<div style=margin:5px>" + addHtmlValue("Session Start", xx) + "</div>";
113 var js = "function iderServerCall() { return { ider: 1, floppyPath: Q('xxFloppyImagesSelect').value, cdromPath: Q('xxCdromImagesSelect').value, iderStart: Q('xxIderStartType').value }; }";
115 try { ws.send(JSON.stringify({ action: 'dialog', args: { html: html, js: js }, buttons: 3 })); } catch (ex) { }
119 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) { }
124 case 'dialogResponse': {
125 if (command.args.ider == 1) { // Start IDER Session
126 // Decode and validate file paths
127 if ((command.args.floppyPath != null) && (typeof command.args.floppyPath != 'string')) { command.args.floppyPath = null; } else { command.args.floppyPath = decodeURIComponent(command.args.floppyPath); }
128 if ((command.args.cdromPath != null) && (typeof command.args.cdromPath != 'string')) { command.args.cdromPath = null; } else { command.args.cdromPath = decodeURIComponent(command.args.cdromPath); }
129 // TODO: Double check that "." or ".." are not used.
130 if ((command.args.floppyPath != null) && (command.args.floppyPath.indexOf('..') >= 0)) { delete command.args.floppyPath; }
131 if ((command.args.cdromPath != null) && (command.args.cdromPath.indexOf('..') >= 0)) { delete command.args.cdromPath; }
133 // Get the disk image paths
134 var domainx = 'domain' + ((domain.id == '') ? '' : ('-' + domain.id));
135 var useridx = user._id.split('/')[2];
136 var floppyPath = null, cdromPath = null;
137 if (command.args.floppyPath) { floppyPath = parent.parent.path.join(parent.parent.filespath, domainx, 'user-' + useridx, command.args.floppyPath); }
138 if (command.args.cdromPath) { cdromPath = parent.parent.path.join(parent.parent.filespath, domainx, 'user-' + useridx, command.args.cdromPath); }
140 // Setup the IDER session
141 obj.ider = amtMeshRedirModule.CreateAmtRedirect(amtMeshIderModule.CreateAmtRemoteIder(parent, parent.parent), domain, user, parent, parent.parent);
142 obj.ider.onStateChanged = onIderStateChange;
143 obj.ider.m.iderStart = command.args.iderStart;
144 obj.ider.m.sectorStats = iderSectorStats;
145 obj.ider.tlsv1only = req.query.tlsv1only;
148 var iderError = obj.ider.m.diskSetup(floppyPath, cdromPath);
150 // Error with the disk images, unable to start IDER
151 if (iderError != 0) { try { ws.send(JSON.stringify({ action: 'error', code: iderError })); } catch (ex) { } break; }
153 // Start the IDER session
154 obj.ider.Start(req.query.host, req.query.port, req.query.tls);
160 // Unknown user action
161 console.log('Unknown IDER action from user ' + user.name + ': ' + command.action + '.');
167 function onIderStateChange(sender, state) {
168 try { ws.send(JSON.stringify({ action: 'state', state: state })); } catch (ex) { }
171 // Close the websocket connection and clean up.
172 obj.ider.onStateChanged = null;
173 obj.ider.m.sectorStats = null;
180 function iderSectorStats(mode, dev, total, start, len) {
181 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) { }
184 // Recursivly read all of the files in a fonder
185 function readFsRec(dir, func) {
187 fs.readdir(dir, function (err, list) {
188 if (err) return func(err);
189 var pending = list.length;
190 if (!pending) return func(null, results);
191 list.forEach(function (file) {
192 file = path.resolve(dir, file);
193 fs.stat(file, function (err, stat) {
194 if (stat && stat.isDirectory()) {
195 readFsRec(file, function (err, res) { results = results.concat(res); if (!--pending) func(null, results); });
197 results.push(file); if (!--pending) func(null, results);
204 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>'; }