diff --git a/MeshCentralServer.njsproj b/MeshCentralServer.njsproj
index 4b72e4a7..ff4fcd96 100644
--- a/MeshCentralServer.njsproj
+++ b/MeshCentralServer.njsproj
@@ -91,6 +91,9 @@
+
+
+
diff --git a/amt-ider-module.js b/amt-ider-module.js
new file mode 100644
index 00000000..b7d90e44
--- /dev/null
+++ b/amt-ider-module.js
@@ -0,0 +1,690 @@
+/**
+* @description IDER Handling Module
+* @author Ylian Saint-Hilaire
+* @version v0.0.2
+*/
+
+// Construct a Intel AMT IDER object
+module.exports.CreateAmtRemoteIder = function () {
+ var obj = {};
+ obj.debug = false;
+ obj.protocol = 3; // IDER
+ obj.bytesToAmt = 0;
+ obj.bytesFromAmt = 0;
+ obj.rx_timeout = 30000; // Default 30000
+ obj.tx_timeout = 0; // Default 0
+ obj.heartbeat = 20000; // Default 20000
+ obj.version = 1;
+ obj.acc = "";
+ obj.inSequence = 0;
+ obj.outSequence = 0;
+ obj.iderinfo = null;
+ obj.enabled = false;
+ obj.iderStart = 0; // OnReboot = 0, Graceful = 1, Now = 2
+ obj.floppy = null;
+ obj.cdrom = null;
+ obj.floppyReady = false;
+ obj.cdromReady = false;
+ //obj.pingTimer = null;
+ // ###BEGIN###{IDERStats}
+ obj.sectorStats = null;
+ // ###END###{IDERStats}
+
+ // Private method
+ // ###BEGIN###{IDERDebug}
+ function debug() { if (obj.debug) { console.log(...arguments); } }
+ // ###END###{IDERDebug}
+
+ // Mode Sense
+ var IDE_ModeSence_LS120Disk_Page_Array = String.fromCharCode(0x00, 0x26, 0x31, 0x80, 0x00, 0x00, 0x00, 0x00, 0x05, 0x1E, 0x10, 0xA9, 0x08, 0x20, 0x02, 0x00, 0x03, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xD0, 0x00, 0x00);
+ var IDE_ModeSence_3F_LS120_Array = String.fromCharCode(0x00, 0x5c, 0x24, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0a, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x16, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x05, 0x1E, 0x10, 0xA9, 0x08, 0x20, 0x02, 0x00, 0x03, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xD0, 0x00, 0x00, 0x08, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x06, 0x00, 0x00, 0x00, 0x11, 0x24, 0x31);
+ var IDE_ModeSence_FloppyDisk_Page_Array = String.fromCharCode(0x00, 0x26, 0x24, 0x80, 0x00, 0x00, 0x00, 0x00, 0x05, 0x1E, 0x04, 0xB0, 0x02, 0x12, 0x02, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xD0, 0x00, 0x00);
+ var IDE_ModeSence_3F_Floppy_Array = String.fromCharCode(0x00, 0x5c, 0x24, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0a, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x16, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x05, 0x1e, 0x04, 0xb0, 0x02, 0x12, 0x02, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xd0, 0x00, 0x00, 0x08, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x06, 0x00, 0x00, 0x00, 0x11, 0x24, 0x31);
+ var IDE_ModeSence_CD_1A_Array = String.fromCharCode(0x00, 0x12, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
+ //var IDE_ModeSence_CD_1B_Array = String.fromCharCode(0x00, 0x12, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x1B, 0x0A, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
+ var IDE_ModeSence_CD_1D_Array = String.fromCharCode(0x00, 0x12, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x1D, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
+ var IDE_ModeSence_CD_2A_Array = String.fromCharCode(0x00, 0x20, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x18, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
+ //var IDE_ModeSence_CD_01_Array = String.fromCharCode(0x00, 0x0E, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00);
+ var IDE_ModeSence_3F_CD_Array = String.fromCharCode(0x00, 0x28, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x18, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
+
+ // 0x46 constant data
+ var IDE_CD_ConfigArrayHeader = String.fromCharCode(0x00, 0x00,0x00, 0x28, 0x00, 0x00, 0x00, 0x08);
+ var IDE_CD_ConfigArrayProfileList = String.fromCharCode(0x00, 0x00, 0x03, 0x04, 0x00, 0x08, 0x01, 0x00);
+ var IDE_CD_ConfigArrayCore = String.fromCharCode(0x00, 0x01, 0x03, 0x04, 0x00, 0x00, 0x00, 0x02);
+ var IDE_CD_Morphing = String.fromCharCode(0x00, 0x02, 0x03, 0x04, 0x00, 0x00, 0x00, 0x00);
+ var IDE_CD_ConfigArrayRemovable = String.fromCharCode(0x00, 0x03, 0x03, 0x04, 0x29, 0x00, 0x00, 0x02);
+ var IDE_CD_ConfigArrayRandom = String.fromCharCode(0x00, 0x10, 0x01, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x01, 0x00, 0x00);
+ var IDE_CD_Read = String.fromCharCode(0x00, 0x1E, 0x03, 0x00);
+ var IDE_CD_PowerManagement = String.fromCharCode(0x01, 0x00, 0x03, 0x00);
+ var IDE_CD_Timeout = String.fromCharCode(0x01, 0x05, 0x03, 0x00);
+
+ // 0x01 constant data
+ var IDE_ModeSence_FloppyError_Recovery_Array = String.fromCharCode(0x00, 0x12, 0x24, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0A, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00);
+ var IDE_ModeSence_Ls120Error_Recovery_Array = String.fromCharCode(0x00, 0x12, 0x31, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0A, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00);
+ var IDE_ModeSence_CDError_Recovery_Array = String.fromCharCode(0x00, 0x0E, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00);
+
+
+ // Private method, called by parent when it change state
+ obj.xxStateChange = function (newstate) {
+ // ###BEGIN###{IDERDebug}
+ debug("IDER-StateChange", newstate);
+ // ###END###{IDERDebug}
+ if (newstate == 0) { obj.Stop(); }
+ if (newstate == 3) { obj.Start(); }
+ }
+
+ obj.Start = function () {
+ // ###BEGIN###{IDERDebug}
+ debug("IDER-Start");
+ debug(obj.floppy, obj.cdrom);
+ // ###END###{IDERDebug}
+ obj.bytesToAmt = 0;
+ obj.bytesFromAmt = 0;
+ obj.inSequence = 0;
+ obj.outSequence = 0;
+
+ // Send first command, OPEN_SESSION
+ obj.SendCommand(0x40, ShortToStrX(obj.rx_timeout) + ShortToStrX(obj.tx_timeout) + ShortToStrX(obj.heartbeat) + IntToStrX(obj.version));
+
+ // Send sector stats
+ // ###BEGIN###{IDERStats}
+ if (obj.sectorStats) {
+ obj.sectorStats(0, 0, obj.floppy?(obj.floppy.size >> 9):0);
+ obj.sectorStats(0, 1, obj.cdrom ? (obj.cdrom.size >> 11) : 0);
+ }
+ // ###END###{IDERStats}
+
+ // Setup the ping timer
+ //obj.pingTimer = setInterval(function () { obj.SendCommand(0x44); }, 5000);
+ }
+
+ obj.Stop = function () {
+ // ###BEGIN###{IDERDebug}
+ debug("IDER-Stop");
+ // ###END###{IDERDebug}
+ //if (obj.pingTimer) { clearInterval(obj.pingTimer); obj.pingTimer = null; }
+ obj.parent.Stop();
+ }
+
+ // Private method
+ obj.ProcessData = function (data) {
+ obj.bytesFromAmt += data.length;
+ obj.acc += data;
+ // ###BEGIN###{IDERDebug}
+ debug('IDER-ProcessData', obj.acc.length, rstr2hex(obj.acc));
+ // ###END###{IDERDebug}
+
+ // Process as many commands as possible
+ while (true) {
+ var len = obj.ProcessDataEx();
+ if (len == 0) return;
+ if (obj.inSequence != ReadIntX(obj.acc, 4)) {
+ // ###BEGIN###{IDERDebug}
+ debug('ERROR: Out of sequence', obj.inSequence, ReadIntX(obj.acc, 4));
+ // ###END###{IDERDebug}
+ obj.Stop();
+ return;
+ }
+ obj.inSequence++;
+ obj.acc = obj.acc.substring(len);
+ }
+ }
+
+ // Private method
+ obj.SendCommand = function (cmdid, data, completed, dma) {
+ if (data == null) { data = ''; }
+ var attributes = ((cmdid > 50) && (completed == true)) ? 2 : 0;
+ if (dma) { attributes += 1; }
+ var x = String.fromCharCode(cmdid, 0, 0, attributes) + IntToStrX(obj.outSequence++) + data;
+ obj.parent.xxSend(x);
+ obj.bytesToAmt += x.length;
+ // ###BEGIN###{IDERDebug}
+ if (cmdid != 0x4B) { debug('IDER-SendData', x.length, rstr2hex(x)); }
+ // ###END###{IDERDebug}
+ }
+
+ // CommandEndResponse (SCSI_SENSE)
+ obj.SendCommandEndResponse = function (error, sense, device, asc, asq) {
+ if (error) { obj.SendCommand(0x51, String.fromCharCode(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xc5, 0, 3, 0, 0, 0, device, 0x50, 0, 0, 0), true); }
+ else { obj.SendCommand(0x51, String.fromCharCode(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x87, (sense << 4), 3, 0, 0, 0, device, 0x51, sense, asc, asq), true); }
+ }
+
+ // DataToHost (SCSI_READ)
+ obj.SendDataToHost = function (device, completed, data, dma) {
+ var dmalen = (dma) ? 0 : data.length;
+ if (completed == true) {
+ obj.SendCommand(0x54, String.fromCharCode(0, (data.length & 0xff), (data.length >> 8), 0, dma ? 0xb4 : 0xb5, 0, 2, 0, (dmalen & 0xff), (dmalen >> 8), device, 0x58, 0x85, 0, 3, 0, 0, 0, device, 0x50, 0, 0, 0, 0, 0, 0) + data, completed, dma);
+ } else {
+ obj.SendCommand(0x54, String.fromCharCode(0, (data.length & 0xff), (data.length >> 8), 0, dma ? 0xb4 : 0xb5, 0, 2, 0, (dmalen & 0xff), (dmalen >> 8), device, 0x58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) + data, completed, dma);
+ }
+ }
+
+ // GetDataFromHost (SCSI_CHUNK)
+ obj.SendGetDataFromHost = function (device, chunksize) {
+ obj.SendCommand(0x52, String.fromCharCode(0, (chunksize & 0xff), (chunksize >> 8), 0, 0xb5, 0, 0, 0, (chunksize & 0xff), (chunksize >> 8), device, 0x58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), false);
+ }
+
+ // DisableEnableFeatures (STATUS_DATA)
+ // If type is REGS_TOGGLE (3), 4 bytes of data must be provided.
+ obj.SendDisableEnableFeatures = function (type, data) { if (data == null) { data = ''; } obj.SendCommand(0x48, String.fromCharCode(type) + data); }
+
+ // Private method
+ obj.ProcessDataEx = function () {
+ if (obj.acc.length < 8) return 0;
+
+ // First 8 bytes are the header
+ // CommandID + 0x000000 + Sequence Number
+
+ switch(obj.acc.charCodeAt(0)) {
+ case 0x41: // OPEN_SESSION
+ if (obj.acc.length < 30) return 0;
+ var len = obj.acc.charCodeAt(29);
+ if (obj.acc.length < (30 + len)) return 0;
+ obj.iderinfo = {};
+ obj.iderinfo.major = obj.acc.charCodeAt(8);
+ obj.iderinfo.minor = obj.acc.charCodeAt(9);
+ obj.iderinfo.fwmajor = obj.acc.charCodeAt(10);
+ obj.iderinfo.fwminor = obj.acc.charCodeAt(11);
+ obj.iderinfo.readbfr = ReadShortX(obj.acc, 16);
+ obj.iderinfo.writebfr = ReadShortX(obj.acc, 18);
+ obj.iderinfo.proto = obj.acc.charCodeAt(21);
+ obj.iderinfo.iana = ReadIntX(obj.acc, 25);
+ // ###BEGIN###{IDERDebug}
+ debug(obj.iderinfo);
+ // ###END###{IDERDebug}
+
+ if (obj.iderinfo.proto != 0) {
+ // ###BEGIN###{IDERDebug}
+ debug("Unknown proto", obj.iderinfo.proto);
+ // ###END###{IDERDebug}
+ obj.Stop();
+ }
+ if (obj.iderinfo.readbfr > 8192) {
+ // ###BEGIN###{IDERDebug}
+ debug("Illegal read buffer size", obj.iderinfo.readbfr);
+ // ###END###{IDERDebug}
+ obj.Stop();
+ }
+ if (obj.iderinfo.writebfr > 8192) {
+ // ###BEGIN###{IDERDebug}
+ debug("Illegal write buffer size", obj.iderinfo.writebfr);
+ // ###END###{IDERDebug}
+ obj.Stop();
+ }
+
+ if (obj.iderStart == 0) { obj.SendDisableEnableFeatures(3, IntToStrX(0x01 + 0x08)); } // OnReboot
+ else if (obj.iderStart == 1) { obj.SendDisableEnableFeatures(3, IntToStrX(0x01 + 0x10)); } // Graceful
+ else if (obj.iderStart == 2) { obj.SendDisableEnableFeatures(3, IntToStrX(0x01 + 0x18)); } // Now
+ //obj.SendDisableEnableFeatures(1); // GetSupportedFeatures
+ return 30 + len;
+ case 0x43: // CLOSE
+ // ###BEGIN###{IDERDebug}
+ debug('CLOSE');
+ // ###END###{IDERDebug}
+ obj.Stop();
+ return 8;
+ case 0x44: // KEEPALIVEPING
+ obj.SendCommand(0x45); // Send PONG back
+ return 8;
+ case 0x45: // KEEPALIVEPONG
+ // ###BEGIN###{IDERDebug}
+ debug('PONG');
+ // ###END###{IDERDebug}
+ return 8;
+ case 0x46: // RESETOCCURED
+ if (obj.acc.length < 9) return 0;
+ var resetMask = obj.acc.charCodeAt(8);
+ if (g_media === null) {
+ // No operations are pending
+ obj.SendCommand(0x47); // Send ResetOccuredResponse
+ // ###BEGIN###{IDERDebug}
+ debug('RESETOCCURED1', resetMask);
+ // ###END###{IDERDebug}
+ } else {
+ // Operations are being done, sent the reset once completed.
+ g_reset = true;
+ // ###BEGIN###{IDERDebug}
+ debug('RESETOCCURED2', resetMask);
+ // ###END###{IDERDebug}
+ }
+ return 9;
+ case 0x49: // STATUS_DATA - DisableEnableFeaturesReply
+ if (obj.acc.length < 13) return 0;
+ var type = obj.acc.charCodeAt(8);
+ var value = ReadIntX(obj.acc, 9);
+ // ###BEGIN###{IDERDebug}
+ debug('STATUS_DATA', type, value);
+ // ###END###{IDERDebug}
+ switch (type)
+ {
+ case 1: // REGS_AVAIL
+ if (value & 1) {
+ if (obj.iderStart == 0) { obj.SendDisableEnableFeatures(3, IntToStrX(0x01 + 0x08)); } // OnReboot
+ else if (obj.iderStart == 1) { obj.SendDisableEnableFeatures(3, IntToStrX(0x01 + 0x10)); } // Graceful
+ else if (obj.iderStart == 2) { obj.SendDisableEnableFeatures(3, IntToStrX(0x01 + 0x18)); } // Now
+ }
+ break;
+ case 2: // REGS_STATUS
+ obj.enabled = (value & 2) ? true : false;
+ // ###BEGIN###{IDERDebug}
+ debug("IDER Status: " + obj.enabled);
+ // ###END###{IDERDebug}
+ break;
+ case 3: // REGS_TOGGLE
+ if (value != 1) {
+ // ###BEGIN###{IDERDebug}
+ debug("Register toggle failure");
+ // ###END###{IDERDebug}
+ } //else { obj.SendDisableEnableFeatures(2); }
+ break;
+ }
+ return 13;
+ case 0x4A: // ERROR OCCURED
+ if (obj.acc.length < 11) return 0;
+ // ###BEGIN###{IDERDebug}
+ debug('IDER: ABORT', obj.acc.charCodeAt(8));
+ // ###END###{IDERDebug}
+ //obj.Stop();
+ return 11;
+ case 0x4B: // HEARTBEAT
+ // ###BEGIN###{IDERDebug}
+ //debug('HEARTBEAT');
+ // ###END###{IDERDebug}
+ return 8;
+ case 0x50: // COMMAND WRITTEN
+ if (obj.acc.length < 28) return 0;
+ var device = (obj.acc.charCodeAt(14) & 0x10) ? 0xB0 : 0xA0;
+ var deviceFlags = obj.acc.charCodeAt(14);
+ var cdb = obj.acc.substring(16, 28);
+ var featureRegister = obj.acc.charCodeAt(9);
+ // ###BEGIN###{IDERDebug}
+ debug('SCSI_CMD', device, rstr2hex(cdb), featureRegister, deviceFlags);
+ // ###END###{IDERDebug}
+ handleSCSI(device, cdb, featureRegister, deviceFlags);
+ return 28;
+ case 0x53: // DATA FROM HOST
+ if (obj.acc.length < 14) return 0;
+ var len = ReadShortX(obj.acc, 9);
+ if (obj.acc.length < (14 + len)) return 0;
+ // ###BEGIN###{IDERDebug}
+ debug('SCSI_WRITE, len = ' + (14 + len));
+ // ###END###{IDERDebug}
+ obj.SendCommand(0x51, String.fromCharCode(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87, 0x70, 0x03, 0x00, 0x00, 0x00, 0xa0, 0x51, 0x07, 0x27, 0x00), true);
+ return 14 + len;
+ default:
+ // ###BEGIN###{IDERDebug}
+ debug('Unknown IDER command', obj.acc[0]);
+ // ###END###{IDERDebug}
+ obj.Stop();
+ break;
+ }
+ return 0;
+ }
+
+ function handleSCSI(dev, cdb, featureRegister, deviceFlags)
+ {
+ var lba;
+ var len;
+
+ switch(cdb.charCodeAt(0))
+ {
+ case 0x00: // TEST_UNIT_READY:
+ // ###BEGIN###{IDERDebug}
+ debug("SCSI: TEST_UNIT_READY", dev);
+ // ###END###{IDERDebug}
+ switch (dev) {
+ case 0xA0: // DEV_FLOPPY
+ if (obj.floppy == null) { obj.SendCommandEndResponse(1, 0x02, dev, 0x3a, 0x00); return -1; }
+ if (obj.floppyReady == false) { obj.floppyReady = true; obj.SendCommandEndResponse(1, 0x06, dev, 0x28, 0x00); return -1; } // Switch to ready
+ break;
+ case 0xB0: // DEV_CDDVD
+ if (obj.cdrom == null) { obj.SendCommandEndResponse(1, 0x02, dev, 0x3a, 0x00); return -1; }
+ if (obj.cdromReady == false) { obj.cdromReady = true; obj.SendCommandEndResponse(1, 0x06, dev, 0x28, 0x00); return -1; } // Switch to ready
+ break;
+ default:
+ // ###BEGIN###{IDERDebug}
+ debug("SCSI Internal error 3", dev);
+ // ###END###{IDERDebug}
+ return -1;
+ }
+ obj.SendCommandEndResponse(1, 0x00, dev, 0x00, 0x00); // Indicate ready
+ break;
+ case 0x08: // READ_6
+ lba = ((cdb.charCodeAt(1) & 0x1f) << 16) + (cdb.charCodeAt(2) << 8) + cdb.charCodeAt(3);
+ len = cdb.charCodeAt(4);
+ if (len == 0) { len = 256; }
+ // ###BEGIN###{IDERDebug}
+ debug("SCSI: READ_6", dev, lba, len);
+ // ###END###{IDERDebug}
+ sendDiskData(dev, lba, len, featureRegister);
+ break;
+ case 0x0a: // WRITE_6
+ lba = ((cdb.charCodeAt(1) & 0x1f) << 16) + (cdb.charCodeAt(2) << 8) + cdb.charCodeAt(3);
+ len = cdb.charCodeAt(4);
+ if (len == 0) { len = 256; }
+ // ###BEGIN###{IDERDebug}
+ debug("SCSI: WRITE_6", dev, lba, len);
+ // ###END###{IDERDebug}
+ obj.SendCommandEndResponse(1, 0x02, dev, 0x3a, 0x00); // Write is not supported, remote no medium.
+ return -1;
+ /*
+ case 0x15: // MODE_SELECT_6:
+ // ###BEGIN###{IDERDebug}
+ debug("SCSI ERROR: MODE_SELECT_6", dev);
+ // ###END###{IDERDebug}
+ obj.SendCommandEndResponse(1, 0x05, dev, 0x20, 0x00);
+ return -1;
+ */
+ case 0x1a: // MODE_SENSE_6
+ // ###BEGIN###{IDERDebug}
+ debug("SCSI: MODE_SENSE_6", dev);
+ // ###END###{IDERDebug}
+ if ((cdb.charCodeAt(2) == 0x3f) && (cdb.charCodeAt(3) == 0x00)) {
+ var a = 0, b = 0;
+ switch (dev) {
+ case 0xA0: // DEV_FLOPPY
+ if (obj.floppy == null) { obj.SendCommandEndResponse(1, 0x02, dev, 0x3a, 0x00); return -1; }
+ a = 0x00;
+ b = 0x80; // Read only = 0x80, Read write = 0x00
+ break;
+ case 0xB0: // DEV_CDDVD
+ if (obj.cdrom == null) { obj.SendCommandEndResponse(1, 0x02, dev, 0x3a, 0x00); return -1; }
+ a = 0x05;
+ b = 0x80;
+ break;
+ default:
+ // ###BEGIN###{IDERDebug}
+ debug("SCSI Internal error 6", dev);
+ // ###END###{IDERDebug}
+ return -1;
+ }
+ obj.SendDataToHost(dev, true, String.fromCharCode(0, a, b, 0), featureRegister & 1);
+ return;
+ }
+ obj.SendCommandEndResponse(1, 0x05, dev, 0x24, 0x00);
+ break;
+ case 0x1b: // START_STOP (Called when you eject the CDROM)
+ //var immediate = cdb.charCodeAt(1) & 0x01;
+ //var loej = cdb.charCodeAt(4) & 0x02;
+ //var start = cdb.charCodeAt(4) & 0x01;
+ obj.SendCommandEndResponse(1, 0, dev);
+ break;
+ case 0x1e: // LOCK_UNLOCK - ALLOW_MEDIUM_REMOVAL
+ // ###BEGIN###{IDERDebug}
+ debug("SCSI: ALLOW_MEDIUM_REMOVAL", dev);
+ // ###END###{IDERDebug}
+ if ((dev == 0xA0) && (obj.floppy == null)) { obj.SendCommandEndResponse(1, 0x02, dev, 0x3a, 0x00); return -1; }
+ if ((dev == 0xB0) && (obj.cdrom == null)) { obj.SendCommandEndResponse(1, 0x02, dev, 0x3a, 0x00); return -1; }
+ obj.SendCommandEndResponse(1, 0x00, dev, 0x00, 0x00);
+ break;
+ case 0x23: // READ_FORMAT_CAPACITIES (Floppy only)
+ // ###BEGIN###{IDERDebug}
+ debug("SCSI: READ_FORMAT_CAPACITIES", dev);
+ // ###END###{IDERDebug}
+ var buflen = ReadShort(cdb, 7);
+ var mediaStatus = 0, sectors;
+ var mcSize = buflen / 8; // Capacity descriptor size is 8
+
+ switch (dev) {
+ case 0xA0: // DEV_FLOPPY
+ if ((obj.floppy == null) || (obj.floppy.size == 0)) { obj.SendCommandEndResponse(0, 0x05, dev, 0x24, 0x00); return -1; }
+ sectors = (obj.floppy.size >> 9) - 1;
+ break;
+ case 0xB0: // DEV_CDDVD
+ if ((obj.cdrom == null) || (obj.cdrom.size == 0)) { obj.SendCommandEndResponse(0, 0x05, dev, 0x24, 0x00); return -1; }
+ sectors = (obj.cdrom.size >> 11) - 1; // Number 2048 byte blocks
+ break;
+ default:
+ // ###BEGIN###{IDERDebug}
+ debug("SCSI Internal error 4", dev);
+ // ###END###{IDERDebug}
+ return -1;
+ }
+
+ obj.SendDataToHost(dev, true, IntToStr(8) + String.fromCharCode(0x00, 0x00, 0x0b, 0x40, 0x02, 0x00, 0x02, 0x00), featureRegister & 1);
+ break;
+ case 0x25: // READ_CAPACITY
+ // ###BEGIN###{IDERDebug}
+ debug("SCSI: READ_CAPACITY", dev);
+ // ###END###{IDERDebug}
+ var len = 0;
+ switch(dev)
+ {
+ case 0xA0: // DEV_FLOPPY
+ if ((obj.floppy == null) || (obj.floppy.size == 0)) { obj.SendCommandEndResponse(0, 0x02, dev, 0x3a, 0x00); return -1; }
+ if (obj.floppy != null) { len = (obj.floppy.size >> 9) - 1; }
+ // ###BEGIN###{IDERDebug}
+ debug('DEV_FLOPPY', len); // Number 512 byte blocks
+ // ###END###{IDERDebug}
+ break;
+ case 0xB0: // DEV_CDDVD
+ if ((obj.floppy == null) || (obj.floppy.size == 0)) { obj.SendCommandEndResponse(0, 0x02, dev, 0x3a, 0x00); return -1; }
+ if (obj.cdrom != null) { len = (obj.cdrom.size >> 11) - 1; } // Number 2048 byte blocks
+ // ###BEGIN###{IDERDebug}
+ debug('DEV_CDDVD', len);
+ // ###END###{IDERDebug}
+ break;
+ default:
+ // ###BEGIN###{IDERDebug}
+ debug("SCSI Internal error 4", dev);
+ // ###END###{IDERDebug}
+ return -1;
+ }
+ //if (dev == 0xA0) { dev = 0x00; } else { dev = 0x10; } // Weird but seems to work.
+ // ###BEGIN###{IDERDebug}
+ debug("SCSI: READ_CAPACITY2", dev, deviceFlags);
+ // ###END###{IDERDebug}
+ obj.SendDataToHost(deviceFlags, true, IntToStr(len) + String.fromCharCode(0, 0, ((dev == 0xB0) ? 0x08 : 0x02), 0), featureRegister & 1);
+ break;
+ case 0x28: // READ_10
+ lba = ReadInt(cdb, 2);
+ len = ReadShort(cdb, 7);
+ // ###BEGIN###{IDERDebug}
+ debug("SCSI: READ_10", dev, lba, len);
+ // ###END###{IDERDebug}
+ sendDiskData(dev, lba, len, featureRegister);
+ break;
+ case 0x2a: // WRITE_10 (Floppy only)
+ case 0x2e: // WRITE_AND_VERIFY (Floppy only)
+ lba = ReadInt(cdb, 2);
+ len = ReadShort(cdb, 7);
+ // ###BEGIN###{IDERDebug}
+ debug("SCSI: WRITE_10", dev, lba, len);
+ // ###END###{IDERDebug}
+ obj.SendGetDataFromHost(dev, 512 * len); // Floppy writes only, accept sectors of 512 bytes
+ break;
+ case 0x43: // READ_TOC (CD Audio only)
+ var buflen = ReadShort(cdb, 7);
+ var msf = cdb.charCodeAt(1) & 0x02;
+ var format = cdb.charCodeAt(2) & 0x07;
+ if (format == 0) { format = cdb.charCodeAt(9) >> 6; }
+ // ###BEGIN###{IDERDebug}
+ debug("SCSI: READ_TOC, dev=" + dev + ", buflen=" + buflen + ", msf=" + msf + ", format=" + format);
+ // ###END###{IDERDebug}
+
+ switch (dev) {
+ case 0xA0: // DEV_FLOPPY
+ obj.SendCommandEndResponse(1, 0x05, dev, 0x20, 0x00); // Not implemented
+ return -1;
+ case 0xB0: // DEV_CDDVD
+ // NOP
+ break;
+ default:
+ // ###BEGIN###{IDERDebug}
+ debug("SCSI Internal error 9", dev);
+ // ###END###{IDERDebug}
+ return -1;
+ }
+
+ if (format == 1) { obj.SendDataToHost(dev, true, String.fromCharCode(0x00, 0x0a, 0x01, 0x01, 0x00, 0x14, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00), featureRegister & 1); }
+ else if (format == 0) {
+ if (msf) {
+ obj.SendDataToHost(dev, true, String.fromCharCode(0x00, 0x12, 0x01, 0x01, 0x00, 0x14, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x14, 0xaa, 0x00, 0x00, 0x00, 0x34, 0x13), featureRegister & 1);
+ } else {
+ obj.SendDataToHost(dev, true, String.fromCharCode(0x00, 0x12, 0x01, 0x01, 0x00, 0x14, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00), featureRegister & 1);
+ }
+ }
+ break;
+ case 0x46: // GET_CONFIGURATION
+ var sendall = (cdb.charCodeAt(1) != 2);
+ var firstcode = ReadShort(cdb, 2);
+ var buflen = ReadShort(cdb, 7);
+
+ // ###BEGIN###{IDERDebug}
+ debug("SCSI: GET_CONFIGURATION", dev, sendall, firstcode, buflen);
+ // ###END###{IDERDebug}
+
+ if (buflen == 0) { obj.SendDataToHost(dev, true, IntToStr(0x003c) + IntToStr(0x0008), featureRegister & 1); return -1; } // TODO: Fixed this return, it's not correct.
+
+ // Set the header
+ var r = IntToStr(0x0008);
+
+ // Add the data
+ if (firstcode == 0) { r += IDE_CD_ConfigArrayProfileList; }
+ if ((firstcode == 0x1) || (sendall && (firstcode < 0x1))) { r += IDE_CD_ConfigArrayCore; }
+ if ((firstcode == 0x2) || (sendall && (firstcode < 0x2))) { r += IDE_CD_Morphing; }
+ if ((firstcode == 0x3) || (sendall && (firstcode < 0x3))) { r += IDE_CD_ConfigArrayRemovable; }
+ if ((firstcode == 0x10) || (sendall && (firstcode < 0x10))) { r += IDE_CD_ConfigArrayRandom; }
+ if ((firstcode == 0x1E) || (sendall && (firstcode < 0x1E))) { r += IDE_CD_Read; }
+ if ((firstcode == 0x100) || (sendall && (firstcode < 0x100))) { r += IDE_CD_PowerManagement; }
+ if ((firstcode == 0x105) || (sendall && (firstcode < 0x105))) { r += IDE_CD_Timeout; }
+
+ // Set the length
+ r = IntToStr(r.length) + r;
+
+ // Cut the length to buflen if needed
+ if (r.length > buflen) { r = r.substring(0, buflen); }
+
+ obj.SendDataToHost(dev, true, r, featureRegister & 1);
+ return -1;
+ case 0x4a: // GET_EV_STATUS - GET_EVENT_STATUS_NOTIFICATION
+ //var buflen = (cdb.charCodeAt(7) << 8) + cdb.charCodeAt(8);
+ //if (buflen == 0) { obj.SendDataToHost(dev, true, IntToStr(0x003c) + IntToStr(0x0008), featureRegister & 1); return -1; } // TODO: Fixed this return, it's not correct.
+ // ###BEGIN###{IDERDebug}
+ debug("SCSI: GET_EVENT_STATUS_NOTIFICATION", dev, cdb.charCodeAt(1), cdb.charCodeAt(4), cdb.charCodeAt(9));
+ // ###END###{IDERDebug}
+ if ((cdb.charCodeAt(1) != 0x01) && (cdb.charCodeAt(4) != 0x10)) {
+ // ###BEGIN###{IDERDebug}
+ debug('SCSI ERROR');
+ // ###END###{IDERDebug}
+ obj.SendCommandEndResponse(1, 0x05, dev, 0x26, 0x01);
+ break;
+ }
+ var present = 0x00;
+ if ((dev == 0xA0) && (obj.floppy != null)) { present = 0x02; }
+ else if ((dev == 0xB0) && (obj.cdrom != null)) { present = 0x02; }
+ obj.SendDataToHost(dev, true, String.fromCharCode(0x00, present, 0x80, 0x00), featureRegister & 1); // This is the original version, 4 bytes long
+ break;
+ case 0x4c:
+ obj.SendCommand(0x51, IntToStrX(0) + IntToStrX(0) + IntToStrX(0) + String.fromCharCode(0x87, 0x50, 0x03, 0x00, 0x00, 0x00, 0xb0, 0x51, 0x05, 0x20, 0x00), true);
+ break;
+ case 0x51: // READ_DISC_INFO
+ // ###BEGIN###{IDERDebug}
+ debug("SCSI READ_DISC_INFO", dev);
+ // ###END###{IDERDebug}
+ obj.SendCommandEndResponse(0, 0x05, dev, 0x20, 0x00); // Correct
+ return -1;
+ case 0x55: // MODE_SELECT_10:
+ // ###BEGIN###{IDERDebug}
+ debug("SCSI ERROR: MODE_SELECT_10", dev);
+ // ###END###{IDERDebug}
+ obj.SendCommandEndResponse(1, 0x05, dev, 0x20, 0x00);
+ return -1;
+ case 0x5a: // MODE_SENSE_10
+ // ###BEGIN###{IDERDebug}
+ debug("SCSI: MODE_SENSE_10", dev, cdb.charCodeAt(2) & 0x3f);
+ // ###END###{IDERDebug}
+ var buflen = ReadShort(cdb, 7);
+ //var pc = cdb.charCodeAt(2) & 0xc0;
+ var r = null;
+
+ if (buflen == 0) { obj.SendDataToHost(dev, true, IntToStr(0x003c) + IntToStr(0x0008), featureRegister & 1); return -1; } // TODO: Fixed this return, it's not correct.
+
+ // 1.44 mb floppy or LS120 (sectorCount == 0x3c300)
+ var sectorCount = 0;
+ if (dev == 0xA0) {
+ if (obj.floppy != null) { sectorCount = (obj.floppy.size >> 9); }
+ } else {
+ if (obj.cdrom != null) { sectorCount = (obj.cdrom.size >> 11); }
+ }
+
+ switch (cdb.charCodeAt(2) & 0x3f) {
+ case 0x01: if (dev == 0xA0) { r = (sectorCount <= 0xb40)?IDE_ModeSence_FloppyError_Recovery_Array:IDE_ModeSence_Ls120Error_Recovery_Array; } else { r = IDE_ModeSence_CDError_Recovery_Array; } break;
+ case 0x05: if (dev == 0xA0) { r = (sectorCount <= 0xb40)?IDE_ModeSence_FloppyDisk_Page_Array:IDE_ModeSence_LS120Disk_Page_Array; } break;
+ case 0x3f: if (dev == 0xA0) { r = (sectorCount <= 0xb40)?IDE_ModeSence_3F_Floppy_Array:IDE_ModeSence_3F_LS120_Array; } else { r = IDE_ModeSence_3F_CD_Array; } break;
+ case 0x1A: if (dev == 0xB0) { r = IDE_ModeSence_CD_1A_Array; } break;
+ case 0x1D: if (dev == 0xB0) { r = IDE_ModeSence_CD_1D_Array; } break;
+ case 0x2A: if (dev == 0xB0) { r = IDE_ModeSence_CD_2A_Array; } break;
+ }
+
+ if (r == null) {
+ obj.SendCommandEndResponse(0, 0x05, dev, 0x20, 0x00); // TODO: Send proper error!!!
+ } else {
+ // Set disk to read only (we don't support write).
+ //ms_data[3] = ms_data[3] | 0x80;
+ obj.SendDataToHost(dev, true, r, featureRegister & 1);
+ }
+ break;
+ default: // UNKNOWN COMMAND
+ // ###BEGIN###{IDERDebug}
+ debug("IDER: Unknown SCSI command", cdb.charCodeAt(0));
+ // ###END###{IDERDebug}
+ obj.SendCommandEndResponse(0, 0x05, dev, 0x20, 0x00);
+ return -1;
+ }
+ return 0;
+ }
+
+ function sendDiskData(dev, lba, len, featureRegister) {
+ var media = null;
+ var mediaBlocks = 0;
+ if (dev == 0xA0) { media = obj.floppy; if (obj.floppy != null) { mediaBlocks = (obj.floppy.size >> 9); } }
+ if (dev == 0xB0) { media = obj.cdrom; if (obj.cdrom != null) { mediaBlocks = (obj.cdrom.size >> 11); } }
+ if ((len < 0) || (lba + len > mediaBlocks)) { obj.SendCommandEndResponse(1, 0x05, dev, 0x21, 0x00); return 0; }
+ if (len == 0) { obj.SendCommandEndResponse(1, 0x00, dev, 0x00, 0x00); return 0; }
+ if (media != null) {
+ // Send sector stats
+ // ###BEGIN###{IDERStats}
+ if (obj.sectorStats) { obj.sectorStats(1, (dev == 0xA0) ? 0 : 1, mediaBlocks, lba, len); }
+ // ###END###{IDERStats}
+ if (dev == 0xA0) { lba <<= 9; len <<= 9; } else { lba <<= 11; len <<= 11; }
+ if (g_media !== null) {
+ console.log('IDERERROR: Read while performing read');
+ obj.Stop();
+ } else {
+ // obj.iderinfo.readbfr // TODO: MaxRead
+ g_media = media;
+ g_dev = dev;
+ g_lba = lba;
+ g_len = len;
+ sendDiskDataEx(featureRegister);
+ }
+ }
+ }
+
+ var g_reset = false;
+ var g_media = null;
+ var g_dev;
+ var g_lba;
+ var g_len;
+ function sendDiskDataEx(featureRegister) {
+ var len = g_len, lba = g_lba;
+ if (g_len > obj.iderinfo.readbfr) { len = obj.iderinfo.readbfr; }
+ g_len -= len;
+ g_lba += len;
+ var fr = new FileReader();
+ fr.onload = function () {
+ obj.SendDataToHost(g_dev, (g_len == 0), this.result, featureRegister & 1);
+ if ((g_len > 0) && (g_reset == false)) {
+ sendDiskDataEx(featureRegister);
+ } else {
+ g_media = null;
+ if (g_reset) { obj.SendCommand(0x47); g_reset = false; } // Send ResetOccuredResponse
+ }
+ };
+ //console.log('Read from ' + lba + ' to ' + (lba + len) + ', total of ' + len);
+ fr.readAsBinaryString(g_media.slice(lba, lba + len));
+ }
+
+ return obj;
+}
diff --git a/amt-ider.js b/amt-ider.js
new file mode 100644
index 00000000..bd412388
--- /dev/null
+++ b/amt-ider.js
@@ -0,0 +1,115 @@
+/**
+* @description MeshCentral Server IDER handler
+* @author Ylian Saint-Hilaire & Bryan Roe
+* @copyright Intel Corporation 2018-2019
+* @license Apache-2.0
+* @version v0.0.1
+*/
+
+/*jslint node: true */
+/*jshint node: true */
+/*jshint strict:false */
+/*jshint -W097 */
+/*jshint esversion: 6 */
+"use strict";
+
+// Construct a MeshAgent object, called upon connection
+module.exports.CreateAmtIderSession = function (parent, db, ws, req, args, domain, user) {
+ const fs = require('fs');
+ const path = require('path');
+ const common = parent.common;
+ const amtMeshRedirModule = require('./amt-redir-mesh.js');
+ const amtMeshIderModule = require('./amt-ider-module.js');
+
+ console.log('New Server IDER session from ' + user.name);
+
+ var obj = {};
+ obj.user = user;
+ obj.domain = domain;
+ obj.ider = null;
+
+ // Send a message to the user
+ //obj.send = function (data) { try { if (typeof data == 'string') { ws.send(Buffer.from(data, 'binary')); } else { ws.send(data); } } catch (e) { } }
+
+ // Disconnect this user
+ obj.close = function (arg) {
+ if ((arg == 1) || (arg == null)) { try { ws.close(); parent.parent.debug(1, 'Soft disconnect'); } catch (e) { console.log(e); } } // Soft close, close the websocket
+ 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
+ };
+
+ try {
+
+ // Check if the user is logged in
+ if (user == null) { try { ws.close(); } catch (e) { } return; }
+
+ // When data is received from the web socket
+ ws.on('message', processWebSocketData);
+
+ // If error, do nothing
+ ws.on('error', function (err) { console.log(err); obj.close(0); });
+
+ // If the web socket is closed
+ ws.on('close', function (req) { obj.close(0); });
+
+ // We are all set, start receiving data
+ ws._socket.resume();
+
+ } catch (e) { console.log(e); }
+
+ // Process incoming web socket data from the browser
+ function processWebSocketData(msg) {
+ var command, i = 0, mesh = null, meshid = null, nodeid = null, meshlinks = null, change = 0;
+ try { command = JSON.parse(msg.toString('utf8')); } catch (e) { return; }
+ if (common.validateString(command.action, 3, 32) == false) return; // Action must be a string between 3 and 32 chars
+
+ switch (command.action) {
+ case 'ping': { try { ws.send(JSON.stringify({ action: 'pong' })); } catch (ex) { } break; }
+ case 'selector': {
+ var r = { action: 'selector', args: { html: 'Click ok to start IDER session.' }, buttons: 3 };
+ // TODO: Return a list of disk images for the user to select.
+ try { ws.send(JSON.stringify(r)); } catch (ex) { }
+ break;
+ }
+ case 'selectorResponse': {
+ console.log('selectorResponse', command.args, req.query);
+
+ // TODO: Start IDER Session
+ // req.query = { host: 'node//KV6AZh3KoEzr71IaM40KqpBXQCn0qysZrMYlCOcvivNkV2$zfP2MXBE4IizBn1Bw', port: '16994', tls: '0', serverauth: '1', tls1only: '1' }
+
+ command.args = {
+ floppyPath: '',
+ cdromPath: '',
+ iderStart: 1,
+ tlsv1only: true
+ };
+
+ obj.ider = amtMeshRedirModule.CreateAmtRedirect(amtMeshIderModule.CreateAmtRemoteIder(), domain, user, parent, parent.parent);
+ obj.ider.onStateChanged = onIderStateChange;
+ obj.ider.m.debug = true;
+ obj.ider.m.floppy = command.args.floppyPath;
+ obj.ider.m.cdrom = command.args.cdromPath;
+ obj.ider.m.iderStart = command.args.iderStart;
+ obj.ider.m.sectorStats = iderSectorStats;
+ obj.ider.tlsv1only = req.query.tlsv1only;
+ obj.ider.Start(req.query.host, req.query.port, req.query.tls);
+
+ break;
+ }
+ default: {
+ // Unknown user action
+ console.log('Unknown IDER action from user ' + user.name + ': ' + command.action + '.');
+ break;
+ }
+ }
+ }
+
+ function onIderStateChange(sender, state) {
+ console.log('onIderStateChange', state);
+ }
+
+ function iderSectorStats(mode, dev, total, start, len) {
+ console.log('iderSectorStats', mode, dev, total, start, len);
+ }
+
+ return obj;
+};
\ No newline at end of file
diff --git a/amt-redir-mesh.js b/amt-redir-mesh.js
new file mode 100644
index 00000000..70add123
--- /dev/null
+++ b/amt-redir-mesh.js
@@ -0,0 +1,567 @@
+/**
+* @description Intel AMT Redirection Transport Module - using Node
+* @author Ylian Saint-Hilaire
+* @version v0.0.1f
+*/
+
+// Construct a MeshServer object
+module.exports.CreateAmtRedirect = function (module, domain, user, webserver, meshcentral) {
+ var obj = {};
+ obj.m = module; // This is the inner module (Terminal or Desktop)
+ module.parent = obj;
+ obj.State = 0;
+ obj.net = require('net');
+ obj.tls = require('tls');
+ obj.crypto = require('crypto');
+ obj.constants = require('constants');
+ obj.socket = null;
+ obj.host = null;
+ obj.port = 0;
+ obj.amtuser = null;
+ obj.amtpass = null;
+ obj.connectstate = 0;
+ obj.protocol = module.protocol; // 1 = SOL, 2 = KVM, 3 = IDER
+ obj.xtlsoptions = null;
+ obj.redirTrace = true;
+
+ obj.amtaccumulator = "";
+ obj.amtsequence = 1;
+ obj.amtkeepalivetimer = null;
+ obj.authuri = "/RedirectionService";
+
+ obj.onStateChanged = null;
+ obj.forwardclient = null;
+
+ // Mesh Rights
+ const MESHRIGHT_EDITMESH = 1;
+ const MESHRIGHT_MANAGEUSERS = 2;
+ const MESHRIGHT_MANAGECOMPUTERS = 4;
+ const MESHRIGHT_REMOTECONTROL = 8;
+ const MESHRIGHT_AGENTCONSOLE = 16;
+ const MESHRIGHT_SERVERFILES = 32;
+ const MESHRIGHT_WAKEDEVICE = 64;
+ const MESHRIGHT_SETNOTES = 128;
+
+ // Site rights
+ const SITERIGHT_SERVERBACKUP = 1;
+ const SITERIGHT_MANAGEUSERS = 2;
+ const SITERIGHT_SERVERRESTORE = 4;
+ const SITERIGHT_FILEACCESS = 8;
+ const SITERIGHT_SERVERUPDATE = 16;
+ const SITERIGHT_LOCKED = 32;
+
+ function Debug(lvl) {
+ //if ((arguments.length < 2) && (lvl > meshcentral.debugLevel)) return;
+ var a = []; for (var i = 1; i < arguments.length; i++) { a.push(arguments[i]); } console.log(...a);
+ }
+
+ obj.Start = function (host, port, tls, tlsFingerprint, tlsoptions) {
+ console.log('Amt-Redir-Start', host, port, tls, tlsFingerprint, tlsoptions);
+
+ obj.host = host;
+ obj.port = port;
+ obj.xtls = tls;
+ obj.xtlsoptions = tlsoptions;
+ obj.xtlsFingerprint = tlsFingerprint;
+ obj.connectstate = 0;
+
+ Debug(1, 'AMT redir for ' + user.name + ' to ' + host + '.');
+
+ obj.xxStateChange(1);
+
+ // Fetch information about the target
+ meshcentral.db.Get(host, function (err, docs) {
+ if (docs.length == 0) { console.log('ERR: Node not found'); obj.xxStateChange(0); return; }
+ var node = docs[0];
+ if (!node.intelamt) { console.log('ERR: Not AMT node'); obj.xxStateChange(0); return; }
+
+ obj.amtuser = node.intelamt.user;
+ obj.amtpass = node.intelamt.pass;
+ console.log('amtuser', obj.amtuser, obj.amtpass);
+
+ // Check if this user has permission to manage this computer
+ var meshlinks = user.links[node.meshid];
+ if ((!meshlinks) || (!meshlinks.rights) || ((meshlinks.rights & MESHRIGHT_REMOTECONTROL) == 0)) { console.log('ERR: Access denied (2)'); obj.xxStateChange(0); return; }
+
+ // Check what connectivity is available for this node
+ var state = meshcentral.GetConnectivityState(host);
+ var conn = 0;
+ if (!state || state.connectivity == 0) { Debug(1, 'ERR: No routing possible (1)'); obj.xxStateChange(0); return; } else { conn = state.connectivity; }
+
+ /*
+ // Check what server needs to handle this connection
+ if ((meshcentral.multiServer != null) && (cookie == null)) { // If a cookie is provided, don't allow the connection to jump again to a different server
+ var server = obj.parent.GetRoutingServerId(req.query.host, 2); // Check for Intel CIRA connection
+ if (server != null) {
+ if (server.serverid != obj.parent.serverId) {
+ // Do local Intel CIRA routing using a different server
+ Debug(1, 'Route Intel AMT CIRA connection to peer server: ' + server.serverid);
+ obj.parent.multiServer.createPeerRelay(ws, req, server.serverid, user);
+ return;
+ }
+ } else {
+ server = obj.parent.GetRoutingServerId(req.query.host, 4); // Check for local Intel AMT connection
+ if ((server != null) && (server.serverid != obj.parent.serverId)) {
+ // Do local Intel AMT routing using a different server
+ Debug(1, 'Route Intel AMT direct connection to peer server: ' + server.serverid);
+ obj.parent.multiServer.createPeerRelay(ws, req, server.serverid, user);
+ return;
+ }
+ }
+ }
+ */
+
+ // If Intel AMT CIRA connection is available, use it
+ if (((conn & 2) != 0) && (meshcentral.mpsserver.ciraConnections[host] != null)) {
+ Debug(1, 'Opening Intel AMT CIRA transport connection to ' + host + '.');
+
+ var ciraconn = meshcentral.mpsserver.ciraConnections[host];
+
+ /*
+ // Compute target port, look at the CIRA port mappings, if non-TLS is allowed, use that, if not use TLS
+ var port = 16993;
+ //if (node.intelamt.tls == 0) port = 16992; // DEBUG: Allow TLS flag to set TLS mode within CIRA
+ if (ciraconn.tag.boundPorts.indexOf(16992) >= 0) port = 16992; // RELEASE: Always use non-TLS mode if available within CIRA
+ if (req.query.p == 2) port += 2;
+
+ // Setup a new CIRA channel
+ if ((port == 16993) || (port == 16995)) {
+ // Perform TLS - ( TODO: THIS IS BROKEN on Intel AMT v7 but works on v10, Not sure why. Well, could be broken TLS 1.0 in firmware )
+ var ser = new SerialTunnel();
+ var chnl = parent.mpsserver.SetupCiraChannel(ciraconn, port);
+
+ // let's chain up the TLSSocket <-> SerialTunnel <-> CIRA APF (chnl)
+ // Anything that needs to be forwarded by SerialTunnel will be encapsulated by chnl write
+ ser.forwardwrite = function (msg) {
+ // TLS ---> CIRA
+ chnl.write(msg.toString('binary'));
+ };
+
+ // When APF tunnel return something, update SerialTunnel buffer
+ chnl.onData = function (ciraconn, data) {
+ // CIRA ---> TLS
+ Debug(3, 'Relay TLS CIRA data', data.length);
+ if (data.length > 0) { try { ser.updateBuffer(Buffer.from(data, 'binary')); } catch (e) { } }
+ };
+
+ // Handle CIRA tunnel state change
+ chnl.onStateChange = function (ciraconn, state) {
+ Debug(2, 'Relay TLS CIRA state change', state);
+ if (state == 0) { try { ws.close(); } catch (e) { } }
+ };
+
+ // TLSSocket to encapsulate TLS communication, which then tunneled via SerialTunnel an then wrapped through CIRA APF
+ const TLSSocket = require('tls').TLSSocket;
+ const tlsoptions = { secureProtocol: ((req.query.tls1only == 1) ? 'TLSv1_method' : 'SSLv23_method'), ciphers: 'RSA+AES:!aNULL:!MD5:!DSS', secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_COMPRESSION | constants.SSL_OP_CIPHER_SERVER_PREFERENCE, rejectUnauthorized: false };
+ const tlsock = new TLSSocket(ser, tlsoptions);
+ tlsock.on('error', function (err) { Debug(1, "CIRA TLS Connection Error ", err); });
+ tlsock.on('secureConnect', function () { Debug(2, "CIRA Secure TLS Connection"); ws._socket.resume(); });
+
+ // Decrypted tunnel from TLS communcation to be forwarded to websocket
+ tlsock.on('data', function (data) {
+ // AMT/TLS ---> WS
+ try {
+ data = data.toString('binary');
+ if (ws.interceptor) { data = ws.interceptor.processAmtData(data); } // Run data thru interceptor
+ //ws.send(Buffer.from(data, 'binary'));
+ ws.send(data);
+ } catch (e) { }
+ });
+
+ // If TLS is on, forward it through TLSSocket
+ ws.forwardclient = tlsock;
+ ws.forwardclient.xtls = 1;
+ } else {
+ // Without TLS
+ ws.forwardclient = parent.mpsserver.SetupCiraChannel(ciraconn, port);
+ ws.forwardclient.xtls = 0;
+ ws._socket.resume();
+ }
+
+ // When data is received from the web socket, forward the data into the associated CIRA cahnnel.
+ // If the CIRA connection is pending, the CIRA channel has built-in buffering, so we are ok sending anyway.
+ ws.on('message', function (msg) {
+ // WS ---> AMT/TLS
+ msg = msg.toString('binary');
+ if (ws.interceptor) { msg = ws.interceptor.processBrowserData(msg); } // Run data thru interceptor
+ if (ws.forwardclient.xtls == 1) { ws.forwardclient.write(Buffer.from(msg, 'binary')); } else { ws.forwardclient.write(msg); }
+ });
+
+ // If error, close the associated TCP connection.
+ ws.on('error', function (err) {
+ console.log('CIRA server websocket error from ' + ws._socket.remoteAddress + ', ' + err.toString().split('\r')[0] + '.');
+ Debug(1, 'Websocket relay closed on error.');
+ if (ws.forwardclient && ws.forwardclient.close) { ws.forwardclient.close(); } // TODO: If TLS is used, we need to close the socket that is wrapped by TLS
+ });
+
+ // If the web socket is closed, close the associated TCP connection.
+ ws.on('close', function (req) {
+ Debug(1, 'Websocket relay closed.');
+ if (ws.forwardclient && ws.forwardclient.close) { ws.forwardclient.close(); } // TODO: If TLS is used, we need to close the socket that is wrapped by TLS
+ });
+
+ ws.forwardclient.onStateChange = function (ciraconn, state) {
+ Debug(2, 'Relay CIRA state change', state);
+ if (state == 0) { try { ws.close(); } catch (e) { } }
+ };
+
+ ws.forwardclient.onData = function (ciraconn, data) {
+ Debug(4, 'Relay CIRA data', data.length);
+ if (ws.interceptor) { data = ws.interceptor.processAmtData(data); } // Run data thru interceptor
+ if (data.length > 0) { try { ws.send(Buffer.from(data, 'binary')); } catch (e) { } } // TODO: Add TLS support
+ };
+
+ ws.forwardclient.onSendOk = function (ciraconn) {
+ // TODO: Flow control? (Dont' really need it with AMT, but would be nice)
+ //console.log('onSendOk');
+ };
+
+ // Fetch Intel AMT credentials & Setup interceptor
+ if (req.query.p == 1) {
+ Debug(3, 'INTERCEPTOR1', { host: node.host, port: port, user: node.intelamt.user, pass: node.intelamt.pass });
+ ws.interceptor = obj.interceptor.CreateHttpInterceptor({ host: node.host, port: port, user: node.intelamt.user, pass: node.intelamt.pass });
+ ws.interceptor.blockAmtStorage = true;
+ }
+ else if (req.query.p == 2) {
+ Debug(3, 'INTERCEPTOR2', { user: node.intelamt.user, pass: node.intelamt.pass });
+ ws.interceptor = obj.interceptor.CreateRedirInterceptor({ user: node.intelamt.user, pass: node.intelamt.pass });
+ ws.interceptor.blockAmtStorage = true;
+ }
+ */
+
+ return;
+ }
+
+ // If Intel AMT direct connection is possible, option a direct socket
+ if ((conn & 4) != 0) { // We got a new web socket connection, initiate a TCP connection to the target Intel AMT host/port.
+ Debug(1, 'Opening Intel AMT transport connection to ' + host + '.');
+
+ /*
+ // When data is received from the web socket, forward the data into the associated TCP connection.
+ ws.on('message', function (msg) {
+ if (obj.parent.debugLevel >= 1) { // DEBUG
+ Debug(1, 'TCP relay data to ' + node.host + ', ' + msg.length + ' bytes');
+ if (obj.parent.debugLevel >= 4) { Debug(4, ' ' + msg.toString('hex')); }
+ }
+ msg = msg.toString('binary');
+ if (ws.interceptor) { msg = ws.interceptor.processBrowserData(msg); } // Run data thru interceptor
+ ws.forwardclient.write(Buffer.from(msg, 'binary')); // Forward data to the associated TCP connection.
+ });
+
+ // If error, close the associated TCP connection.
+ ws.on('error', function (err) {
+ console.log('Error with relay web socket connection from ' + ws._socket.remoteAddress + ', ' + err.toString().split('\r')[0] + '.');
+ Debug(1, 'Error with relay web socket connection from ' + ws._socket.remoteAddress + '.');
+ if (ws.forwardclient) { try { ws.forwardclient.destroy(); } catch (e) { } }
+ });
+
+ // If the web socket is closed, close the associated TCP connection.
+ ws.on('close', function () {
+ Debug(1, 'Closing relay web socket connection to ' + req.query.host + '.');
+ if (ws.forwardclient) { try { ws.forwardclient.destroy(); } catch (e) { } }
+ });
+ */
+
+ if (tls != 1) {
+ // If this is TCP (without TLS) set a normal TCP socket
+ obj.forwardclient = new obj.net.Socket();
+ obj.forwardclient.setEncoding('binary');
+ //obj.forwardclient.xstate = 0;
+ //obj.forwardclient.forwardwsocket = ws;
+ } else {
+ // If TLS is going to be used, setup a TLS socket
+ var tlsoptions = { secureProtocol: ((req.query.tls1only == 1) ? 'TLSv1_method' : 'SSLv23_method'), ciphers: 'RSA+AES:!aNULL:!MD5:!DSS', secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_COMPRESSION | constants.SSL_OP_CIPHER_SERVER_PREFERENCE, rejectUnauthorized: false };
+ obj.forwardclient = obj.tls.connect(port, node.host, tlsoptions, function () {
+ // The TLS connection method is the same as TCP, but located a bit differently.
+ Debug(2, 'TLS Intel AMT transport connected to ' + node.host + ':' + port + '.');
+ //ws.forwardclient.xstate = 1;
+ //ws._socket.resume();
+ obj.xxOnSocketConnected();
+ });
+ obj.forwardclient.setEncoding('binary');
+ //obj.forwardclient.xstate = 0;
+ //obj.forwardclient.forwardwsocket = ws;
+ }
+
+ // When we receive data on the TCP connection, forward it back into the web socket connection.
+ obj.forwardclient.on('data', function (data) {
+ //if (obj.parent.debugLevel >= 1) { // DEBUG
+ Debug(1, 'Intel AMT transport data from ' + node.host + ', ' + data.length + ' bytes.');
+ //if (obj.parent.debugLevel >= 4) { Debug(4, ' ' + Buffer.from(data, 'binary').toString('hex')); }
+ //}
+ obj.xxOnSocketData(data);
+ });
+
+ // If the TCP connection closes, disconnect the associated web socket.
+ obj.forwardclient.on('close', function () {
+ Debug(1, 'Intel AMT transport relay disconnected from ' + node.host + '.');
+ obj.xxStateChange(0);
+ });
+
+ // If the TCP connection causes an error, disconnect the associated web socket.
+ obj.forwardclient.on('error', function (err) {
+ Debug(1, 'Intel AMT transport relay error from ' + node.host + ': ' + err.errno);
+ obj.xxStateChange(0);
+ });
+
+ if (node.intelamt.tls == 0) {
+ // A TCP connection to Intel AMT just connected, start forwarding.
+ obj.forwardclient.connect(port, node.host, function () {
+ Debug(1, 'Intel AMT transport connected to ' + node.host + ':' + port + '.');
+ //obj.forwardclient.xstate = 1;
+ //ws._socket.resume();
+ obj.xxOnSocketConnected();
+ });
+ }
+
+ return;
+ }
+
+ });
+ }
+
+ // Get the certificate of Intel AMT
+ obj.getPeerCertificate = function () { if (obj.xtls == true) { return obj.socket.getPeerCertificate(); } return null; }
+
+ obj.xxOnSocketConnected = function () {
+ console.log('xxOnSocketConnected');
+ if (!obj.xtlsoptions || !obj.xtlsoptions.meshServerConnect) {
+ if (obj.xtls == true) {
+ obj.xtlsCertificate = obj.socket.getPeerCertificate();
+ if ((obj.xtlsFingerprint != 0) && (obj.xtlsCertificate.fingerprint.split(':').join('').toLowerCase() != obj.xtlsFingerprint)) { obj.Stop(); return; }
+ }
+ }
+
+ if (obj.redirTrace) { console.log("REDIR-CONNECTED"); }
+ //obj.Debug("Socket Connected");
+ obj.xxStateChange(2);
+ if (obj.protocol == 1) obj.xxSend(obj.RedirectStartSol); // TODO: Put these strings in higher level module to tighten code
+ if (obj.protocol == 2) obj.xxSend(obj.RedirectStartKvm); // Don't need these is the feature if not compiled-in.
+ if (obj.protocol == 3) obj.xxSend(obj.RedirectStartIder);
+ }
+
+ obj.xxOnSocketData = function (data) {
+ if (!data || obj.connectstate == -1) return;
+ if (obj.redirTrace) { console.log("REDIR-RECV(" + data.length + "): " + webserver.common.rstr2hex(data)); }
+ //obj.Debug("Recv(" + data.length + "): " + webserver.common.rstr2hex(data));
+ if (obj.protocol == 2 && obj.connectstate == 1) { return obj.m.ProcessData(data); } // KVM traffic, forward it directly.
+ obj.amtaccumulator += data;
+ //obj.Debug("Recv(" + obj.amtaccumulator.length + "): " + webserver.common.rstr2hex(obj.amtaccumulator));
+ while (obj.amtaccumulator.length >= 1) {
+ var cmdsize = 0;
+ switch (obj.amtaccumulator.charCodeAt(0)) {
+ case 0x11: // StartRedirectionSessionReply (17)
+ if (obj.amtaccumulator.length < 4) return;
+ var statuscode = obj.amtaccumulator.charCodeAt(1);
+ switch (statuscode) {
+ case 0: // STATUS_SUCCESS
+ if (obj.amtaccumulator.length < 13) return;
+ var oemlen = obj.amtaccumulator.charCodeAt(12);
+ if (obj.amtaccumulator.length < 13 + oemlen) return;
+ obj.xxSend(String.fromCharCode(0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)); // Query authentication support
+ cmdsize = (13 + oemlen);
+ break;
+ default:
+ obj.Stop();
+ break;
+ }
+ break;
+ case 0x14: // AuthenticateSessionReply (20)
+ if (obj.amtaccumulator.length < 9) return;
+ var authDataLen = webserver.common.ReadIntX(obj.amtaccumulator, 5);
+ if (obj.amtaccumulator.length < 9 + authDataLen) return;
+ var status = obj.amtaccumulator.charCodeAt(1);
+ var authType = obj.amtaccumulator.charCodeAt(4);
+ var authData = [];
+ for (i = 0; i < authDataLen; i++) { authData.push(obj.amtaccumulator.charCodeAt(9 + i)); }
+ var authDataBuf = obj.amtaccumulator.substring(9, 9 + authDataLen);
+ cmdsize = 9 + authDataLen;
+ if (authType == 0) {
+ // ###BEGIN###{Mode-NodeWebkit}
+ if (obj.amtuser == '*') {
+ if (authData.indexOf(2) >= 0) {
+ // Kerberos Auth
+ var ticket;
+ if (kerberos && kerberos != null) {
+ var ticketReturn = kerberos.getTicket('HTTP' + ((obj.tls == 1)?'S':'') + '/' + ((obj.amtpass == '') ? (obj.host + ':' + obj.port) : obj.amtpass));
+ if (ticketReturn.returnCode == 0 || ticketReturn.returnCode == 0x90312) {
+ ticket = ticketReturn.ticket;
+ if (process.platform.indexOf('win') >= 0) {
+ // Clear kerberos tickets on both 32 and 64bit Windows platforms
+ try { require('child_process').exec('%windir%\\system32\\klist purge', function (error, stdout, stderr) { if (error) { require('child_process').exec('%windir%\\sysnative\\klist purge', function (error, stdout, stderr) { if (error) { console.error('Unable to purge kerberos tickets'); } }); } }); } catch (e) { console.log(e); }
+ }
+ } else {
+ console.error('Unexpected Kerberos error code: ' + ticketReturn.returnCode);
+ }
+ }
+ if (ticket) {
+ obj.xxSend(String.fromCharCode(0x13, 0x00, 0x00, 0x00, 0x02) + webserver.common.IntToStrX(ticket.length) + ticket);
+ } else {
+ obj.Stop();
+ }
+ }
+ else obj.Stop();
+ } else {
+ // ###END###{Mode-NodeWebkit}
+ // Query
+ if (authData.indexOf(4) >= 0) {
+ // Good Digest Auth (With cnonce and all)
+ obj.xxSend(String.fromCharCode(0x13, 0x00, 0x00, 0x00, 0x04) + webserver.common.IntToStrX(obj.amtuser.length + obj.authuri.length + 8) + String.fromCharCode(obj.amtuser.length) + obj.amtuser + String.fromCharCode(0x00, 0x00) + String.fromCharCode(obj.authuri.length) + obj.authuri + String.fromCharCode(0x00, 0x00, 0x00, 0x00));
+ }
+ else if (authData.indexOf(3) >= 0) {
+ // Bad Digest Auth (Not sure why this is supported, cnonce is not used!)
+ obj.xxSend(String.fromCharCode(0x13, 0x00, 0x00, 0x00, 0x03) + webserver.common.IntToStrX(obj.amtuser.length + obj.authuri.length + 7) + String.fromCharCode(obj.amtuser.length) + obj.amtuser + String.fromCharCode(0x00, 0x00) + String.fromCharCode(obj.authuri.length) + obj.authuri + String.fromCharCode(0x00, 0x00, 0x00));
+ }
+ else if (authData.indexOf(1) >= 0) {
+ // Basic Auth (Probably a good idea to not support this unless this is an old version of Intel AMT)
+ obj.xxSend(String.fromCharCode(0x13, 0x00, 0x00, 0x00, 0x01) + webserver.common.IntToStrX(obj.amtuser.length + obj.amtpass.length + 2) + String.fromCharCode(obj.amtuser.length) + obj.amtuser + String.fromCharCode(obj.amtpass.length) + obj.amtpass);
+ }
+ else obj.Stop();
+ // ###BEGIN###{Mode-NodeWebkit}
+ }
+ // ###END###{Mode-NodeWebkit}
+ }
+ else if ((authType == 3 || authType == 4) && status == 1) {
+ var curptr = 0;
+
+ // Realm
+ var realmlen = authDataBuf.charCodeAt(curptr);
+ var realm = authDataBuf.substring(curptr + 1, curptr + 1 + realmlen);
+ curptr += (realmlen + 1);
+
+ // Nonce
+ var noncelen = authDataBuf.charCodeAt(curptr);
+ var nonce = authDataBuf.substring(curptr + 1, curptr + 1 + noncelen);
+ curptr += (noncelen + 1);
+
+ // QOP
+ var qoplen = 0;
+ var qop = null;
+ var cnonce = obj.xxRandomValueHex(32);
+ var snc = '00000002';
+ var extra = '';
+ if (authType == 4) {
+ qoplen = authDataBuf.charCodeAt(curptr);
+ qop = authDataBuf.substring(curptr + 1, curptr + 1 + qoplen);
+ curptr += (qoplen + 1);
+ extra = snc + ":" + cnonce + ":" + qop + ":";
+ }
+ var digest = hex_md5(hex_md5(obj.amtuser + ":" + realm + ":" + obj.amtpass) + ":" + nonce + ":" + extra + hex_md5("POST:" + obj.authuri));
+
+ var totallen = obj.amtuser.length + realm.length + nonce.length + obj.authuri.length + cnonce.length + snc.length + digest.length + 7;
+ if (authType == 4) totallen += (qop.length + 1);
+ var buf = String.fromCharCode(0x13, 0x00, 0x00, 0x00, authType) + webserver.common.IntToStrX(totallen) + String.fromCharCode(obj.amtuser.length) + obj.amtuser + String.fromCharCode(realm.length) + realm + String.fromCharCode(nonce.length) + nonce + String.fromCharCode(obj.authuri.length) + obj.authuri + String.fromCharCode(cnonce.length) + cnonce + String.fromCharCode(snc.length) + snc + String.fromCharCode(digest.length) + digest;
+ if (authType == 4) buf += (String.fromCharCode(qop.length) + qop);
+ obj.xxSend(buf);
+ }
+ else if (status == 0) { // Success
+ if (obj.protocol == 1) {
+ // Serial-over-LAN: Send Intel AMT serial settings...
+ var MaxTxBuffer = 10000;
+ var TxTimeout = 100;
+ var TxOverflowTimeout = 0;
+ var RxTimeout = 10000;
+ var RxFlushTimeout = 100;
+ var Heartbeat = 0;//5000;
+ obj.xxSend(String.fromCharCode(0x20, 0x00, 0x00, 0x00) + ToIntStr(obj.amtsequence++) + ToShortStr(MaxTxBuffer) + ToShortStr(TxTimeout) + ToShortStr(TxOverflowTimeout) + ToShortStr(RxTimeout) + ToShortStr(RxFlushTimeout) + ToShortStr(Heartbeat) + ToIntStr(0));
+ }
+ if (obj.protocol == 2) {
+ // Remote Desktop: Send traffic directly...
+ obj.xxSend(String.fromCharCode(0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
+ }
+ } else obj.Stop();
+ break;
+ case 0x21: // Response to settings (33)
+ if (obj.amtaccumulator.length < 23) break;
+ cmdsize = 23;
+ obj.xxSend(String.fromCharCode(0x27, 0x00, 0x00, 0x00) + ToIntStr(obj.amtsequence++) + String.fromCharCode(0x00, 0x00, 0x1B, 0x00, 0x00, 0x00));
+ if (obj.protocol == 1) { obj.amtkeepalivetimer = setInterval(obj.xxSendAmtKeepAlive, 2000); }
+ obj.connectstate = 1;
+ obj.xxStateChange(3);
+ break;
+ case 0x29: // Serial Settings (41)
+ if (obj.amtaccumulator.length < 10) break;
+ cmdsize = 10;
+ break;
+ case 0x2A: // Incoming display data (42)
+ if (obj.amtaccumulator.length < 10) break;
+ var cs = (10 + ((obj.amtaccumulator.charCodeAt(9) & 0xFF) << 8) + (obj.amtaccumulator.charCodeAt(8) & 0xFF));
+ if (obj.amtaccumulator.length < cs) break;
+ obj.m.ProcessData(obj.amtaccumulator.substring(10, cs));
+ cmdsize = cs;
+ break;
+ case 0x2B: // Keep alive message (43)
+ if (obj.amtaccumulator.length < 8) break;
+ cmdsize = 8;
+ break;
+ case 0x41:
+ if (obj.amtaccumulator.length < 8) break;
+ obj.connectstate = 1;
+ obj.m.Start();
+ // KVM traffic, forward rest of accumulator directly.
+ if (obj.amtaccumulator.length > 8) { obj.m.ProcessData(obj.amtaccumulator.substring(8)); }
+ cmdsize = obj.amtaccumulator.length;
+ break;
+ default:
+ console.log("Unknown Intel AMT command: " + obj.amtaccumulator.charCodeAt(0) + " acclen=" + obj.amtaccumulator.length);
+ obj.Stop();
+ return;
+ }
+ if (cmdsize == 0) return;
+ obj.amtaccumulator = obj.amtaccumulator.substring(cmdsize);
+ }
+ }
+
+ obj.xxSend = function (x) {
+ console.log("REDIR-SEND(" + x.length + ")");
+ if (obj.redirTrace) { console.log("REDIR-SEND(" + x.length + "): " + webserver.common.rstr2hex(x)); }
+ //obj.Debug("Send(" + x.length + "): " + webserver.common.rstr2hex(x));
+ obj.forwardclient.write(new Buffer(x, "binary"));
+ }
+
+ obj.Send = function (x) {
+ if (obj.forwardclient == null || obj.connectstate != 1) return;
+ if (obj.protocol == 1) { obj.xxSend(String.fromCharCode(0x28, 0x00, 0x00, 0x00) + ToIntStr(obj.amtsequence++) + ToShortStr(x.length) + x); } else { obj.xxSend(x); }
+ }
+
+ obj.xxSendAmtKeepAlive = function () {
+ if (obj.forwardclient == null) return;
+ obj.xxSend(String.fromCharCode(0x2B, 0x00, 0x00, 0x00) + ToIntStr(obj.amtsequence++));
+ }
+
+ obj.xxRandomValueHex = function(len) { return obj.crypto.randomBytes(Math.ceil(len / 2)).toString('hex').slice(0, len); }
+
+ obj.xxOnSocketClosed = function () {
+ if (obj.redirTrace) { console.log("REDIR-CLOSED"); }
+ //obj.Debug("Socket Closed");
+ obj.Stop();
+ }
+
+ obj.xxStateChange = function(newstate) {
+ if (obj.State == newstate) return;
+ obj.State = newstate;
+ obj.m.xxStateChange(obj.State);
+ if (obj.onStateChanged != null) obj.onStateChanged(obj, obj.State);
+ }
+
+ obj.Stop = function () {
+ if (obj.redirTrace) { console.log("REDIR-CLOSED"); }
+ //obj.Debug("Socket Stopped");
+ obj.xxStateChange(0);
+ obj.connectstate = -1;
+ obj.amtaccumulator = "";
+ if (obj.forwardclient != null) { obj.forwardclient.destroy(); obj.forwardclient = null; }
+ if (obj.amtkeepalivetimer != null) { clearInterval(obj.amtkeepalivetimer); obj.amtkeepalivetimer = null; }
+ }
+
+ obj.RedirectStartSol = String.fromCharCode(0x10, 0x00, 0x00, 0x00, 0x53, 0x4F, 0x4C, 0x20);
+ obj.RedirectStartKvm = String.fromCharCode(0x10, 0x01, 0x00, 0x00, 0x4b, 0x56, 0x4d, 0x52);
+ obj.RedirectStartIder = String.fromCharCode(0x10, 0x00, 0x00, 0x00, 0x49, 0x44, 0x45, 0x52);
+
+ function hex_md5(str) { return meshcentral.certificateOperations.forge.md.md5.create().update(str).digest().toHex(); }
+
+ return obj;
+}
+
+function ToIntStr(v) { return String.fromCharCode((v & 0xFF), ((v >> 8) & 0xFF), ((v >> 16) & 0xFF), ((v >> 24) & 0xFF)); }
+function ToShortStr(v) { return String.fromCharCode((v & 0xFF), ((v >> 8) & 0xFF)); }
diff --git a/public/commander.htm b/public/commander.htm
index 8181f2aa..64a75dfb 100644
--- a/public/commander.htm
+++ b/public/commander.htm
@@ -1,4 +1,4 @@
-
Disconnected
Loading...
System Status
Hardware Information
Event Log
Network Settings
User Accounts
Serial-over-LAN Terminal
Intel® AMT Redirection port or Serial-over-LAN feature is disabled, click here to enable it.
Remote computer is not powered on, click here to issue a power command.
Remote Desktop
Intel® AMT Redirection port or KVM feature is disabled, click here to enable it.
Remote computer is not powered on, click here to issue a power command.
Audit Log
Security Settings
Internet Settings
System Defense
Agent Presence
Script Editor
Script
Compiled Script
Variables
Console
Storage
Event Subscriptions
Wake Alarms
Permission
Granted Permissions
*Minimum 8 characters with upper, lowercase, 0-9, and one of !@#$%^&*()+-
Warning:Some power actions may result in data loss and may disconnect the desktop, terminal or disk redirection sessions.
Consent Display
Image Encoding
Software KVM
Quality
Scaling
Not Required
Required for KVM only
Always Required
Authentication
Encryption
This will save the entire state of Intel® AMT for this machine into file. Passwords will not be saved, but some sensitive data may be included.
Disabled
ICMP response
RMCP response
ICMP & RMCP response
Dynamic DNS client
Defaut Interval is 1440 minutes, Default TTL is 900 seconds.
Remote Command
Boot Source
Boot Media Index
IDER Boot Device
Verbocity
After wake