| 1 | /* |
| 2 | Copyright 2020-2021 Intel Corporation |
| 3 | |
| 4 | Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | you may not use this file except in compliance with the License. |
| 6 | You may obtain a copy of the License at |
| 7 | |
| 8 | http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | |
| 10 | Unless required by applicable law or agreed to in writing, software |
| 11 | distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | See the License for the specific language governing permissions and |
| 14 | limitations under the License. |
| 15 | |
| 16 | @description MeshCentral Server IDER handler |
| 17 | @author Ylian Saint-Hilaire |
| 18 | @version v0.3.0 |
| 19 | */ |
| 20 | |
| 21 | /*jslint node: true */ |
| 22 | /*jshint node: true */ |
| 23 | /*jshint strict:false */ |
| 24 | /*jshint -W097 */ |
| 25 | /*jshint esversion: 6 */ |
| 26 | "use strict"; |
| 27 | |
| 28 | // Construct a MeshAgent object, called upon connection |
| 29 | module.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'); |
| 35 | |
| 36 | //console.log('New Server IDER session from ' + user.name); |
| 37 | |
| 38 | var obj = {}; |
| 39 | obj.user = user; |
| 40 | obj.domain = domain; |
| 41 | obj.ider = null; |
| 42 | |
| 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 |
| 47 | }; |
| 48 | |
| 49 | try { |
| 50 | |
| 51 | // Check if the user is logged in |
| 52 | if (user == null) { try { ws.close(); } catch (e) { } return; } |
| 53 | |
| 54 | // When data is received from the web socket |
| 55 | ws.on('message', processWebSocketData); |
| 56 | |
| 57 | // If error, do nothing |
| 58 | ws.on('error', function (err) { console.log(err); obj.close(0); }); |
| 59 | |
| 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; } |
| 64 | }); |
| 65 | |
| 66 | // We are all set, start receiving data |
| 67 | ws._socket.resume(); |
| 68 | |
| 69 | } catch (e) { console.log(e); } |
| 70 | |
| 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 |
| 76 | |
| 77 | switch (command.action) { |
| 78 | case 'ping': { try { ws.send(JSON.stringify({ action: 'pong' })); } catch (ex) { } break; } |
| 79 | case 'start': { |
| 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); |
| 84 | |
| 85 | // Look for a list of disk images for the user to select. |
| 86 | if (fs.existsSync(userPath)) { |
| 87 | // Do something |
| 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)); } |
| 93 | } |
| 94 | var xx, sel = true, html = "<div style='margin:10px 5px 10px 5px'>Select disk images & start type.</div>"; |
| 95 | |
| 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; } |
| 99 | xx += "</select>"; |
| 100 | html += "<div style=margin:5px>" + addHtmlValue("Floppy Image", xx) + "</div>"; |
| 101 | |
| 102 | // CDROM image selection |
| 103 | sel = true; |
| 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; } |
| 106 | xx += "</select>"; |
| 107 | html += "<div style=margin:5px>" + addHtmlValue("CDROM Image", xx) + "</div>"; |
| 108 | |
| 109 | // Start type |
| 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>"; |
| 112 | |
| 113 | var js = "function iderServerCall() { return { ider: 1, floppyPath: Q('xxFloppyImagesSelect').value, cdromPath: Q('xxCdromImagesSelect').value, iderStart: Q('xxIderStartType').value }; }"; |
| 114 | |
| 115 | try { ws.send(JSON.stringify({ action: 'dialog', args: { html: html, js: js }, buttons: 3 })); } catch (ex) { } |
| 116 | }); |
| 117 | } else { |
| 118 | // No user folder |
| 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) { } |
| 120 | } |
| 121 | |
| 122 | break; |
| 123 | } |
| 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; } |
| 132 | |
| 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); } |
| 139 | |
| 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; |
| 146 | |
| 147 | // Setup disk images |
| 148 | var iderError = obj.ider.m.diskSetup(floppyPath, cdromPath); |
| 149 | |
| 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; } |
| 152 | |
| 153 | // Start the IDER session |
| 154 | obj.ider.Start(req.query.host, req.query.port, req.query.tls); |
| 155 | } |
| 156 | |
| 157 | break; |
| 158 | } |
| 159 | default: { |
| 160 | // Unknown user action |
| 161 | console.log('Unknown IDER action from user ' + user.name + ': ' + command.action + '.'); |
| 162 | break; |
| 163 | } |
| 164 | } |
| 165 | } |
| 166 | |
| 167 | function onIderStateChange(sender, state) { |
| 168 | try { ws.send(JSON.stringify({ action: 'state', state: state })); } catch (ex) { } |
| 169 | switch (state) { |
| 170 | case 0: |
| 171 | // Close the websocket connection and clean up. |
| 172 | obj.ider.onStateChanged = null; |
| 173 | obj.ider.m.sectorStats = null; |
| 174 | obj.ider = null; |
| 175 | obj.close(); |
| 176 | break; |
| 177 | } |
| 178 | } |
| 179 | |
| 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) { } |
| 182 | } |
| 183 | |
| 184 | // Recursivly read all of the files in a fonder |
| 185 | function readFsRec(dir, func) { |
| 186 | var results = []; |
| 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); }); |
| 196 | } else { |
| 197 | results.push(file); if (!--pending) func(null, results); |
| 198 | } |
| 199 | }); |
| 200 | }); |
| 201 | }); |
| 202 | }; |
| 203 | |
| 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>'; } |
| 205 | |
| 206 | return obj; |
| 207 | }; |