EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
amt-wsman-comm.js
Go to the documentation of this file.
1/*
2Copyright 2020-2021 Intel Corporation
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
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
29var 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
537module.exports = CreateWsmanComm;