2Copyright 2020-2021 Intel Corporation
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
8 http://www.apache.org/licenses/LICENSE-2.0
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.
16@description Intel AMT redirection stack
17@author Ylian Saint-Hilaire
23/*jshint strict:false */
25/*jshint esversion: 6 */
28// Construct a MeshServer object
29module.exports.CreateAmtRedirect = function (module, domain, user, webserver, meshcentral) {
31 obj.m = module; // This is the inner module (Terminal or Desktop)
34 obj.net = require('net');
35 obj.tls = require('tls');
36 obj.crypto = require('crypto');
37 const constants = require('constants');
42 obj.protocol = module.protocol; // 1 = SOL, 2 = KVM, 3 = IDER
43 obj.xtlsoptions = null;
44 obj.redirTrace = false;
45 obj.tls1only = 0; // TODO
47 obj.amtaccumulator = '';
49 obj.amtkeepalivetimer = null;
50 obj.authuri = '/RedirectionService';
52 obj.onStateChanged = null;
53 obj.forwardclient = null;
56 const MESHRIGHT_EDITMESH = 1;
57 const MESHRIGHT_MANAGEUSERS = 2;
58 const MESHRIGHT_MANAGECOMPUTERS = 4;
59 const MESHRIGHT_REMOTECONTROL = 8;
60 const MESHRIGHT_AGENTCONSOLE = 16;
61 const MESHRIGHT_SERVERFILES = 32;
62 const MESHRIGHT_WAKEDEVICE = 64;
63 const MESHRIGHT_SETNOTES = 128;
66 const SITERIGHT_SERVERBACKUP = 1;
67 const SITERIGHT_MANAGEUSERS = 2;
68 const SITERIGHT_SERVERRESTORE = 4;
69 const SITERIGHT_FILEACCESS = 8;
70 const SITERIGHT_SERVERUPDATE = 16;
71 const SITERIGHT_LOCKED = 32;
74 if ((arguments.length < 2) || (meshcentral.debugLevel == null) || (lvl > meshcentral.debugLevel)) return;
75 var a = []; for (var i = 1; i < arguments.length; i++) { a.push(arguments[i]); } console.log(...a);
78 // Older NodeJS does not support the keyword "class", so we do without using this syntax
79 // TODO: Validate that it's the same as above and that it works.
80 function SerialTunnel(options) {
81 var obj = new require('stream').Duplex(options);
82 obj.forwardwrite = null;
83 obj.updateBuffer = function (chunk) { this.push(chunk); };
84 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
85 obj._read = function (size) { }; // Push nothing, anything to read should be pushed from updateBuffer()
89 obj.Start = function (nodeid) {
90 //console.log('Amt-Redir-Start', nodeid);
92 Debug(1, 'AMT redir for ' + user.name + ' to ' + nodeid + '.');
95 // Fetch information about the target
96 meshcentral.db.Get(nodeid, function (err, docs) {
97 if (docs.length == 0) { console.log('ERR: Node not found'); obj.Stop(); return; }
99 if (!node.intelamt) { console.log('ERR: Not AMT node'); obj.Stop(); return; }
101 obj.amtuser = node.intelamt.user;
102 obj.amtpass = node.intelamt.pass;
104 // Check if this user has permission to manage this computer
105 var meshlinks = user.links[node.meshid];
106 if ((!meshlinks) || (!meshlinks.rights) || ((meshlinks.rights & MESHRIGHT_REMOTECONTROL) == 0)) { console.log('ERR: Access denied (2)'); obj.Stop(); return; }
108 // Check what connectivity is available for this node
109 var state = meshcentral.GetConnectivityState(nodeid);
111 if (!state || state.connectivity == 0) { Debug(1, 'ERR: No routing possible (1)'); obj.Stop(); return; } else { conn = state.connectivity; }
114 // Check what server needs to handle this connection
115 if ((meshcentral.multiServer != null) && (cookie == null)) { // If a cookie is provided, don't allow the connection to jump again to a different server
116 var server = obj.parent.GetRoutingServerId(nodeid, 2); // Check for Intel CIRA connection
117 if (server != null) {
118 if (server.serverid != obj.parent.serverId) {
119 // Do local Intel CIRA routing using a different server
120 Debug(1, 'Route Intel AMT CIRA connection to peer server: ' + server.serverid);
121 obj.parent.multiServer.createPeerRelay(ws, req, server.serverid, user);
125 server = obj.parent.GetRoutingServerId(nodeid, 4); // Check for local Intel AMT connection
126 if ((server != null) && (server.serverid != obj.parent.serverId)) {
127 // Do local Intel AMT routing using a different server
128 Debug(1, 'Route Intel AMT direct connection to peer server: ' + server.serverid);
129 obj.parent.multiServer.createPeerRelay(ws, req, server.serverid, user);
136 // If Intel AMT CIRA connection is available, use it
137 var ciraconn = meshcentral.mpsserver.GetConnectionToNode(nodeid, null, true); // Request an OOB connection
138 if (ciraconn != null) {
139 Debug(1, 'Opening Intel AMT CIRA transport connection to ' + nodeid + '.');
141 // Compute target port, look at the CIRA port mappings, if non-TLS is allowed, use that, if not use TLS
143 if (ciraconn.tag.boundPorts.indexOf(16994) >= 0) port = 16994; // RELEASE: Always use non-TLS mode if available within CIRA
145 // Setup a new CIRA channel
146 if ((port == 16993) || (port == 16995)) {
147 // 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 )
148 var ser = new SerialTunnel();
149 var chnl = meshcentral.mpsserver.SetupChannel(ciraconn, port);
151 // let's chain up the TLSSocket <-> SerialTunnel <-> CIRA APF (chnl)
152 // Anything that needs to be forwarded by SerialTunnel will be encapsulated by chnl write
153 ser.forwardwrite = function (msg) {
155 chnl.write(msg.toString('binary'));
158 // When APF tunnel return something, update SerialTunnel buffer
159 chnl.onData = function (ciraconn, data) {
161 Debug(3, 'Relay TLS CIRA data', data.length);
162 if (data.length > 0) { try { ser.updateBuffer(Buffer.from(data, 'binary')); } catch (e) { } }
165 // Handle CIRA tunnel state change
166 chnl.onStateChange = function (ciraconn, state) {
167 Debug(2, 'Relay TLS CIRA state change', state);
168 if (state == 0) { try { ws.close(); } catch (e) { } }
171 // TLSSocket to encapsulate TLS communication, which then tunneled via SerialTunnel an then wrapped through CIRA APF
172 const TLSSocket = require('tls').TLSSocket;
173 const tlsoptions = { 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 | constants.SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION, rejectUnauthorized: false };
174 if (obj.tls1only == 1) {
175 tlsoptions.secureProtocol = 'TLSv1_method';
177 tlsoptions.minVersion = 'TLSv1';
179 const tlsock = new TLSSocket(ser, tlsoptions);
180 tlsock.on('error', function (err) { Debug(1, "CIRA TLS Connection Error ", err); });
181 tlsock.on('secureConnect', function () { Debug(2, "CIRA Secure TLS Connection"); ws._socket.resume(); });
183 // Decrypted tunnel from TLS communcation to be forwarded to websocket
184 tlsock.on('data', function (data) {
187 data = data.toString('binary');
188 //ws.send(Buffer.from(data, 'binary'));
193 // If TLS is on, forward it through TLSSocket
194 obj.forwardclient = tlsock;
195 obj.forwardclient.xtls = 1;
198 obj.forwardclient = meshcentral.mpsserver.SetupChannel(ciraconn, port);
199 obj.forwardclient.xtls = 0;
202 obj.forwardclient.onStateChange = function (ciraconn, state) {
203 Debug(2, 'Intel AMT CIRA relay state change', state);
204 if (state == 0) { try { obj.Stop(); } catch (e) { } }
205 else if (state == 2) { obj.xxOnSocketConnected(); }
208 obj.forwardclient.onData = function (ciraconn, data) {
209 Debug(4, 'Intel AMT CIRA data', data.length);
210 if (data.length > 0) { obj.xxOnSocketData(data); } // TODO: Add TLS support
213 obj.forwardclient.onSendOk = function (ciraconn) {
214 // TODO: Flow control? (Dont' really need it with AMT, but would be nice)
215 Debug(4, 'Intel AMT CIRA sendok');
221 // If Intel AMT direct connection is possible, option a direct socket
222 if ((conn & 4) != 0) { // We got a new web socket connection, initiate a TCP connection to the target Intel AMT host/port.
223 Debug(1, 'Opening Intel AMT transport connection to ' + nodeid + '.');
225 // Compute target port
227 if (node.intelamt.tls > 0) port = 16995; // This is a direct connection, use TLS when possible
229 if (node.intelamt.tls != 1) {
230 // If this is TCP (without TLS) set a normal TCP socket
231 obj.forwardclient = new obj.net.Socket();
232 obj.forwardclient.setEncoding('binary');
234 // If TLS is going to be used, setup a TLS socket
235 var tlsoptions = { 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 | constants.SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION, rejectUnauthorized: false };
236 if (obj.tls1only == 1) {
237 tlsoptions.secureProtocol = 'TLSv1_method';
239 tlsoptions.minVersion = 'TLSv1';
241 obj.forwardclient = obj.tls.connect(port, node.host, tlsoptions, function () {
242 // The TLS connection method is the same as TCP, but located a bit differently.
243 Debug(2, 'TLS Intel AMT transport connected to ' + node.host + ':' + port + '.');
244 obj.xxOnSocketConnected();
246 obj.forwardclient.setEncoding('binary');
249 // When we receive data on the TCP connection, forward it back into the web socket connection.
250 obj.forwardclient.on('data', function (data) {
251 //if (obj.parent.debugLevel >= 1) { // DEBUG
252 Debug(1, 'Intel AMT transport data from ' + node.host + ', ' + data.length + ' bytes.');
253 Debug(4, ' ' + Buffer.from(data, 'binary').toString('hex'));
254 //if (obj.parent.debugLevel >= 4) { Debug(4, ' ' + Buffer.from(data, 'binary').toString('hex')); }
256 obj.xxOnSocketData(data);
259 // If the TCP connection closes, disconnect the associated web socket.
260 obj.forwardclient.on('close', function () {
261 Debug(1, 'Intel AMT transport relay disconnected from ' + node.host + '.');
265 // If the TCP connection causes an error, disconnect the associated web socket.
266 obj.forwardclient.on('error', function (err) {
267 Debug(1, 'Intel AMT transport relay error from ' + node.host + ': ' + err.errno);
271 if (node.intelamt.tls == 0) {
272 // A TCP connection to Intel AMT just connected, start forwarding.
273 obj.forwardclient.connect(port, node.host, function () {
274 Debug(1, 'Intel AMT transport connected to ' + node.host + ':' + port + '.');
275 obj.xxOnSocketConnected();
285 // Get the certificate of Intel AMT
286 obj.getPeerCertificate = function () { if (obj.xtls == true) { return obj.socket.getPeerCertificate(); } return null; }
288 obj.xxOnSocketConnected = function () {
289 //console.log('xxOnSocketConnected');
290 if (!obj.xtlsoptions || !obj.xtlsoptions.meshServerConnect) {
291 if (obj.xtls == true) {
292 obj.xtlsCertificate = obj.socket.getPeerCertificate();
293 if ((obj.xtlsFingerprint != 0) && (obj.xtlsCertificate.fingerprint.split(':').join('').toLowerCase() != obj.xtlsFingerprint)) { obj.Stop(); return; }
297 if (obj.redirTrace) { console.log("REDIR-CONNECTED"); }
298 //obj.Debug("Socket Connected");
299 obj.xxStateChange(2);
300 if (obj.protocol == 1) obj.xxSend(obj.RedirectStartSol); // TODO: Put these strings in higher level module to tighten code
301 if (obj.protocol == 2) obj.xxSend(obj.RedirectStartKvm); // Don't need these is the feature if not compiled-in.
302 if (obj.protocol == 3) obj.xxSend(obj.RedirectStartIder);
305 obj.xxOnSocketData = function (data) {
306 if (!data || obj.connectstate == -1) return;
307 if (obj.redirTrace) { console.log("REDIR-RECV(" + data.length + "): " + webserver.common.rstr2hex(data)); }
308 //obj.Debug("Recv(" + data.length + "): " + webserver.common.rstr2hex(data));
309 if ((obj.protocol > 1) && (obj.connectstate == 1)) { return obj.m.ProcessData(data); } // KVM traffic, forward it directly.
310 obj.amtaccumulator += data;
311 //obj.Debug("Recv(" + obj.amtaccumulator.length + "): " + webserver.common.rstr2hex(obj.amtaccumulator));
312 while (obj.amtaccumulator.length >= 1) {
314 switch (obj.amtaccumulator.charCodeAt(0)) {
315 case 0x11: // StartRedirectionSessionReply (17)
316 if (obj.amtaccumulator.length < 4) return;
317 var statuscode = obj.amtaccumulator.charCodeAt(1);
318 switch (statuscode) {
319 case 0: // STATUS_SUCCESS
320 if (obj.amtaccumulator.length < 13) return;
321 var oemlen = obj.amtaccumulator.charCodeAt(12);
322 if (obj.amtaccumulator.length < 13 + oemlen) return;
323 obj.xxSend(String.fromCharCode(0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)); // Query authentication support
324 cmdsize = (13 + oemlen);
331 case 0x14: // AuthenticateSessionReply (20)
332 if (obj.amtaccumulator.length < 9) return;
333 var authDataLen = webserver.common.ReadIntX(obj.amtaccumulator, 5);
334 if (obj.amtaccumulator.length < 9 + authDataLen) return;
335 var status = obj.amtaccumulator.charCodeAt(1);
336 var authType = obj.amtaccumulator.charCodeAt(4);
338 for (var i = 0; i < authDataLen; i++) { authData.push(obj.amtaccumulator.charCodeAt(9 + i)); }
339 var authDataBuf = obj.amtaccumulator.substring(9, 9 + authDataLen);
340 cmdsize = 9 + authDataLen;
343 // This is Kerberos code, not supported in MeshCentral.
344 if (obj.amtuser == '*') {
345 if (authData.indexOf(2) >= 0) {
348 if (kerberos && kerberos != null) {
349 var ticketReturn = kerberos.getTicket('HTTP' + ((obj.tls == 1)?'S':'') + '/' + ((obj.amtpass == '') ? (obj.host + ':' + obj.port) : obj.amtpass));
350 if (ticketReturn.returnCode == 0 || ticketReturn.returnCode == 0x90312) {
351 ticket = ticketReturn.ticket;
352 if (process.platform.indexOf('win') >= 0) {
353 // Clear kerberos tickets on both 32 and 64bit Windows platforms
354 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); }
357 console.error('Unexpected Kerberos error code: ' + ticketReturn.returnCode);
361 obj.xxSend(String.fromCharCode(0x13, 0x00, 0x00, 0x00, 0x02) + webserver.common.IntToStrX(ticket.length) + ticket);
370 if (authData.indexOf(4) >= 0) {
371 // Good Digest Auth (With cnonce and all)
372 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));
375 else if (authData.indexOf(3) >= 0) {
376 // Bad Digest Auth (Not sure why this is supported, cnonce is not used!)
377 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));
379 else if (authData.indexOf(1) >= 0) {
380 // Basic Auth (Probably a good idea to not support this unless this is an old version of Intel AMT)
381 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);
389 else if ((authType == 3 || authType == 4) && status == 1) {
393 var realmlen = authDataBuf.charCodeAt(curptr);
394 var realm = authDataBuf.substring(curptr + 1, curptr + 1 + realmlen);
395 curptr += (realmlen + 1);
398 var noncelen = authDataBuf.charCodeAt(curptr);
399 var nonce = authDataBuf.substring(curptr + 1, curptr + 1 + noncelen);
400 curptr += (noncelen + 1);
405 var cnonce = obj.xxRandomValueHex(32);
406 var snc = '00000002';
409 qoplen = authDataBuf.charCodeAt(curptr);
410 qop = authDataBuf.substring(curptr + 1, curptr + 1 + qoplen);
411 curptr += (qoplen + 1);
412 extra = snc + ":" + cnonce + ":" + qop + ":";
414 var digest = hex_md5(hex_md5(obj.amtuser + ":" + realm + ":" + obj.amtpass) + ":" + nonce + ":" + extra + hex_md5("POST:" + obj.authuri));
416 var totallen = obj.amtuser.length + realm.length + nonce.length + obj.authuri.length + cnonce.length + snc.length + digest.length + 7;
417 if (authType == 4) totallen += (qop.length + 1);
418 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;
419 if (authType == 4) buf += (String.fromCharCode(qop.length) + qop);
422 else if (status == 0) { // Success
424 if (obj.protocol == 1) {
425 // Serial-over-LAN: Send Intel AMT serial settings...
426 var MaxTxBuffer = 10000;
428 var TxOverflowTimeout = 0;
429 var RxTimeout = 10000;
430 var RxFlushTimeout = 100;
431 var Heartbeat = 0;//5000;
432 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));
434 if (obj.protocol == 2) {
435 // Remote Desktop: Send traffic directly...
436 obj.xxSend(String.fromCharCode(0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
439 if (obj.protocol == 3) { // IDE-R
440 obj.connectstate = 1;
442 if (obj.amtaccumulator.length > cmdsize) { obj.m.ProcessData(obj.amtaccumulator.substring(cmdsize)); }
443 cmdsize = obj.amtaccumulator.length;
447 case 0x21: // Response to settings (33)
448 if (obj.amtaccumulator.length < 23) break;
450 obj.xxSend(String.fromCharCode(0x27, 0x00, 0x00, 0x00) + ToIntStr(obj.amtsequence++) + String.fromCharCode(0x00, 0x00, 0x1B, 0x00, 0x00, 0x00));
451 if (obj.protocol == 1) { obj.amtkeepalivetimer = setInterval(obj.xxSendAmtKeepAlive, 2000); }
452 obj.connectstate = 1;
453 obj.xxStateChange(3);
455 case 0x29: // Serial Settings (41)
456 if (obj.amtaccumulator.length < 10) break;
459 case 0x2A: // Incoming display data (42)
460 if (obj.amtaccumulator.length < 10) break;
461 var cs = (10 + ((obj.amtaccumulator.charCodeAt(9) & 0xFF) << 8) + (obj.amtaccumulator.charCodeAt(8) & 0xFF));
462 if (obj.amtaccumulator.length < cs) break;
463 obj.m.ProcessData(obj.amtaccumulator.substring(10, cs));
466 case 0x2B: // Keep alive message (43)
467 if (obj.amtaccumulator.length < 8) break;
471 if (obj.amtaccumulator.length < 8) break;
472 obj.connectstate = 1;
474 // KVM traffic, forward rest of accumulator directly.
475 if (obj.amtaccumulator.length > 8) { obj.m.ProcessData(obj.amtaccumulator.substring(8)); }
476 cmdsize = obj.amtaccumulator.length;
479 console.log("Unknown Intel AMT command: " + obj.amtaccumulator.charCodeAt(0) + " acclen=" + obj.amtaccumulator.length);
483 if (cmdsize == 0) return;
484 obj.amtaccumulator = obj.amtaccumulator.substring(cmdsize);
488 obj.xxSend = function (x) {
489 if (typeof x == 'string') {
490 if (obj.redirTrace) { console.log("REDIR-SEND(" + x.length + "): " + Buffer.from(x, 'binary').toString('hex'), typeof x); }
491 //obj.Debug("Send(" + x.length + "): " + webserver.common.rstr2hex(x));
492 //obj.forwardclient.write(x); // FIXES CIRA
493 obj.forwardclient.write(Buffer.from(x, 'binary'));
495 if (obj.redirTrace) { console.log("REDIR-SEND(" + x.length + "): " + x.toString('hex'), typeof x); }
496 //obj.Debug("Send(" + x.length + "): " + webserver.common.rstr2hex(x));
497 //obj.forwardclient.write(x); // FIXES CIRA
498 obj.forwardclient.write(x);
502 obj.Send = function (x) {
503 if (obj.forwardclient == null || obj.connectstate != 1) return;
504 if (obj.protocol == 1) { obj.xxSend(String.fromCharCode(0x28, 0x00, 0x00, 0x00) + ToIntStr(obj.amtsequence++) + ToShortStr(x.length) + x); } else { obj.xxSend(x); }
507 obj.xxSendAmtKeepAlive = function () {
508 if (obj.forwardclient == null) return;
509 obj.xxSend(String.fromCharCode(0x2B, 0x00, 0x00, 0x00) + ToIntStr(obj.amtsequence++));
512 obj.xxRandomValueHex = function(len) { return obj.crypto.randomBytes(Math.ceil(len / 2)).toString('hex').slice(0, len); }
514 obj.xxOnSocketClosed = function () {
515 if (obj.redirTrace) { console.log('REDIR-CLOSED'); }
516 //obj.Debug("Socket Closed");
520 obj.xxStateChange = function(newstate) {
521 if (obj.State == newstate) return;
522 obj.State = newstate;
523 obj.m.xxStateChange(obj.State);
524 if (obj.onStateChanged != null) obj.onStateChanged(obj, obj.State);
527 obj.Stop = function () {
528 if (obj.redirTrace) { console.log('REDIR-CLOSED'); }
529 //obj.Debug("Socket Stopped");
530 obj.xxStateChange(0);
531 obj.connectstate = -1;
532 obj.amtaccumulator = '';
533 if (obj.forwardclient != null) { try { obj.forwardclient.destroy(); } catch (ex) { } delete obj.forwardclient; }
534 if (obj.amtkeepalivetimer != null) { clearInterval(obj.amtkeepalivetimer); delete obj.amtkeepalivetimer; }
537 obj.RedirectStartSol = String.fromCharCode(0x10, 0x00, 0x00, 0x00, 0x53, 0x4F, 0x4C, 0x20);
538 obj.RedirectStartKvm = String.fromCharCode(0x10, 0x01, 0x00, 0x00, 0x4b, 0x56, 0x4d, 0x52);
539 obj.RedirectStartIder = String.fromCharCode(0x10, 0x00, 0x00, 0x00, 0x49, 0x44, 0x45, 0x52);
541 function hex_md5(str) { return meshcentral.certificateOperations.forge.md.md5.create().update(str).digest().toHex(); }
546function ToIntStr(v) { return String.fromCharCode((v & 0xFF), ((v >> 8) & 0xFF), ((v >> 16) & 0xFF), ((v >> 24) & 0xFF)); }
547function ToShortStr(v) { return String.fromCharCode((v & 0xFF), ((v >> 8) & 0xFF)); }