2017-08-28 19:27:45 +03:00
/ * *
2018-01-04 23:15:21 +03:00
* @ description MeshCentral Intel ( R ) AMT MPS server
2017-08-28 19:27:45 +03:00
* @ author Ylian Saint - Hilaire
2021-01-10 01:31:09 +03:00
* @ copyright Intel Corporation 2018 - 2021
2018-01-04 23:15:21 +03:00
* @ license Apache - 2.0
2017-08-28 19:27:45 +03:00
* @ version v0 . 0.1
* /
2018-08-30 03:40:30 +03:00
/*jslint node: true */
/*jshint node: true */
/*jshint strict:false */
/*jshint -W097 */
/*jshint esversion: 6 */
2019-12-28 02:18:43 +03:00
'use strict' ;
2018-08-27 22:24:15 +03:00
2017-08-28 19:27:45 +03:00
// Construct a Intel AMT MPS server object
module . exports . CreateMpsServer = function ( parent , db , args , certificates ) {
var obj = { } ;
2020-07-27 10:11:27 +03:00
obj . fs = require ( 'fs' ) ;
obj . path = require ( 'path' ) ;
2017-08-28 19:27:45 +03:00
obj . parent = parent ;
obj . db = db ;
2017-09-25 21:00:57 +03:00
obj . args = args ;
2017-08-28 19:27:45 +03:00
obj . certificates = certificates ;
2020-10-10 01:44:09 +03:00
obj . ciraConnections = { } ; // NodeID --> [ Socket ]
2019-01-06 01:15:14 +03:00
var tlsSessionStore = { } ; // Store TLS session information for quick resume.
var tlsSessionStoreCount = 0 ; // Number of cached TLS session information in store.
2019-02-27 02:49:44 +03:00
const constants = ( require ( 'crypto' ) . constants ? require ( 'crypto' ) . constants : require ( 'constants' ) ) ; // require('constants') is deprecated in Node 11.10, use require('crypto').constants instead.
2019-12-28 02:18:43 +03:00
const common = require ( './common.js' ) ;
const net = require ( 'net' ) ;
const tls = require ( 'tls' ) ;
2019-01-06 01:15:14 +03:00
const MAX _IDLE = 90000 ; // 90 seconds max idle time, higher than the typical KEEP-ALIVE periode of 60 seconds
2017-09-25 21:00:57 +03:00
2020-07-27 10:11:27 +03:00
// This MPS server is also a tiny HTTPS server. HTTP responses are here.
obj . httpResponses = {
'/' : '<!DOCTYPE html><html><head><meta charset=\"UTF-8\"></head><body>MeshCentral MPS server.<br />Intel® AMT computers should connect here.</body></html>'
//'/text.ico': { file: 'c:\\temp\\test.iso', maxserve: 3, maxtime: Date.now() + 15000 }
} ;
2020-10-16 21:57:29 +03:00
// Set the MPS external port only if it's not set to zero and we are not in LAN mode.
if ( ( args . lanonly != true ) && ( args . mpsport !== 0 ) ) {
if ( obj . args . mpstlsoffload ) {
obj . server = net . createServer ( onConnection ) ;
} else {
2021-04-12 08:44:43 +03:00
// Note that in order to support older Intel AMT CIRA connections, we have to turn on TLSv1.
2020-10-16 21:57:29 +03:00
obj . server = tls . createServer ( { key : certificates . mps . key , cert : certificates . mps . cert , minVersion : 'TLSv1' , requestCert : true , rejectUnauthorized : false , ciphers : "HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA" , secureOptions : constants . SSL _OP _NO _SSLv2 | constants . SSL _OP _NO _SSLv3 | constants . SSL _OP _NO _COMPRESSION } , onConnection ) ;
//obj.server.on('error', function () { console.log('MPS tls server error'); });
obj . server . on ( 'newSession' , function ( id , data , cb ) { if ( tlsSessionStoreCount > 1000 ) { tlsSessionStoreCount = 0 ; tlsSessionStore = { } ; } tlsSessionStore [ id . toString ( 'hex' ) ] = data ; tlsSessionStoreCount ++ ; cb ( ) ; } ) ;
obj . server . on ( 'resumeSession' , function ( id , cb ) { cb ( null , tlsSessionStore [ id . toString ( 'hex' ) ] || null ) ; } ) ;
}
obj . server . listen ( args . mpsport , args . mpsportbind , function ( ) {
console . log ( "MeshCentral Intel(R) AMT server running on " + certificates . AmtMpsName + ":" + args . mpsport + ( ( args . mpsaliasport != null ) ? ( ", alias port " + args . mpsaliasport ) : "" ) + "." ) ;
obj . parent . authLog ( 'mps' , 'Server listening on ' + ( ( args . mpsportbind != null ) ? args . mpsportbind : '0.0.0.0' ) + ' port ' + args . mpsport + '.' ) ;
} ) . on ( "error" , function ( err ) { console . error ( "ERROR: MeshCentral Intel(R) AMT server port " + args . mpsport + " is not available." ) ; if ( args . exactports ) { process . exit ( ) ; } } ) ;
2017-09-25 21:00:57 +03:00
2020-10-16 21:57:29 +03:00
obj . server . on ( 'tlsClientError' , function ( err , tlssocket ) { if ( args . mpsdebug ) { var remoteAddress = tlssocket . remoteAddress ; if ( tlssocket . remoteFamily == 'IPv6' ) { remoteAddress = '[' + remoteAddress + ']' ; } console . log ( 'MPS:Invalid TLS connection from ' + remoteAddress + ':' + tlssocket . remotePort + '.' ) ; } } ) ;
}
2020-02-17 21:24:32 +03:00
2019-12-28 02:18:43 +03:00
obj . parent . updateServerState ( 'mps-port' , args . mpsport ) ;
obj . parent . updateServerState ( 'mps-name' , certificates . AmtMpsName ) ;
if ( args . mpsaliasport != null ) { obj . parent . updateServerState ( 'mps-alias-port' , args . mpsaliasport ) ; }
2018-03-14 22:10:13 +03:00
2018-05-04 03:21:59 +03:00
const APFProtocol = {
2017-08-28 19:27:45 +03:00
UNKNOWN : 0 ,
DISCONNECT : 1 ,
SERVICE _REQUEST : 5 ,
SERVICE _ACCEPT : 6 ,
USERAUTH _REQUEST : 50 ,
USERAUTH _FAILURE : 51 ,
USERAUTH _SUCCESS : 52 ,
GLOBAL _REQUEST : 80 ,
REQUEST _SUCCESS : 81 ,
REQUEST _FAILURE : 82 ,
CHANNEL _OPEN : 90 ,
CHANNEL _OPEN _CONFIRMATION : 91 ,
CHANNEL _OPEN _FAILURE : 92 ,
CHANNEL _WINDOW _ADJUST : 93 ,
CHANNEL _DATA : 94 ,
CHANNEL _CLOSE : 97 ,
PROTOCOLVERSION : 192 ,
KEEPALIVE _REQUEST : 208 ,
KEEPALIVE _REPLY : 209 ,
KEEPALIVE _OPTIONS _REQUEST : 210 ,
2020-10-10 01:44:09 +03:00
KEEPALIVE _OPTIONS _REPLY : 211 ,
2020-10-12 19:29:34 +03:00
JSON _CONTROL : 250 // This is a Mesh specific command that sends JSON to and from the MPS server.
2018-08-30 03:40:30 +03:00
} ;
/ *
2018-05-04 03:21:59 +03:00
const APFDisconnectCode = {
2017-08-28 19:27:45 +03:00
HOST _NOT _ALLOWED _TO _CONNECT : 1 ,
PROTOCOL _ERROR : 2 ,
KEY _EXCHANGE _FAILED : 3 ,
RESERVED : 4 ,
MAC _ERROR : 5 ,
COMPRESSION _ERROR : 6 ,
SERVICE _NOT _AVAILABLE : 7 ,
PROTOCOL _VERSION _NOT _SUPPORTED : 8 ,
HOST _KEY _NOT _VERIFIABLE : 9 ,
CONNECTION _LOST : 10 ,
BY _APPLICATION : 11 ,
TOO _MANY _CONNECTIONS : 12 ,
AUTH _CANCELLED _BY _USER : 13 ,
NO _MORE _AUTH _METHODS _AVAILABLE : 14 ,
INVALID _CREDENTIALS : 15 ,
CONNECTION _TIMED _OUT : 16 ,
BY _POLICY : 17 ,
TEMPORARILY _UNAVAILABLE : 18
2018-08-30 03:40:30 +03:00
} ;
2018-05-04 03:21:59 +03:00
const APFChannelOpenFailCodes = {
2017-08-28 19:27:45 +03:00
ADMINISTRATIVELY _PROHIBITED : 1 ,
CONNECT _FAILED : 2 ,
UNKNOWN _CHANNEL _TYPE : 3 ,
RESOURCE _SHORTAGE : 4 ,
2018-08-30 03:40:30 +03:00
} ;
* /
2018-05-04 03:21:59 +03:00
const APFChannelOpenFailureReasonCode = {
2017-08-28 19:27:45 +03:00
AdministrativelyProhibited : 1 ,
ConnectFailed : 2 ,
UnknownChannelType : 3 ,
ResourceShortage : 4 ,
2018-08-30 03:40:30 +03:00
} ;
2019-05-02 01:02:03 +03:00
// Stat counters
var connectionCount = 0 ;
var userAuthRequestCount = 0 ;
var incorrectPasswordCount = 0 ;
var meshNotFoundCount = 0 ;
var unknownTlsNodeCount = 0 ;
var unknownTlsMeshIdCount = 0 ;
var addedTlsDeviceCount = 0 ;
var unknownNodeCount = 0 ;
var unknownMeshIdCount = 0 ;
var addedDeviceCount = 0 ;
var ciraTimeoutCount = 0 ;
var protocolVersionCount = 0 ;
var badUserNameLengthCount = 0 ;
var channelOpenCount = 0 ;
var channelOpenConfirmCount = 0 ;
var channelOpenFailCount = 0 ;
var channelCloseCount = 0 ;
var disconnectCommandCount = 0 ;
var socketClosedCount = 0 ;
var socketErrorCount = 0 ;
2019-06-06 01:24:07 +03:00
var maxDomainDevicesReached = 0 ;
2019-05-02 01:02:03 +03:00
2020-10-10 01:44:09 +03:00
// Add a CIRA connection to the connection list
function addCiraConnection ( socket ) {
// Check if there is already a connection of the same type
var sameType = false , connections = obj . ciraConnections [ socket . tag . nodeid ] ;
if ( connections != null ) { for ( var i in connections ) { var conn = connections [ i ] ; if ( conn . tag . connType === socket . tag . connType ) { sameType = true ; } } }
// Add this connection to the connections list
if ( connections == null ) { obj . ciraConnections [ socket . tag . nodeid ] = [ socket ] ; } else { obj . ciraConnections [ socket . tag . nodeid ] . push ( socket ) ; }
// Update connectivity state
// Report the new state of a CIRA/Relay/LMS connection after a short delay. This is to wait for the connection to have the bounded ports setup before we advertise this new connection.
socket . xxStartHold = 1 ;
var f = function setConnFunc ( ) {
delete setConnFunc . socket . xxStartHold ;
const ciraArray = obj . ciraConnections [ setConnFunc . socket . tag . nodeid ] ;
if ( ( ciraArray != null ) && ( ( ciraArray . indexOf ( setConnFunc . socket ) >= 0 ) ) ) { // Check if this connection is still present
if ( setConnFunc . socket . tag . connType == 0 ) {
// Intel AMT CIRA connection. This connection indicates the remote device is present.
obj . parent . SetConnectivityState ( setConnFunc . socket . tag . meshid , setConnFunc . socket . tag . nodeid , setConnFunc . socket . tag . connectTime , 2 , 7 ) ; // 7 = Present
} else if ( setConnFunc . socket . tag . connType == 1 ) {
// Intel AMT Relay connection. This connection does not give any information about the remote device's power state.
obj . parent . SetConnectivityState ( setConnFunc . socket . tag . meshid , setConnFunc . socket . tag . nodeid , setConnFunc . socket . tag . connectTime , 8 , 0 ) ; // 0 = Unknown
}
2020-10-16 02:08:36 +03:00
// Intel AMT LMS connection (connType == 2), we don't notify of these connections except telling the Intel AMT manager about them.
// If the AMT manager is present, start management of this device
if ( obj . parent . amtManager != null ) { obj . parent . amtManager . startAmtManagement ( setConnFunc . socket . tag . nodeid , setConnFunc . socket . tag . connType , setConnFunc . socket ) ; }
2020-10-10 01:44:09 +03:00
}
}
f . socket = socket ;
2020-10-08 22:50:27 +03:00
setTimeout ( f , 300 ) ;
}
2020-10-10 01:44:09 +03:00
// Remove a CIRA connection from the connection list
function removeCiraConnection ( socket ) {
2020-10-16 02:08:36 +03:00
// If the AMT manager is present, stop management of this device
if ( obj . parent . amtManager != null ) { obj . parent . amtManager . stopAmtManagement ( socket . tag . nodeid , socket . tag . connType , socket ) ; }
2020-10-10 01:44:09 +03:00
// Remove the connection from the list if present.
const ciraArray = obj . ciraConnections [ socket . tag . nodeid ] ;
if ( ciraArray == null ) return ;
var i = ciraArray . indexOf ( socket ) ;
if ( i == - 1 ) return ;
ciraArray . splice ( i , 1 ) ;
if ( ciraArray . length == 0 ) { delete obj . ciraConnections [ socket . tag . nodeid ] ; } else { obj . ciraConnections [ socket . tag . nodeid ] = ciraArray ; }
// If we are removing a connection during the hold period, don't clear any state since it was never set.
if ( socket . xxStartHold == 1 ) return ;
// Check if there is already a connection of the same type
var sameType = false , connections = obj . ciraConnections [ socket . tag . nodeid ] ;
if ( connections != null ) { for ( var i in connections ) { var conn = connections [ i ] ; if ( conn . tag . connType === socket . tag . connType ) { sameType = true ; } } }
if ( sameType == true ) return ; // if there is a connection of the same type, don't change the connection state.
// Update connectivity state
if ( socket . tag . connType == 0 ) {
obj . parent . ClearConnectivityState ( socket . tag . meshid , socket . tag . nodeid , 2 ) ; // CIRA
} else if ( socket . tag . connType == 1 ) {
obj . parent . ClearConnectivityState ( socket . tag . meshid , socket . tag . nodeid , 8 ) ; // Relay
}
}
2019-05-02 01:02:03 +03:00
// Return statistics about this MPS server
obj . getStats = function ( ) {
2020-10-10 01:44:09 +03:00
var ciraConnectionCount = 0 ;
for ( var i in obj . ciraConnections ) { ciraConnectionCount += obj . ciraConnections [ i ] . length ; }
2019-05-02 01:02:03 +03:00
return {
2020-10-10 01:44:09 +03:00
ciraConnections : ciraConnectionCount ,
2019-05-02 01:02:03 +03:00
tlsSessionStore : Object . keys ( tlsSessionStore ) . length ,
connectionCount : connectionCount ,
userAuthRequestCount : userAuthRequestCount ,
incorrectPasswordCount : incorrectPasswordCount ,
meshNotFoundCount : meshNotFoundCount ,
unknownTlsNodeCount : unknownTlsNodeCount ,
unknownTlsMeshIdCount : unknownTlsMeshIdCount ,
addedTlsDeviceCount : addedTlsDeviceCount ,
unknownNodeCount : unknownNodeCount ,
unknownMeshIdCount : unknownMeshIdCount ,
addedDeviceCount : addedDeviceCount ,
ciraTimeoutCount : ciraTimeoutCount ,
protocolVersionCount : protocolVersionCount ,
badUserNameLengthCount : badUserNameLengthCount ,
channelOpenCount : channelOpenCount ,
channelOpenConfirmCount : channelOpenConfirmCount ,
channelOpenFailCount : channelOpenFailCount ,
channelCloseCount : channelCloseCount ,
disconnectCommandCount : disconnectCommandCount ,
socketClosedCount : socketClosedCount ,
2019-06-06 01:24:07 +03:00
socketErrorCount : socketErrorCount ,
2019-10-04 22:18:56 +03:00
maxDomainDevicesReached : maxDomainDevicesReached
2019-05-02 01:02:03 +03:00
} ;
}
2019-10-04 22:18:56 +03:00
// Required for TLS piping to MQTT broker
2019-10-03 03:57:18 +03:00
function SerialTunnel ( options ) {
var obj = new require ( 'stream' ) . Duplex ( options ) ;
obj . forwardwrite = null ;
obj . updateBuffer = function ( chunk ) { this . push ( chunk ) ; } ;
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
obj . _read = function ( size ) { } ; // Push nothing, anything to read should be pushed from updateBuffer()
return obj ;
2019-10-04 22:18:56 +03:00
}
2019-10-03 03:57:18 +03:00
2019-10-04 22:18:56 +03:00
// Return's the length of an MQTT packet
2019-10-03 03:57:18 +03:00
function getMQTTPacketLength ( chunk ) {
var packet _len = 0 ;
2019-10-04 22:18:56 +03:00
if ( chunk . readUInt8 ( 0 ) == 16 ) {
if ( chunk . readUInt8 ( 1 ) < 128 ) {
2019-10-03 03:57:18 +03:00
packet _len += chunk . readUInt8 ( 1 ) + 2 ;
} else {
// continuation bit, get real value and do next
packet _len += ( chunk . readUInt8 ( 1 ) & 0x7F ) + 2 ;
if ( chunk . readUInt8 ( 2 ) < 128 ) {
packet _len += 1 + chunk . readUInt8 ( 2 ) * 128 ;
} else {
packet _len += 1 + ( chunk . readUInt8 ( 2 ) & 0x7F ) * 128 ;
if ( chunk . readUInt8 ( 3 ) < 128 ) {
packet _len += 1 + chunk . readUInt8 ( 3 ) * 128 * 128 ;
} else {
packet _len += 1 + ( chunk . readUInt8 ( 3 ) & 0x7F ) * 128 * 128 ;
if ( chunk . readUInt8 ( 4 ) < 128 ) {
packet _len += 1 + chunk . readUInt8 ( 4 ) * 128 * 128 * 128 ;
} else {
2019-10-04 22:18:56 +03:00
packet _len += 1 + ( chunk . readUInt8 ( 4 ) & 0x7F ) * 128 * 128 * 128 ;
2019-10-03 03:57:18 +03:00
}
2019-10-04 22:18:56 +03:00
}
2019-10-03 03:57:18 +03:00
}
}
}
return packet _len ;
}
2020-10-23 00:35:12 +03:00
obj . onWebSocketConnection = function ( socket , req ) {
2020-10-09 05:47:24 +03:00
connectionCount ++ ;
2020-10-10 01:44:09 +03:00
// connType: 0 = CIRA, 1 = Relay, 2 = LMS
socket . tag = { first : true , connType : 0 , clientCert : null , accumulator : '' , activetunnels : 0 , boundPorts : [ ] , websocket : true , socket : socket , host : null , nextchannelid : 4 , channels : { } , nextsourceport : 0 } ;
socket . SetupChannel = function SetupChannel ( targetport ) { return SetupChannel . parent . SetupChannel ( SetupChannel . conn , targetport ) ; }
socket . SetupChannel . parent = obj ;
socket . SetupChannel . conn = socket ;
2020-10-09 05:47:24 +03:00
socket . websocket = 1 ;
2020-10-18 05:23:24 +03:00
socket . ControlMsg = function ControlMsg ( message ) { return ControlMsg . parent . SendJsonControl ( ControlMsg . conn , message ) ; }
socket . ControlMsg . parent = obj ;
socket . ControlMsg . conn = socket ;
2020-10-23 00:35:12 +03:00
socket . remoteAddr = req . clientIp ;
socket . remotePort = socket . _socket . remotePort ;
2021-05-05 20:59:47 +03:00
socket . _socket . bytesReadEx = 0 ;
socket . _socket . bytesWrittenEx = 0 ;
2020-10-09 05:47:24 +03:00
parent . debug ( 'mps' , "New CIRA websocket connection" ) ;
socket . on ( 'message' , function ( data ) {
if ( args . mpsdebug ) { var buf = Buffer . from ( data , 'binary' ) ; console . log ( "MPS <-- (" + buf . length + "):" + buf . toString ( 'hex' ) ) ; } // Print out received bytes
2021-05-05 20:59:47 +03:00
// Traffic accounting
parent . webserver . trafficStats . LMSIn += ( this . _socket . bytesRead - this . _socket . bytesReadEx ) ;
parent . webserver . trafficStats . LMSOut += ( this . _socket . bytesWritten - this . _socket . bytesWrittenEx ) ;
this . _socket . bytesReadEx = this . _socket . bytesRead ;
this . _socket . bytesWrittenEx = this . _socket . bytesWritten ;
2020-10-09 05:47:24 +03:00
this . tag . accumulator += data . toString ( 'binary' ) ; // Append as binary string
try {
// Parse all of the APF data we can
var l = 0 ;
do { l = ProcessCommand ( this ) ; if ( l > 0 ) { this . tag . accumulator = this . tag . accumulator . substring ( l ) ; } } while ( l > 0 ) ;
if ( l < 0 ) { this . terminate ( ) ; }
} catch ( e ) {
console . log ( e ) ;
}
} ) ;
socket . addListener ( 'close' , function ( ) {
2021-05-05 20:59:47 +03:00
// Traffic accounting
parent . webserver . trafficStats . LMSIn += ( this . _socket . bytesRead - this . _socket . bytesReadEx ) ;
parent . webserver . trafficStats . LMSOut += ( this . _socket . bytesWritten - this . _socket . bytesWrittenEx ) ;
this . _socket . bytesReadEx = this . _socket . bytesRead ;
this . _socket . bytesWrittenEx = this . _socket . bytesWritten ;
2020-10-09 05:47:24 +03:00
socketClosedCount ++ ;
parent . debug ( 'mps' , "CIRA websocket closed" , this . tag . meshid , this . tag . nodeid ) ;
2020-10-10 01:44:09 +03:00
removeCiraConnection ( socket ) ;
2020-10-09 05:47:24 +03:00
} ) ;
socket . addListener ( 'error' , function ( e ) {
socketErrorCount ++ ;
parent . debug ( 'mps' , "CIRA websocket connection error" , e ) ;
} ) ;
}
2019-10-04 22:18:56 +03:00
// Called when a new TLS/TCP connection is accepted
2017-08-28 19:27:45 +03:00
function onConnection ( socket ) {
2019-05-02 01:02:03 +03:00
connectionCount ++ ;
2020-10-10 01:44:09 +03:00
// connType: 0 = CIRA, 1 = Relay, 2 = LMS
2018-11-07 06:41:21 +03:00
if ( obj . args . mpstlsoffload ) {
2020-10-10 01:44:09 +03:00
socket . tag = { first : true , connType : 0 , clientCert : null , accumulator : '' , activetunnels : 0 , boundPorts : [ ] , socket : socket , host : null , nextchannelid : 4 , channels : { } , nextsourceport : 0 } ;
2017-09-25 21:00:57 +03:00
} else {
2020-10-10 01:44:09 +03:00
socket . tag = { first : true , connType : 0 , clientCert : socket . getPeerCertificate ( true ) , accumulator : '' , activetunnels : 0 , boundPorts : [ ] , socket : socket , host : null , nextchannelid : 4 , channels : { } , nextsourceport : 0 } ;
2017-09-25 21:00:57 +03:00
}
2020-10-10 01:44:09 +03:00
socket . SetupChannel = function SetupChannel ( targetport ) { return SetupChannel . parent . SetupChannel ( SetupChannel . conn , targetport ) ; }
socket . SetupChannel . parent = obj ;
socket . SetupChannel . conn = socket ;
2020-10-19 08:02:48 +03:00
socket . ControlMsg = function ControlMsg ( message ) { return ControlMsg . parent . SendJsonControl ( ControlMsg . conn , message ) ; }
socket . ControlMsg . parent = obj ;
socket . ControlMsg . conn = socket ;
2021-05-05 20:59:47 +03:00
socket . bytesReadEx = 0 ;
socket . bytesWrittenEx = 0 ;
2020-10-23 00:35:12 +03:00
socket . remoteAddr = cleanRemoteAddr ( socket . remoteAddress ) ;
//socket.remotePort is already present, no need to set it.
2019-12-28 02:18:43 +03:00
socket . setEncoding ( 'binary' ) ;
2019-08-23 01:31:39 +03:00
parent . debug ( 'mps' , "New CIRA connection" ) ;
2018-05-03 21:09:29 +03:00
// Setup the CIRA keep alive timer
2018-05-04 03:21:59 +03:00
socket . setTimeout ( MAX _IDLE ) ;
2019-12-28 02:18:43 +03:00
socket . on ( 'timeout' , ( ) => { ciraTimeoutCount ++ ; parent . debug ( 'mps' , "CIRA timeout, disconnecting." ) ; try { socket . end ( ) ; } catch ( e ) { } } ) ;
2018-05-03 21:09:29 +03:00
2020-10-09 05:47:24 +03:00
socket . addListener ( 'close' , function ( ) {
2021-05-05 20:59:47 +03:00
// Traffic accounting
parent . webserver . trafficStats . CIRAIn += ( this . bytesRead - this . bytesReadEx ) ;
parent . webserver . trafficStats . CIRAOut += ( this . bytesWritten - this . bytesWrittenEx ) ;
this . bytesReadEx = this . bytesRead ;
this . bytesWrittenEx = this . bytesWritten ;
2020-10-09 05:47:24 +03:00
socketClosedCount ++ ;
parent . debug ( 'mps' , 'CIRA connection closed' ) ;
2020-10-10 01:44:09 +03:00
removeCiraConnection ( socket ) ;
2020-10-09 05:47:24 +03:00
} ) ;
socket . addListener ( 'error' , function ( e ) {
socketErrorCount ++ ;
parent . debug ( 'mps' , 'CIRA connection error' , e ) ;
//console.log("MPS Error: " + socket.remoteAddress);
} ) ;
2019-12-28 02:18:43 +03:00
socket . addListener ( 'data' , function ( data ) {
2020-10-09 00:25:16 +03:00
if ( args . mpsdebug ) { var buf = Buffer . from ( data , 'binary' ) ; console . log ( "MPS --> (" + buf . length + "):" + buf . toString ( 'hex' ) ) ; } // Print out received bytes
2021-05-05 20:59:47 +03:00
// Traffic accounting
parent . webserver . trafficStats . CIRAIn += ( this . bytesRead - this . bytesReadEx ) ;
parent . webserver . trafficStats . CIRAOut += ( this . bytesWritten - this . bytesWrittenEx ) ;
this . bytesReadEx = this . bytesRead ;
this . bytesWrittenEx = this . bytesWritten ;
2017-08-28 19:27:45 +03:00
socket . tag . accumulator += data ;
2018-08-30 03:40:30 +03:00
2017-08-28 19:27:45 +03:00
// Detect if this is an HTTPS request, if it is, return a simple answer and disconnect. This is useful for debugging access to the MPS port.
if ( socket . tag . first == true ) {
2020-07-27 10:11:27 +03:00
if ( socket . tag . accumulator . length < 5 ) return ;
2017-08-28 19:27:45 +03:00
//if (!socket.tag.clientCert.subject) { console.log("MPS Connection, no client cert: " + socket.remoteAddress); socket.write('HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\nMeshCentral2 MPS server.\r\nNo client certificate given.'); socket.end(); return; }
2020-07-27 10:11:27 +03:00
if ( ( socket . tag . accumulator . substring ( 0 , 4 ) == 'GET ' ) || ( socket . tag . accumulator . substring ( 0 , 5 ) == 'HEAD ' ) ) {
if ( args . mpsdebug ) { console . log ( "MPS Connection, HTTP request detected: " + socket . remoteAddress ) ; }
socket . removeAllListeners ( 'data' ) ;
socket . removeAllListeners ( 'close' ) ;
socket . on ( 'data' , onHttpData ) ;
socket . on ( 'close' , onHttpClose ) ;
obj . httpSocket = socket ;
onHttpData . call ( socket , data ) ;
return ;
}
2019-10-04 22:18:56 +03:00
// If the MQTT broker is active, look for inbound MQTT connections
if ( parent . mqttbroker != null ) {
2019-12-28 02:18:43 +03:00
var chunk = Buffer . from ( socket . tag . accumulator , 'binary' ) ;
2019-10-04 22:18:56 +03:00
var packet _len = 0 ;
if ( chunk . readUInt8 ( 0 ) == 16 ) { packet _len = getMQTTPacketLength ( chunk ) ; }
if ( chunk . readUInt8 ( 0 ) == 16 && ( socket . tag . accumulator . length < packet _len ) ) return ; // Minimum MQTT detection
// check if it is MQTT, need more initial packet to probe
2019-12-28 02:18:43 +03:00
if ( chunk . readUInt8 ( 0 ) == 16 && ( ( chunk . slice ( 4 , 8 ) . toString ( ) === 'MQTT' ) || ( chunk . slice ( 5 , 9 ) . toString ( ) === 'MQTT' )
|| ( chunk . slice ( 6 , 10 ) . toString ( ) === 'MQTT' ) || ( chunk . slice ( 7 , 11 ) . toString ( ) === 'MQTT' ) ) ) {
parent . debug ( 'mps' , "MQTT connection detected." ) ;
2020-07-27 10:11:27 +03:00
socket . removeAllListeners ( 'data' ) ;
socket . removeAllListeners ( 'close' ) ;
2019-10-04 22:18:56 +03:00
socket . setNoDelay ( true ) ;
socket . serialtunnel = SerialTunnel ( ) ;
2019-10-05 03:24:30 +03:00
socket . serialtunnel . xtransport = 'mps' ;
socket . serialtunnel . xip = socket . remoteAddress ;
2019-12-28 02:18:43 +03:00
socket . on ( 'data' , function ( b ) { socket . serialtunnel . updateBuffer ( Buffer . from ( b , 'binary' ) ) } ) ;
socket . serialtunnel . forwardwrite = function ( b ) { socket . write ( b , 'binary' ) }
socket . on ( 'close' , function ( ) { socket . serialtunnel . emit ( 'end' ) ; } ) ;
2019-10-04 22:18:56 +03:00
// Pass socket wrapper to the MQTT broker
parent . mqttbroker . handle ( socket . serialtunnel ) ;
socket . unshift ( socket . tag . accumulator ) ;
return ;
}
2019-10-03 03:57:18 +03:00
}
2019-10-04 22:18:56 +03:00
2017-08-28 19:27:45 +03:00
socket . tag . first = false ;
2018-08-30 03:40:30 +03:00
2017-08-28 19:27:45 +03:00
// Setup this node with certificate authentication
if ( socket . tag . clientCert && socket . tag . clientCert . subject && socket . tag . clientCert . subject . O && socket . tag . clientCert . subject . O . length == 64 ) {
// This is a node where the MeshID is indicated within the CIRA certificate
var domainid = '' , meshid ;
var xx = socket . tag . clientCert . subject . O . split ( '/' ) ;
2017-10-16 03:36:06 +03:00
if ( xx . length == 1 ) { meshid = xx [ 0 ] ; } else { domainid = xx [ 0 ] . toLowerCase ( ) ; meshid = xx [ 1 ] ; }
2017-08-28 19:27:45 +03:00
2019-06-06 01:24:07 +03:00
// Check the incoming domain
var domain = obj . parent . config . domains [ domainid ] ;
if ( domain == null ) { console . log ( 'CIRA connection for invalid domain. meshid: ' + meshid ) ; socket . end ( ) ; return ; }
socket . tag . domain = domain ;
2017-08-28 19:27:45 +03:00
socket . tag . domainid = domainid ;
socket . tag . meshid = 'mesh/' + domainid + '/' + meshid ;
2017-10-16 03:36:06 +03:00
socket . tag . nodeid = 'node/' + domainid + '/' + require ( 'crypto' ) . createHash ( 'sha384' ) . update ( common . hex2rstr ( socket . tag . clientCert . modulus , 'binary' ) ) . digest ( 'base64' ) . replace ( /\+/g , '@' ) . replace ( /\//g , '$' ) ;
2017-08-28 19:27:45 +03:00
socket . tag . name = socket . tag . clientCert . subject . CN ;
socket . tag . connectTime = Date . now ( ) ;
socket . tag . host = '' ;
2019-10-11 02:07:32 +03:00
// Fetch the node
obj . db . Get ( socket . tag . nodeid , function ( err , nodes ) {
if ( ( nodes == null ) || ( nodes . length !== 1 ) ) {
var mesh = obj . parent . webserver . meshes [ socket . tag . meshid ] ;
if ( mesh == null ) {
unknownTlsMeshIdCount ++ ;
console . log ( 'ERROR: Intel AMT CIRA connected with unknown groupid: ' + socket . tag . meshid ) ;
socket . end ( ) ;
return ;
} else if ( mesh . mtype == 1 ) {
// Check if we already have too many devices for this domain
if ( domain . limits && ( typeof domain . limits . maxdevices == 'number' ) ) {
db . isMaxType ( domain . limits . maxdevices , 'node' , domain . id , function ( ismax , count ) {
if ( ismax == true ) {
// Too many devices in this domain.
maxDomainDevicesReached ++ ;
console . log ( 'Too many devices on this domain to accept the CIRA connection. meshid: ' + socket . tag . meshid ) ;
socket . end ( ) ;
2019-06-06 01:24:07 +03:00
} else {
2021-03-08 22:06:39 +03:00
// Attempts reverse DNS loopup on the device IP address
require ( 'dns' ) . reverse ( socket . remoteAddr , function ( err , hostnames ) {
var hostname = socket . remoteAddr ;
if ( ( err == null ) && ( hostnames != null ) && ( hostnames . length > 0 ) ) { hostname = hostnames [ 0 ] ; }
// We are under the limit, create the new device.
// Node is not in the database, add it. Credentials will be empty until added by the user.
var device = { type : 'node' , mtype : 1 , _id : socket . tag . nodeid , meshid : socket . tag . meshid , name : socket . tag . name , icon : ( socket . tag . meiState . isBatteryPowered ) ? 2 : 1 , host : hostname , domain : domainid , intelamt : { user : ( typeof socket . tag . meiState . amtuser == 'string' ) ? socket . tag . meiState . amtuser : '' , pass : ( typeof socket . tag . meiState . amtpass == 'string' ) ? socket . tag . meiState . amtpass : '' , tls : 0 , state : 2 } } ;
if ( ( typeof socket . tag . meiState . desc == 'string' ) && ( socket . tag . meiState . desc . length > 0 ) && ( socket . tag . meiState . desc . length < 1024 ) ) { device . desc = socket . tag . meiState . desc ; }
obj . db . Set ( device ) ;
// Event the new node
addedTlsDeviceCount ++ ;
var change = 'CIRA added device ' + socket . tag . name + ' to mesh ' + mesh . name ;
obj . parent . DispatchEvent ( [ '*' , socket . tag . meshid ] , obj , { etype : 'node' , action : 'addnode' , node : parent . webserver . CloneSafeNode ( device ) , msg : change , domain : domainid } ) ;
// Add the connection to the MPS connection list
addCiraConnection ( socket ) ;
} ) ;
2019-06-06 01:24:07 +03:00
}
2019-10-11 02:07:32 +03:00
} ) ;
return ;
2017-08-28 19:27:45 +03:00
} else {
2021-03-08 22:06:39 +03:00
// Attempts reverse DNS loopup on the device IP address
require ( 'dns' ) . reverse ( socket . remoteAddr , function ( err , hostnames ) {
var hostname = socket . remoteAddr ;
if ( ( err == null ) && ( hostnames != null ) && ( hostnames . length > 0 ) ) { hostname = hostnames [ 0 ] ; }
2017-08-28 19:27:45 +03:00
2021-03-08 22:06:39 +03:00
// Node is not in the database, add it. Credentials will be empty until added by the user.
var device = { type : 'node' , mtype : 1 , _id : socket . tag . nodeid , meshid : socket . tag . meshid , name : socket . tag . name , icon : ( socket . tag . meiState . isBatteryPowered ) ? 2 : 1 , host : hostname , domain : domainid , intelamt : { user : ( typeof socket . tag . meiState . amtuser == 'string' ) ? socket . tag . meiState . amtuser : '' , pass : ( typeof socket . tag . meiState . amtpass == 'string' ) ? socket . tag . meiState . amtpass : '' , tls : 0 , state : 2 } } ;
if ( ( typeof socket . tag . meiState . desc == 'string' ) && ( socket . tag . meiState . desc . length > 0 ) && ( socket . tag . meiState . desc . length < 1024 ) ) { device . desc = socket . tag . meiState . desc ; }
obj . db . Set ( device ) ;
// Event the new node
addedTlsDeviceCount ++ ;
var change = 'CIRA added device ' + socket . tag . name + ' to mesh ' + mesh . name ;
obj . parent . DispatchEvent ( [ '*' , socket . tag . meshid ] , obj , { etype : 'node' , action : 'addnode' , node : parent . webserver . CloneSafeNode ( device ) , msg : change , domain : domainid } ) ;
} ) ;
2019-10-11 02:07:32 +03:00
}
} else {
// New CIRA connection for unknown node, disconnect.
unknownTlsNodeCount ++ ;
console . log ( 'CIRA connection for unknown node with incorrect group type. meshid: ' + socket . tag . meshid ) ;
socket . end ( ) ;
return ;
}
2017-08-28 19:27:45 +03:00
} else {
2019-10-11 02:07:32 +03:00
// Node is already present
var node = nodes [ 0 ] ;
socket . tag . meshid = node . meshid ; // Correct the MeshID if the node has moved.
if ( ( node . intelamt != null ) && ( node . intelamt . state == 2 ) ) { socket . tag . host = node . intelamt . host ; }
2017-08-28 19:27:45 +03:00
}
2019-10-11 02:07:32 +03:00
// Add the connection to the MPS connection list
2020-10-10 01:44:09 +03:00
addCiraConnection ( socket ) ;
2017-08-28 19:27:45 +03:00
} ) ;
} else {
// This node connected without certificate authentication, use password auth
//console.log('Intel AMT CIRA connected without certificate authentication');
}
}
try {
// Parse all of the APF data we can
var l = 0 ;
do { l = ProcessCommand ( socket ) ; if ( l > 0 ) { socket . tag . accumulator = socket . tag . accumulator . substring ( l ) ; } } while ( l > 0 ) ;
if ( l < 0 ) { socket . end ( ) ; }
} catch ( e ) {
console . log ( e ) ;
}
} ) ;
2020-10-09 05:47:24 +03:00
}
2017-10-15 09:22:19 +03:00
2020-10-09 05:47:24 +03:00
// Process one APF command
function ProcessCommand ( socket ) {
var cmd = socket . tag . accumulator . charCodeAt ( 0 ) ;
var len = socket . tag . accumulator . length ;
var data = socket . tag . accumulator ;
if ( len == 0 ) { return 0 ; }
switch ( cmd ) {
case APFProtocol . KEEPALIVE _REQUEST : {
if ( len < 5 ) return 0 ;
parent . debug ( 'mpscmd' , '--> KEEPALIVE_REQUEST' ) ;
SendKeepAliveReply ( socket , common . ReadInt ( data , 1 ) ) ;
return 5 ;
}
case APFProtocol . KEEPALIVE _REPLY : {
if ( len < 5 ) return 0 ;
parent . debug ( 'mpscmd' , '--> KEEPALIVE_REPLY' ) ;
return 5 ;
}
case APFProtocol . PROTOCOLVERSION : {
if ( len < 93 ) return 0 ;
protocolVersionCount ++ ;
socket . tag . MajorVersion = common . ReadInt ( data , 1 ) ;
socket . tag . MinorVersion = common . ReadInt ( data , 5 ) ;
socket . tag . SystemId = guidToStr ( common . rstr2hex ( data . substring ( 13 , 29 ) ) ) . toLowerCase ( ) ;
parent . debug ( 'mpscmd' , '--> PROTOCOLVERSION' , socket . tag . MajorVersion , socket . tag . MinorVersion , socket . tag . SystemId ) ;
return 93 ;
}
case APFProtocol . USERAUTH _REQUEST : {
if ( len < 13 ) return 0 ;
userAuthRequestCount ++ ;
var usernameLen = common . ReadInt ( data , 1 ) ;
var username = data . substring ( 5 , 5 + usernameLen ) ;
var serviceNameLen = common . ReadInt ( data , 5 + usernameLen ) ;
var serviceName = data . substring ( 9 + usernameLen , 9 + usernameLen + serviceNameLen ) ;
var methodNameLen = common . ReadInt ( data , 9 + usernameLen + serviceNameLen ) ;
var methodName = data . substring ( 13 + usernameLen + serviceNameLen , 13 + usernameLen + serviceNameLen + methodNameLen ) ;
var passwordLen = 0 , password = null ;
if ( methodName == 'password' ) {
passwordLen = common . ReadInt ( data , 14 + usernameLen + serviceNameLen + methodNameLen ) ;
password = data . substring ( 18 + usernameLen + serviceNameLen + methodNameLen , 18 + usernameLen + serviceNameLen + methodNameLen + passwordLen ) ;
2017-08-28 19:27:45 +03:00
}
2020-10-09 05:47:24 +03:00
//console.log('MPS:USERAUTH_REQUEST user=' + username + ', service=' + serviceName + ', method=' + methodName + ', password=' + password);
parent . debug ( 'mpscmd' , '--> USERAUTH_REQUEST user=' + username + ', service=' + serviceName + ', method=' + methodName + ', password=' + password ) ;
2020-10-23 01:10:39 +03:00
// If the login uses a cookie, check this now
if ( ( username == '**MeshAgentApfTunnel**' ) && ( password != null ) ) {
const cookie = parent . decodeCookie ( password , parent . loginCookieEncryptionKey ) ;
2021-04-01 23:53:34 +03:00
if ( ( cookie == null ) || ( cookie . a !== 'apf' ) ) {
incorrectPasswordCount ++ ;
socket . ControlMsg ( { action : 'console' , msg : 'Invalid login username/password' } ) ;
parent . debug ( 'mps' , 'Incorrect password' , username , password ) ;
SendUserAuthFail ( socket ) ;
return - 1 ;
}
if ( obj . parent . webserver . meshes [ cookie . m ] == null ) {
meshNotFoundCount ++ ;
socket . ControlMsg ( { action : 'console' , msg : 'Device group not found' } ) ;
parent . debug ( 'mps' , 'Device group not found' , username , password ) ;
SendUserAuthFail ( socket ) ;
return - 1 ;
}
2020-10-23 01:10:39 +03:00
// Setup the connection
socket . tag . nodeid = cookie . n ;
socket . tag . meshid = cookie . m ;
socket . tag . connectTime = Date . now ( ) ;
// Add the connection to the MPS connection list
addCiraConnection ( socket ) ;
SendUserAuthSuccess ( socket ) ; // Notify the auth success on the CIRA connection
return 18 + usernameLen + serviceNameLen + methodNameLen + passwordLen ;
} else {
// Check the CIRA password
2021-04-01 23:53:34 +03:00
if ( ( args . mpspass != null ) && ( password != args . mpspass ) ) {
incorrectPasswordCount ++ ;
socket . ControlMsg ( { action : 'console' , msg : 'Invalid login username/password' } ) ;
parent . debug ( 'mps' , 'Incorrect password' , username , password ) ;
SendUserAuthFail ( socket ) ;
return - 1 ;
}
2020-10-23 01:10:39 +03:00
// Check the CIRA username, which should be the start of the MeshID.
2021-04-01 23:53:34 +03:00
if ( usernameLen != 16 ) {
badUserNameLengthCount ++ ;
socket . ControlMsg ( { action : 'console' , msg : 'Username length not 16' } ) ;
parent . debug ( 'mps' , 'Username length not 16' , username , password ) ;
SendUserAuthFail ( socket ) ;
return - 1 ;
}
2021-04-02 00:07:46 +03:00
// Find the device group for this CIRA connection. Since Intel AMT does not allow @ or $ in the username, we escape these.
// For possible for CIRA-LMS connections to still send @ or $, so we need to escape both sides.
var meshIdStart = ( '/' + username ) . replace ( /\@/g , 'X' ) . replace ( /\$/g , 'X' ) , mesh = null ;
if ( obj . parent . webserver . meshes ) {
for ( var i in obj . parent . webserver . meshes ) {
if ( obj . parent . webserver . meshes [ i ] . _id . replace ( /\@/g , 'X' ) . replace ( /\$/g , 'X' ) . indexOf ( meshIdStart ) > 0 ) {
mesh = obj . parent . webserver . meshes [ i ] ; break ;
}
}
}
2021-04-01 23:53:34 +03:00
if ( mesh == null ) {
meshNotFoundCount ++ ;
socket . ControlMsg ( { action : 'console' , msg : 'Device group not found' } ) ;
parent . debug ( 'mps' , 'Device group not found' , username , password ) ;
SendUserAuthFail ( socket ) ;
return - 1 ;
}
2020-10-23 01:10:39 +03:00
}
2020-10-09 05:47:24 +03:00
// If this is a agent-less mesh, use the device guid 3 times as ID.
if ( mesh . mtype == 1 ) {
// Intel AMT GUID (socket.tag.SystemId) will be used as NodeID
var systemid = socket . tag . SystemId . split ( '-' ) . join ( '' ) ;
var nodeid = Buffer . from ( systemid + systemid + systemid , 'hex' ) . toString ( 'base64' ) . replace ( /\+/g , '@' ) . replace ( /\//g , '$' ) ;
var domain = obj . parent . config . domains [ mesh . domain ] ;
socket . tag . domain = domain ;
socket . tag . domainid = mesh . domain ;
2020-12-10 11:03:00 +03:00
if ( socket . tag . name == null ) { socket . tag . name = '' ; }
2020-10-09 05:47:24 +03:00
socket . tag . nodeid = 'node/' + mesh . domain + '/' + nodeid ; // Turn 16bit systemid guid into 48bit nodeid that is base64 encoded
socket . tag . meshid = mesh . _id ;
socket . tag . connectTime = Date . now ( ) ;
2019-06-06 01:24:07 +03:00
2020-10-09 05:47:24 +03:00
obj . db . Get ( socket . tag . nodeid , function ( err , nodes ) {
if ( ( nodes == null ) || ( nodes . length !== 1 ) ) {
// Check if we already have too many devices for this domain
if ( domain . limits && ( typeof domain . limits . maxdevices == 'number' ) ) {
db . isMaxType ( domain . limits . maxdevices , 'node' , mesh . domain , function ( ismax , count ) {
if ( ismax == true ) {
// Too many devices in this domain.
maxDomainDevicesReached ++ ;
console . log ( 'Too many devices on this domain to accept the CIRA connection. meshid: ' + socket . tag . meshid ) ;
socket . end ( ) ;
} else {
2021-03-08 22:06:39 +03:00
// Attempts reverse DNS loopup on the device IP address
require ( 'dns' ) . reverse ( socket . remoteAddr , function ( err , hostnames ) {
var hostname = socket . remoteAddr ;
if ( ( err == null ) && ( hostnames != null ) && ( hostnames . length > 0 ) ) { hostname = hostnames [ 0 ] ; }
2020-10-09 05:47:24 +03:00
2021-03-08 22:06:39 +03:00
// We are under the limit, create the new device.
// Node is not in the database, add it. Credentials will be empty until added by the user.
var device = { type : 'node' , mtype : 1 , _id : socket . tag . nodeid , meshid : socket . tag . meshid , name : socket . tag . name , icon : ( socket . tag . meiState . isBatteryPowered ) ? 2 : 1 , host : hostname , domain : mesh . domain , intelamt : { user : ( typeof socket . tag . meiState . amtuser == 'string' ) ? socket . tag . meiState . amtuser : '' , pass : ( typeof socket . tag . meiState . amtpass == 'string' ) ? socket . tag . meiState . amtpass : '' , tls : 0 , state : 2 } } ;
if ( ( typeof socket . tag . meiState . desc == 'string' ) && ( socket . tag . meiState . desc . length > 0 ) && ( socket . tag . meiState . desc . length < 1024 ) ) { device . desc = socket . tag . meiState . desc ; }
obj . db . Set ( device ) ;
// Event the new node
addedDeviceCount ++ ;
var change = 'CIRA added device ' + socket . tag . name + ' to group ' + mesh . name ;
obj . parent . DispatchEvent ( [ '*' , socket . tag . meshid ] , obj , { etype : 'node' , action : 'addnode' , node : parent . webserver . CloneSafeNode ( device ) , msg : change , domain : mesh . domain } ) ;
2020-10-09 05:47:24 +03:00
2021-03-08 22:06:39 +03:00
// Add the connection to the MPS connection list
addCiraConnection ( socket ) ;
SendUserAuthSuccess ( socket ) ; // Notify the auth success on the CIRA connection
} ) ;
2020-10-09 05:47:24 +03:00
}
} ) ;
return ;
2019-02-21 02:26:27 +03:00
} else {
2021-03-08 22:06:39 +03:00
// Attempts reverse DNS loopup on the device IP address
require ( 'dns' ) . reverse ( socket . remoteAddr , function ( err , hostnames ) {
var hostname = socket . remoteAddr ;
if ( ( err == null ) && ( hostnames != null ) && ( hostnames . length > 0 ) ) { hostname = hostnames [ 0 ] ; }
// Node is not in the database, add it. Credentials will be empty until added by the user.
var device = { type : 'node' , mtype : 1 , _id : socket . tag . nodeid , meshid : socket . tag . meshid , name : socket . tag . name , icon : ( socket . tag . meiState && socket . tag . meiState . isBatteryPowered ) ? 2 : 1 , host : hostname , domain : mesh . domain , intelamt : { user : ( ( socket . tag . meiState ) && ( typeof socket . tag . meiState . amtuser == 'string' ) ) ? socket . tag . meiState . amtuser : '' , pass : ( ( socket . tag . meiState ) && ( typeof socket . tag . meiState . amtpass == 'string' ) ) ? socket . tag . meiState . amtpass : '' , tls : 0 , state : 2 } } ;
if ( ( socket . tag . meiState != null ) && ( typeof socket . tag . meiState . desc == 'string' ) && ( socket . tag . meiState . desc . length > 0 ) && ( socket . tag . meiState . desc . length < 1024 ) ) { device . desc = socket . tag . meiState . desc ; }
obj . db . Set ( device ) ;
// Event the new node
addedDeviceCount ++ ;
var change = 'CIRA added device ' + socket . tag . name + ' to group ' + mesh . name ;
obj . parent . DispatchEvent ( [ '*' , socket . tag . meshid ] , obj , { etype : 'node' , action : 'addnode' , node : parent . webserver . CloneSafeNode ( device ) , msg : change , domain : mesh . domain } ) ;
} ) ;
2019-02-21 02:26:27 +03:00
}
2020-10-09 05:47:24 +03:00
} else {
// Node is already present
var node = nodes [ 0 ] ;
socket . tag . meshid = node . meshid ; // Correct the MeshID if the node has moved.
if ( ( node . intelamt != null ) && ( node . intelamt . state == 2 ) ) { socket . tag . host = node . intelamt . host ; }
}
2017-08-28 19:27:45 +03:00
2020-10-09 05:47:24 +03:00
// Add the connection to the MPS connection list
2020-10-10 01:44:09 +03:00
addCiraConnection ( socket ) ;
2020-10-09 05:47:24 +03:00
SendUserAuthSuccess ( socket ) ; // Notify the auth success on the CIRA connection
} ) ;
} else if ( mesh . mtype == 2 ) { // If this is a agent mesh, search the mesh for this device UUID
// Intel AMT GUID (socket.tag.SystemId) will be used to search the node
2021-01-12 00:02:30 +03:00
obj . db . getAmtUuidMeshNode ( mesh . _id , socket . tag . SystemId , function ( err , nodes ) { // TODO: Need to optimize this request with indexes
2020-10-09 05:47:24 +03:00
if ( ( nodes == null ) || ( nodes . length === 0 ) || ( obj . parent . webserver . meshes == null ) ) {
// New CIRA connection for unknown node, disconnect.
unknownNodeCount ++ ;
console . log ( 'CIRA connection for unknown node. groupid: ' + mesh . _id + ', uuid: ' + socket . tag . SystemId ) ;
socket . end ( ) ;
return ;
}
2019-02-21 02:26:27 +03:00
2020-10-09 05:47:24 +03:00
// Looking at nodes that match this UUID, select one in the same domain and mesh type.
var node = null ;
for ( var i in nodes ) {
if ( mesh . domain == nodes [ i ] . domain ) {
var nodemesh = obj . parent . webserver . meshes [ nodes [ i ] . meshid ] ;
if ( ( nodemesh != null ) && ( nodemesh . mtype == 2 ) ) { node = nodes [ i ] ; }
2019-10-11 02:07:32 +03:00
}
2020-10-09 05:47:24 +03:00
}
2019-10-11 02:07:32 +03:00
2020-10-09 05:47:24 +03:00
if ( node == null ) {
// New CIRA connection for unknown node, disconnect.
unknownNodeCount ++ ;
console . log ( 'CIRA connection for unknown node. candidate(s): ' + nodes . length + ', groupid: ' + mesh . _id + ', uuid: ' + socket . tag . SystemId ) ;
socket . end ( ) ;
return ;
}
2019-10-11 02:07:32 +03:00
2020-10-09 05:47:24 +03:00
// Node is present
if ( ( node . intelamt != null ) && ( node . intelamt . state == 2 ) ) { socket . tag . host = node . intelamt . host ; }
socket . tag . nodeid = node . _id ;
socket . tag . meshid = node . meshid ; // Correct the MeshID if the node has moved.
socket . tag . connectTime = Date . now ( ) ;
// Add the connection to the MPS connection list
2020-10-10 01:44:09 +03:00
addCiraConnection ( socket ) ;
2020-10-09 05:47:24 +03:00
SendUserAuthSuccess ( socket ) ; // Notify the auth success on the CIRA connection
} ) ;
} else { // Unknown mesh type
// New CIRA connection for unknown node, disconnect.
unknownMeshIdCount ++ ;
console . log ( 'CIRA connection to a unknown group type. groupid: ' + socket . tag . meshid ) ;
socket . end ( ) ;
return ;
2017-08-28 19:27:45 +03:00
}
2020-10-09 05:47:24 +03:00
return 18 + usernameLen + serviceNameLen + methodNameLen + passwordLen ;
}
case APFProtocol . SERVICE _REQUEST : {
if ( len < 5 ) return 0 ;
var xserviceNameLen = common . ReadInt ( data , 1 ) ;
if ( len < 5 + xserviceNameLen ) return 0 ;
var xserviceName = data . substring ( 5 , 5 + xserviceNameLen ) ;
parent . debug ( 'mpscmd' , '--> SERVICE_REQUEST' , xserviceName ) ;
if ( xserviceName == "pfwd@amt.intel.com" ) { SendServiceAccept ( socket , "pfwd@amt.intel.com" ) ; }
if ( xserviceName == "auth@amt.intel.com" ) { SendServiceAccept ( socket , "auth@amt.intel.com" ) ; }
return 5 + xserviceNameLen ;
}
case APFProtocol . GLOBAL _REQUEST : {
if ( len < 14 ) return 0 ;
var requestLen = common . ReadInt ( data , 1 ) ;
if ( len < 14 + requestLen ) return 0 ;
var request = data . substring ( 5 , 5 + requestLen ) ;
//var wantResponse = data.charCodeAt(5 + requestLen);
if ( request == 'tcpip-forward' ) {
var addrLen = common . ReadInt ( data , 6 + requestLen ) ;
if ( len < 14 + requestLen + addrLen ) return 0 ;
var addr = data . substring ( 10 + requestLen , 10 + requestLen + addrLen ) ;
var port = common . ReadInt ( data , 10 + requestLen + addrLen ) ;
parent . debug ( 'mpscmd' , '--> GLOBAL_REQUEST' , request , addr + ':' + port ) ;
if ( socket . tag . boundPorts . indexOf ( port ) == - 1 ) { socket . tag . boundPorts . push ( port ) ; }
SendTcpForwardSuccessReply ( socket , port ) ;
return 14 + requestLen + addrLen ;
2017-08-28 19:27:45 +03:00
}
2020-10-09 05:47:24 +03:00
if ( request == 'cancel-tcpip-forward' ) {
var addrLen = common . ReadInt ( data , 6 + requestLen ) ;
if ( len < 14 + requestLen + addrLen ) return 0 ;
var addr = data . substring ( 10 + requestLen , 10 + requestLen + addrLen ) ;
var port = common . ReadInt ( data , 10 + requestLen + addrLen ) ;
parent . debug ( 'mpscmd' , '--> GLOBAL_REQUEST' , request , addr + ':' + port ) ;
var portindex = socket . tag . boundPorts . indexOf ( port ) ;
if ( portindex >= 0 ) { socket . tag . boundPorts . splice ( portindex , 1 ) ; }
SendTcpForwardCancelReply ( socket ) ;
return 14 + requestLen + addrLen ;
2017-08-28 19:27:45 +03:00
}
2018-08-30 03:40:30 +03:00
2020-10-09 05:47:24 +03:00
if ( request == 'udp-send-to@amt.intel.com' ) {
var addrLen = common . ReadInt ( data , 6 + requestLen ) ;
if ( len < 26 + requestLen + addrLen ) return 0 ;
var addr = data . substring ( 10 + requestLen , 10 + requestLen + addrLen ) ;
var port = common . ReadInt ( data , 10 + requestLen + addrLen ) ;
var oaddrLen = common . ReadInt ( data , 14 + requestLen + addrLen ) ;
if ( len < 26 + requestLen + addrLen + oaddrLen ) return 0 ;
var oaddr = data . substring ( 18 + requestLen , 18 + requestLen + addrLen ) ;
var oport = common . ReadInt ( data , 18 + requestLen + addrLen + oaddrLen ) ;
var datalen = common . ReadInt ( data , 22 + requestLen + addrLen + oaddrLen ) ;
if ( len < 26 + requestLen + addrLen + oaddrLen + datalen ) return 0 ;
parent . debug ( 'mpscmd' , '--> GLOBAL_REQUEST' , request , addr + ':' + port , oaddr + ':' + oport , datalen ) ;
// TODO
return 26 + requestLen + addrLen + oaddrLen + datalen ;
}
2017-08-28 19:27:45 +03:00
2020-10-09 05:47:24 +03:00
return 6 + requestLen ;
}
case APFProtocol . CHANNEL _OPEN : {
if ( len < 33 ) return 0 ;
var ChannelTypeLength = common . ReadInt ( data , 1 ) ;
if ( len < ( 33 + ChannelTypeLength ) ) return 0 ;
// Decode channel identifiers and window size
var ChannelType = data . substring ( 5 , 5 + ChannelTypeLength ) ;
var SenderChannel = common . ReadInt ( data , 5 + ChannelTypeLength ) ;
var WindowSize = common . ReadInt ( data , 9 + ChannelTypeLength ) ;
// Decode the target
var TargetLen = common . ReadInt ( data , 17 + ChannelTypeLength ) ;
if ( len < ( 33 + ChannelTypeLength + TargetLen ) ) return 0 ;
var Target = data . substring ( 21 + ChannelTypeLength , 21 + ChannelTypeLength + TargetLen ) ;
var TargetPort = common . ReadInt ( data , 21 + ChannelTypeLength + TargetLen ) ;
// Decode the source
var SourceLen = common . ReadInt ( data , 25 + ChannelTypeLength + TargetLen ) ;
if ( len < ( 33 + ChannelTypeLength + TargetLen + SourceLen ) ) return 0 ;
var Source = data . substring ( 29 + ChannelTypeLength + TargetLen , 29 + ChannelTypeLength + TargetLen + SourceLen ) ;
var SourcePort = common . ReadInt ( data , 29 + ChannelTypeLength + TargetLen + SourceLen ) ;
channelOpenCount ++ ;
parent . debug ( 'mpscmd' , '--> CHANNEL_OPEN' , ChannelType , SenderChannel , WindowSize , Target + ':' + TargetPort , Source + ':' + SourcePort ) ;
// Check if we understand this channel type
//if (ChannelType.toLowerCase() == "direct-tcpip")
{
// We don't understand this channel type, send an error back
SendChannelOpenFailure ( socket , SenderChannel , APFChannelOpenFailureReasonCode . UnknownChannelType ) ;
2017-08-28 19:27:45 +03:00
return 33 + ChannelTypeLength + TargetLen + SourceLen ;
}
2020-10-09 05:47:24 +03:00
/ *
// This is a correct connection. Lets get it setup
var MeshAmtEventEndpoint = { ServerChannel : GetNextBindId ( ) , AmtChannel : SenderChannel , MaxWindowSize : 2048 , CurrentWindowSize : 2048 , SendWindow : WindowSize , InfoHeader : "Target: " + Target + ":" + TargetPort + ", Source: " + Source + ":" + SourcePort } ;
// TODO: Connect this socket for a WSMAN event
SendChannelOpenConfirmation ( socket , SenderChannel , MeshAmtEventEndpoint . ServerChannel , MeshAmtEventEndpoint . MaxWindowSize ) ;
* /
return 33 + ChannelTypeLength + TargetLen + SourceLen ;
}
case APFProtocol . CHANNEL _OPEN _CONFIRMATION :
{
if ( len < 17 ) return 0 ;
var RecipientChannel = common . ReadInt ( data , 1 ) ;
var SenderChannel = common . ReadInt ( data , 5 ) ;
var WindowSize = common . ReadInt ( data , 9 ) ;
socket . tag . activetunnels ++ ;
var cirachannel = socket . tag . channels [ RecipientChannel ] ;
if ( cirachannel == null ) { /*console.log("MPS Error in CHANNEL_OPEN_CONFIRMATION: Unable to find channelid " + RecipientChannel);*/ return 17 ; }
cirachannel . amtchannelid = SenderChannel ;
cirachannel . sendcredits = cirachannel . amtCiraWindow = WindowSize ;
channelOpenConfirmCount ++ ;
parent . debug ( 'mpscmd' , '--> CHANNEL_OPEN_CONFIRMATION' , RecipientChannel , SenderChannel , WindowSize ) ;
if ( cirachannel . closing == 1 ) {
// Close this channel
SendChannelClose ( cirachannel . socket , cirachannel . amtchannelid ) ;
} else {
cirachannel . state = 2 ;
// Send any pending data
if ( cirachannel . sendBuffer != null ) {
2017-08-28 19:27:45 +03:00
if ( cirachannel . sendBuffer . length <= cirachannel . sendcredits ) {
// Send the entire pending buffer
SendChannelData ( cirachannel . socket , cirachannel . amtchannelid , cirachannel . sendBuffer ) ;
cirachannel . sendcredits -= cirachannel . sendBuffer . length ;
delete cirachannel . sendBuffer ;
if ( cirachannel . onSendOk ) { cirachannel . onSendOk ( cirachannel ) ; }
} else {
// Send a part of the pending buffer
2020-10-14 22:01:26 +03:00
SendChannelData ( cirachannel . socket , cirachannel . amtchannelid , cirachannel . sendBuffer . slice ( 0 , cirachannel . sendcredits ) ) ;
cirachannel . sendBuffer = cirachannel . sendBuffer . slice ( cirachannel . sendcredits ) ;
2017-08-28 19:27:45 +03:00
cirachannel . sendcredits = 0 ;
}
}
2020-10-09 05:47:24 +03:00
// Indicate the channel is open
if ( cirachannel . onStateChange ) { cirachannel . onStateChange ( cirachannel , cirachannel . state ) ; }
2017-08-28 19:27:45 +03:00
}
2020-10-09 05:47:24 +03:00
return 17 ;
}
case APFProtocol . CHANNEL _OPEN _FAILURE :
{
if ( len < 17 ) return 0 ;
var RecipientChannel = common . ReadInt ( data , 1 ) ;
var ReasonCode = common . ReadInt ( data , 5 ) ;
channelOpenFailCount ++ ;
parent . debug ( 'mpscmd' , '--> CHANNEL_OPEN_FAILURE' , RecipientChannel , ReasonCode ) ;
var cirachannel = socket . tag . channels [ RecipientChannel ] ;
if ( cirachannel == null ) { console . log ( "MPS Error in CHANNEL_OPEN_FAILURE: Unable to find channelid " + RecipientChannel ) ; return 17 ; }
if ( cirachannel . state > 0 ) {
cirachannel . state = 0 ;
if ( cirachannel . onStateChange ) { cirachannel . onStateChange ( cirachannel , cirachannel . state ) ; }
delete socket . tag . channels [ RecipientChannel ] ;
2017-08-28 19:27:45 +03:00
}
2020-10-09 05:47:24 +03:00
return 17 ;
}
case APFProtocol . CHANNEL _CLOSE :
{
if ( len < 5 ) return 0 ;
var RecipientChannel = common . ReadInt ( data , 1 ) ;
channelCloseCount ++ ;
parent . debug ( 'mpscmd' , '--> CHANNEL_CLOSE' , RecipientChannel ) ;
var cirachannel = socket . tag . channels [ RecipientChannel ] ;
if ( cirachannel == null ) { console . log ( "MPS Error in CHANNEL_CLOSE: Unable to find channelid " + RecipientChannel ) ; return 5 ; }
socket . tag . activetunnels -- ;
if ( cirachannel . state > 0 ) {
cirachannel . state = 0 ;
if ( cirachannel . onStateChange ) { cirachannel . onStateChange ( cirachannel , cirachannel . state ) ; }
SendChannelClose ( cirachannel . socket , cirachannel . amtchannelid ) ;
delete socket . tag . channels [ RecipientChannel ] ;
}
return 5 ;
}
case APFProtocol . CHANNEL _WINDOW _ADJUST :
{
if ( len < 9 ) return 0 ;
var RecipientChannel = common . ReadInt ( data , 1 ) ;
var ByteToAdd = common . ReadInt ( data , 5 ) ;
var cirachannel = socket . tag . channels [ RecipientChannel ] ;
if ( cirachannel == null ) { console . log ( "MPS Error in CHANNEL_WINDOW_ADJUST: Unable to find channelid " + RecipientChannel ) ; return 9 ; }
cirachannel . sendcredits += ByteToAdd ;
parent . debug ( 'mpscmd' , '--> CHANNEL_WINDOW_ADJUST' , RecipientChannel , ByteToAdd , cirachannel . sendcredits ) ;
if ( cirachannel . state == 2 && cirachannel . sendBuffer != null ) {
// Compute how much data we can send
if ( cirachannel . sendBuffer . length <= cirachannel . sendcredits ) {
// Send the entire pending buffer
SendChannelData ( cirachannel . socket , cirachannel . amtchannelid , cirachannel . sendBuffer ) ;
cirachannel . sendcredits -= cirachannel . sendBuffer . length ;
delete cirachannel . sendBuffer ;
if ( cirachannel . onSendOk ) { cirachannel . onSendOk ( cirachannel ) ; }
} else {
// Send a part of the pending buffer
2020-10-14 22:01:26 +03:00
SendChannelData ( cirachannel . socket , cirachannel . amtchannelid , cirachannel . sendBuffer . slice ( 0 , cirachannel . sendcredits ) ) ;
cirachannel . sendBuffer = cirachannel . sendBuffer . slice ( cirachannel . sendcredits ) ;
2020-10-09 05:47:24 +03:00
cirachannel . sendcredits = 0 ;
}
2018-08-30 03:40:30 +03:00
}
2020-10-09 05:47:24 +03:00
return 9 ;
}
case APFProtocol . CHANNEL _DATA :
{
if ( len < 9 ) return 0 ;
var RecipientChannel = common . ReadInt ( data , 1 ) ;
var LengthOfData = common . ReadInt ( data , 5 ) ;
if ( len < ( 9 + LengthOfData ) ) return 0 ;
parent . debug ( 'mpscmddata' , '--> CHANNEL_DATA' , RecipientChannel , LengthOfData ) ;
var cirachannel = socket . tag . channels [ RecipientChannel ] ;
if ( cirachannel == null ) { console . log ( "MPS Error in CHANNEL_DATA: Unable to find channelid " + RecipientChannel ) ; return 9 + LengthOfData ; }
cirachannel . amtpendingcredits += LengthOfData ;
2020-10-14 03:46:29 +03:00
if ( cirachannel . onData ) { cirachannel . onData ( cirachannel , Buffer . from ( data . substring ( 9 , 9 + LengthOfData ) , 'binary' ) ) ; }
2020-10-09 05:47:24 +03:00
if ( cirachannel . amtpendingcredits > ( cirachannel . ciraWindow / 2 ) ) {
SendChannelWindowAdjust ( cirachannel . socket , cirachannel . amtchannelid , cirachannel . amtpendingcredits ) ; // Adjust the buffer window
cirachannel . amtpendingcredits = 0 ;
2017-08-28 19:27:45 +03:00
}
2020-10-09 05:47:24 +03:00
return 9 + LengthOfData ;
}
case APFProtocol . DISCONNECT :
{
if ( len < 7 ) return 0 ;
var ReasonCode = common . ReadInt ( data , 1 ) ;
disconnectCommandCount ++ ;
parent . debug ( 'mpscmd' , '--> DISCONNECT' , ReasonCode ) ;
2020-10-10 01:44:09 +03:00
removeCiraConnection ( socket ) ;
2020-10-09 05:47:24 +03:00
return 7 ;
}
2020-10-12 19:29:34 +03:00
case APFProtocol . JSON _CONTROL : // This is a Mesh specific command that sends JSON to and from the MPS server.
2020-10-10 01:44:09 +03:00
{
if ( len < 5 ) return 0 ;
2020-10-12 19:29:34 +03:00
var jsondatalen = common . ReadInt ( data , 1 ) ;
if ( len < ( 5 + jsondatalen ) ) return 0 ;
var jsondata = null , jsondatastr = data . substring ( 5 , 5 + jsondatalen ) ;
try { jsondata = JSON . parse ( jsondatastr ) ; } catch ( ex ) { }
if ( ( jsondata == null ) || ( typeof jsondata . action != 'string' ) ) return ;
2021-03-04 10:49:53 +03:00
parent . debug ( 'mpscmd' , '--> JSON_CONTROL' , jsondata . action ) ;
2020-10-12 19:29:34 +03:00
switch ( jsondata . action ) {
case 'connType' :
if ( ( socket . tag . connType != 0 ) || ( socket . tag . SystemId != null ) ) return ; // Once set, the connection type can't be changed.
if ( typeof jsondata . value != 'number' ) return ;
socket . tag . connType = jsondata . value ; // 0 = CIRA, 1 = Relay, 2 = LMS
2020-10-20 08:23:46 +03:00
//obj.SendJsonControl(socket, { action: 'mestate' }); // Request an MEI state refresh
break ;
case 'meiState' :
if ( socket . tag . connType != 2 ) break ; // Only accept MEI state on CIRA-LMS connection
socket . tag . meiState = jsondata . value ;
2020-12-10 11:03:00 +03:00
if ( ( ( socket . tag . name == '' ) || ( socket . tag . name == null ) ) && ( typeof jsondata . value . OsHostname == 'string' ) ) { socket . tag . name = jsondata . value . OsHostname ; }
2020-10-21 22:19:00 +03:00
if ( obj . parent . amtManager != null ) { obj . parent . amtManager . mpsControlMessage ( socket . tag . nodeid , socket , socket . tag . connType , jsondata ) ; }
break ;
case 'deactivate' :
2021-03-04 10:49:53 +03:00
case 'startTlsHostConfig' :
2021-03-05 10:52:48 +03:00
case 'stopConfiguration' :
2021-03-04 10:49:53 +03:00
if ( socket . tag . connType != 2 ) break ; // Only accept MEI state on CIRA-LMS connection
if ( obj . parent . amtManager != null ) { obj . parent . amtManager . mpsControlMessage ( socket . tag . nodeid , socket , socket . tag . connType , jsondata ) ; }
break ;
2020-10-10 01:44:09 +03:00
}
2020-10-12 19:29:34 +03:00
return 5 + jsondatalen ;
2020-10-10 01:44:09 +03:00
}
2020-10-09 05:47:24 +03:00
default :
{
parent . debug ( 'mpscmd' , '--> Unknown CIRA command: ' + cmd ) ;
return - 1 ;
}
2017-08-28 19:27:45 +03:00
}
}
2018-08-30 03:40:30 +03:00
2017-08-28 19:27:45 +03:00
// Disconnect CIRA tunnel
obj . close = function ( socket ) {
2018-05-03 02:19:45 +03:00
try { socket . end ( ) ; } catch ( e ) { }
2020-10-10 01:44:09 +03:00
removeCiraConnection ( socket ) ;
} ;
// Disconnect all CIRA tunnel for a given NodeId
obj . closeAllForNode = function ( nodeid ) {
var connections = obj . ciraConnections [ nodeid ] ;
if ( connections == null ) return ;
for ( var i in connections ) { obj . close ( connections [ i ] ) ; }
2018-08-30 03:40:30 +03:00
} ;
2017-08-28 19:27:45 +03:00
2020-10-18 05:23:24 +03:00
obj . SendJsonControl = function ( socket , data ) {
if ( socket . tag . connType == 0 ) return ; // This command is valid only for connections that are not really CIRA.
2021-03-04 10:49:53 +03:00
if ( typeof data == 'object' ) { parent . debug ( 'mpscmd' , '<-- JSON_CONTROL' , data . action ) ; data = JSON . stringify ( data ) ; } else { parent . debug ( 'mpscmd' , '<-- JSON_CONTROL' ) ; }
2020-10-18 05:23:24 +03:00
Write ( socket , String . fromCharCode ( APFProtocol . JSON _CONTROL ) + common . IntToStr ( data . length ) + data ) ;
}
2017-08-28 19:27:45 +03:00
function SendServiceAccept ( socket , service ) {
2020-10-09 00:25:16 +03:00
parent . debug ( 'mpscmd' , '<-- SERVICE_ACCEPT' , service ) ;
2017-08-28 19:27:45 +03:00
Write ( socket , String . fromCharCode ( APFProtocol . SERVICE _ACCEPT ) + common . IntToStr ( service . length ) + service ) ;
}
2018-08-30 03:40:30 +03:00
2017-08-28 19:27:45 +03:00
function SendTcpForwardSuccessReply ( socket , port ) {
2020-10-09 00:25:16 +03:00
parent . debug ( 'mpscmd' , '<-- REQUEST_SUCCESS' , port ) ;
2017-08-28 19:27:45 +03:00
Write ( socket , String . fromCharCode ( APFProtocol . REQUEST _SUCCESS ) + common . IntToStr ( port ) ) ;
}
2018-08-30 03:40:30 +03:00
2017-08-28 19:27:45 +03:00
function SendTcpForwardCancelReply ( socket ) {
2020-10-09 00:25:16 +03:00
parent . debug ( 'mpscmd' , '<-- REQUEST_SUCCESS' ) ;
2017-08-28 19:27:45 +03:00
Write ( socket , String . fromCharCode ( APFProtocol . REQUEST _SUCCESS ) ) ;
}
2018-08-30 03:40:30 +03:00
/ *
2017-08-28 19:27:45 +03:00
function SendKeepAliveRequest ( socket , cookie ) {
2020-10-09 00:25:16 +03:00
parent . debug ( 'mpscmd' , '<-- KEEPALIVE_REQUEST' , cookie ) ;
2017-08-28 19:27:45 +03:00
Write ( socket , String . fromCharCode ( APFProtocol . KEEPALIVE _REQUEST ) + common . IntToStr ( cookie ) ) ;
}
2018-08-30 03:40:30 +03:00
* /
2017-08-28 19:27:45 +03:00
function SendKeepAliveReply ( socket , cookie ) {
2020-10-09 00:25:16 +03:00
parent . debug ( 'mpscmd' , '<-- KEEPALIVE_REPLY' , cookie ) ;
2017-08-28 19:27:45 +03:00
Write ( socket , String . fromCharCode ( APFProtocol . KEEPALIVE _REPLY ) + common . IntToStr ( cookie ) ) ;
}
2018-08-30 03:40:30 +03:00
2017-08-28 19:27:45 +03:00
function SendChannelOpenFailure ( socket , senderChannel , reasonCode ) {
2020-10-09 00:25:16 +03:00
parent . debug ( 'mpscmd' , '<-- CHANNEL_OPEN_FAILURE' , senderChannel , reasonCode ) ;
2017-08-28 19:27:45 +03:00
Write ( socket , String . fromCharCode ( APFProtocol . CHANNEL _OPEN _FAILURE ) + common . IntToStr ( senderChannel ) + common . IntToStr ( reasonCode ) + common . IntToStr ( 0 ) + common . IntToStr ( 0 ) ) ;
}
2018-08-30 03:40:30 +03:00
/ *
2017-08-28 19:27:45 +03:00
function SendChannelOpenConfirmation ( socket , recipientChannelId , senderChannelId , initialWindowSize ) {
2020-10-09 00:25:16 +03:00
parent . debug ( 'mpscmd' , '<-- CHANNEL_OPEN_CONFIRMATION' , recipientChannelId , senderChannelId , initialWindowSize ) ;
2017-08-28 19:27:45 +03:00
Write ( socket , String . fromCharCode ( APFProtocol . CHANNEL _OPEN _CONFIRMATION ) + common . IntToStr ( recipientChannelId ) + common . IntToStr ( senderChannelId ) + common . IntToStr ( initialWindowSize ) + common . IntToStr ( - 1 ) ) ;
}
2018-08-30 03:40:30 +03:00
* /
2017-08-28 19:27:45 +03:00
function SendChannelOpen ( socket , direct , channelid , windowsize , target , targetport , source , sourceport ) {
2019-12-28 02:18:43 +03:00
var connectionType = ( ( direct == true ) ? 'direct-tcpip' : 'forwarded-tcpip' ) ;
2019-03-05 10:48:45 +03:00
if ( ( target == null ) || ( target == null ) ) target = '' ; // TODO: Reports of target being undefined that causes target.length to fail. This is a hack.
2020-10-09 00:25:16 +03:00
parent . debug ( 'mpscmd' , '<-- CHANNEL_OPEN' , connectionType , channelid , windowsize , target + ':' + targetport , source + ':' + sourceport ) ;
2017-08-28 19:27:45 +03:00
Write ( socket , String . fromCharCode ( APFProtocol . CHANNEL _OPEN ) + common . IntToStr ( connectionType . length ) + connectionType + common . IntToStr ( channelid ) + common . IntToStr ( windowsize ) + common . IntToStr ( - 1 ) + common . IntToStr ( target . length ) + target + common . IntToStr ( targetport ) + common . IntToStr ( source . length ) + source + common . IntToStr ( sourceport ) ) ;
}
2018-08-30 03:40:30 +03:00
2017-08-28 19:27:45 +03:00
function SendChannelClose ( socket , channelid ) {
2020-10-09 00:25:16 +03:00
parent . debug ( 'mpscmd' , '<-- CHANNEL_CLOSE' , channelid ) ;
2017-08-28 19:27:45 +03:00
Write ( socket , String . fromCharCode ( APFProtocol . CHANNEL _CLOSE ) + common . IntToStr ( channelid ) ) ;
}
2018-08-30 03:40:30 +03:00
2020-10-14 22:01:26 +03:00
// Send a buffer to a given channel
2017-08-28 19:27:45 +03:00
function SendChannelData ( socket , channelid , data ) {
2020-10-14 22:01:26 +03:00
parent . debug ( 'mpscmddata' , '<-- CHANNEL_DATA' , channelid , data . length ) ;
const buf = Buffer . alloc ( 9 + data . length ) ;
buf [ 0 ] = APFProtocol . CHANNEL _DATA ; // CHANNEL_DATA
buf . writeInt32BE ( channelid , 1 ) ; // ChannelID
buf . writeInt32BE ( data . length , 5 ) ; // Data Length
data . copy ( buf , 9 , 0 ) ;
WriteBuffer ( socket , buf ) ;
2017-08-28 19:27:45 +03:00
}
2018-08-30 03:40:30 +03:00
2017-08-28 19:27:45 +03:00
function SendChannelWindowAdjust ( socket , channelid , bytestoadd ) {
2020-10-09 00:25:16 +03:00
parent . debug ( 'mpscmd' , '<-- CHANNEL_WINDOW_ADJUST' , channelid , bytestoadd ) ;
2017-08-28 19:27:45 +03:00
Write ( socket , String . fromCharCode ( APFProtocol . CHANNEL _WINDOW _ADJUST ) + common . IntToStr ( channelid ) + common . IntToStr ( bytestoadd ) ) ;
}
2018-08-30 03:40:30 +03:00
/ *
2017-08-28 19:27:45 +03:00
function SendDisconnect ( socket , reasonCode ) {
2020-10-09 00:25:16 +03:00
parent . debug ( 'mpscmd' , '<-- DISCONNECT' , reasonCode ) ;
2018-08-30 03:40:30 +03:00
Write ( socket , String . fromCharCode ( APFProtocol . DISCONNECT ) + common . IntToStr ( reasonCode ) + common . ShortToStr ( 0 ) ) ;
2017-08-28 19:27:45 +03:00
}
2018-08-30 03:40:30 +03:00
* /
2017-08-28 19:27:45 +03:00
function SendUserAuthFail ( socket ) {
2020-10-09 00:25:16 +03:00
parent . debug ( 'mpscmd' , '<-- USERAUTH_FAILURE' ) ;
2017-08-28 19:27:45 +03:00
Write ( socket , String . fromCharCode ( APFProtocol . USERAUTH _FAILURE ) + common . IntToStr ( 8 ) + 'password' + common . ShortToStr ( 0 ) ) ;
}
function SendUserAuthSuccess ( socket ) {
2020-10-09 00:25:16 +03:00
parent . debug ( 'mpscmd' , '<-- USERAUTH_SUCCESS' ) ;
2017-08-28 19:27:45 +03:00
Write ( socket , String . fromCharCode ( APFProtocol . USERAUTH _SUCCESS ) ) ;
}
2020-10-14 22:01:26 +03:00
// Send a string or buffer
2017-08-28 19:27:45 +03:00
function Write ( socket , data ) {
2020-10-14 03:46:29 +03:00
try {
if ( args . mpsdebug ) {
// Print out sent bytes
var buf = Buffer . from ( data , 'binary' ) ;
console . log ( 'MPS <-- (' + buf . length + '):' + buf . toString ( 'hex' ) ) ;
if ( socket . websocket == 1 ) { socket . send ( buf ) ; } else { socket . write ( buf ) ; }
} else {
if ( socket . websocket == 1 ) { socket . send ( Buffer . from ( data , 'binary' ) ) ; } else { socket . write ( Buffer . from ( data , 'binary' ) ) ; }
}
} catch ( ex ) { }
2017-08-28 19:27:45 +03:00
}
2018-08-30 03:40:30 +03:00
2020-10-14 22:01:26 +03:00
// Send a buffer
function WriteBuffer ( socket , data ) {
try {
if ( args . mpsdebug ) { console . log ( 'MPS <-- (' + buf . length + '):' + data . toString ( 'hex' ) ) ; } // Print out sent bytes
if ( socket . websocket == 1 ) { socket . send ( data ) ; } else { socket . write ( data ) ; }
} catch ( ex ) { }
}
2020-10-10 01:44:09 +03:00
// Returns a CIRA/Relay/LMS connection to a nodeid, use the best possible connection, CIRA first, Relay second, LMS third.
// if oob is set to true, don't allow an LMS connection.
obj . GetConnectionToNode = function ( nodeid , targetport , oob ) {
var connectionArray = obj . ciraConnections [ nodeid ] ;
if ( connectionArray == null ) return null ;
var selectConn = null ;
// Select the best connection, which is the one with the lowest connType value.
for ( var i in connectionArray ) {
var conn = connectionArray [ i ] ;
if ( ( oob === true ) && ( conn . tag . connType == 2 ) ) continue ; // If an OOB connection is required, don't allow LMS connections.
if ( ( typeof oob === 'number' ) && ( conn . tag . connType !== oob ) ) continue ; // if OOB specifies an exact connection type, filter on this type.
if ( ( targetport != null ) && ( conn . tag . boundPorts . indexOf ( targetport ) == - 1 ) ) continue ; // This connection does not route to the target port.
if ( ( selectConn == null ) || ( conn . tag . connType < selectConn . tag . connType ) ) { selectConn = conn ; }
}
return selectConn ;
}
// Setup a new channel to a nodeid, use the best possible connection, CIRA first, Relay second, LMS third.
// if oob is set to true, don't allow an LMS connection.
obj . SetupChannelToNode = function ( nodeid , targetport , oob ) {
var conn = obj . GetConnectionToNode ( nodeid , targetport , oob ) ;
if ( conn == null ) return null ;
return obj . SetupChannel ( conn , targetport ) ;
2020-10-08 09:13:45 +03:00
}
2020-10-08 22:50:27 +03:00
// Setup a new channel
obj . SetupChannel = function ( socket , targetport ) {
2017-08-28 19:27:45 +03:00
var sourceport = ( socket . tag . nextsourceport ++ % 30000 ) + 1024 ;
var cirachannel = { targetport : targetport , channelid : socket . tag . nextchannelid ++ , socket : socket , state : 1 , sendcredits : 0 , amtpendingcredits : 0 , amtCiraWindow : 0 , ciraWindow : 32768 } ;
2020-10-10 01:44:09 +03:00
SendChannelOpen ( socket , false , cirachannel . channelid , cirachannel . ciraWindow , socket . tag . host , targetport , '1.2.3.4' , sourceport ) ;
2018-08-30 03:40:30 +03:00
2017-08-28 19:27:45 +03:00
// This function writes data to this CIRA channel
cirachannel . write = function ( data ) {
if ( cirachannel . state == 0 ) return false ;
2020-10-14 22:01:26 +03:00
if ( typeof data == 'string' ) { data = Buffer . from ( data , 'binary' ) ; } // Make sure we always handle buffers when sending data.
2019-03-05 10:48:45 +03:00
if ( cirachannel . state == 1 || cirachannel . sendcredits == 0 || cirachannel . sendBuffer != null ) {
2018-07-27 02:31:43 +03:00
// Channel is connected, but we are out of credits. Add the data to the outbound buffer.
2020-10-14 22:01:26 +03:00
if ( cirachannel . sendBuffer == null ) { cirachannel . sendBuffer = data ; } else { cirachannel . sendBuffer = Buffer . concat ( [ cirachannel . sendBuffer , data ] ) ; }
2018-07-27 02:31:43 +03:00
return true ;
}
2017-08-28 19:27:45 +03:00
// Compute how much data we can send
if ( data . length <= cirachannel . sendcredits ) {
// Send the entire message
SendChannelData ( cirachannel . socket , cirachannel . amtchannelid , data ) ;
cirachannel . sendcredits -= data . length ;
return true ;
}
// Send a part of the message
2020-10-14 22:01:26 +03:00
cirachannel . sendBuffer = data . slice ( cirachannel . sendcredits ) ;
SendChannelData ( cirachannel . socket , cirachannel . amtchannelid , data . slice ( 0 , cirachannel . sendcredits ) ) ;
2017-08-28 19:27:45 +03:00
cirachannel . sendcredits = 0 ;
return false ;
2018-08-30 03:40:30 +03:00
} ;
2017-08-28 19:27:45 +03:00
// This function closes this CIRA channel
cirachannel . close = function ( ) {
if ( cirachannel . state == 0 || cirachannel . closing == 1 ) return ;
if ( cirachannel . state == 1 ) { cirachannel . closing = 1 ; cirachannel . state = 0 ; if ( cirachannel . onStateChange ) { cirachannel . onStateChange ( cirachannel , cirachannel . state ) ; } return ; }
cirachannel . state = 0 ;
cirachannel . closing = 1 ;
SendChannelClose ( cirachannel . socket , cirachannel . amtchannelid ) ;
if ( cirachannel . onStateChange ) { cirachannel . onStateChange ( cirachannel , cirachannel . state ) ; }
2018-08-30 03:40:30 +03:00
} ;
2017-08-28 19:27:45 +03:00
socket . tag . channels [ cirachannel . channelid ] = cirachannel ;
return cirachannel ;
2018-08-30 03:40:30 +03:00
} ;
2017-08-28 19:27:45 +03:00
2019-10-11 02:07:32 +03:00
// Change a node to a new meshid, this is called when a node changes groups.
obj . changeDeviceMesh = function ( nodeid , newMeshId ) {
2020-10-10 01:44:09 +03:00
var connectionArray = obj . ciraConnections [ nodeid ] ;
if ( connectionArray == null ) return ;
for ( var i in connectionArray ) {
var socket = connectionArray [ i ] ;
if ( ( socket != null ) && ( socket . tag != null ) ) { socket . tag . meshid = newMeshId ; }
}
2019-10-11 02:07:32 +03:00
}
2020-07-27 10:11:27 +03:00
// Called when handling incoming HTTP data
function onHttpData ( data ) {
if ( this . xdata == null ) { this . xdata = data ; } else { this . xdata += data ; }
var headersize = this . xdata . indexOf ( '\r\n\r\n' ) ;
if ( headersize < 0 ) { if ( this . xdata . length > 4096 ) { this . end ( ) ; } return ; }
var headers = this . xdata . substring ( 0 , headersize ) . split ( '\r\n' ) ;
if ( headers . length < 1 ) { this . end ( ) ; return ; }
var headerObj = { } ;
for ( var i = 1 ; i < headers . length ; i ++ ) { var j = headers [ i ] . indexOf ( ': ' ) ; if ( i > 0 ) { headerObj [ headers [ i ] . substring ( 0 , j ) . toLowerCase ( ) ] = headers [ i ] . substring ( j + 2 ) ; } }
var hostHeader = ( headerObj [ 'host' ] != null ) ? ( 'Host: ' + headerObj [ 'host' ] + '\r\n' ) : '' ;
var directives = headers [ 0 ] . split ( ' ' ) ;
if ( ( directives . length != 3 ) || ( ( directives [ 0 ] != 'GET' ) && ( directives [ 0 ] != 'HEAD' ) ) ) { this . end ( ) ; return ; }
//console.log('WebServer, request', directives[0], directives[1]);
var responseCode = 404 , responseType = 'application/octet-stream' , responseData = '' , r = null ;
2021-03-23 21:23:55 +03:00
// Check if this is a cookie request
if ( directives [ 1 ] . startsWith ( '/c/' ) ) {
var cookie = obj . parent . decodeCookie ( directives [ 1 ] . substring ( 3 ) . split ( '.' ) [ 0 ] , obj . parent . loginCookieEncryptionKey , 30 ) ; // 30 minute timeout
if ( ( cookie != null ) && ( cookie . a == 'f' ) && ( typeof cookie . f == 'string' ) ) {
// Send the file header and pipe the rest of the file
var filestats = null ;
try { filestats = obj . fs . statSync ( cookie . f ) ; } catch ( ex ) { }
if ( ( filestats == null ) || ( typeof filestats . size != 'number' ) || ( filestats . size <= 0 ) ) {
responseCode = 404 ; responseType = 'text/html' ; responseData = 'File not found' ;
} else {
2021-04-07 20:27:25 +03:00
this . write ( 'HTTP/1.1 200 OK\r\n' + hostHeader + 'Content-Type: ' + responseType + '\r\nConnection: keep-alive\r\nCache-Control: no-cache\r\nContent-Length: ' + filestats . size + '\r\n\r\n' ) ;
2021-03-23 21:23:55 +03:00
if ( directives [ 0 ] == 'GET' ) { obj . fs . createReadStream ( cookie . f , { flags : 'r' } ) . pipe ( this ) ; }
delete this . xdata ;
return ;
}
}
} else {
// Check if we have a preset response
if ( obj . httpResponses != null ) { r = obj . httpResponses [ directives [ 1 ] ] ; }
if ( ( r != null ) && ( r . maxtime != null ) && ( r . maxtime < Date . now ( ) ) ) { r = null ; delete obj . httpResponses [ directives [ 1 ] ] ; } // Check if this entry is expired.
if ( r != null ) {
if ( typeof r == 'string' ) {
responseCode = 200 ; responseType = 'text/html' ; responseData = r ;
} else if ( typeof r == 'object' ) {
responseCode = 200 ;
if ( r . type ) { responseType = r . type ; }
if ( r . data ) { responseData = r . data ; }
if ( r . shortfile ) { try { responseData = obj . fs . readFileSync ( r . shortfile ) ; } catch ( ex ) { responseCode = 404 ; responseType = 'text/html' ; responseData = 'File not found' ; } }
if ( r . file ) {
// Send the file header and pipe the rest of the file
var filestats = null ;
try { filestats = obj . fs . statSync ( r . file ) ; } catch ( ex ) { }
if ( ( filestats == null ) || ( typeof filestats . size != 'number' ) || ( filestats . size <= 0 ) ) {
responseCode = 404 ; responseType = 'text/html' ; responseData = 'File not found' ;
} else {
2021-04-07 20:27:25 +03:00
this . write ( 'HTTP/1.1 200 OK\r\n' + hostHeader + 'Content-Type: ' + responseType + '\r\nConnection: keep-alive\r\nCache-Control: no-cache\r\nContent-Length: ' + filestats . size + '\r\n\r\n' ) ;
2021-03-23 21:23:55 +03:00
if ( directives [ 0 ] == 'GET' ) {
obj . fs . createReadStream ( r . file , { flags : 'r' } ) . pipe ( this ) ;
if ( typeof r . maxserve == 'number' ) { r . maxserve -- ; if ( r . maxserve == 0 ) { delete obj . httpResponses [ directives [ 1 ] ] ; } } // Check if this entry was server the maximum amount of times.
}
delete this . xdata ;
return ;
2020-07-27 10:11:27 +03:00
}
}
}
2021-03-23 21:23:55 +03:00
} else {
responseType = 'text/html' ;
responseData = 'Invalid request' ;
2020-07-27 10:11:27 +03:00
}
}
2021-04-07 20:27:25 +03:00
this . write ( 'HTTP/1.1 ' + responseCode + ' OK\r\n' + hostHeader + 'Connection: keep-alive\r\nCache-Control: no-cache\r\nContent-Type: ' + responseType + '\r\nContent-Length: ' + responseData . length + '\r\n\r\n' ) ;
2020-07-27 10:11:27 +03:00
this . write ( responseData ) ;
delete this . xdata ;
}
// Called when handling HTTP data and the socket closes
function onHttpClose ( ) { }
// Add a HTTP file response
obj . addHttpFileResponse = function ( path , file , maxserve , minutes ) {
var r = { file : file } ;
if ( typeof maxserve == 'number' ) { r . maxserve = maxserve ; }
if ( typeof minutes == 'number' ) { r . maxtime = Date . now ( ) + ( 60000 * minutes ) ; }
obj . httpResponses [ path ] = r ;
// Clean up any expired files
const now = Date . now ( ) ;
for ( var i in obj . httpResponses ) { if ( ( obj . httpResponses [ i ] . maxtime != null ) && ( obj . httpResponses [ i ] . maxtime < now ) ) { delete obj . httpResponses [ i ] ; } }
}
2017-08-28 19:27:45 +03:00
function guidToStr ( g ) { return g . substring ( 6 , 8 ) + g . substring ( 4 , 6 ) + g . substring ( 2 , 4 ) + g . substring ( 0 , 2 ) + "-" + g . substring ( 10 , 12 ) + g . substring ( 8 , 10 ) + "-" + g . substring ( 14 , 16 ) + g . substring ( 12 , 14 ) + "-" + g . substring ( 16 , 20 ) + "-" + g . substring ( 20 ) ; }
2018-08-30 03:40:30 +03:00
2020-10-23 00:35:12 +03:00
// Clean a IPv6 address that encodes a IPv4 address
function cleanRemoteAddr ( addr ) { if ( typeof addr != 'string' ) { return null ; } if ( addr . indexOf ( '::ffff:' ) == 0 ) { return addr . substring ( 7 ) ; } else { return addr ; } }
2020-07-27 10:11:27 +03:00
// Example, this will add a file to stream, served 2 times max and 3 minutes max.
//obj.addHttpFileResponse('/a.png', 'c:\\temp\\MC2-LetsEncrypt.png', 2, 3);
2017-08-28 19:27:45 +03:00
return obj ;
2018-08-30 03:40:30 +03:00
} ;