2017-08-28 12:27:45 -04:00
/ * *
2018-01-04 15:15:21 -05:00
* @ description MeshCentral connection relay module
2017-08-28 12:27:45 -04:00
* @ author Ylian Saint - Hilaire
2020-01-02 21:30:12 -05:00
* @ copyright Intel Corporation 2018 - 2020
2018-01-04 15:15:21 -05:00
* @ license Apache - 2.0
2017-08-28 12:27:45 -04:00
* @ version v0 . 0.1
* /
2018-08-30 15:05:23 -04:00
/*jslint node: true */
/*jshint node: true */
/*jshint strict:false */
/*jshint -W097 */
/*jshint esversion: 6 */
"use strict" ;
2018-08-27 15:24:15 -04:00
2018-10-15 20:21:37 -04:00
module . exports . CreateMeshRelay = function ( parent , ws , req , domain , user , cookie ) {
2017-08-28 12:27:45 -04:00
var obj = { } ;
obj . ws = ws ;
2017-10-23 17:09:58 -04:00
obj . id = req . query . id ;
2019-08-11 01:34:21 -04:00
obj . user = user ;
2019-10-15 18:50:11 -04:00
obj . ruserid = null ;
2019-08-14 19:51:45 -04:00
obj . req = req ; // Used in multi-server.js
2019-04-28 23:31:08 -04:00
2019-10-15 18:50:11 -04:00
// Check relay authentication
2019-12-09 16:43:02 -05:00
if ( ( user == null ) && ( obj . req . query != null ) && ( obj . req . query . rauth != null ) ) {
const rcookie = parent . parent . decodeCookie ( obj . req . query . rauth , parent . parent . loginCookieEncryptionKey , 240 ) ; // Cookie with 4 hour timeout
2019-10-15 18:50:11 -04:00
if ( rcookie . ruserid != null ) { obj . ruserid = rcookie . ruserid ; }
}
// If there is no authentication, drop this connection
2019-12-09 16:43:02 -05:00
if ( ( obj . id != null ) && ( obj . id . startsWith ( 'meshmessenger/' ) == false ) && ( obj . user == null ) && ( obj . ruserid == null ) ) { try { ws . close ( ) ; parent . parent . debug ( 'relay' , 'Relay: Connection with no authentication (' + cleanRemoteAddr ( obj . req . ip ) + ')' ) ; } catch ( e ) { console . log ( e ) ; } return ; }
2019-10-15 18:50:11 -04:00
2019-04-28 23:31:08 -04:00
// Relay session count (we may remove this in the future)
obj . relaySessionCounted = true ;
parent . relaySessionCount ++ ;
2017-08-28 12:27:45 -04:00
2018-10-16 13:52:05 -04:00
// Mesh Rights
const MESHRIGHT _EDITMESH = 1 ;
const MESHRIGHT _MANAGEUSERS = 2 ;
const MESHRIGHT _MANAGECOMPUTERS = 4 ;
const MESHRIGHT _REMOTECONTROL = 8 ;
const MESHRIGHT _AGENTCONSOLE = 16 ;
const MESHRIGHT _SERVERFILES = 32 ;
const MESHRIGHT _WAKEDEVICE = 64 ;
const MESHRIGHT _SETNOTES = 128 ;
2018-11-27 20:13:01 -05:00
const MESHRIGHT _REMOTEVIEW = 256 ;
2018-10-16 13:52:05 -04:00
// Site rights
const SITERIGHT _SERVERBACKUP = 1 ;
const SITERIGHT _MANAGEUSERS = 2 ;
const SITERIGHT _SERVERRESTORE = 4 ;
const SITERIGHT _FILEACCESS = 8 ;
const SITERIGHT _SERVERUPDATE = 16 ;
const SITERIGHT _LOCKED = 32 ;
2019-04-28 23:31:08 -04:00
// Clean a IPv6 address that encodes a IPv4 address
function cleanRemoteAddr ( addr ) { if ( addr . startsWith ( '::ffff:' ) ) { return addr . substring ( 7 ) ; } else { return addr ; } }
2017-10-23 17:09:58 -04:00
// Disconnect this agent
obj . close = function ( arg ) {
2019-12-09 16:43:02 -05:00
if ( ( arg == 1 ) || ( arg == null ) ) { try { ws . close ( ) ; parent . parent . debug ( 'relay' , 'Relay: Soft disconnect (' + cleanRemoteAddr ( obj . req . ip ) + ')' ) ; } catch ( e ) { console . log ( e ) ; } } // Soft close, close the websocket
if ( arg == 2 ) { try { ws . _socket . _parent . end ( ) ; parent . parent . debug ( 'relay' , 'Relay: Hard disconnect (' + cleanRemoteAddr ( obj . req . ip ) + ')' ) ; } catch ( e ) { console . log ( e ) ; } } // Hard close, close the TCP socket
2019-04-28 23:31:08 -04:00
// Aggressive cleanup
delete obj . id ;
delete obj . ws ;
delete obj . peer ;
2018-08-30 15:05:23 -04:00
} ;
2017-10-23 17:09:58 -04:00
obj . sendAgentMessage = function ( command , userid , domainid ) {
2019-04-12 17:19:03 -04:00
var rights , mesh ;
2017-10-23 17:09:58 -04:00
if ( command . nodeid == null ) return false ;
2019-04-28 23:31:08 -04:00
var user = parent . users [ userid ] ;
2017-10-23 17:09:58 -04:00
if ( user == null ) return false ;
var splitnodeid = command . nodeid . split ( '/' ) ;
// Check that we are in the same domain and the user has rights over this node.
if ( ( splitnodeid [ 0 ] == 'node' ) && ( splitnodeid [ 1 ] == domainid ) ) {
// Get the user object
// See if the node is connected
2019-04-28 23:31:08 -04:00
var agent = parent . wsagents [ command . nodeid ] ;
2017-10-23 17:09:58 -04:00
if ( agent != null ) {
// Check if we have permission to send a message to that node
2019-12-27 18:18:43 -05:00
rights = user . links [ agent . dbMeshKey ] ; // TODO: Need to include user group / node rights
2019-04-12 17:19:03 -04:00
mesh = parent . meshes [ agent . dbMeshKey ] ;
if ( ( rights != null ) && ( mesh != null ) || ( ( rights & 16 ) != 0 ) ) { // TODO: 16 is console permission, may need more gradular permission checking
2019-10-17 13:09:16 -04:00
if ( ws . sessionId ) { command . sessionid = ws . sessionId ; } // Set the session id, required for responses.
2017-10-23 17:09:58 -04:00
command . rights = rights . rights ; // Add user rights flags to the message
2019-04-12 17:19:03 -04:00
command . consent = mesh . consent ; // Add user consent
if ( typeof domain . userconsentflags == 'number' ) { command . consent |= domain . userconsentflags ; } // Add server required consent flags
command . username = user . name ; // Add user name
2019-12-11 18:44:10 -05:00
if ( typeof domain . desktopprivacybartext == 'string' ) { command . privacybartext = domain . desktopprivacybartext ; } // Privacy bar text
2017-10-23 17:09:58 -04:00
delete command . nodeid ; // Remove the nodeid since it's implyed.
agent . send ( JSON . stringify ( command ) ) ;
return true ;
}
} else {
// Check if a peer server is connected to this agent
2019-04-28 23:31:08 -04:00
var routing = parent . parent . GetRoutingServerId ( command . nodeid , 1 ) ; // 1 = MeshAgent routing type
2017-10-23 17:09:58 -04:00
if ( routing != null ) {
// Check if we have permission to send a message to that node
2019-12-27 18:18:43 -05:00
rights = user . links [ routing . meshid ] ; // TODO: Need to include user groups / node rights
2019-04-12 17:19:03 -04:00
mesh = parent . meshes [ routing . meshid ] ;
2017-10-23 17:09:58 -04:00
if ( rights != null || ( ( rights & 16 ) != 0 ) ) { // TODO: 16 is console permission, may need more gradular permission checking
2019-10-17 13:09:16 -04:00
if ( ws . sessionId ) { command . fromSessionid = ws . sessionId ; } // Set the session id, required for responses.
2017-10-23 17:09:58 -04:00
command . rights = rights . rights ; // Add user rights flags to the message
2019-04-12 17:19:03 -04:00
command . consent = mesh . consent ; // Add user consent
if ( typeof domain . userconsentflags == 'number' ) { command . consent |= domain . userconsentflags ; } // Add server required consent flags
command . username = user . name ; // Add user name
2019-12-11 18:44:10 -05:00
if ( typeof domain . desktopprivacybartext == 'string' ) { command . privacybartext = domain . desktopprivacybartext ; } // Privacy bar text
2019-04-28 23:31:08 -04:00
parent . parent . multiServer . DispatchMessageSingleServer ( command , routing . serverid ) ;
2017-10-23 17:09:58 -04:00
return true ;
}
}
}
}
return false ;
2018-08-30 15:05:23 -04:00
} ;
2018-10-15 20:21:37 -04:00
2017-10-31 19:19:58 -04:00
function performRelay ( ) {
if ( obj . id == null ) { try { obj . close ( ) ; } catch ( e ) { } return null ; } // Attempt to connect without id, drop this.
ws . _socket . setKeepAlive ( true , 240000 ) ; // Set TCP keep alive
2017-10-23 17:09:58 -04:00
2018-12-02 02:41:57 -05:00
// If this is a MeshMessenger session, the ID is the two userid's and authentication must match one of them.
if ( obj . id . startsWith ( 'meshmessenger/' ) ) {
2019-04-28 23:31:08 -04:00
if ( ( obj . id . startsWith ( 'meshmessenger/user/' ) == true ) && ( user == null ) ) { try { obj . close ( ) ; } catch ( e ) { } return null ; } // If user-to-user, both sides need to be authenticated.
2018-12-02 02:41:57 -05:00
var x = obj . id . split ( '/' ) , user1 = x [ 1 ] + '/' + x [ 2 ] + '/' + x [ 3 ] , user2 = x [ 4 ] + '/' + x [ 5 ] + '/' + x [ 6 ] ;
2018-12-07 19:36:27 -05:00
if ( ( x [ 1 ] != 'user' ) && ( x [ 4 ] != 'user' ) ) { try { obj . close ( ) ; } catch ( e ) { } return null ; } // MeshMessenger session must have at least one authenticated user
if ( ( x [ 1 ] == 'user' ) && ( x [ 4 ] == 'user' ) ) {
// If this is a user-to-user session, you must be authenticated to join.
2019-04-28 23:31:08 -04:00
if ( ( user . _id != user1 ) && ( user . _id != user2 ) ) { try { obj . close ( ) ; } catch ( e ) { } return null ; }
2018-12-07 19:36:27 -05:00
} else {
// If only one side of the session is a user
// !!!!! TODO: Need to make sure that one of the two sides is the correct user. !!!!!
}
2018-12-02 02:41:57 -05:00
}
2017-10-31 19:19:58 -04:00
// Validate that the id is valid, we only need to do this on non-authenticated sessions.
// TODO: Figure out when this needs to be done.
/ *
if ( ! parent . args . notls ) {
// Check the identifier, if running without TLS, skip this.
var ids = obj . id . split ( ':' ) ;
2019-04-28 23:31:08 -04:00
if ( ids . length != 3 ) { ws . close ( ) ; delete obj . id ; return null ; } // Invalid ID, drop this.
if ( parent . crypto . createHmac ( 'SHA384' , parent . relayRandom ) . update ( ids [ 0 ] + ':' + ids [ 1 ] ) . digest ( 'hex' ) != ids [ 2 ] ) { ws . close ( ) ; delete obj . id ; return null ; } // Invalid HMAC, drop this.
if ( ( Date . now ( ) - parseInt ( ids [ 1 ] ) ) > 120000 ) { ws . close ( ) ; delete obj . id ; return null ; } // Expired time, drop this.
2017-10-31 19:19:58 -04:00
obj . id = ids [ 0 ] ;
}
* /
// Check the peer connection status
{
var relayinfo = parent . wsrelays [ obj . id ] ;
if ( relayinfo ) {
if ( relayinfo . state == 1 ) {
// Check that at least one connection is authenticated
if ( ( obj . authenticated != true ) && ( relayinfo . peer1 . authenticated != true ) ) {
2019-04-28 23:31:08 -04:00
ws . close ( ) ;
2019-12-09 16:43:02 -05:00
parent . parent . debug ( 'relay' , 'Relay without-auth: ' + obj . id + ' (' + cleanRemoteAddr ( obj . req . ip ) + ')' ) ;
2019-04-28 23:31:08 -04:00
delete obj . id ;
delete obj . ws ;
delete obj . peer ;
2017-10-31 19:19:58 -04:00
return null ;
}
2019-10-15 18:50:11 -04:00
// Check that both connection are for the same user
if ( ! obj . id . startsWith ( 'meshmessenger/' ) ) {
var u1 = obj . user ? obj . user . _id : obj . ruserid ;
var u2 = relayinfo . peer1 . user ? relayinfo . peer1 . user . _id : relayinfo . peer1 . ruserid ;
2019-10-17 13:09:16 -04:00
if ( parent . args . user != null ) { // If the server is setup with a default user, correct the userid now.
if ( u1 != null ) { u1 = 'user/' + domain . id + '/' + parent . args . user . toLowerCase ( ) ; }
if ( u2 != null ) { u2 = 'user/' + domain . id + '/' + parent . args . user . toLowerCase ( ) ; }
}
2019-10-15 18:50:11 -04:00
if ( u1 != u2 ) {
ws . close ( ) ;
2019-12-09 16:43:02 -05:00
parent . parent . debug ( 'relay' , 'Relay auth mismatch (' + u1 + ' != ' + u2 + '): ' + obj . id + ' (' + cleanRemoteAddr ( obj . req . ip ) + ')' ) ;
2019-10-15 18:50:11 -04:00
delete obj . id ;
delete obj . ws ;
delete obj . peer ;
return null ;
}
}
2017-10-31 19:19:58 -04:00
// Connect to peer
obj . peer = relayinfo . peer1 ;
obj . peer . peer = obj ;
relayinfo . peer2 = obj ;
relayinfo . state = 2 ;
2018-12-01 00:23:10 -05:00
relayinfo . peer1 . ws . _socket . resume ( ) ; // Release the traffic
relayinfo . peer2 . ws . _socket . resume ( ) ; // Release the traffic
2019-08-01 18:35:23 -04:00
ws . time = relayinfo . peer1 . ws . time = Date . now ( ) ;
2017-08-28 12:27:45 -04:00
2017-10-31 19:19:58 -04:00
relayinfo . peer1 . ws . peer = relayinfo . peer2 . ws ;
relayinfo . peer2 . ws . peer = relayinfo . peer1 . ws ;
2019-05-13 17:06:13 -04:00
// Remove the timeout
if ( relayinfo . timeout ) { clearTimeout ( relayinfo . timeout ) ; delete relayinfo . timeout ; }
2019-08-05 14:30:07 -04:00
// Setup session recording
2019-08-11 01:34:21 -04:00
var sessionUser = obj . user ;
2019-08-05 14:30:07 -04:00
if ( sessionUser == null ) { sessionUser = obj . peer . user ; }
2019-12-09 16:43:02 -05:00
if ( ( sessionUser != null ) && ( domain . sessionrecording == true || ( ( typeof domain . sessionrecording == 'object' ) && ( ( domain . sessionrecording . protocols == null ) || ( domain . sessionrecording . protocols . indexOf ( parseInt ( obj . req . query . p ) ) >= 0 ) ) ) ) ) {
2019-09-25 20:06:35 -04:00
// Get the computer name
2019-12-09 16:43:02 -05:00
parent . db . Get ( obj . req . query . nodeid , function ( err , nodes ) {
2019-09-25 20:06:35 -04:00
var xusername = '' , xdevicename = '' , xdevicename2 = null ;
if ( ( nodes != null ) && ( nodes . length == 1 ) ) { xdevicename2 = nodes [ 0 ] . name ; xdevicename = '-' + parent . common . makeFilename ( nodes [ 0 ] . name ) ; }
// Get the username and make it acceptable as a filename
if ( sessionUser . _id ) { xusername = '-' + parent . common . makeFilename ( sessionUser . _id . split ( '/' ) [ 2 ] ) ; }
var now = new Date ( Date . now ( ) ) ;
var recFilename = 'relaysession' + ( ( domain . id == '' ) ? '' : '-' ) + domain . id + '-' + now . getUTCFullYear ( ) + '-' + parent . common . zeroPad ( now . getUTCMonth ( ) , 2 ) + '-' + parent . common . zeroPad ( now . getUTCDate ( ) , 2 ) + '-' + parent . common . zeroPad ( now . getUTCHours ( ) , 2 ) + '-' + parent . common . zeroPad ( now . getUTCMinutes ( ) , 2 ) + '-' + parent . common . zeroPad ( now . getUTCSeconds ( ) , 2 ) + xusername + xdevicename + '-' + obj . id + '.mcrec'
var recFullFilename = null ;
if ( domain . sessionrecording . filepath ) {
try { parent . parent . fs . mkdirSync ( domain . sessionrecording . filepath ) ; } catch ( e ) { }
recFullFilename = parent . parent . path . join ( domain . sessionrecording . filepath , recFilename ) ;
2019-08-06 16:27:24 -04:00
} else {
2019-09-25 20:06:35 -04:00
try { parent . parent . fs . mkdirSync ( parent . parent . recordpath ) ; } catch ( e ) { }
recFullFilename = parent . parent . path . join ( parent . parent . recordpath , recFilename ) ;
2019-08-06 16:27:24 -04:00
}
2019-09-25 20:06:35 -04:00
parent . parent . fs . open ( recFullFilename , 'w' , function ( err , fd ) {
if ( err != null ) {
// Unable to record
try { ws . send ( 'c' ) ; } catch ( ex ) { } // Send connect to both peers
try { relayinfo . peer1 . ws . send ( 'c' ) ; } catch ( ex ) { }
} else {
// Write the recording file header
2019-12-09 16:43:02 -05:00
var metadata = { magic : 'MeshCentralRelaySession' , ver : 1 , userid : sessionUser . _id , username : sessionUser . name , sessionid : obj . id , ipaddr1 : cleanRemoteAddr ( obj . req . ip ) , ipaddr2 : cleanRemoteAddr ( obj . peer . req . ip ) , time : new Date ( ) . toLocaleString ( ) , protocol : ( ( ( obj . req == null ) || ( obj . req . query == null ) ) ? null : obj . req . query . p ) , nodeid : ( ( ( obj . req == null ) || ( obj . req . query == null ) ) ? null : obj . req . query . nodeid ) } ;
2019-09-25 20:06:35 -04:00
if ( xdevicename2 != null ) { metadata . devicename = xdevicename2 ; }
var firstBlock = JSON . stringify ( metadata ) ;
2019-12-09 16:43:02 -05:00
recordingEntry ( fd , 1 , ( ( obj . req . query . browser ) ? 2 : 0 ) , firstBlock , function ( ) {
2020-02-14 14:20:06 -05:00
try { relayinfo . peer1 . ws . logfile = ws . logfile = { fd : fd , lock : false , filename : recFullFilename } ; } catch ( ex ) {
2019-10-10 14:13:25 -04:00
try { ws . send ( 'c' ) ; } catch ( ex ) { } // Send connect to both peers, 'cr' indicates the session is being recorded.
try { relayinfo . peer1 . ws . send ( 'c' ) ; } catch ( ex ) { }
return ;
}
2019-09-25 20:06:35 -04:00
try { ws . send ( 'cr' ) ; } catch ( ex ) { } // Send connect to both peers, 'cr' indicates the session is being recorded.
try { relayinfo . peer1 . ws . send ( 'cr' ) ; } catch ( ex ) { }
} ) ;
}
} ) ;
2019-08-05 14:30:07 -04:00
} ) ;
} else {
// Send session start
2019-08-14 19:51:45 -04:00
try { ws . send ( 'c' ) ; } catch ( ex ) { } // Send connect to both peers
try { relayinfo . peer1 . ws . send ( 'c' ) ; } catch ( ex ) { }
2019-08-05 14:30:07 -04:00
}
2019-12-09 16:43:02 -05:00
parent . parent . debug ( 'relay' , 'Relay connected: ' + obj . id + ' (' + cleanRemoteAddr ( obj . req . ip ) + ' --> ' + cleanRemoteAddr ( obj . peer . req . ip ) + ')' ) ;
2019-08-01 18:35:23 -04:00
// Log the connection
2019-08-11 01:34:21 -04:00
if ( sessionUser != null ) {
2019-08-06 16:27:24 -04:00
var msg = 'Started relay session' ;
2019-12-09 16:43:02 -05:00
if ( obj . req . query . p == 1 ) { msg = 'Started terminal session' ; }
else if ( obj . req . query . p == 2 ) { msg = 'Started desktop session' ; }
else if ( obj . req . query . p == 5 ) { msg = 'Started file management session' ; }
2019-08-31 22:40:50 -04:00
var event = { etype : 'relay' , action : 'relaylog' , domain : domain . id , userid : sessionUser . _id , username : sessionUser . name , msg : msg + ' \"' + obj . id + '\" from ' + cleanRemoteAddr ( obj . peer . req . ip ) + ' to ' + cleanRemoteAddr ( req . ip ) , protocol : req . query . p , nodeid : req . query . nodeid } ;
2019-08-11 01:34:21 -04:00
parent . parent . DispatchEvent ( [ '*' , sessionUser . _id ] , obj , event ) ;
2019-08-01 18:35:23 -04:00
}
2017-10-31 19:19:58 -04:00
} else {
// Connected already, drop (TODO: maybe we should re-connect?)
2019-04-28 23:31:08 -04:00
ws . close ( ) ;
2019-12-09 16:43:02 -05:00
parent . parent . debug ( 'relay' , 'Relay duplicate: ' + obj . id + ' (' + cleanRemoteAddr ( obj . req . ip ) + ')' ) ;
2019-04-28 23:31:08 -04:00
delete obj . id ;
delete obj . ws ;
delete obj . peer ;
2017-09-06 13:45:09 -04:00
return null ;
}
2017-08-28 12:27:45 -04:00
} else {
2017-10-31 19:19:58 -04:00
// Wait for other relay connection
2018-12-01 00:23:10 -05:00
ws . _socket . pause ( ) ; // Hold traffic until the other connection
2019-05-13 17:06:13 -04:00
parent . wsrelays [ obj . id ] = { peer1 : obj , state : 1 , timeout : setTimeout ( function ( ) { closeBothSides ( ) ; } , 30000 ) } ;
2019-12-09 16:43:02 -05:00
parent . parent . debug ( 'relay' , 'Relay holding: ' + obj . id + ' (' + cleanRemoteAddr ( obj . req . ip ) + ') ' + ( obj . authenticated ? 'Authenticated' : '' ) ) ;
2017-09-17 20:22:18 -04:00
2017-10-31 19:19:58 -04:00
// Check if a peer server has this connection
if ( parent . parent . multiServer != null ) {
2019-04-28 23:31:08 -04:00
var rsession = parent . wsPeerRelays [ obj . id ] ;
if ( ( rsession != null ) && ( rsession . serverId > parent . parent . serverId ) ) {
2017-10-31 19:19:58 -04:00
// We must initiate the connection to the peer
2019-12-09 16:43:02 -05:00
parent . parent . multiServer . createPeerRelay ( ws , req , rsession . serverId , obj . req . session . userid ) ;
2017-10-31 19:19:58 -04:00
delete parent . wsrelays [ obj . id ] ;
} else {
// Send message to other peers that we have this connection
parent . parent . multiServer . DispatchMessage ( JSON . stringify ( { action : 'relay' , id : obj . id } ) ) ;
}
2017-09-17 20:22:18 -04:00
}
}
2017-08-28 12:27:45 -04:00
}
}
2018-01-16 20:30:34 -05:00
2018-12-11 20:52:37 -05:00
ws . flushSink = function ( ) { try { ws . _socket . resume ( ) ; } catch ( ex ) { console . log ( ex ) ; } } ;
2017-08-28 12:27:45 -04:00
// When data is received from the mesh relay web socket
2017-09-01 14:23:22 -04:00
ws . on ( 'message' , function ( data ) {
2017-12-19 11:50:19 -05:00
//console.log(typeof data, data.length);
2018-01-16 20:30:34 -05:00
if ( this . peer != null ) {
2018-07-06 13:13:19 -04:00
//if (typeof data == 'string') { console.log('Relay: ' + data); } else { console.log('Relay:' + data.length + ' byte(s)'); }
2018-12-11 20:52:37 -05:00
try {
this . _socket . pause ( ) ;
2019-08-05 18:22:00 -04:00
if ( this . logfile != null ) {
2019-08-05 14:30:07 -04:00
// Write data to log file then perform relay
var xthis = this ;
2019-12-09 16:43:02 -05:00
recordingEntry ( this . logfile . fd , 2 , ( ( obj . req . query . browser ) ? 2 : 0 ) , data , function ( ) { xthis . peer . send ( data , ws . flushSink ) ; } ) ;
2019-08-05 14:30:07 -04:00
} else {
// Perform relay
this . peer . send ( data , ws . flushSink ) ;
}
2018-12-11 20:52:37 -05:00
} catch ( ex ) { console . log ( ex ) ; }
2018-01-16 20:30:34 -05:00
}
2017-09-01 14:23:22 -04:00
} ) ;
2017-08-28 12:27:45 -04:00
2019-01-02 21:34:50 -05:00
// If error, close both sides of the relay.
2018-12-11 20:52:37 -05:00
ws . on ( 'error' , function ( err ) {
2019-04-28 23:31:08 -04:00
parent . relaySessionErrorCount ++ ;
if ( obj . relaySessionCounted ) { parent . relaySessionCount -- ; delete obj . relaySessionCounted ; }
2019-12-09 16:43:02 -05:00
console . log ( 'Relay error from ' + cleanRemoteAddr ( obj . req . ip ) + ', ' + err . toString ( ) . split ( '\r' ) [ 0 ] + '.' ) ;
2019-01-02 21:03:34 -05:00
closeBothSides ( ) ;
2018-12-11 20:52:37 -05:00
} ) ;
2017-08-28 12:27:45 -04:00
2019-01-02 21:34:50 -05:00
// If the relay web socket is closed, close both sides.
2017-08-28 12:27:45 -04:00
ws . on ( 'close' , function ( req ) {
2019-04-28 23:31:08 -04:00
if ( obj . relaySessionCounted ) { parent . relaySessionCount -- ; delete obj . relaySessionCounted ; }
2019-01-02 21:03:34 -05:00
closeBothSides ( ) ;
} ) ;
// Close both our side and the peer side.
function closeBothSides ( ) {
2017-08-28 12:27:45 -04:00
if ( obj . id != null ) {
var relayinfo = parent . wsrelays [ obj . id ] ;
2017-09-17 20:22:18 -04:00
if ( relayinfo != null ) {
if ( relayinfo . state == 2 ) {
var peer = ( relayinfo . peer1 == obj ) ? relayinfo . peer2 : relayinfo . peer1 ;
2019-08-05 14:30:07 -04:00
// Close the recording file
2020-02-14 14:20:06 -05:00
if ( ws . logfile != null ) {
recordingEntry ( ws . logfile . fd , 3 , 0 , 'MeshCentralMCREC' , function ( fd , tag ) {
parent . parent . fs . close ( fd ) ;
tag . ws . logfile = null ;
tag . pws . logfile = null ;
// Now that the recording file is closed, check if we need to index this file.
if ( domain . sessionrecording . index == true ) { parent . parent . certificateOperations . acceleratorPerformOperation ( 'indexMcRec' , tag . logfile . filename ) ; }
} , { ws : ws , pws : peer . ws , logfile : ws . logfile } ) ;
}
2019-08-05 14:30:07 -04:00
// Disconnect the peer
2019-04-28 23:31:08 -04:00
try { if ( peer . relaySessionCounted ) { parent . relaySessionCount -- ; delete peer . relaySessionCounted ; } } catch ( ex ) { console . log ( ex ) ; }
2019-12-09 16:43:02 -05:00
parent . parent . debug ( 'relay' , 'Relay disconnect: ' + obj . id + ' (' + cleanRemoteAddr ( obj . req . ip ) + ' --> ' + cleanRemoteAddr ( peer . req . ip ) + ')' ) ;
2017-09-17 20:22:18 -04:00
try { peer . ws . close ( ) ; } catch ( e ) { } // Soft disconnect
try { peer . ws . _socket . _parent . end ( ) ; } catch ( e ) { } // Hard disconnect
2019-04-28 23:31:08 -04:00
2019-08-01 18:35:23 -04:00
// Log the disconnection
2019-08-01 18:44:08 -04:00
if ( ws . time ) {
2019-08-06 16:27:24 -04:00
var msg = 'Ended relay session' ;
2019-12-09 16:43:02 -05:00
if ( obj . req . query . p == 1 ) { msg = 'Ended terminal session' ; }
else if ( obj . req . query . p == 2 ) { msg = 'Ended desktop session' ; }
else if ( obj . req . query . p == 5 ) { msg = 'Ended file management session' ; }
2019-08-01 18:44:08 -04:00
if ( user ) {
2019-12-09 16:43:02 -05:00
var event = { etype : 'relay' , action : 'relaylog' , domain : domain . id , userid : user . _id , username : parent . users [ user . _id ] . name , msg : msg + ' \"' + obj . id + '\" from ' + cleanRemoteAddr ( obj . peer . req . ip ) + ' to ' + cleanRemoteAddr ( obj . req . ip ) + ', ' + Math . floor ( ( Date . now ( ) - ws . time ) / 1000 ) + ' second(s)' , protocol : obj . req . query . p , nodeid : obj . req . query . nodeid } ;
2019-08-01 18:44:08 -04:00
parent . parent . DispatchEvent ( [ '*' , user . _id ] , obj , event ) ;
} else if ( peer . user ) {
2019-12-09 16:43:02 -05:00
var event = { etype : 'relay' , action : 'relaylog' , domain : domain . id , userid : peer . user . _id , username : parent . users [ peer . user . _id ] . name , msg : msg + ' \"' + obj . id + '\" from ' + cleanRemoteAddr ( obj . peer . req . ip ) + ' to ' + cleanRemoteAddr ( obj . req . ip ) + ', ' + Math . floor ( ( Date . now ( ) - ws . time ) / 1000 ) + ' second(s)' , protocol : obj . req . query . p , nodeid : obj . req . query . nodeid } ;
2019-08-01 18:44:08 -04:00
parent . parent . DispatchEvent ( [ '*' , peer . user . _id ] , obj , event ) ;
}
2019-08-01 18:35:23 -04:00
}
2019-04-28 23:31:08 -04:00
// Aggressive peer cleanup
delete peer . id ;
delete peer . ws ;
delete peer . peer ;
2017-09-17 20:22:18 -04:00
} else {
2019-12-09 16:43:02 -05:00
parent . parent . debug ( 'relay' , 'Relay disconnect: ' + obj . id + ' (' + cleanRemoteAddr ( obj . req . ip ) + ')' ) ;
2017-09-17 20:22:18 -04:00
}
2019-05-13 17:06:13 -04:00
try { ws . close ( ) ; } catch ( ex ) { }
2017-09-17 20:22:18 -04:00
delete parent . wsrelays [ obj . id ] ;
2017-08-28 12:27:45 -04:00
}
}
2019-04-28 23:31:08 -04:00
// Aggressive cleanup
delete obj . id ;
delete obj . ws ;
delete obj . peer ;
2019-01-02 21:03:34 -05:00
}
2018-08-30 15:05:23 -04:00
2019-08-07 19:07:12 -04:00
// Record a new entry in a recording log
2019-08-13 14:49:05 -04:00
function recordingEntry ( fd , type , flags , data , func , tag ) {
2019-08-07 19:07:12 -04:00
try {
if ( typeof data == 'string' ) {
// String write
var blockData = Buffer . from ( data ) , header = Buffer . alloc ( 16 ) ; // Header: Type (2) + Flags (2) + Size(4) + Time(8)
header . writeInt16BE ( type , 0 ) ; // Type (1 = Header, 2 = Network Data)
header . writeInt16BE ( flags , 2 ) ; // Flags (1 = Binary, 2 = User)
header . writeInt32BE ( blockData . length , 4 ) ; // Size
header . writeIntBE ( new Date ( ) , 10 , 6 ) ; // Time
var block = Buffer . concat ( [ header , blockData ] ) ;
2019-08-13 14:49:05 -04:00
parent . parent . fs . write ( fd , block , 0 , block . length , function ( ) { func ( fd , tag ) ; } ) ;
2019-08-07 19:07:12 -04:00
} else {
// Binary write
var header = Buffer . alloc ( 16 ) ; // Header: Type (2) + Flags (2) + Size(4) + Time(8)
header . writeInt16BE ( type , 0 ) ; // Type (1 = Header, 2 = Network Data)
header . writeInt16BE ( flags | 1 , 2 ) ; // Flags (1 = Binary, 2 = User)
header . writeInt32BE ( data . length , 4 ) ; // Size
header . writeIntBE ( new Date ( ) , 10 , 6 ) ; // Time
var block = Buffer . concat ( [ header , data ] ) ;
2019-08-13 14:49:05 -04:00
parent . parent . fs . write ( fd , block , 0 , block . length , function ( ) { func ( fd , tag ) ; } ) ;
2019-08-07 19:07:12 -04:00
}
2019-08-13 14:49:05 -04:00
} catch ( ex ) { console . log ( ex ) ; func ( fd , tag ) ; }
2019-08-07 19:07:12 -04:00
}
2018-10-16 13:52:05 -04:00
// Mark this relay session as authenticated if this is the user end.
2019-04-28 23:31:08 -04:00
obj . authenticated = ( user != null ) ;
2018-10-16 13:52:05 -04:00
if ( obj . authenticated ) {
// Kick off the routing, if we have agent routing instructions, process them here.
// Routing instructions can only be given by a authenticated user
2019-04-28 23:31:08 -04:00
if ( ( cookie != null ) && ( cookie . nodeid != null ) && ( cookie . tcpport != null ) && ( cookie . domainid != null ) ) {
2018-10-16 13:52:05 -04:00
// We have routing instructions in the cookie, but first, check user access for this node.
2019-04-28 23:31:08 -04:00
parent . db . Get ( cookie . nodeid , function ( err , docs ) {
2018-10-16 13:52:05 -04:00
if ( docs . length == 0 ) { console . log ( 'ERR: Node not found' ) ; try { obj . close ( ) ; } catch ( e ) { } return ; } // Disconnect websocket
2019-10-17 13:09:16 -04:00
const node = docs [ 0 ] ;
2018-10-16 13:52:05 -04:00
// Check if this user has permission to manage this computer
2019-10-17 13:09:16 -04:00
const meshlinks = user . links [ node . meshid ] ;
2018-10-16 13:52:05 -04:00
if ( ( ! meshlinks ) || ( ! meshlinks . rights ) || ( ( meshlinks . rights & MESHRIGHT _REMOTECONTROL ) == 0 ) ) { console . log ( 'ERR: Access denied (2)' ) ; try { obj . close ( ) ; } catch ( e ) { } return ; }
// Send connection request to agent
2019-10-17 13:09:16 -04:00
const rcookie = parent . parent . encodeCookie ( { ruserid : user . _id } , parent . parent . loginCookieEncryptionKey ) ;
2018-10-16 13:52:05 -04:00
if ( obj . id == undefined ) { obj . id = ( '' + Math . random ( ) ) . substring ( 2 ) ; } // If there is no connection id, generate one.
2019-10-17 13:09:16 -04:00
const command = { nodeid : cookie . nodeid , action : 'msg' , type : 'tunnel' , value : '*/meshrelay.ashx?id=' + obj . id + '&rauth=' + rcookie , tcpport : cookie . tcpport , tcpaddr : cookie . tcpaddr } ;
2019-08-22 18:31:39 -04:00
parent . parent . debug ( 'relay' , 'Relay: Sending agent tunnel command: ' + JSON . stringify ( command ) ) ;
2019-12-09 16:43:02 -05:00
if ( obj . sendAgentMessage ( command , user . _id , cookie . domainid ) == false ) { delete obj . id ; parent . parent . debug ( 'relay' , 'Relay: Unable to contact this agent (' + cleanRemoteAddr ( obj . req . ip ) + ')' ) ; }
2018-10-16 13:52:05 -04:00
performRelay ( ) ;
} ) ;
return obj ;
2019-12-09 16:43:02 -05:00
} else if ( ( obj . req . query . nodeid != null ) && ( ( obj . req . query . tcpport != null ) || ( obj . req . query . udpport != null ) ) ) {
2018-10-16 13:52:05 -04:00
// We have routing instructions in the URL arguments, but first, check user access for this node.
2019-12-09 16:43:02 -05:00
parent . db . Get ( obj . req . query . nodeid , function ( err , docs ) {
2018-10-16 13:52:05 -04:00
if ( docs . length == 0 ) { console . log ( 'ERR: Node not found' ) ; try { obj . close ( ) ; } catch ( e ) { } return ; } // Disconnect websocket
2019-10-17 13:09:16 -04:00
const node = docs [ 0 ] ;
2018-10-16 13:52:05 -04:00
// Check if this user has permission to manage this computer
2019-10-17 13:09:16 -04:00
const meshlinks = user . links [ node . meshid ] ;
2018-10-16 13:52:05 -04:00
if ( ( ! meshlinks ) || ( ! meshlinks . rights ) || ( ( meshlinks . rights & MESHRIGHT _REMOTECONTROL ) == 0 ) ) { console . log ( 'ERR: Access denied (2)' ) ; try { obj . close ( ) ; } catch ( e ) { } return ; }
// Send connection request to agent
if ( obj . id == null ) { obj . id = ( '' + Math . random ( ) ) . substring ( 2 ) ; } // If there is no connection id, generate one.
2019-10-17 13:09:16 -04:00
const rcookie = parent . parent . encodeCookie ( { ruserid : user . _id } , parent . parent . loginCookieEncryptionKey ) ;
2019-05-06 21:44:23 -04:00
2019-12-09 16:43:02 -05:00
if ( obj . req . query . tcpport != null ) {
const command = { nodeid : obj . req . query . nodeid , action : 'msg' , type : 'tunnel' , value : '*/meshrelay.ashx?id=' + obj . id + '&rauth=' + rcookie , tcpport : obj . req . query . tcpport , tcpaddr : ( ( obj . req . query . tcpaddr == null ) ? '127.0.0.1' : obj . req . query . tcpaddr ) } ;
2019-08-22 18:31:39 -04:00
parent . parent . debug ( 'relay' , 'Relay: Sending agent TCP tunnel command: ' + JSON . stringify ( command ) ) ;
2019-12-09 16:43:02 -05:00
if ( obj . sendAgentMessage ( command , user . _id , domain . id ) == false ) { delete obj . id ; parent . parent . debug ( 'relay' , 'Relay: Unable to contact this agent (' + cleanRemoteAddr ( obj . req . ip ) + ')' ) ; }
} else if ( obj . req . query . udpport != null ) {
const command = { nodeid : obj . req . query . nodeid , action : 'msg' , type : 'tunnel' , value : '*/meshrelay.ashx?id=' + obj . id + '&rauth=' + rcookie , udpport : obj . req . query . udpport , udpaddr : ( ( obj . req . query . udpaddr == null ) ? '127.0.0.1' : obj . req . query . udpaddr ) } ;
2019-08-22 18:31:39 -04:00
parent . parent . debug ( 'relay' , 'Relay: Sending agent UDP tunnel command: ' + JSON . stringify ( command ) ) ;
2019-12-09 16:43:02 -05:00
if ( obj . sendAgentMessage ( command , user . _id , domain . id ) == false ) { delete obj . id ; parent . parent . debug ( 'relay' , 'Relay: Unable to contact this agent (' + cleanRemoteAddr ( obj . req . ip ) + ')' ) ; }
2019-05-06 21:44:23 -04:00
}
2018-10-16 13:52:05 -04:00
performRelay ( ) ;
} ) ;
return obj ;
}
}
// If this is not an authenticated session, or the session does not have routing instructions, just go ahead an connect to existing session.
performRelay ( ) ;
2017-08-28 12:27:45 -04:00
return obj ;
2018-08-30 15:05:23 -04:00
} ;
2019-08-05 23:59:24 -04:00
/ *
Relay session recording required that "SessionRecording" : true be set in the domain section of the config . json .
Once done , a folder "meshcentral-recordings" will be created next to "meshcentral-data" that will contain all
of the recording files with the . mcrec extension .
The recording files are binary and contain a set of :
< HEADER > < DATABLOCK > < HEADER > < DATABLOCK > < HEADER > < DATABLOCK > < HEADER > < DATABLOCK > ...
The header is always 16 bytes long and is encoded like this :
2019-08-13 14:49:05 -04:00
TYPE 2 bytes , 1 = Header , 2 = Network Data , 3 = EndBlock
2019-08-05 23:59:24 -04:00
FLAGS 2 bytes , 0x0001 = Binary , 0x0002 = User
SIZE 4 bytes , Size of the data following this header .
TIME 8 bytes , Time this record was written , number of milliseconds since 1 January , 1970 UTC .
All values are BigEndian encoded . The first data block is of TYPE 1 and contains a JSON string with information
about this recording . It looks something like this :
{
magic : 'MeshCentralRelaySession' ,
ver : 1 ,
userid : "user\domain\userid" ,
username : "username" ,
sessionid : "RandomValue" ,
ipaddr1 : 1.2 . 3.4 ,
ipaddr2 : 1.2 . 3.5 ,
time : new Date ( ) . toLocaleString ( )
}
The rest of the data blocks are all network traffic that was relayed thru the server . They are of TYPE 2 and have
2019-08-06 16:27:24 -04:00
a given size and timestamp . When looking at network traffic the flags are important :
2019-08-05 23:59:24 -04:00
- If traffic has the first ( 0x0001 ) flag set , the data is binary otherwise it ' s a string .
- If the traffic has the second ( 0x0002 ) flag set , traffic is coming from the user 's browser, if not, it' s coming from the MeshAgent .
* /