| 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 Intel AMT WSMAN communication module for NodeJS |
| 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 WSMAN stack communication object |
| 29 | var CreateWsmanComm = function (host, port, user, pass, tls, tlsoptions, mpsConnection) { |
| 30 | //console.log('CreateWsmanComm', host, port, user, pass, tls, tlsoptions); |
| 31 | |
| 32 | var obj = {}; |
| 33 | obj.PendingAjax = []; // List of pending AJAX calls. When one frees up, another will start. |
| 34 | obj.ActiveAjaxCount = 0; // Number of currently active AJAX calls |
| 35 | obj.MaxActiveAjaxCount = 1; // Maximum number of activate AJAX calls at the same time. |
| 36 | obj.FailAllError = 0; // Set this to non-zero to fail all AJAX calls with that error status, 999 causes responses to be silent. |
| 37 | obj.challengeParams = null; |
| 38 | obj.noncecounter = 1; |
| 39 | obj.authcounter = 0; |
| 40 | |
| 41 | obj.net = require('net'); |
| 42 | obj.tls = require('tls'); |
| 43 | obj.crypto = require('crypto'); |
| 44 | obj.constants = require('constants'); |
| 45 | obj.socket = null; |
| 46 | obj.socketState = 0; |
| 47 | obj.kerberosDone = 0; |
| 48 | obj.amtVersion = null; |
| 49 | |
| 50 | obj.Address = '/wsman'; |
| 51 | obj.cnonce = obj.crypto.randomBytes(16).toString('hex'); // Generate a random client nonce |
| 52 | |
| 53 | obj.host = host; |
| 54 | obj.port = port; |
| 55 | obj.user = user; |
| 56 | obj.pass = pass; |
| 57 | obj.xtls = tls; |
| 58 | obj.xtlsoptions = tlsoptions; |
| 59 | obj.mpsConnection = mpsConnection; // Link to a MPS connection, this can be CIRA, Relay or LMS. If null, local sockets are used as transport. |
| 60 | obj.xtlsFingerprint; |
| 61 | obj.xtlsCertificate = null; |
| 62 | obj.xtlsCheck = 0; // 0 = No TLS, 1 = CA Checked, 2 = Pinned, 3 = Untrusted |
| 63 | obj.xtlsSkipHostCheck = 0; |
| 64 | obj.xtlsMethod = 0; |
| 65 | obj.xtlsDataReceived = false; |
| 66 | obj.digestRealmMatch = null; |
| 67 | obj.digestRealm = null; |
| 68 | |
| 69 | // Private method |
| 70 | obj.Debug = function (msg) { console.log(msg); } |
| 71 | |
| 72 | // Used to add TLS to a steam |
| 73 | function SerialTunnel(options) { |
| 74 | var obj = new require('stream').Duplex(options); |
| 75 | obj.forwardwrite = null; |
| 76 | obj.updateBuffer = function (chunk) { try { this.push(chunk); } catch (ex) { } }; |
| 77 | obj._write = function (chunk, encoding, callback) { if (obj.forwardwrite != null) { obj.forwardwrite(chunk); } else { console.err("Failed to fwd _write."); } if (callback) callback(); }; // Pass data written to forward |
| 78 | obj._read = function (size) { }; // Push nothing, anything to read should be pushed from updateBuffer() |
| 79 | return obj; |
| 80 | } |
| 81 | |
| 82 | // Private method |
| 83 | // pri = priority, if set to 1, the call is high priority and put on top of the stack. |
| 84 | obj.PerformAjax = function (postdata, callback, tag, pri, url, action) { |
| 85 | if ((obj.ActiveAjaxCount == 0 || ((obj.ActiveAjaxCount < obj.MaxActiveAjaxCount) && (obj.challengeParams != null))) && obj.PendingAjax.length == 0) { |
| 86 | // There are no pending AJAX calls, perform the call now. |
| 87 | obj.PerformAjaxEx(postdata, callback, tag, url, action); |
| 88 | } else { |
| 89 | // If this is a high priority call, put this call in front of the array, otherwise put it in the back. |
| 90 | if (pri == 1) { obj.PendingAjax.unshift([postdata, callback, tag, url, action]); } else { obj.PendingAjax.push([postdata, callback, tag, url, action]); } |
| 91 | } |
| 92 | } |
| 93 | |
| 94 | // Private method |
| 95 | obj.PerformNextAjax = function () { |
| 96 | if (obj.ActiveAjaxCount >= obj.MaxActiveAjaxCount || obj.PendingAjax.length == 0) return; |
| 97 | var x = obj.PendingAjax.shift(); |
| 98 | obj.PerformAjaxEx(x[0], x[1], x[2], x[3], x[4]); |
| 99 | obj.PerformNextAjax(); |
| 100 | } |
| 101 | |
| 102 | // Private method |
| 103 | obj.PerformAjaxEx = function (postdata, callback, tag, url, action) { |
| 104 | if (obj.FailAllError != 0) { obj.gotNextMessagesError({ status: obj.FailAllError }, 'error', null, [postdata, callback, tag, url, action]); return; } |
| 105 | if (!postdata) postdata = ''; |
| 106 | //obj.Debug('SEND: ' + postdata); // DEBUG |
| 107 | |
| 108 | obj.ActiveAjaxCount++; |
| 109 | return obj.PerformAjaxExNodeJS(postdata, callback, tag, url, action); |
| 110 | } |
| 111 | |
| 112 | // NODE.js specific private method |
| 113 | obj.pendingAjaxCall = []; |
| 114 | |
| 115 | // NODE.js specific private method |
| 116 | obj.PerformAjaxExNodeJS = function (postdata, callback, tag, url, action) { obj.PerformAjaxExNodeJS2(postdata, callback, tag, url, action, 5); } |
| 117 | |
| 118 | // NODE.js specific private method |
| 119 | obj.PerformAjaxExNodeJS2 = function (postdata, callback, tag, url, action, retry) { |
| 120 | if ((retry <= 0) || (obj.FailAllError != 0)) { |
| 121 | // Too many retry, fail here. |
| 122 | obj.ActiveAjaxCount--; |
| 123 | if (obj.FailAllError != 999) obj.gotNextMessages(null, 'error', { status: ((obj.FailAllError == 0) ? 408 : obj.FailAllError) }, [postdata, callback, tag, url, action]); // 408 is timeout error |
| 124 | obj.PerformNextAjax(); |
| 125 | return; |
| 126 | } |
| 127 | obj.pendingAjaxCall.push([postdata, callback, tag, url, action, retry]); |
| 128 | if (obj.socketState == 0) { obj.xxConnectHttpSocket(); } |
| 129 | else if (obj.socketState == 2) { obj.sendRequest(postdata, url, action); } |
| 130 | } |
| 131 | |
| 132 | // NODE.js specific private method |
| 133 | obj.sendRequest = function (postdata, url, action) { |
| 134 | url = url ? url : '/wsman'; |
| 135 | action = action ? action : 'POST'; |
| 136 | var h = action + ' ' + url + ' HTTP/1.1\r\n'; |
| 137 | if (obj.challengeParams != null) { |
| 138 | obj.digestRealm = obj.challengeParams['realm']; |
| 139 | if (obj.digestRealmMatch && (obj.digestRealm != obj.digestRealmMatch)) { |
| 140 | obj.FailAllError = 997; // Cause all new responses to be silent. 997 = Digest Realm check error |
| 141 | obj.CancelAllQueries(997); |
| 142 | return; |
| 143 | } |
| 144 | } |
| 145 | if ((obj.user == '*') && (kerberos != null)) { |
| 146 | // Kerberos Auth |
| 147 | if (obj.kerberosDone == 0) { |
| 148 | var ticketName = 'HTTP' + ((obj.tls == 1) ? 'S' : '') + '/' + ((obj.pass == '') ? (obj.host + ':' + obj.port) : obj.pass); |
| 149 | // Ask for the new Kerberos ticket |
| 150 | //console.log('kerberos.getTicket', ticketName); |
| 151 | var ticketReturn = kerberos.getTicket(ticketName); |
| 152 | if (ticketReturn.returnCode == 0 || ticketReturn.returnCode == 0x90312) { |
| 153 | h += 'Authorization: Negotiate ' + ticketReturn.ticket + '\r\n'; |
| 154 | if (process.platform.indexOf('win') >= 0) { |
| 155 | // Clear kerberos tickets on both 32 and 64bit Windows platforms |
| 156 | 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); } |
| 157 | } |
| 158 | } else { |
| 159 | console.log('Unexpected Kerberos error code: ' + ticketReturn.returnCode); |
| 160 | } |
| 161 | obj.kerberosDone = 1; |
| 162 | } |
| 163 | } else if (obj.challengeParams != null) { |
| 164 | var response = hex_md5(hex_md5(obj.user + ':' + obj.challengeParams['realm'] + ':' + obj.pass) + ':' + obj.challengeParams['nonce'] + ':' + nonceHex(obj.noncecounter) + ':' + obj.cnonce + ':' + obj.challengeParams['qop'] + ':' + hex_md5(action + ':' + url + ((obj.challengeParams['qop'] == 'auth-int') ? (':' + hex_md5(postdata)) : ''))); |
| 165 | h += 'Authorization: ' + obj.renderDigest({ 'username': obj.user, 'realm': obj.challengeParams['realm'], 'nonce': obj.challengeParams['nonce'], 'uri': url, 'qop': obj.challengeParams['qop'], 'response': response, 'nc': nonceHex(obj.noncecounter++), 'cnonce': obj.cnonce }) + '\r\n'; |
| 166 | } |
| 167 | h += 'Host: ' + obj.host + ':' + obj.port + '\r\nContent-Length: ' + postdata.length + '\r\n\r\n' + postdata; // Use Content-Length |
| 168 | //h += 'Host: ' + obj.host + ':' + obj.port + '\r\nTransfer-Encoding: chunked\r\n\r\n' + postdata.length.toString(16).toUpperCase() + '\r\n' + postdata + '\r\n0\r\n\r\n'; // Use Chunked-Encoding |
| 169 | obj.xxSend(h); |
| 170 | //console.log('SEND: ' + h); // Display send packet |
| 171 | } |
| 172 | |
| 173 | // Parse the HTTP digest header and return a list of key & values. |
| 174 | obj.parseDigest = function (header) { return correctedQuoteSplit(header.substring(7)).reduce(function (obj, s) { var parts = s.trim().split('='); obj[parts[0]] = parts[1].replace(new RegExp('\"', 'g'), ''); return obj; }, {}) } |
| 175 | |
| 176 | // Split a string on quotes but do not do it when in quotes |
| 177 | function correctedQuoteSplit(str) { return str.split(',').reduce(function (a, c) { if (a.ic) { a.st[a.st.length - 1] += ',' + c } else { a.st.push(c) } if (c.split('"').length % 2 == 0) { a.ic = !a.ic } return a; }, { st: [], ic: false }).st } |
| 178 | function nonceHex(v) { var s = ('00000000' + v.toString(16)); return s.substring(s.length - 8); } |
| 179 | |
| 180 | // NODE.js specific private method |
| 181 | obj.renderDigest = function (params) { |
| 182 | var paramsnames = []; |
| 183 | for (var i in params) { paramsnames.push(i); } |
| 184 | return 'Digest ' + paramsnames.reduce(function (s1, ii) { return s1 + ',' + (((ii == 'nc') || (ii == 'qop')) ? (ii + '=' + params[ii]) : (ii + '="' + params[ii] + '"')); }, '').substring(1); |
| 185 | } |
| 186 | |
| 187 | // NODE.js specific private method |
| 188 | obj.xxConnectHttpSocket = function () { |
| 189 | //obj.Debug("xxConnectHttpSocket"); |
| 190 | obj.socketParseState = 0; |
| 191 | obj.socketAccumulator = ''; |
| 192 | obj.socketHeader = null; |
| 193 | obj.socketData = ''; |
| 194 | obj.socketState = 1; |
| 195 | obj.kerberosDone = 0; |
| 196 | |
| 197 | if (obj.mpsConnection != null) { |
| 198 | if (obj.xtls != 1) { |
| 199 | // Setup a new channel using the CIRA/Relay/LMS connection |
| 200 | obj.socket = obj.mpsConnection.SetupChannel(obj.port); |
| 201 | if (obj.socket == null) { obj.xxOnSocketClosed(); return; } |
| 202 | |
| 203 | // Connect without TLS |
| 204 | obj.socket.onData = function (ccon, data) { obj.xxOnSocketData(data); } |
| 205 | obj.socket.onStateChange = function (ccon, state) { |
| 206 | if (state == 0) { |
| 207 | // Channel closed |
| 208 | obj.socketParseState = 0; |
| 209 | obj.socketAccumulator = ''; |
| 210 | obj.socketHeader = null; |
| 211 | obj.socketData = ''; |
| 212 | obj.socketState = 0; |
| 213 | obj.xxOnSocketClosed(); |
| 214 | } else if (state == 2) { |
| 215 | // Channel open success |
| 216 | obj.xxOnSocketConnected(); |
| 217 | } |
| 218 | } |
| 219 | } else { |
| 220 | // Setup a new channel using the CIRA/Relay/LMS connection |
| 221 | obj.cirasocket = obj.mpsConnection.SetupChannel(obj.port); |
| 222 | if (obj.cirasocket == null) { obj.xxOnSocketClosed(); return; } |
| 223 | |
| 224 | // Connect with TLS |
| 225 | var ser = new SerialTunnel(); |
| 226 | |
| 227 | // let's chain up the TLSSocket <-> SerialTunnel <-> CIRA APF (chnl) |
| 228 | // Anything that needs to be forwarded by SerialTunnel will be encapsulated by chnl write |
| 229 | ser.forwardwrite = function (msg) { try { obj.cirasocket.write(msg); } catch (ex) { } }; // TLS ---> CIRA |
| 230 | |
| 231 | // When APF tunnel return something, update SerialTunnel buffer |
| 232 | obj.cirasocket.onData = function (ciraconn, data) { if (data.length > 0) { try { ser.updateBuffer(Buffer.from(data, 'binary')); } catch (e) { } } }; // CIRA ---> TLS |
| 233 | |
| 234 | // Handle CIRA tunnel state change |
| 235 | obj.cirasocket.onStateChange = function (ciraconn, state) { |
| 236 | if (state == 0) { obj.xxOnSocketClosed(); } |
| 237 | if (state == 2) { |
| 238 | // TLSSocket to encapsulate TLS communication, which then tunneled via SerialTunnel an then wrapped through CIRA APF |
| 239 | var options = { socket: ser, ciphers: 'RSA+AES:!aNULL:!MD5:!DSS', secureOptions: obj.constants.SSL_OP_NO_SSLv2 | obj.constants.SSL_OP_NO_SSLv3 | obj.constants.SSL_OP_NO_COMPRESSION | obj.constants.SSL_OP_CIPHER_SERVER_PREFERENCE | obj.constants.SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION, rejectUnauthorized: false }; |
| 240 | if (obj.xtlsMethod == 1) { |
| 241 | options.secureProtocol = 'TLSv1_method'; |
| 242 | } else { |
| 243 | options.minVersion = 'TLSv1'; |
| 244 | } |
| 245 | if (obj.xtlsoptions) { |
| 246 | if (obj.xtlsoptions.ca) { options.ca = obj.xtlsoptions.ca; } |
| 247 | if (obj.xtlsoptions.cert) { options.cert = obj.xtlsoptions.cert; } |
| 248 | if (obj.xtlsoptions.key) { options.key = obj.xtlsoptions.key; } |
| 249 | } |
| 250 | |
| 251 | obj.socket = obj.tls.connect(obj.port, obj.host, options, obj.xxOnSocketConnected); |
| 252 | obj.socket.setEncoding('binary'); |
| 253 | obj.socket.setTimeout(60000); // Set socket idle timeout |
| 254 | obj.socket.on('error', function (ex) { obj.xtlsMethod = 1 - obj.xtlsMethod; }); |
| 255 | obj.socket.on('close', obj.xxOnSocketClosed); |
| 256 | obj.socket.on('timeout', obj.destroy); |
| 257 | |
| 258 | // Decrypted tunnel from TLS communcation to be forwarded to websocket |
| 259 | obj.socket.on('data', function (data) { try { obj.xxOnSocketData(data.toString('binary')); } catch (e) { } }); // AMT/TLS ---> WS |
| 260 | |
| 261 | // If TLS is on, forward it through TLSSocket |
| 262 | obj.forwardclient = obj.socket; |
| 263 | obj.forwardclient.xtls = 1; |
| 264 | } |
| 265 | }; |
| 266 | } |
| 267 | } else { |
| 268 | // Direct connection |
| 269 | if (obj.xtls != 1) { |
| 270 | // Direct connect without TLS |
| 271 | obj.socket = new obj.net.Socket(); |
| 272 | obj.socket.setEncoding('binary'); |
| 273 | obj.socket.setTimeout(60000); // Set socket idle timeout |
| 274 | obj.socket.on('data', obj.xxOnSocketData); |
| 275 | obj.socket.on('close', obj.xxOnSocketClosed); |
| 276 | obj.socket.on('timeout', obj.destroy); |
| 277 | obj.socket.on('error', obj.xxOnSocketClosed); |
| 278 | obj.socket.connect(obj.port, obj.host, obj.xxOnSocketConnected); |
| 279 | } else { |
| 280 | // Direct connect with TLS |
| 281 | var options = { ciphers: 'RSA+AES:!aNULL:!MD5:!DSS', secureOptions: obj.constants.SSL_OP_NO_SSLv2 | obj.constants.SSL_OP_NO_SSLv3 | obj.constants.SSL_OP_NO_COMPRESSION | obj.constants.SSL_OP_CIPHER_SERVER_PREFERENCE | obj.constants.SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION, rejectUnauthorized: false }; |
| 282 | if (obj.xtlsMethod == 1) { |
| 283 | options.secureProtocol = 'TLSv1_method'; |
| 284 | } else { |
| 285 | options.minVersion = 'TLSv1'; |
| 286 | } |
| 287 | if (obj.xtlsoptions) { |
| 288 | if (obj.xtlsoptions.ca) { options.ca = obj.xtlsoptions.ca; } |
| 289 | if (obj.xtlsoptions.cert) { options.cert = obj.xtlsoptions.cert; } |
| 290 | if (obj.xtlsoptions.key) { options.key = obj.xtlsoptions.key; } |
| 291 | } |
| 292 | obj.socket = obj.tls.connect(obj.port, obj.host, options, obj.xxOnSocketConnected); |
| 293 | obj.socket.setEncoding('binary'); |
| 294 | obj.socket.setTimeout(28000); // Set socket idle timeout of 28 seconds |
| 295 | obj.socket.on('data', obj.xxOnSocketData); |
| 296 | obj.socket.on('close', obj.xxOnSocketClosed); |
| 297 | obj.socket.on('timeout', obj.destroy); |
| 298 | obj.socket.on('error', function (ex) { if (ex.message && ex.message.indexOf('sslv3 alert bad record mac') >= 0) { obj.xtlsMethod = 1 - obj.xtlsMethod; } }); |
| 299 | } |
| 300 | obj.socket.setNoDelay(true); // Disable nagle. We will encode each WSMAN request as a single send block and want to send it at once. This may help Intel AMT handle pipelining? |
| 301 | } |
| 302 | } |
| 303 | |
| 304 | // Get the certificate of Intel AMT |
| 305 | obj.getPeerCertificate = function () { if (obj.xtls == 1) { return obj.socket.getPeerCertificate(); } return null; } |
| 306 | obj.getPeerCertificateFingerprint = function () { if (obj.xtls == 1) { return obj.socket.getPeerCertificate().fingerprint.split(':').join('').toLowerCase(); } return null; } |
| 307 | |
| 308 | // Check if the certificate matched the certificate hash. |
| 309 | function checkCertHash(cert, hash) { |
| 310 | // Check not required |
| 311 | if (hash == 0) return true; |
| 312 | |
| 313 | // SHA1 compare |
| 314 | if (cert.fingerprint.split(':').join('').toLowerCase() == hash) return true; |
| 315 | |
| 316 | // SHA256 compare |
| 317 | if ((hash.length == 64) && (obj.crypto.createHash('sha256').update(cert.raw).digest('hex') == hash)) { return true; } |
| 318 | |
| 319 | // SHA384 compare |
| 320 | if ((hash.length == 96) && (obj.crypto.createHash('sha384').update(cert.raw).digest('hex') == hash)) { return true; } |
| 321 | |
| 322 | return false; |
| 323 | } |
| 324 | |
| 325 | // NODE.js specific private method |
| 326 | obj.xxOnSocketConnected = function () { |
| 327 | if (obj.socket == null) return; |
| 328 | // check TLS certificate for webrelay and direct only |
| 329 | if (obj.xtls == 1) { |
| 330 | obj.xtlsCertificate = obj.socket.getPeerCertificate(); |
| 331 | |
| 332 | // Setup the forge certificate check |
| 333 | var camatch = 0; |
| 334 | if ((obj.xtlsoptions != null) && (obj.xtlsoptions.ca != null)) { |
| 335 | var forgeCert = forge.pki.certificateFromAsn1(forge.asn1.fromDer(atob(obj.xtlsCertificate.raw.toString('base64')))); |
| 336 | var caStore = forge.pki.createCaStore(obj.xtlsoptions.ca); |
| 337 | // Got thru all certificates in the store and look for a match. |
| 338 | for (var i in caStore.certs) { |
| 339 | if (camatch == 0) { |
| 340 | var c = caStore.certs[i], verified = false; |
| 341 | try { verified = c.verify(forgeCert); } catch (e) { } |
| 342 | if (verified == true) { camatch = c; } |
| 343 | } |
| 344 | } |
| 345 | // We found a match, check that the CommonName matches the hostname |
| 346 | if ((obj.xtlsSkipHostCheck == 0) && (camatch != 0)) { |
| 347 | amtcertname = forgeCert.subject.getField('CN').value; |
| 348 | if (amtcertname.toLowerCase() != obj.host.toLowerCase()) { camatch = 0; } |
| 349 | } |
| 350 | } |
| 351 | if ((camatch == 0) && (checkCertHash(obj.xtlsCertificate, obj.xtlsFingerprint) == false)) { |
| 352 | obj.FailAllError = 998; // Cause all new responses to be silent. 998 = TLS Certificate check error |
| 353 | obj.CancelAllQueries(998); |
| 354 | return; |
| 355 | } |
| 356 | if ((obj.xtlsFingerprint == 0) && (camatch == 0)) { obj.xtlsCheck = 3; } else { obj.xtlsCheck = (camatch == 0) ? 2 : 1; } |
| 357 | } else { obj.xtlsCheck = 0; } |
| 358 | obj.socketState = 2; |
| 359 | obj.socketParseState = 0; |
| 360 | for (i in obj.pendingAjaxCall) { obj.sendRequest(obj.pendingAjaxCall[i][0], obj.pendingAjaxCall[i][3], obj.pendingAjaxCall[i][4]); } |
| 361 | } |
| 362 | |
| 363 | // NODE.js specific private method |
| 364 | obj.xxOnSocketData = function (data) { |
| 365 | //console.log('RECV: ' + data); |
| 366 | obj.xtlsDataReceived = true; |
| 367 | if (typeof data === 'object') { |
| 368 | // This is an ArrayBuffer, convert it to a string array (used in IE) |
| 369 | var binary = "", bytes = new Uint8Array(data), length = bytes.byteLength; |
| 370 | for (var i = 0; i < length; i++) { binary += String.fromCharCode(bytes[i]); } |
| 371 | data = binary; |
| 372 | } |
| 373 | else if (typeof data !== 'string') return; |
| 374 | |
| 375 | obj.socketAccumulator += data; |
| 376 | while (true) { |
| 377 | //console.log('ACC(' + obj.socketAccumulator + '): ' + obj.socketAccumulator); |
| 378 | if (obj.socketParseState == 0) { |
| 379 | var headersize = obj.socketAccumulator.indexOf('\r\n\r\n'); |
| 380 | if (headersize < 0) return; |
| 381 | //obj.Debug("Header: "+obj.socketAccumulator.substring(0, headersize)); // Display received HTTP header |
| 382 | obj.socketHeader = obj.socketAccumulator.substring(0, headersize).split('\r\n'); |
| 383 | if (obj.amtVersion == null) { for (var i in obj.socketHeader) { if (obj.socketHeader[i].indexOf('Server: Intel(R) Active Management Technology ') == 0) { obj.amtVersion = obj.socketHeader[i].substring(46); } } } |
| 384 | obj.socketAccumulator = obj.socketAccumulator.substring(headersize + 4); |
| 385 | obj.socketParseState = 1; |
| 386 | obj.socketData = ''; |
| 387 | obj.socketXHeader = { Directive: obj.socketHeader[0].split(' ') }; |
| 388 | for (i in obj.socketHeader) { |
| 389 | if (i != 0) { |
| 390 | var x2 = obj.socketHeader[i].indexOf(':'); |
| 391 | obj.socketXHeader[obj.socketHeader[i].substring(0, x2).toLowerCase()] = obj.socketHeader[i].substring(x2 + 2); |
| 392 | } |
| 393 | } |
| 394 | } |
| 395 | if (obj.socketParseState == 1) { |
| 396 | var csize = -1; |
| 397 | if ((obj.socketXHeader['connection'] != undefined) && (obj.socketXHeader['connection'].toLowerCase() == 'close') && ((obj.socketXHeader["transfer-encoding"] == undefined) || (obj.socketXHeader["transfer-encoding"].toLowerCase() != 'chunked'))) { |
| 398 | // The body ends with a close, in this case, we will only process the header |
| 399 | csize = 0; |
| 400 | } else if (obj.socketXHeader['content-length'] != undefined) { |
| 401 | // The body length is specified by the content-length |
| 402 | csize = parseInt(obj.socketXHeader['content-length']); |
| 403 | if (obj.socketAccumulator.length < csize) return; |
| 404 | var data = obj.socketAccumulator.substring(0, csize); |
| 405 | obj.socketAccumulator = obj.socketAccumulator.substring(csize); |
| 406 | obj.socketData = data; |
| 407 | csize = 0; |
| 408 | } else { |
| 409 | // The body is chunked |
| 410 | var clen = obj.socketAccumulator.indexOf('\r\n'); |
| 411 | if (clen < 0) return; // Chunk length not found, exit now and get more data. |
| 412 | // Chunk length if found, lets see if we can get the data. |
| 413 | csize = parseInt(obj.socketAccumulator.substring(0, clen), 16); |
| 414 | if (obj.socketAccumulator.length < clen + 2 + csize + 2) return; |
| 415 | // We got a chunk with all of the data, handle the chunck now. |
| 416 | var data = obj.socketAccumulator.substring(clen + 2, clen + 2 + csize); |
| 417 | obj.socketAccumulator = obj.socketAccumulator.substring(clen + 2 + csize + 2); |
| 418 | try { obj.socketData += data; } catch (ex) { console.log(ex, typeof data, data.length); } |
| 419 | } |
| 420 | if (csize == 0) { |
| 421 | //obj.Debug("xxOnSocketData DONE: (" + obj.socketData.length + "): " + obj.socketData); |
| 422 | obj.xxProcessHttpResponse(obj.socketXHeader, obj.socketData); |
| 423 | obj.socketParseState = 0; |
| 424 | obj.socketHeader = null; |
| 425 | } |
| 426 | } |
| 427 | } |
| 428 | } |
| 429 | |
| 430 | // NODE.js specific private method |
| 431 | obj.xxProcessHttpResponse = function (header, data) { |
| 432 | //obj.Debug("xxProcessHttpResponse: " + header.Directive[1]); |
| 433 | |
| 434 | var s = parseInt(header.Directive[1]); |
| 435 | if (isNaN(s)) s = 500; |
| 436 | if (s == 401 && ++(obj.authcounter) < 3) { |
| 437 | obj.challengeParams = obj.parseDigest(header['www-authenticate']); // Set the digest parameters, after this, the socket will close and we will auto-retry |
| 438 | if (obj.challengeParams['qop'] != null) { |
| 439 | var qopList = obj.challengeParams['qop'].split(','); |
| 440 | for (var i in qopList) { qopList[i] = qopList[i].trim(); } |
| 441 | if (qopList.indexOf('auth-int') >= 0) { obj.challengeParams['qop'] = 'auth-int'; } else { obj.challengeParams['qop'] = 'auth'; } |
| 442 | } |
| 443 | if (obj.mpsConnection == null) { obj.socket.end(); } else { obj.socket.close(); } |
| 444 | } else { |
| 445 | var r = obj.pendingAjaxCall.shift(); |
| 446 | if ((r == null) || (r.length < 1)) { /*console.log("pendingAjaxCall error, " + r);*/ return; } // Get a response without any pending requests. |
| 447 | //if (s != 200) { obj.Debug("Error, status=" + s + "\r\n\r\nreq=" + r[0] + "\r\n\r\nresp=" + data); } // Debug: Display the request & response if something did not work. |
| 448 | obj.authcounter = 0; |
| 449 | obj.ActiveAjaxCount--; |
| 450 | obj.gotNextMessages(data, 'success', { status: s }, r); |
| 451 | obj.PerformNextAjax(); |
| 452 | } |
| 453 | } |
| 454 | |
| 455 | // NODE.js specific private method |
| 456 | obj.xxOnSocketClosed = function () { |
| 457 | //obj.Debug("xxOnSocketClosed"); |
| 458 | obj.socketState = 0; |
| 459 | if (obj.socket != null) { |
| 460 | if (obj.socket.removeAllListeners) { |
| 461 | // Do not remove the error handler since it may still get triggered. |
| 462 | obj.socket.removeAllListeners('data'); |
| 463 | obj.socket.removeAllListeners('close'); |
| 464 | obj.socket.removeAllListeners('timeout'); |
| 465 | } |
| 466 | try { |
| 467 | if (obj.mpsConnection == null) { |
| 468 | obj.socket.destroy(); |
| 469 | } else { |
| 470 | if (obj.cirasocket != null) { obj.cirasocket.close(); } else { obj.socket.close(); } |
| 471 | } |
| 472 | } catch (ex) { } |
| 473 | obj.socket = null; |
| 474 | obj.cirasocket = null; |
| 475 | } |
| 476 | if (obj.pendingAjaxCall.length > 0) { |
| 477 | var r = obj.pendingAjaxCall.shift(), retry = r[5]; |
| 478 | setTimeout(function () { obj.PerformAjaxExNodeJS2(r[0], r[1], r[2], r[3], r[4], --retry) }, 500); // Wait half a second and try again |
| 479 | } |
| 480 | } |
| 481 | |
| 482 | obj.destroy = function () { |
| 483 | if (obj.socket != null) { |
| 484 | if (obj.socket.removeAllListeners) { |
| 485 | // Do not remove the error handler since it may still get triggered. |
| 486 | obj.socket.removeAllListeners('data'); |
| 487 | obj.socket.removeAllListeners('close'); |
| 488 | obj.socket.removeAllListeners('timeout'); |
| 489 | } |
| 490 | try { |
| 491 | if (obj.mpsConnection == null) { |
| 492 | obj.socket.destroy(); |
| 493 | } else { |
| 494 | if (obj.cirasocket != null) { obj.cirasocket.close(); } else { obj.socket.close(); } |
| 495 | } |
| 496 | } catch (ex) { } |
| 497 | delete obj.socket; |
| 498 | delete obj.cirasocket; |
| 499 | obj.socketState = 0; |
| 500 | } |
| 501 | } |
| 502 | |
| 503 | // NODE.js specific private method |
| 504 | obj.xxSend = function (x) { |
| 505 | //console.log('xxSend', x); |
| 506 | if (obj.socketState == 2) { obj.socket.write(Buffer.from(x, 'binary')); } |
| 507 | } |
| 508 | |
| 509 | // Cancel all pending queries with given status |
| 510 | obj.CancelAllQueries = function (s) { |
| 511 | obj.FailAllError = s; |
| 512 | while (obj.PendingAjax.length > 0) { var x = obj.PendingAjax.shift(); x[1](null, s, x[2]); } |
| 513 | obj.destroy(); |
| 514 | } |
| 515 | |
| 516 | // Private method |
| 517 | obj.gotNextMessages = function (data, status, request, callArgs) { |
| 518 | if (obj.FailAllError == 999) return; |
| 519 | if (obj.FailAllError != 0) { try { callArgs[1](null, obj.FailAllError, callArgs[2]); } catch (ex) { console.error(ex); } return; } |
| 520 | if (request.status != 200) { try { callArgs[1](null, request.status, callArgs[2]); } catch (ex) { console.error(ex); } return; } |
| 521 | try { callArgs[1](data, 200, callArgs[2]); } catch (ex) { console.error(ex); } |
| 522 | } |
| 523 | |
| 524 | // Private method |
| 525 | obj.gotNextMessagesError = function (request, status, errorThrown, callArgs) { |
| 526 | if (obj.FailAllError == 999) return; |
| 527 | if (obj.FailAllError != 0) { try { callArgs[1](null, obj.FailAllError, callArgs[2]); } catch (ex) { console.error(ex); } return; } |
| 528 | try { callArgs[1](obj, null, { Header: { HttpError: request.status } }, request.status, callArgs[2]); } catch (ex) { console.error(ex); } |
| 529 | } |
| 530 | |
| 531 | // MD5 digest hash |
| 532 | function hex_md5(str) { return obj.crypto.createHash('md5').update(str).digest('hex'); } |
| 533 | |
| 534 | return obj; |
| 535 | } |
| 536 | |
| 537 | module.exports = CreateWsmanComm; |