| 1 | /** |
| 2 | * @description Intel AMT Redirection Transport Module - using websocket relay |
| 3 | * @author Ylian Saint-Hilaire |
| 4 | * @version v2.0.0 |
| 5 | */ |
| 6 | |
| 7 | // Construct a MeshServer object |
| 8 | var CreateAmtRedirect = function (module, authCookie) { |
| 9 | var obj = {}; |
| 10 | obj.m = module; // This is the inner module (Terminal or Desktop) |
| 11 | module.parent = obj; |
| 12 | obj.authCookie = authCookie; |
| 13 | obj.State = 0; |
| 14 | obj.socket = null; |
| 15 | // ###BEGIN###{!Mode-Firmware} |
| 16 | obj.host = null; |
| 17 | obj.port = 0; |
| 18 | obj.user = null; |
| 19 | obj.pass = null; |
| 20 | obj.authuri = '/RedirectionService'; |
| 21 | obj.tlsv1only = 0; |
| 22 | obj.inDataCount = 0; |
| 23 | // ###END###{!Mode-Firmware} |
| 24 | obj.connectstate = 0; |
| 25 | obj.protocol = module.protocol; // 1 = SOL, 2 = KVM, 3 = IDER |
| 26 | obj.acc = null; |
| 27 | obj.amtsequence = 1; |
| 28 | obj.amtkeepalivetimer = null; |
| 29 | obj.onStateChanged = null; |
| 30 | |
| 31 | function arrToStr(arr) { return String.fromCharCode.apply(null, arr); } |
| 32 | function randomHex(length) { var r = ''; for (var i = 0; i < length; i++) { r += 'abcdef0123456789'.charAt(Math.floor(Math.random() * 16)); } return r; } |
| 33 | |
| 34 | obj.Start = function (host, port, user, pass, tls) { |
| 35 | obj.host = host; |
| 36 | obj.port = port; |
| 37 | obj.user = user; |
| 38 | obj.pass = pass; |
| 39 | obj.tls = tls; |
| 40 | obj.connectstate = 0; |
| 41 | obj.inDataCount = 0; |
| 42 | var url = window.location.protocol.replace('http', 'ws') + '//' + window.location.host + window.location.pathname.substring(0, window.location.pathname.lastIndexOf('/')) + '/webrelay.ashx?p=2&host=' + host + '&port=' + port + '&tls=' + tls + ((user == '*') ? '&serverauth=1' : '') + ((typeof pass === 'undefined') ? ('&serverauth=1&user=' + user) : ''); // The 'p=2' indicates to the relay that this is a REDIRECTION session |
| 43 | if ((authCookie != null) && (authCookie != '')) { url += '&auth=' + authCookie; } |
| 44 | obj.socket = new WebSocket(url); |
| 45 | obj.socket.binaryType = 'arraybuffer'; |
| 46 | obj.socket.onopen = obj.xxOnSocketConnected; |
| 47 | obj.socket.onmessage = obj.xxOnMessage; |
| 48 | obj.socket.onclose = obj.xxOnSocketClosed; |
| 49 | obj.xxStateChange(1); |
| 50 | } |
| 51 | |
| 52 | obj.xxOnSocketConnected = function () { |
| 53 | obj.xxStateChange(2); |
| 54 | if (obj.protocol == 1) obj.directSend(new Uint8Array([0x10, 0x00, 0x00, 0x00, 0x53, 0x4F, 0x4C, 0x20])); // SOL |
| 55 | if (obj.protocol == 2) obj.directSend(new Uint8Array([0x10, 0x01, 0x00, 0x00, 0x4b, 0x56, 0x4d, 0x52])); // KVM |
| 56 | if (obj.protocol == 3) obj.directSend(new Uint8Array([0x10, 0x00, 0x00, 0x00, 0x49, 0x44, 0x45, 0x52])); // IDER |
| 57 | } |
| 58 | |
| 59 | obj.xxOnMessage = function (e) { |
| 60 | if (!e.data || obj.connectstate == -1) return; |
| 61 | obj.inDataCount++; |
| 62 | |
| 63 | // KVM traffic, forward it directly. |
| 64 | if ((obj.connectstate == 1) && ((obj.protocol == 2) || (obj.protocol == 3))) { |
| 65 | return obj.m.ProcessBinaryData ? obj.m.ProcessBinaryData(e.data) : obj.m.ProcessData(arrToStr(e.data)); |
| 66 | } |
| 67 | |
| 68 | // Append to accumulator |
| 69 | if (obj.acc == null) { |
| 70 | obj.acc = e.data; |
| 71 | } else { |
| 72 | var tmp = new Uint8Array(obj.acc.byteLength + e.data.byteLength); |
| 73 | tmp.set(new Uint8Array(obj.acc), 0); |
| 74 | tmp.set(new Uint8Array(e.data), obj.acc.byteLength); |
| 75 | obj.acc = tmp.buffer; |
| 76 | } |
| 77 | |
| 78 | //console.log('Redir Recv', obj.acc); |
| 79 | while ((obj.acc != null) && (obj.acc.byteLength >= 1)) { |
| 80 | var cmdsize = 0, accArray = new Uint8Array(obj.acc); |
| 81 | switch (accArray[0]) { |
| 82 | case 0x11: // StartRedirectionSessionReply (17) |
| 83 | if (accArray.byteLength < 4) return; |
| 84 | var statuscode = accArray[1]; |
| 85 | switch (statuscode) { |
| 86 | case 0: // STATUS_SUCCESS |
| 87 | if (accArray.byteLength < 13) return; |
| 88 | var oemlen = accArray[12]; |
| 89 | if (accArray.byteLength < 13 + oemlen) return; |
| 90 | obj.directSend(new Uint8Array([0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])); // Query for available authentication |
| 91 | cmdsize = (13 + oemlen); |
| 92 | break; |
| 93 | default: |
| 94 | obj.Stop(1); |
| 95 | break; |
| 96 | } |
| 97 | break; |
| 98 | case 0x14: // AuthenticateSessionReply (20) |
| 99 | if (accArray.byteLength < 9) return; |
| 100 | var authDataLen = new DataView(obj.acc).getUint32(5, true); |
| 101 | if (accArray.byteLength < 9 + authDataLen) return; |
| 102 | var status = accArray[1], authType = accArray[4], authData = []; |
| 103 | for (i = 0; i < authDataLen; i++) { authData.push(accArray[9 + i]); } |
| 104 | var authDataBuf = new Uint8Array(obj.acc.slice(9, 9 + authDataLen)); |
| 105 | cmdsize = 9 + authDataLen; |
| 106 | if (authType == 0) { |
| 107 | // Query |
| 108 | if (authData.indexOf(4) >= 0) { |
| 109 | // Good Digest Auth (With cnonce and all) |
| 110 | obj.xxSend(String.fromCharCode(0x13, 0x00, 0x00, 0x00, 0x04) + IntToStrX(obj.user.length + obj.authuri.length + 8) + String.fromCharCode(obj.user.length) + obj.user + String.fromCharCode(0x00, 0x00) + String.fromCharCode(obj.authuri.length) + obj.authuri + String.fromCharCode(0x00, 0x00, 0x00, 0x00)); |
| 111 | } |
| 112 | /* |
| 113 | else if (authData.indexOf(3) >= 0) { |
| 114 | // Bad Digest Auth (Not sure why this is supported, cnonce is not used!) |
| 115 | obj.xxSend(String.fromCharCode(0x13, 0x00, 0x00, 0x00, 0x03) + IntToStrX(obj.user.length + obj.authuri.length + 7) + String.fromCharCode(obj.user.length) + obj.user + String.fromCharCode(0x00, 0x00) + String.fromCharCode(obj.authuri.length) + obj.authuri + String.fromCharCode(0x00, 0x00, 0x00)); |
| 116 | } |
| 117 | else if (authData.indexOf(1) >= 0) { |
| 118 | // Basic Auth (Probably a good idea to not support this unless this is an old version of Intel AMT) |
| 119 | obj.xxSend(String.fromCharCode(0x13, 0x00, 0x00, 0x00, 0x01) + IntToStrX(obj.user.length + obj.pass.length + 2) + String.fromCharCode(obj.user.length) + obj.user + String.fromCharCode(obj.pass.length) + obj.pass); |
| 120 | } |
| 121 | */ |
| 122 | else obj.Stop(2); |
| 123 | } else if (((authType == 3) || (authType == 4)) && (status == 1)) { |
| 124 | var curptr = 0; |
| 125 | |
| 126 | // Realm |
| 127 | var realmlen = authDataBuf[curptr]; |
| 128 | var realm = arrToStr(new Uint8Array(authDataBuf.buffer.slice(curptr + 1, curptr + 1 + realmlen))); |
| 129 | curptr += (realmlen + 1); |
| 130 | |
| 131 | // Nonce |
| 132 | var noncelen = authDataBuf[curptr]; |
| 133 | var nonce = arrToStr(new Uint8Array(authDataBuf.buffer.slice(curptr + 1, curptr + 1 + noncelen))); |
| 134 | curptr += (noncelen + 1); |
| 135 | |
| 136 | // QOP |
| 137 | var qoplen = 0; |
| 138 | var qop = null; |
| 139 | var cnonce = randomHex(32); |
| 140 | var snc = '00000002'; |
| 141 | var extra = ''; |
| 142 | if (authType == 4) { |
| 143 | qoplen = authDataBuf[curptr]; |
| 144 | qop = arrToStr(new Uint8Array(authDataBuf.buffer.slice(curptr + 1, curptr + 1 + qoplen))); |
| 145 | curptr += (qoplen + 1); |
| 146 | extra = snc + ':' + cnonce + ':' + qop + ':'; |
| 147 | } |
| 148 | |
| 149 | var digest = hex_md5(hex_md5(obj.user + ':' + realm + ':' + obj.pass) + ':' + nonce + ':' + extra + hex_md5('POST:' + obj.authuri)); |
| 150 | var totallen = obj.user.length + realm.length + nonce.length + obj.authuri.length + cnonce.length + snc.length + digest.length + 7; |
| 151 | if (authType == 4) totallen += (qop.length + 1); |
| 152 | var buf = String.fromCharCode(0x13, 0x00, 0x00, 0x00, authType) + IntToStrX(totallen) + String.fromCharCode(obj.user.length) + obj.user + 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; |
| 153 | if (authType == 4) buf += (String.fromCharCode(qop.length) + qop); |
| 154 | obj.xxSend(buf); |
| 155 | } else if (status == 0) { // Success |
| 156 | switch (obj.protocol) { |
| 157 | case 1: { |
| 158 | // Serial-over-LAN: Send Intel AMT serial settings... |
| 159 | var MaxTxBuffer = 10000; |
| 160 | var TxTimeout = 100; |
| 161 | var TxOverflowTimeout = 0; |
| 162 | var RxTimeout = 10000; |
| 163 | var RxFlushTimeout = 100; |
| 164 | var Heartbeat = 0;//5000; |
| 165 | obj.xxSend(String.fromCharCode(0x20, 0x00, 0x00, 0x00) + IntToStrX(obj.amtsequence++) + ShortToStrX(MaxTxBuffer) + ShortToStrX(TxTimeout) + ShortToStrX(TxOverflowTimeout) + ShortToStrX(RxTimeout) + ShortToStrX(RxFlushTimeout) + ShortToStrX(Heartbeat) + IntToStrX(0)); |
| 166 | break; |
| 167 | } |
| 168 | case 2: { |
| 169 | // Remote Desktop: Send traffic directly... |
| 170 | obj.directSend(new Uint8Array([0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])); |
| 171 | break; |
| 172 | } |
| 173 | case 3: { |
| 174 | // Remote IDER: Send traffic directly... |
| 175 | obj.connectstate = 1; |
| 176 | obj.xxStateChange(3); |
| 177 | break; |
| 178 | } |
| 179 | } |
| 180 | } else obj.Stop(3); |
| 181 | break; |
| 182 | case 0x21: // Response to settings (33) |
| 183 | if (accArray.byteLength < 23) break; |
| 184 | cmdsize = 23; |
| 185 | obj.xxSend(String.fromCharCode(0x27, 0x00, 0x00, 0x00) + IntToStrX(obj.amtsequence++) + String.fromCharCode(0x00, 0x00, 0x1B, 0x00, 0x00, 0x00)); |
| 186 | if (obj.protocol == 1) { obj.amtkeepalivetimer = setInterval(obj.xxSendAmtKeepAlive, 2000); } |
| 187 | obj.connectstate = 1; |
| 188 | obj.xxStateChange(3); |
| 189 | break; |
| 190 | case 0x29: // Serial Settings (41) |
| 191 | if (accArray.byteLength < 10) break; |
| 192 | cmdsize = 10; |
| 193 | break; |
| 194 | case 0x2A: // Incoming display data (42) |
| 195 | if (accArray.byteLength < 10) break; |
| 196 | var cs = (10 + (accArray[9] << 8) + accArray[8]); |
| 197 | if (accArray.byteLength < cs) break; |
| 198 | if (obj.m.ProcessBinaryData) { obj.m.ProcessBinaryData(new Uint8Array(accArray.buffer.slice(10, cs))); } else { obj.m.ProcessData(arrToStr(new Uint8Array(accArray.buffer.slice(10, cs)))); } |
| 199 | cmdsize = cs; |
| 200 | break; |
| 201 | case 0x2B: // Keep alive message (43) |
| 202 | if (accArray.byteLength < 8) break; |
| 203 | cmdsize = 8; |
| 204 | break; |
| 205 | case 0x41: |
| 206 | if (accArray.byteLength < 8) break; |
| 207 | obj.connectstate = 1; |
| 208 | obj.m.Start(); |
| 209 | // KVM traffic, forward rest of accumulator directly. |
| 210 | if (accArray.byteLength > 8) { |
| 211 | if (obj.m.ProcessBinaryData) { obj.m.ProcessBinaryData(new Uint8Array(accArray.buffer.slice(8))); } else { obj.m.ProcessData(arrToStr(new Uint8Array(accArray.buffer.slice(8)))); } |
| 212 | } |
| 213 | cmdsize = accArray.byteLength; |
| 214 | break; |
| 215 | case 0xF0: |
| 216 | // console.log('Session is being recorded'); |
| 217 | obj.serverIsRecording = true; |
| 218 | cmdsize = 1; |
| 219 | break; |
| 220 | default: |
| 221 | console.log('Unknown Intel AMT command: ' + accArray[0] + ' acclen=' + accArray.byteLength); |
| 222 | obj.Stop(4); |
| 223 | return; |
| 224 | } |
| 225 | if (cmdsize == 0) return; |
| 226 | if (cmdsize != obj.acc.byteLength) { obj.acc = obj.acc.slice(cmdsize); } else { obj.acc = null; } |
| 227 | } |
| 228 | } |
| 229 | |
| 230 | obj.directSend = function (arr) { try { obj.socket.send(arr.buffer); } catch (ex) { } } |
| 231 | |
| 232 | obj.xxSend = function (x) { |
| 233 | if ((obj.socket != null) && (obj.socket.readyState == WebSocket.OPEN)) { |
| 234 | var b = new Uint8Array(x.length); |
| 235 | for (var i = 0; i < x.length; ++i) { b[i] = x.charCodeAt(i); } |
| 236 | try { obj.socket.send(b.buffer); } catch (ex) { } |
| 237 | } |
| 238 | } |
| 239 | |
| 240 | obj.Send = obj.send = function (x) { |
| 241 | if (obj.socket == null || obj.connectstate != 1) return; |
| 242 | if (obj.protocol == 1) { obj.xxSend(String.fromCharCode(0x28, 0x00, 0x00, 0x00) + IntToStrX(obj.amtsequence++) + ShortToStrX(x.length) + x); } else { obj.xxSend(x); } |
| 243 | } |
| 244 | |
| 245 | obj.xxSendAmtKeepAlive = function () { if (obj.socket != null) { obj.xxSend(String.fromCharCode(0x2B, 0x00, 0x00, 0x00) + IntToStrX(obj.amtsequence++)); } } |
| 246 | |
| 247 | obj.xxOnSocketClosed = function () { |
| 248 | if ((obj.inDataCount == 0) && (obj.tlsv1only == 0)) { |
| 249 | obj.tlsv1only = 1; |
| 250 | obj.socket = new WebSocket(window.location.protocol.replace('http', 'ws') + '//' + window.location.host + window.location.pathname.substring(0, window.location.pathname.lastIndexOf('/')) + '/webrelay.ashx?p=2&host=' + obj.host + '&port=' + obj.port + '&tls=' + obj.tls + '&tls1only=1' + ((obj.user == '*') ? '&serverauth=1' : '') + ((typeof pass === 'undefined') ? ('&serverauth=1&user=' + obj.user) : '')); // The 'p=2' indicates to the relay that this is a REDIRECTION session |
| 251 | obj.socket.binaryType = 'arraybuffer'; |
| 252 | obj.socket.onopen = obj.xxOnSocketConnected; |
| 253 | obj.socket.onmessage = obj.xxOnMessage; |
| 254 | obj.socket.onclose = obj.xxOnSocketClosed; |
| 255 | } else { |
| 256 | obj.Stop(5); |
| 257 | } |
| 258 | } |
| 259 | |
| 260 | obj.xxStateChange = function (newstate) { |
| 261 | if (obj.State == newstate) return; |
| 262 | obj.State = newstate; |
| 263 | obj.m.xxStateChange(obj.State); |
| 264 | if (obj.onStateChanged != null) obj.onStateChanged(obj, obj.State); |
| 265 | } |
| 266 | |
| 267 | obj.Stop = function (x) { |
| 268 | obj.xxStateChange(0); |
| 269 | obj.connectstate = -1; |
| 270 | obj.acc = null; |
| 271 | if (obj.socket != null) { obj.socket.close(); obj.socket = null; } |
| 272 | if (obj.amtkeepalivetimer != null) { clearInterval(obj.amtkeepalivetimer); obj.amtkeepalivetimer = null; } |
| 273 | } |
| 274 | |
| 275 | return obj; |
| 276 | } |