2017-08-28 19:27:45 +03:00
/ * *
2018-01-04 23:15:21 +03:00
* @ description MeshCentral connection relay module
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 22:05:23 +03:00
/*jslint node: true */
/*jshint node: true */
/*jshint strict:false */
/*jshint -W097 */
/*jshint esversion: 6 */
"use strict" ;
2018-08-27 22:24:15 +03:00
2021-01-31 11:18:19 +03:00
// Mesh Rights
const MESHRIGHT _EDITMESH = 0x00000001 ;
const MESHRIGHT _MANAGEUSERS = 0x00000002 ;
const MESHRIGHT _MANAGECOMPUTERS = 0x00000004 ;
const MESHRIGHT _REMOTECONTROL = 0x00000008 ;
const MESHRIGHT _AGENTCONSOLE = 0x00000010 ;
const MESHRIGHT _SERVERFILES = 0x00000020 ;
const MESHRIGHT _WAKEDEVICE = 0x00000040 ;
const MESHRIGHT _SETNOTES = 0x00000080 ;
const MESHRIGHT _REMOTEVIEWONLY = 0x00000100 ;
const MESHRIGHT _NOTERMINAL = 0x00000200 ;
const MESHRIGHT _NOFILES = 0x00000400 ;
const MESHRIGHT _NOAMT = 0x00000800 ;
const MESHRIGHT _DESKLIMITEDINPUT = 0x00001000 ;
const MESHRIGHT _LIMITEVENTS = 0x00002000 ;
const MESHRIGHT _CHATNOTIFY = 0x00004000 ;
const MESHRIGHT _UNINSTALL = 0x00008000 ;
const MESHRIGHT _NODESKTOP = 0x00010000 ;
const MESHRIGHT _REMOTECOMMAND = 0x00020000 ;
const MESHRIGHT _RESETOFF = 0x00040000 ;
const MESHRIGHT _GUESTSHARING = 0x00080000 ;
const MESHRIGHT _ADMIN = 0xFFFFFFFF ;
2020-10-31 03:08:07 +03:00
2021-05-05 10:18:41 +03:00
// Protocol:
// 1 = Terminal
// 2 = Desktop
// 5 = Files
// 10 = Web-RDP
// 11 = Web-SSH
// 12 = Web-VNC
// 100 = Intel AMT WSMAN
// 101 = Intel AMT Redirection
// 200 = Messenger
2020-10-31 03:08:07 +03:00
function checkDeviceSharePublicIdentifier ( parent , domain , nodeid , pid , func ) {
// Check the public id
parent . db . GetAllTypeNodeFiltered ( [ nodeid ] , domain . id , 'deviceshare' , null , function ( err , docs ) {
if ( ( err != null ) || ( docs . length == 0 ) ) { func ( false ) ; return ; }
// Search for the device share public identifier
var found = false ;
for ( var i = 0 ; i < docs . length ; i ++ ) { if ( docs [ i ] . publicid == pid ) { found = true ; } }
func ( found ) ;
} ) ;
}
2018-10-16 03:21:37 +03:00
module . exports . CreateMeshRelay = function ( parent , ws , req , domain , user , cookie ) {
2020-10-31 03:08:07 +03:00
if ( ( cookie != null ) && ( typeof cookie . nid == 'string' ) && ( typeof cookie . pid == 'string' ) ) {
checkDeviceSharePublicIdentifier ( parent , domain , cookie . nid , cookie . pid , function ( result ) {
// If the identifier if not found, close the connection
if ( result == false ) { try { ws . close ( ) ; } catch ( e ) { } return ; }
// Public device sharing identifier found, continue as normal.
CreateMeshRelayEx ( parent , ws , req , domain , user , cookie ) ;
} ) ;
} else {
CreateMeshRelayEx ( parent , ws , req , domain , user , cookie ) ;
}
}
function CreateMeshRelayEx ( parent , ws , req , domain , user , cookie ) {
2020-08-08 01:51:28 +03:00
const currentTime = Date . now ( ) ;
if ( cookie ) {
2020-08-08 03:21:14 +03:00
if ( ( typeof cookie . expire == 'number' ) && ( cookie . expire <= currentTime ) ) { try { ws . close ( ) ; parent . parent . debug ( 'relay' , 'Relay: Expires cookie (' + req . clientIp + ')' ) ; } catch ( e ) { console . log ( e ) ; } return ; }
if ( typeof cookie . nid == 'string' ) { req . query . nodeid = cookie . nid ; }
2020-08-08 01:51:28 +03:00
}
2017-08-28 19:27:45 +03:00
var obj = { } ;
obj . ws = ws ;
2017-10-24 00:09:58 +03:00
obj . id = req . query . id ;
2019-08-11 08:34:21 +03:00
obj . user = user ;
2019-10-16 01:50:11 +03:00
obj . ruserid = null ;
2019-08-15 02:51:45 +03:00
obj . req = req ; // Used in multi-server.js
2019-04-29 06:31:08 +03:00
2020-10-31 03:08:07 +03:00
// Setup subscription for desktop sharing public identifier
// If the identifier is removed, drop the connection
if ( ( cookie != null ) && ( typeof cookie . pid == 'string' ) ) {
obj . pid = cookie . pid ;
parent . parent . AddEventDispatch ( [ cookie . nid ] , obj ) ;
obj . HandleEvent = function ( source , event , ids , id ) { if ( ( event . action == 'removedDeviceShare' ) && ( obj . pid == event . publicid ) ) { closeBothSides ( ) ; } }
}
2019-10-16 01:50:11 +03:00
// Check relay authentication
2019-12-10 00:43:02 +03: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-16 01:50:11 +03:00
if ( rcookie . ruserid != null ) { obj . ruserid = rcookie . ruserid ; }
}
// If there is no authentication, drop this connection
2020-05-22 07:25:11 +03: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 (' + obj . req . clientIp + ')' ) ; } catch ( e ) { console . log ( e ) ; } return ; }
2019-10-16 01:50:11 +03:00
2019-04-29 06:31:08 +03:00
// Relay session count (we may remove this in the future)
obj . relaySessionCounted = true ;
parent . relaySessionCount ++ ;
2017-08-28 19:27:45 +03:00
2020-04-28 23:57:08 +03:00
// Setup slow relay is requested. This will show down sending any data to this peer.
if ( ( req . query . slowrelay != null ) ) {
var sr = null ;
try { sr = parseInt ( req . query . slowrelay ) ; } catch ( ex ) { }
if ( ( typeof sr == 'number' ) && ( sr > 0 ) && ( sr < 1000 ) ) { obj . ws . slowRelay = sr ; }
}
2018-10-16 20:52:05 +03: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-28 04:13:01 +03:00
const MESHRIGHT _REMOTEVIEW = 256 ;
2018-10-16 20:52:05 +03: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-29 06:31:08 +03: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-24 00:09:58 +03:00
// Disconnect this agent
obj . close = function ( arg ) {
2020-05-22 07:25:11 +03:00
if ( ( arg == 1 ) || ( arg == null ) ) { try { ws . close ( ) ; parent . parent . debug ( 'relay' , 'Relay: Soft disconnect (' + obj . req . clientIp + ')' ) ; } 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 (' + obj . req . clientIp + ')' ) ; } catch ( e ) { console . log ( e ) ; } } // Hard close, close the TCP socket
2019-04-29 06:31:08 +03:00
// Aggressive cleanup
delete obj . id ;
delete obj . ws ;
delete obj . peer ;
2021-05-25 22:35:41 +03:00
if ( obj . expireTimer != null ) { clearTimeout ( obj . expireTimer ) ; delete obj . expireTimer ; }
2020-10-31 03:08:07 +03:00
// Unsubscribe
if ( obj . pid != null ) { parent . parent . RemoveAllEventDispatch ( obj ) ; }
2018-08-30 22:05:23 +03:00
} ;
2017-10-24 00:09:58 +03:00
obj . sendAgentMessage = function ( command , userid , domainid ) {
2019-04-13 00:19:03 +03:00
var rights , mesh ;
2017-10-24 00:09:58 +03:00
if ( command . nodeid == null ) return false ;
2019-04-29 06:31:08 +03:00
var user = parent . users [ userid ] ;
2017-10-24 00:09:58 +03: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-29 06:31:08 +03:00
var agent = parent . wsagents [ command . nodeid ] ;
2017-10-24 00:09:58 +03:00
if ( agent != null ) {
// Check if we have permission to send a message to that node
2020-04-13 19:36:25 +03:00
rights = parent . GetNodeRights ( user , agent . dbMeshKey , agent . dbNodeKey ) ;
mesh = parent . meshes [ agent . dbMeshKey ] ;
2020-08-27 04:55:32 +03:00
if ( ( rights != null ) && ( mesh != null ) || ( ( rights & MESHRIGHT _REMOTECONTROL ) != 0 ) ) {
2020-04-13 19:36:25 +03:00
if ( ws . sessionId ) { command . sessionid = ws . sessionId ; } // Set the session id, required for responses.
2020-08-08 01:51:28 +03:00
command . rights = rights ; // Add user rights flags to the message
if ( typeof command . consent == 'number' ) { command . consent = command . consent | mesh . consent ; } else { command . consent = mesh . consent ; } // Add user consent
2020-04-13 19:36:25 +03:00
if ( typeof domain . userconsentflags == 'number' ) { command . consent |= domain . userconsentflags ; } // Add server required consent flags
command . username = user . name ; // Add user name
2020-07-10 20:33:41 +03:00
command . realname = user . realname ; // Add real name
2020-04-13 19:36:25 +03:00
if ( typeof domain . desktopprivacybartext == 'string' ) { command . privacybartext = domain . desktopprivacybartext ; } // Privacy bar text
delete command . nodeid ; // Remove the nodeid since it's implyed.
agent . send ( JSON . stringify ( command ) ) ;
return true ;
}
2017-10-24 00:09:58 +03:00
} else {
// Check if a peer server is connected to this agent
2020-11-13 09:16:10 +03:00
var routing = parent . parent . GetRoutingServerIdNotSelf ( command . nodeid , 1 ) ; // 1 = MeshAgent routing type
2017-10-24 00:09:58 +03:00
if ( routing != null ) {
// Check if we have permission to send a message to that node
2020-04-13 19:36:25 +03:00
rights = parent . GetNodeRights ( user , routing . meshid , command . nodeid ) ;
mesh = parent . meshes [ routing . meshid ] ;
2020-08-27 04:55:32 +03:00
if ( rights != null || ( ( rights & MESHRIGHT _REMOTECONTROL ) != 0 ) ) {
2020-04-13 19:36:25 +03:00
if ( ws . sessionId ) { command . fromSessionid = ws . sessionId ; } // Set the session id, required for responses.
2020-08-08 01:51:28 +03:00
command . rights = rights ; // Add user rights flags to the message
if ( typeof command . consent == 'number' ) { command . consent = command . consent | mesh . consent ; } else { command . consent = mesh . consent ; } // Add user consent
2020-04-13 19:36:25 +03:00
if ( typeof domain . userconsentflags == 'number' ) { command . consent |= domain . userconsentflags ; } // Add server required consent flags
command . username = user . name ; // Add user name
2020-07-10 20:33:41 +03:00
command . realname = user . realname ; // Add real name
2020-04-13 19:36:25 +03:00
if ( typeof domain . desktopprivacybartext == 'string' ) { command . privacybartext = domain . desktopprivacybartext ; } // Privacy bar text
parent . parent . multiServer . DispatchMessageSingleServer ( command , routing . serverid ) ;
return true ;
}
2017-10-24 00:09:58 +03:00
}
}
}
return false ;
2021-01-31 11:18:19 +03:00
}
// Push any stored message to the peer
obj . pushStoredMessages = function ( ) {
2021-02-04 12:21:40 +03:00
if ( ( obj . storedPushedMessages != null ) && ( obj . peer != null ) ) {
for ( var i in obj . storedPushedMessages ) {
try { obj . peer . ws . send ( JSON . stringify ( { action : 'chat' , msg : obj . storedPushedMessages [ i ] } ) ) ; } catch ( ex ) { console . log ( ex ) ; }
}
2021-01-31 11:18:19 +03:00
}
}
2020-04-17 01:38:15 +03:00
2021-03-22 01:07:58 +03:00
// Push any stored message to the peer
obj . sendPeerImage = function ( ) {
2021-03-22 08:12:20 +03:00
if ( ( typeof obj . id == 'string' ) && obj . id . startsWith ( 'meshmessenger/' ) && ( obj . peer != null ) && ( obj . user != null ) && ( typeof obj . user . flags == 'number' ) && ( obj . user . flags & 1 ) ) {
2021-03-22 01:07:58 +03:00
parent . db . Get ( 'im' + obj . user . _id , function ( err , docs ) {
if ( ( err == null ) && ( docs != null ) && ( docs . length == 1 ) && ( typeof docs [ 0 ] . image == 'string' ) ) {
try { obj . peer . ws . send ( JSON . stringify ( { ctrlChannel : '102938' , type : 'image' , image : docs [ 0 ] . image } ) ) ; } catch ( ex ) { }
}
} ) ;
}
}
2020-04-17 01:38:15 +03:00
// Send a PING/PONG message
function sendPing ( ) {
try { obj . ws . send ( '{"ctrlChannel":"102938","type":"ping"}' ) ; } catch ( ex ) { }
try { if ( obj . peer != null ) { obj . peer . ws . send ( '{"ctrlChannel":"102938","type":"ping"}' ) ; } } catch ( ex ) { }
}
function sendPong ( ) {
try { obj . ws . send ( '{"ctrlChannel":"102938","type":"pong"}' ) ; } catch ( ex ) { }
try { if ( obj . peer != null ) { obj . peer . ws . send ( '{"ctrlChannel":"102938","type":"pong"}' ) ; } } catch ( ex ) { }
}
2017-11-01 02:19:58 +03: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-24 00:09:58 +03:00
2018-12-02 10:41:57 +03: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-29 06:31:08 +03: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 10:41:57 +03:00
var x = obj . id . split ( '/' ) , user1 = x [ 1 ] + '/' + x [ 2 ] + '/' + x [ 3 ] , user2 = x [ 4 ] + '/' + x [ 5 ] + '/' + x [ 6 ] ;
2018-12-08 03:36:27 +03: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-29 06:31:08 +03:00
if ( ( user . _id != user1 ) && ( user . _id != user2 ) ) { try { obj . close ( ) ; } catch ( e ) { } return null ; }
2018-12-08 03:36:27 +03: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 10:41:57 +03:00
}
2017-11-01 02:19:58 +03: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-29 06:31:08 +03: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-11-01 02:19:58 +03: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-29 06:31:08 +03:00
ws . close ( ) ;
2020-05-22 07:25:11 +03:00
parent . parent . debug ( 'relay' , 'Relay without-auth: ' + obj . id + ' (' + obj . req . clientIp + ')' ) ;
2019-04-29 06:31:08 +03:00
delete obj . id ;
delete obj . ws ;
delete obj . peer ;
2017-11-01 02:19:58 +03:00
return null ;
}
2019-10-16 01:50:11 +03: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 20:09:16 +03: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-16 01:50:11 +03:00
if ( u1 != u2 ) {
ws . close ( ) ;
2020-05-22 07:25:11 +03:00
parent . parent . debug ( 'relay' , 'Relay auth mismatch (' + u1 + ' != ' + u2 + '): ' + obj . id + ' (' + obj . req . clientIp + ')' ) ;
2019-10-16 01:50:11 +03:00
delete obj . id ;
delete obj . ws ;
delete obj . peer ;
return null ;
}
}
2021-05-25 23:08:38 +03:00
// Check that both sides have websocket connections, this should never happen.
if ( ( obj . ws == null ) || ( relayinfo . peer1 . ws == null ) ) { relayinfo . peer1 . close ( ) ; obj . close ( ) ; return null ; }
2021-05-25 22:35:41 +03:00
2017-11-01 02:19:58 +03:00
// Connect to peer
obj . peer = relayinfo . peer1 ;
obj . peer . peer = obj ;
relayinfo . peer2 = obj ;
relayinfo . state = 2 ;
2018-12-01 08:23:10 +03:00
relayinfo . peer1 . ws . _socket . resume ( ) ; // Release the traffic
relayinfo . peer2 . ws . _socket . resume ( ) ; // Release the traffic
2019-08-02 01:35:23 +03:00
ws . time = relayinfo . peer1 . ws . time = Date . now ( ) ;
2017-08-28 19:27:45 +03:00
2017-11-01 02:19:58 +03:00
relayinfo . peer1 . ws . peer = relayinfo . peer2 . ws ;
relayinfo . peer2 . ws . peer = relayinfo . peer1 . ws ;
2020-04-14 02:44:12 +03:00
2019-05-14 00:06:13 +03:00
// Remove the timeout
if ( relayinfo . timeout ) { clearTimeout ( relayinfo . timeout ) ; delete relayinfo . timeout ; }
2021-05-05 22:49:18 +03:00
// Check the protocol in use
req . query . p = parseInt ( req . query . p ) ;
if ( typeof req . query . p != 'number' ) { req . query . p = parseInt ( obj . peer . req . query . p ) ; if ( typeof req . query . p != 'number' ) { req . query . p = 0 ; } }
obj . peer . req . query . p = req . query . p ;
// Setup traffic accounting
obj . ws . _socket . bytesReadEx = 0 ;
obj . ws . _socket . bytesWrittenEx = 0 ;
obj . ws . _socket . p = req . query . p ;
obj . peer . ws . _socket . bytesReadEx = 0 ;
obj . peer . ws . _socket . bytesWrittenEx = 0 ;
obj . peer . ws . _socket . p = req . query . p ;
if ( parent . trafficStats . relayIn [ req . query . p ] == null ) { parent . trafficStats . relayIn [ req . query . p ] = 0 ; }
if ( parent . trafficStats . relayOut [ req . query . p ] == null ) { parent . trafficStats . relayOut [ req . query . p ] = 0 ; }
if ( parent . trafficStats . relayCount [ req . query . p ] == null ) { parent . trafficStats . relayCount [ req . query . p ] = 1 ; } else { parent . trafficStats . relayCount [ req . query . p ] ++ ; }
2021-04-01 21:39:35 +03:00
// Setup the agent PING/PONG timers unless requested not to
if ( ( obj . req . query . noping != 1 ) && ( obj . peer . req != null ) && ( obj . peer . req . query != null ) && ( obj . peer . req . query . noping != 1 ) ) {
if ( ( typeof parent . parent . args . agentping == 'number' ) && ( obj . pingtimer == null ) ) { obj . pingtimer = setInterval ( sendPing , parent . parent . args . agentping * 1000 ) ; }
else if ( ( typeof parent . parent . args . agentpong == 'number' ) && ( obj . pongtimer == null ) ) { obj . pongtimer = setInterval ( sendPong , parent . parent . args . agentpong * 1000 ) ; }
}
2020-04-17 01:38:15 +03:00
2019-08-05 21:30:07 +03:00
// Setup session recording
2019-08-11 08:34:21 +03:00
var sessionUser = obj . user ;
2019-08-05 21:30:07 +03:00
if ( sessionUser == null ) { sessionUser = obj . peer . user ; }
2021-04-12 03:36:22 +03:00
// If this is a MeshMessenger session, set the protocol to 200.
var xtextSession = 0 ;
var recordSession = false ;
if ( ( obj . id . startsWith ( 'meshmessenger/node/' ) == true ) && ( sessionUser != null ) && ( domain . sessionrecording == true || ( ( typeof domain . sessionrecording == 'object' ) && ( ( domain . sessionrecording . protocols == null ) || ( domain . sessionrecording . protocols . indexOf ( parseInt ( 200 ) ) >= 0 ) ) ) ) ) {
var split = obj . id . split ( '/' ) ;
obj . req . query . nodeid = split [ 1 ] + '/' + split [ 2 ] + '/' + split [ 3 ] ;
recordSession = true ;
xtextSession = 2 ; // 1 = Raw recording of all strings, 2 = Record chat session messages only.
}
if ( ( obj . req . query . p != null ) && ( obj . req . query . nodeid != null ) && ( sessionUser != null ) && ( domain . sessionrecording == true || ( ( typeof domain . sessionrecording == 'object' ) && ( ( domain . sessionrecording . protocols == null ) || ( domain . sessionrecording . protocols . indexOf ( parseInt ( obj . req . query . p ) ) >= 0 ) ) ) ) ) { recordSession = true ; }
if ( recordSession ) {
2019-09-26 03:06:35 +03:00
// Get the computer name
2019-12-10 00:43:02 +03:00
parent . db . Get ( obj . req . query . nodeid , function ( err , nodes ) {
2020-05-05 23:00:51 +03:00
var xusername = '' , xdevicename = '' , xdevicename2 = null , node = null ;
if ( ( nodes != null ) && ( nodes . length == 1 ) ) { node = nodes [ 0 ] ; xdevicename2 = node . name ; xdevicename = '-' + parent . common . makeFilename ( node . name ) ; }
2020-09-30 01:02:33 +03:00
// Check again if we need to do recording
2021-04-12 03:36:22 +03:00
if ( ( node == null ) || ( domain . sessionrecording . onlyselecteddevicegroups === true ) ) {
var mesh = null ;
if ( node != null ) { mesh = parent . meshes [ node . meshid ] ; }
if ( ( node == null ) || ( mesh == null ) || ( mesh . flags == null ) || ( ( mesh . flags & 4 ) == 0 ) ) {
2020-09-30 01:02:33 +03:00
// Do not record the session, just send session start
try { ws . send ( 'c' ) ; } catch ( ex ) { } // Send connect to both peers
try { relayinfo . peer1 . ws . send ( 'c' ) ; } catch ( ex ) { }
2021-01-31 11:18:19 +03:00
// Send any stored push messages
obj . pushStoredMessages ( ) ;
relayinfo . peer1 . pushStoredMessages ( ) ;
2021-03-22 01:07:58 +03:00
// Send other peer's image
obj . sendPeerImage ( ) ;
relayinfo . peer1 . sendPeerImage ( ) ;
2020-09-30 01:02:33 +03:00
return ;
}
}
2019-09-26 03:06:35 +03:00
// 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 ( ) ) ;
2021-04-12 03:36:22 +03:00
var xsessionid = obj . id ;
if ( ( typeof xsessionid == 'string' ) && ( xsessionid . startsWith ( 'meshmessenger/node/' ) == true ) ) { xsessionid = 'Messenger' }
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 + '-' + xsessionid + ( xtextSession ? '.txt' : '.mcrec' ) ;
2019-09-26 03:06:35 +03:00
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 23:27:24 +03:00
} else {
2019-09-26 03:06:35 +03:00
try { parent . parent . fs . mkdirSync ( parent . parent . recordpath ) ; } catch ( e ) { }
recFullFilename = parent . parent . path . join ( parent . parent . recordpath , recFilename ) ;
2019-08-06 23:27:24 +03:00
}
2019-09-26 03:06:35 +03:00
parent . parent . fs . open ( recFullFilename , 'w' , function ( err , fd ) {
if ( err != null ) {
// Unable to record
2020-05-02 10:22:54 +03:00
parent . parent . debug ( 'relay' , 'Relay: Unable to record to file: ' + recFullFilename ) ;
2019-09-26 03:06:35 +03:00
try { ws . send ( 'c' ) ; } catch ( ex ) { } // Send connect to both peers
try { relayinfo . peer1 . ws . send ( 'c' ) ; } catch ( ex ) { }
2021-01-31 11:18:19 +03:00
// Send any stored push messages
obj . pushStoredMessages ( ) ;
relayinfo . peer1 . pushStoredMessages ( ) ;
2021-03-22 01:07:58 +03:00
// Send other peer's image
obj . sendPeerImage ( ) ;
relayinfo . peer1 . sendPeerImage ( ) ;
2019-09-26 03:06:35 +03:00
} else {
// Write the recording file header
2020-05-02 10:22:54 +03:00
parent . parent . debug ( 'relay' , 'Relay: Started recoding to file: ' + recFullFilename ) ;
2021-03-10 04:38:15 +03:00
var metadata = {
magic : 'MeshCentralRelaySession' ,
ver : 1 ,
userid : sessionUser . _id ,
username : sessionUser . name ,
sessionid : obj . id ,
ipaddr1 : ( obj . req == null ) ? null : obj . req . clientIp ,
ipaddr2 : ( ( obj . peer == null ) || ( obj . peer . req == null ) ) ? null : obj . peer . req . clientIp ,
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 )
} ;
2021-04-12 03:36:22 +03:00
2019-09-26 03:06:35 +03:00
if ( xdevicename2 != null ) { metadata . devicename = xdevicename2 ; }
var firstBlock = JSON . stringify ( metadata ) ;
2021-04-12 03:36:22 +03:00
var logfile = { fd : fd , lock : false , filename : recFullFilename , startTime : Date . now ( ) , size : 0 , text : xtextSession } ;
2020-05-08 00:48:51 +03:00
if ( node != null ) { logfile . nodeid = node . _id ; logfile . meshid = node . meshid ; logfile . name = node . name ; logfile . icon = node . icon ; }
2020-05-05 23:00:51 +03:00
recordingEntry ( logfile , 1 , 0 , firstBlock , function ( ) {
try { relayinfo . peer1 . ws . logfile = ws . logfile = logfile ; } catch ( ex ) {
2019-10-10 21:13:25 +03: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 ) { }
2021-01-31 11:18:19 +03:00
// Send any stored push messages
obj . pushStoredMessages ( ) ;
relayinfo . peer1 . pushStoredMessages ( ) ;
2021-03-22 01:07:58 +03:00
// Send other peer's image
obj . sendPeerImage ( ) ;
relayinfo . peer1 . sendPeerImage ( ) ;
2019-10-10 21:13:25 +03:00
return ;
}
2019-09-26 03:06:35 +03: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 ) { }
2021-01-31 11:18:19 +03:00
// Send any stored push messages
obj . pushStoredMessages ( ) ;
relayinfo . peer1 . pushStoredMessages ( ) ;
2021-03-22 01:07:58 +03:00
// Send other peer's image
obj . sendPeerImage ( ) ;
relayinfo . peer1 . sendPeerImage ( ) ;
2019-09-26 03:06:35 +03:00
} ) ;
}
} ) ;
2019-08-05 21:30:07 +03:00
} ) ;
} else {
// Send session start
2019-08-15 02:51:45 +03:00
try { ws . send ( 'c' ) ; } catch ( ex ) { } // Send connect to both peers
try { relayinfo . peer1 . ws . send ( 'c' ) ; } catch ( ex ) { }
2021-01-31 11:18:19 +03:00
// Send any stored push messages
obj . pushStoredMessages ( ) ;
relayinfo . peer1 . pushStoredMessages ( ) ;
2021-03-22 01:07:58 +03:00
// Send other peer's image
obj . sendPeerImage ( ) ;
relayinfo . peer1 . sendPeerImage ( ) ;
2019-08-05 21:30:07 +03:00
}
2020-05-22 07:25:11 +03:00
parent . parent . debug ( 'relay' , 'Relay connected: ' + obj . id + ' (' + obj . req . clientIp + ' --> ' + obj . peer . req . clientIp + ')' ) ;
2019-08-02 01:35:23 +03:00
// Log the connection
2019-08-11 08:34:21 +03:00
if ( sessionUser != null ) {
2020-09-09 04:59:58 +03:00
var msg = 'Started relay session' , msgid = 13 ;
if ( obj . req . query . p == 1 ) { msg = 'Started terminal session' ; msgid = 14 ; }
else if ( obj . req . query . p == 2 ) { msg = 'Started desktop session' ; msgid = 15 ; }
else if ( obj . req . query . p == 5 ) { msg = 'Started file management session' ; msgid = 16 ; }
var event = { etype : 'relay' , action : 'relaylog' , domain : domain . id , userid : sessionUser . _id , username : sessionUser . name , msgid : msgid , msgArgs : [ obj . id , obj . peer . req . clientIp , req . clientIp ] , msg : msg + ' \"' + obj . id + '\" from ' + obj . peer . req . clientIp + ' to ' + req . clientIp , protocol : req . query . p , nodeid : req . query . nodeid } ;
2019-08-11 08:34:21 +03:00
parent . parent . DispatchEvent ( [ '*' , sessionUser . _id ] , obj , event ) ;
2019-08-02 01:35:23 +03:00
}
2017-11-01 02:19:58 +03:00
} else {
// Connected already, drop (TODO: maybe we should re-connect?)
2019-04-29 06:31:08 +03:00
ws . close ( ) ;
2020-05-22 07:25:11 +03:00
parent . parent . debug ( 'relay' , 'Relay duplicate: ' + obj . id + ' (' + obj . req . clientIp + ')' ) ;
2019-04-29 06:31:08 +03:00
delete obj . id ;
delete obj . ws ;
delete obj . peer ;
2017-09-06 20:45:09 +03:00
return null ;
}
2017-08-28 19:27:45 +03:00
} else {
2021-04-12 03:36:22 +03:00
// Set authenticated side as browser side for messenger sessions
if ( ( obj . id . startsWith ( 'meshmessenger/node/' ) == true ) && obj . authenticated ) { obj . req . query . browser = 1 ; }
2017-11-01 02:19:58 +03:00
// Wait for other relay connection
2021-01-31 11:18:19 +03:00
if ( ( obj . id . startsWith ( 'meshmessenger/node/' ) == true ) && obj . authenticated && ( parent . parent . firebase != null ) ) {
// This is an authenticated messenger session, push messaging may be allowed. Don't hold traffic.
ws . _socket . resume ( ) ; // Don't hold traffic, process push messages
parent . parent . debug ( 'relay' , 'Relay messenger waiting: ' + obj . id + ' (' + obj . req . clientIp + ') ' + ( obj . authenticated ? 'Authenticated' : '' ) ) ;
// Fetch the Push Messaging Token
const idsplit = obj . id . split ( '/' ) ;
const nodeid = idsplit [ 1 ] + '/' + idsplit [ 2 ] + '/' + idsplit [ 3 ] ;
parent . db . Get ( nodeid , function ( err , nodes ) {
if ( ( err == null ) && ( nodes != null ) && ( nodes . length == 1 ) && ( typeof nodes [ 0 ] . pmt == 'string' ) ) {
if ( ( parent . GetNodeRights ( obj . user , nodes [ 0 ] . meshid , nodes [ 0 ] . _id ) & MESHRIGHT _CHATNOTIFY ) != 0 ) {
2021-01-31 15:31:32 +03:00
obj . node = nodes [ 0 ] ;
2021-01-31 11:18:19 +03:00
// Create the peer connection URL, we will include that in push messages
obj . msgurl = req . headers . origin + ( req . url . split ( '/.websocket' ) [ 0 ] . split ( '/meshrelay.ashx' ) . join ( '/messenger' ) ) + '?id=' + req . query . id
}
}
} ) ;
2021-01-31 13:44:08 +03:00
parent . wsrelays [ obj . id ] = { peer1 : obj , state : 1 } ; // No timeout on connections with push notification.
2021-01-31 11:18:19 +03:00
} else {
ws . _socket . pause ( ) ; // Hold traffic until the other connection
parent . parent . debug ( 'relay' , 'Relay holding: ' + obj . id + ' (' + obj . req . clientIp + ') ' + ( obj . authenticated ? 'Authenticated' : '' ) ) ;
2021-01-31 13:44:08 +03:00
parent . wsrelays [ obj . id ] = { peer1 : obj , state : 1 , timeout : setTimeout ( closeBothSides , 60000 ) } ;
2021-01-31 11:18:19 +03:00
}
2017-09-18 03:22:18 +03:00
2017-11-01 02:19:58 +03:00
// Check if a peer server has this connection
if ( parent . parent . multiServer != null ) {
2019-04-29 06:31:08 +03:00
var rsession = parent . wsPeerRelays [ obj . id ] ;
if ( ( rsession != null ) && ( rsession . serverId > parent . parent . serverId ) ) {
2017-11-01 02:19:58 +03:00
// We must initiate the connection to the peer
2019-12-10 00:43:02 +03:00
parent . parent . multiServer . createPeerRelay ( ws , req , rsession . serverId , obj . req . session . userid ) ;
2017-11-01 02:19:58 +03: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-18 03:22:18 +03:00
}
}
2017-08-28 19:27:45 +03:00
}
}
2018-01-17 04:30:34 +03:00
2018-12-12 04:52:37 +03:00
ws . flushSink = function ( ) { try { ws . _socket . resume ( ) ; } catch ( ex ) { console . log ( ex ) ; } } ;
2017-08-28 19:27:45 +03:00
// When data is received from the mesh relay web socket
2017-09-01 21:23:22 +03:00
ws . on ( 'message' , function ( data ) {
2021-05-05 22:49:18 +03:00
// Perform traffic accounting
parent . trafficStats . relayIn [ this . _socket . p ] += ( this . _socket . bytesRead - this . _socket . bytesReadEx ) ;
parent . trafficStats . relayOut [ this . _socket . p ] += ( this . _socket . bytesWritten - this . _socket . bytesWrittenEx ) ;
2018-01-17 04:30:34 +03:00
if ( this . peer != null ) {
2018-07-06 20:13:19 +03:00
//if (typeof data == 'string') { console.log('Relay: ' + data); } else { console.log('Relay:' + data.length + ' byte(s)'); }
2020-04-28 23:57:08 +03:00
if ( this . peer . slowRelay == null ) {
try {
this . _socket . pause ( ) ;
if ( this . logfile != null ) {
// Write data to log file then perform relay
var xthis = this ;
2020-05-05 23:00:51 +03:00
recordingEntry ( this . logfile , 2 , ( ( obj . req . query . browser ) ? 2 : 0 ) , data , function ( ) { xthis . peer . send ( data , ws . flushSink ) ; } ) ;
2020-04-28 23:57:08 +03:00
} else {
// Perform relay
this . peer . send ( data , ws . flushSink ) ;
}
} catch ( ex ) { console . log ( ex ) ; }
} else {
try {
this . _socket . pause ( ) ;
if ( this . logfile != null ) {
// Write data to log file then perform slow relay
var xthis = this ;
2020-05-05 23:00:51 +03:00
recordingEntry ( this . logfile , 2 , ( ( obj . req . query . browser ) ? 2 : 0 ) , data , function ( ) {
2020-04-28 23:57:08 +03:00
setTimeout ( function ( ) { xthis . peer . send ( data , ws . flushSink ) ; } , xthis . peer . slowRelay ) ;
} ) ;
} else {
// Perform slow relay
var xthis = this ;
setTimeout ( function ( ) { xthis . peer . send ( data , ws . flushSink ) ; } , xthis . peer . slowRelay ) ;
}
} catch ( ex ) { console . log ( ex ) ; }
}
2021-01-31 11:18:19 +03:00
} else {
2021-02-03 10:31:44 +03:00
if ( ( typeof data == 'string' ) && ( obj . node != null ) && ( obj . node . pmt != null ) ) {
2021-01-31 11:18:19 +03:00
var command = null ;
try { command = JSON . parse ( data ) ; } catch ( ex ) { return ; }
if ( ( typeof command != 'object' ) || ( command . action != 'chat' ) || ( typeof command . msg != 'string' ) || ( command . msg == '' ) ) return ;
// Store pushed messages
if ( obj . storedPushedMessages == null ) { obj . storedPushedMessages = [ ] ; }
2021-02-04 12:21:40 +03:00
obj . storedPushedMessages . push ( command . msg ) ;
2021-01-31 11:18:19 +03:00
while ( obj . storedPushedMessages . length > 50 ) { obj . storedPushedMessages . shift ( ) ; } // Only keep last 50 notifications
// Send out a push message to the device
command . title = ( domain . title ? domain . title : 'MeshCentral' ) ;
var payload = { notification : { title : command . title , body : command . msg } , data : { url : obj . msgurl } } ;
var options = { priority : 'High' , timeToLive : 5 * 60 } ; // TTL: 5 minutes, priority 'Normal' or 'High'
2021-01-31 15:31:32 +03:00
parent . parent . firebase . sendToDevice ( obj . node , payload , options , function ( id , err , errdesc ) {
2021-01-31 13:44:08 +03:00
if ( err == null ) {
2021-01-31 15:31:32 +03:00
parent . parent . debug ( 'email' , 'Successfully send push message to device ' + obj . node . name + ', title: ' + command . title + ', msg: ' + command . msg ) ;
2021-01-31 11:18:19 +03:00
try { ws . send ( JSON . stringify ( { action : 'ctrl' , value : 1 } ) ) ; } catch ( ex ) { } // Push notification success
2021-01-31 13:44:08 +03:00
} else {
2021-01-31 15:31:32 +03:00
parent . parent . debug ( 'email' , 'Failed to send push message to device ' + obj . node . name + ', title: ' + command . title + ', msg: ' + command . msg + ', error: ' + errdesc ) ;
2021-01-31 11:18:19 +03:00
try { ws . send ( JSON . stringify ( { action : 'ctrl' , value : 2 } ) ) ; } catch ( ex ) { } // Push notification failed
2021-01-31 13:44:08 +03:00
}
} ) ;
2021-01-31 11:18:19 +03:00
}
2018-01-17 04:30:34 +03:00
}
2017-09-01 21:23:22 +03:00
} ) ;
2017-08-28 19:27:45 +03:00
2019-01-03 05:34:50 +03:00
// If error, close both sides of the relay.
2018-12-12 04:52:37 +03:00
ws . on ( 'error' , function ( err ) {
2019-04-29 06:31:08 +03:00
parent . relaySessionErrorCount ++ ;
if ( obj . relaySessionCounted ) { parent . relaySessionCount -- ; delete obj . relaySessionCounted ; }
2020-05-22 07:25:11 +03:00
console . log ( 'Relay error from ' + obj . req . clientIp + ', ' + err . toString ( ) . split ( '\r' ) [ 0 ] + '.' ) ;
2019-01-03 05:03:34 +03:00
closeBothSides ( ) ;
2018-12-12 04:52:37 +03:00
} ) ;
2017-08-28 19:27:45 +03:00
2019-01-03 05:34:50 +03:00
// If the relay web socket is closed, close both sides.
2017-08-28 19:27:45 +03:00
ws . on ( 'close' , function ( req ) {
2021-05-05 22:49:18 +03:00
// Perform traffic accounting
parent . trafficStats . relayIn [ this . _socket . p ] += ( this . _socket . bytesRead - this . _socket . bytesReadEx ) ;
parent . trafficStats . relayOut [ this . _socket . p ] += ( this . _socket . bytesWritten - this . _socket . bytesWrittenEx ) ;
2019-04-29 06:31:08 +03:00
if ( obj . relaySessionCounted ) { parent . relaySessionCount -- ; delete obj . relaySessionCounted ; }
2019-01-03 05:03:34 +03:00
closeBothSides ( ) ;
} ) ;
2021-05-26 01:36:29 +03:00
// Set the session expire timer
function setExpireTimer ( ) {
if ( obj . expireTimer != null ) { clearTimeout ( obj . expireTimer ) ; delete obj . expireTimer ; }
if ( cookie && ( typeof cookie . expire == 'number' ) ) {
2021-06-16 00:05:51 +03:00
const timeToExpire = ( cookie . expire - Date . now ( ) ) ;
if ( timeToExpire < 1 ) {
closeBothSides ( ) ;
} else if ( timeToExpire >= 0x7FFFFFFF ) {
obj . expireTimer = setTimeout ( setExpireTimer , 0x7FFFFFFF ) ; // Since expire timer can't be larger than 0x7FFFFFFF, reset timer after that time.
2021-05-26 01:36:29 +03:00
} else {
obj . expireTimer = setTimeout ( closeBothSides , timeToExpire ) ;
}
}
}
2019-01-03 05:03:34 +03:00
// Close both our side and the peer side.
function closeBothSides ( ) {
2017-08-28 19:27:45 +03:00
if ( obj . id != null ) {
var relayinfo = parent . wsrelays [ obj . id ] ;
2017-09-18 03:22:18 +03:00
if ( relayinfo != null ) {
if ( relayinfo . state == 2 ) {
var peer = ( relayinfo . peer1 == obj ) ? relayinfo . peer2 : relayinfo . peer1 ;
2019-08-05 21:30:07 +03:00
// Disconnect the peer
2019-04-29 06:31:08 +03:00
try { if ( peer . relaySessionCounted ) { parent . relaySessionCount -- ; delete peer . relaySessionCounted ; } } catch ( ex ) { console . log ( ex ) ; }
2020-05-22 07:25:11 +03:00
parent . parent . debug ( 'relay' , 'Relay disconnect: ' + obj . id + ' (' + obj . req . clientIp + ' --> ' + peer . req . clientIp + ')' ) ;
2017-09-18 03:22:18 +03:00
try { peer . ws . close ( ) ; } catch ( e ) { } // Soft disconnect
try { peer . ws . _socket . _parent . end ( ) ; } catch ( e ) { } // Hard disconnect
2019-04-29 06:31:08 +03:00
2019-08-02 01:35:23 +03:00
// Log the disconnection
2019-08-02 01:44:08 +03:00
if ( ws . time ) {
2020-09-09 04:59:58 +03:00
var msg = 'Ended relay session' , msgid = 9 ;
if ( obj . req . query . p == 1 ) { msg = 'Ended terminal session' , msgid = 10 ; }
else if ( obj . req . query . p == 2 ) { msg = 'Ended desktop session' , msgid = 11 ; }
else if ( obj . req . query . p == 5 ) { msg = 'Ended file management session' , msgid = 12 ; }
2021-04-12 03:36:22 +03:00
else if ( obj . req . query . p == 200 ) { msg = 'Ended messenger session' , msgid = 112 ; }
2019-08-02 01:44:08 +03:00
if ( user ) {
2020-09-09 04:59:58 +03:00
var event = { etype : 'relay' , action : 'relaylog' , domain : domain . id , userid : user . _id , username : user . name , msgid : msgid , msgArgs : [ obj . id , obj . req . clientIp , obj . peer . req . clientIp , Math . floor ( ( Date . now ( ) - ws . time ) / 1000 ) ] , msg : msg + ' \"' + obj . id + '\" from ' + obj . req . clientIp + ' to ' + obj . peer . req . clientIp + ', ' + Math . floor ( ( Date . now ( ) - ws . time ) / 1000 ) + ' second(s)' , protocol : obj . req . query . p , nodeid : obj . req . query . nodeid } ;
2019-08-02 01:44:08 +03:00
parent . parent . DispatchEvent ( [ '*' , user . _id ] , obj , event ) ;
} else if ( peer . user ) {
2020-09-09 04:59:58 +03:00
var event = { etype : 'relay' , action : 'relaylog' , domain : domain . id , userid : peer . user . _id , username : peer . user . name , msgid : msgid , msgArgs : [ obj . id , obj . req . clientIp , obj . peer . req . clientIp , Math . floor ( ( Date . now ( ) - ws . time ) / 1000 ) ] , msg : msg + ' \"' + obj . id + '\" from ' + obj . req . clientIp + ' to ' + obj . peer . req . clientIp + ', ' + Math . floor ( ( Date . now ( ) - ws . time ) / 1000 ) + ' second(s)' , protocol : obj . req . query . p , nodeid : obj . req . query . nodeid } ;
2019-08-02 01:44:08 +03:00
parent . parent . DispatchEvent ( [ '*' , peer . user . _id ] , obj , event ) ;
}
2019-08-02 01:35:23 +03:00
}
2019-04-29 06:31:08 +03:00
// Aggressive peer cleanup
delete peer . id ;
delete peer . ws ;
delete peer . peer ;
2020-04-17 01:38:15 +03:00
if ( peer . pingtimer != null ) { clearInterval ( peer . pingtimer ) ; delete peer . pingtimer ; }
if ( peer . pongtimer != null ) { clearInterval ( peer . pongtimer ) ; delete peer . pongtimer ; }
2017-09-18 03:22:18 +03:00
} else {
2020-05-22 07:25:11 +03:00
parent . parent . debug ( 'relay' , 'Relay disconnect: ' + obj . id + ' (' + obj . req . clientIp + ')' ) ;
2017-09-18 03:22:18 +03:00
}
2020-03-04 03:22:50 +03:00
// Close the recording file if needed
if ( ws . logfile != null ) {
var logfile = ws . logfile ;
delete ws . logfile ;
if ( peer . ws ) { delete peer . ws . logfile ; }
2020-05-05 23:00:51 +03:00
recordingEntry ( logfile , 3 , 0 , 'MeshCentralMCREC' , function ( logfile , tag ) {
parent . parent . fs . close ( logfile . fd ) ;
2020-03-04 03:22:50 +03:00
// Now that the recording file is closed, check if we need to index this file.
if ( domain . sessionrecording . index !== false ) { parent . parent . certificateOperations . acceleratorPerformOperation ( 'indexMcRec' , tag . logfile . filename ) ; }
2020-05-05 23:00:51 +03:00
// Compute session length
var sessionLength = null ;
if ( tag . logfile . startTime != null ) { sessionLength = Math . round ( ( Date . now ( ) - tag . logfile . startTime ) / 1000 ) ; }
// Add a event entry about this recording
var basefile = parent . parent . path . basename ( tag . logfile . filename ) ;
var event = { etype : 'relay' , action : 'recording' , domain : domain . id , nodeid : tag . logfile . nodeid , msg : "Finished recording session" + ( sessionLength ? ( ', ' + sessionLength + ' second(s)' ) : '' ) , filename : basefile , size : tag . logfile . size } ;
if ( user ) { event . userids = [ user . _id ] ; } else if ( peer . user ) { event . userids = [ peer . user . _id ] ; }
var xprotocol = ( ( ( obj . req == null ) || ( obj . req . query == null ) ) ? null : obj . req . query . p ) ;
2021-04-12 03:36:22 +03:00
if ( ( xprotocol == null ) && ( logfile . text == 2 ) ) { xprotocol = 200 ; }
2020-05-05 23:00:51 +03:00
if ( xprotocol != null ) { event . protocol = parseInt ( xprotocol ) ; }
var mesh = parent . meshes [ tag . logfile . meshid ] ;
if ( mesh != null ) { event . meshname = mesh . name ; event . meshid = mesh . _id ; }
if ( tag . logfile . startTime ) { event . startTime = tag . logfile . startTime ; event . lengthTime = sessionLength ; }
if ( tag . logfile . name ) { event . name = tag . logfile . name ; }
if ( tag . logfile . icon ) { event . icon = tag . logfile . icon ; }
parent . parent . DispatchEvent ( [ '*' , 'recording' , obj . nodeid , obj . meshid ] , obj , event ) ;
2020-04-30 12:02:23 +03:00
cleanUpRecordings ( ) ;
2020-03-04 03:22:50 +03:00
} , { ws : ws , pws : peer . ws , logfile : logfile } ) ;
}
2019-05-14 00:06:13 +03:00
try { ws . close ( ) ; } catch ( ex ) { }
2017-09-18 03:22:18 +03:00
delete parent . wsrelays [ obj . id ] ;
2017-08-28 19:27:45 +03:00
}
}
2019-04-29 06:31:08 +03:00
// Aggressive cleanup
delete obj . id ;
delete obj . ws ;
delete obj . peer ;
2020-04-17 01:38:15 +03:00
if ( obj . pingtimer != null ) { clearInterval ( obj . pingtimer ) ; delete obj . pingtimer ; }
if ( obj . pongtimer != null ) { clearInterval ( obj . pongtimer ) ; delete obj . pongtimer ; }
2020-10-31 03:08:07 +03:00
// Unsubscribe
if ( obj . pid != null ) { parent . parent . RemoveAllEventDispatch ( obj ) ; }
2019-01-03 05:03:34 +03:00
}
2018-08-30 22:05:23 +03:00
2019-08-08 02:07:12 +03:00
// Record a new entry in a recording log
2020-05-05 23:00:51 +03:00
function recordingEntry ( logfile , type , flags , data , func , tag ) {
2019-08-08 02:07:12 +03:00
try {
2021-04-12 03:36:22 +03:00
if ( logfile . text ) {
// Text recording format
var out = '' ;
const utcDate = new Date ( Date . now ( ) ) ;
if ( type == 1 ) {
// End of start
out = data + '\r\n' + utcDate . toUTCString ( ) + ', ' + "<<<START>>>" + '\r\n' ;
} else if ( type == 3 ) {
// End of log
out = utcDate . toUTCString ( ) + ', ' + "<<<END>>>" + '\r\n' ;
} else if ( typeof data == 'string' ) {
// Log message
if ( logfile . text == 1 ) {
out = utcDate . toUTCString ( ) + ', ' + data + '\r\n' ;
} else if ( logfile . text == 2 ) {
try {
var x = JSON . parse ( data ) ;
if ( typeof x . action == 'string' ) {
if ( ( x . action == 'chat' ) && ( typeof x . msg == 'string' ) ) { out = utcDate . toUTCString ( ) + ', ' + ( ( ( flags & 2 ) ? '--> ' : '<-- ' ) + x . msg + '\r\n' ) ; }
else if ( ( x . action == 'file' ) && ( typeof x . name == 'string' ) && ( typeof x . size == 'number' ) ) { out = utcDate . toUTCString ( ) + ', ' + ( ( ( flags & 2 ) ? '--> ' : '<-- ' ) + "File Transfer" + ', \"' + x . name + '\" (' + x . size + ' ' + "bytes" + ')\r\n' ) ; }
2021-04-13 01:36:41 +03:00
} else if ( x . ctrlChannel == null ) { out = utcDate . toUTCString ( ) + ', ' + data + '\r\n' ; }
2021-04-12 03:36:22 +03:00
} catch ( ex ) {
out = utcDate . toUTCString ( ) + ', ' + data + '\r\n' ;
}
}
}
if ( out != null ) {
// Log this event
const block = Buffer . from ( out ) ;
parent . parent . fs . write ( logfile . fd , block , 0 , block . length , function ( ) { func ( logfile , tag ) ; } ) ;
logfile . size += block . length ;
} else {
// Skip logging this.
func ( logfile , tag ) ;
}
2019-08-08 02:07:12 +03:00
} else {
2021-04-12 03:36:22 +03:00
// Binary recording format
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 ] ) ;
parent . parent . fs . write ( logfile . fd , block , 0 , block . length , function ( ) { func ( logfile , tag ) ; } ) ;
logfile . size += block . length ;
} 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 ] ) ;
parent . parent . fs . write ( logfile . fd , block , 0 , block . length , function ( ) { func ( logfile , tag ) ; } ) ;
logfile . size += block . length ;
}
2019-08-08 02:07:12 +03:00
}
2020-05-05 23:00:51 +03:00
} catch ( ex ) { console . log ( ex ) ; func ( logfile , tag ) ; }
2019-08-08 02:07:12 +03:00
}
2020-08-08 01:51:28 +03:00
// If this session has a expire time, setup the expire timer now.
2021-05-26 01:36:29 +03:00
setExpireTimer ( ) ;
2020-08-08 01:51:28 +03:00
2018-10-16 20:52:05 +03:00
// Mark this relay session as authenticated if this is the user end.
2019-04-29 06:31:08 +03:00
obj . authenticated = ( user != null ) ;
2018-10-16 20:52:05 +03:00
if ( obj . authenticated ) {
2021-07-08 21:08:53 +03:00
// To build the connection URL, if we are using a sub-domain or one with a DNS, we need to craft the URL correctly.
var xdomain = ( domain . dns == null ) ? domain . id : '' ;
if ( xdomain != '' ) xdomain += '/' ;
2018-10-16 20:52:05 +03:00
// 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-29 06:31:08 +03:00
if ( ( cookie != null ) && ( cookie . nodeid != null ) && ( cookie . tcpport != null ) && ( cookie . domainid != null ) ) {
2018-10-16 20:52:05 +03:00
// We have routing instructions in the cookie, but first, check user access for this node.
2019-04-29 06:31:08 +03:00
parent . db . Get ( cookie . nodeid , function ( err , docs ) {
2018-10-16 20:52:05 +03:00
if ( docs . length == 0 ) { console . log ( 'ERR: Node not found' ) ; try { obj . close ( ) ; } catch ( e ) { } return ; } // Disconnect websocket
2019-10-17 20:09:16 +03:00
const node = docs [ 0 ] ;
2020-04-14 02:44:12 +03:00
2018-10-16 20:52:05 +03:00
// Check if this user has permission to manage this computer
2020-04-02 02:41:35 +03:00
if ( ( parent . GetNodeRights ( user , node . meshid , node . _id ) & MESHRIGHT _REMOTECONTROL ) == 0 ) { console . log ( 'ERR: Access denied (1)' ) ; try { obj . close ( ) ; } catch ( e ) { } return ; }
2020-04-14 12:53:40 +03:00
2018-10-16 20:52:05 +03:00
// Send connection request to agent
2019-10-17 20:09:16 +03:00
const rcookie = parent . parent . encodeCookie ( { ruserid : user . _id } , parent . parent . loginCookieEncryptionKey ) ;
2020-08-08 01:01:28 +03:00
if ( obj . id == null ) { obj . id = ( '' + Math . random ( ) ) . substring ( 2 ) ; } // If there is no connection id, generate one.
2021-07-08 21:08:53 +03:00
const command = { nodeid : cookie . nodeid , action : 'msg' , type : 'tunnel' , userid : user . _id , value : '*/' + xdomain + 'meshrelay.ashx?id=' + obj . id + '&rauth=' + rcookie , tcpport : cookie . tcpport , tcpaddr : cookie . tcpaddr , soptions : { } } ;
2020-06-13 15:41:53 +03:00
if ( typeof domain . consentmessages == 'object' ) {
2020-06-13 21:36:50 +03:00
if ( typeof domain . consentmessages . title == 'string' ) { command . soptions . consentTitle = domain . consentmessages . title ; }
2020-06-13 15:41:53 +03:00
if ( typeof domain . consentmessages . desktop == 'string' ) { command . soptions . consentMsgDesktop = domain . consentmessages . desktop ; }
if ( typeof domain . consentmessages . terminal == 'string' ) { command . soptions . consentMsgTerminal = domain . consentmessages . terminal ; }
if ( typeof domain . consentmessages . files == 'string' ) { command . soptions . consentMsgFiles = domain . consentmessages . files ; }
}
2020-06-13 21:36:50 +03:00
if ( typeof domain . notificationmessages == 'object' ) {
if ( typeof domain . notificationmessages . title == 'string' ) { command . soptions . notifyTitle = domain . notificationmessages . title ; }
if ( typeof domain . notificationmessages . desktop == 'string' ) { command . soptions . notifyMsgDesktop = domain . notificationmessages . desktop ; }
if ( typeof domain . notificationmessages . terminal == 'string' ) { command . soptions . notifyMsgTerminal = domain . notificationmessages . terminal ; }
if ( typeof domain . notificationmessages . files == 'string' ) { command . soptions . notifyMsgFiles = domain . notificationmessages . files ; }
}
2019-08-23 01:31:39 +03:00
parent . parent . debug ( 'relay' , 'Relay: Sending agent tunnel command: ' + JSON . stringify ( command ) ) ;
2020-05-22 07:25:11 +03:00
if ( obj . sendAgentMessage ( command , user . _id , cookie . domainid ) == false ) { delete obj . id ; parent . parent . debug ( 'relay' , 'Relay: Unable to contact this agent (' + obj . req . clientIp + ')' ) ; }
2018-10-16 20:52:05 +03:00
performRelay ( ) ;
} ) ;
return obj ;
2019-12-10 00:43:02 +03:00
} else if ( ( obj . req . query . nodeid != null ) && ( ( obj . req . query . tcpport != null ) || ( obj . req . query . udpport != null ) ) ) {
2018-10-16 20:52:05 +03:00
// We have routing instructions in the URL arguments, but first, check user access for this node.
2019-12-10 00:43:02 +03:00
parent . db . Get ( obj . req . query . nodeid , function ( err , docs ) {
2018-10-16 20:52:05 +03:00
if ( docs . length == 0 ) { console . log ( 'ERR: Node not found' ) ; try { obj . close ( ) ; } catch ( e ) { } return ; } // Disconnect websocket
2019-10-17 20:09:16 +03:00
const node = docs [ 0 ] ;
2020-04-14 02:44:12 +03:00
2018-10-16 20:52:05 +03:00
// Check if this user has permission to manage this computer
2020-04-02 02:41:35 +03:00
if ( ( parent . GetNodeRights ( user , node . meshid , node . _id ) & MESHRIGHT _REMOTECONTROL ) == 0 ) { console . log ( 'ERR: Access denied (2)' ) ; try { obj . close ( ) ; } catch ( e ) { } return ; }
2020-04-14 12:53:40 +03:00
2018-10-16 20:52:05 +03:00
// 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 20:09:16 +03:00
const rcookie = parent . parent . encodeCookie ( { ruserid : user . _id } , parent . parent . loginCookieEncryptionKey ) ;
2019-05-07 04:44:23 +03:00
2019-12-10 00:43:02 +03:00
if ( obj . req . query . tcpport != null ) {
2021-07-08 21:08:53 +03:00
const command = { nodeid : obj . req . query . nodeid , action : 'msg' , type : 'tunnel' , userid : user . _id , value : '*/' + xdomain + '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 ) , soptions : { } } ;
2020-06-13 15:41:53 +03:00
if ( typeof domain . consentmessages == 'object' ) {
2020-06-13 21:36:50 +03:00
if ( typeof domain . consentmessages . title == 'string' ) { command . soptions . consentTitle = domain . consentmessages . title ; }
2020-06-13 15:41:53 +03:00
if ( typeof domain . consentmessages . desktop == 'string' ) { command . soptions . consentMsgDesktop = domain . consentmessages . desktop ; }
if ( typeof domain . consentmessages . terminal == 'string' ) { command . soptions . consentMsgTerminal = domain . consentmessages . terminal ; }
if ( typeof domain . consentmessages . files == 'string' ) { command . soptions . consentMsgFiles = domain . consentmessages . files ; }
}
2020-06-13 21:36:50 +03:00
if ( typeof domain . notificationmessages == 'object' ) {
if ( typeof domain . notificationmessages . title == 'string' ) { command . soptions . notifyTitle = domain . notificationmessages . title ; }
if ( typeof domain . notificationmessages . desktop == 'string' ) { command . soptions . notifyMsgDesktop = domain . notificationmessages . desktop ; }
if ( typeof domain . notificationmessages . terminal == 'string' ) { command . soptions . notifyMsgTerminal = domain . notificationmessages . terminal ; }
if ( typeof domain . notificationmessages . files == 'string' ) { command . soptions . notifyMsgFiles = domain . notificationmessages . files ; }
}
2019-08-23 01:31:39 +03:00
parent . parent . debug ( 'relay' , 'Relay: Sending agent TCP tunnel command: ' + JSON . stringify ( command ) ) ;
2020-05-22 07:25:11 +03:00
if ( obj . sendAgentMessage ( command , user . _id , domain . id ) == false ) { delete obj . id ; parent . parent . debug ( 'relay' , 'Relay: Unable to contact this agent (' + obj . req . clientIp + ')' ) ; }
2019-12-10 00:43:02 +03:00
} else if ( obj . req . query . udpport != null ) {
2021-07-08 21:08:53 +03:00
const command = { nodeid : obj . req . query . nodeid , action : 'msg' , type : 'tunnel' , userid : user . _id , value : '*/' + xdomain + '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 ) , soptions : { } } ;
2020-06-13 15:41:53 +03:00
if ( typeof domain . consentmessages == 'object' ) {
2020-06-13 21:36:50 +03:00
if ( typeof domain . consentmessages . title == 'string' ) { command . soptions . consentTitle = domain . consentmessages . title ; }
2020-06-13 15:41:53 +03:00
if ( typeof domain . consentmessages . desktop == 'string' ) { command . soptions . consentMsgDesktop = domain . consentmessages . desktop ; }
if ( typeof domain . consentmessages . terminal == 'string' ) { command . soptions . consentMsgTerminal = domain . consentmessages . terminal ; }
if ( typeof domain . consentmessages . files == 'string' ) { command . soptions . consentMsgFiles = domain . consentmessages . files ; }
}
2020-06-13 21:36:50 +03:00
if ( typeof domain . notificationmessages == 'object' ) {
if ( typeof domain . notificationmessages . title == 'string' ) { command . soptions . notifyTitle = domain . notificationmessages . title ; }
if ( typeof domain . notificationmessages . desktop == 'string' ) { command . soptions . notifyMsgDesktop = domain . notificationmessages . desktop ; }
if ( typeof domain . notificationmessages . terminal == 'string' ) { command . soptions . notifyMsgTerminal = domain . notificationmessages . terminal ; }
if ( typeof domain . notificationmessages . files == 'string' ) { command . soptions . notifyMsgFiles = domain . notificationmessages . files ; }
}
2019-08-23 01:31:39 +03:00
parent . parent . debug ( 'relay' , 'Relay: Sending agent UDP tunnel command: ' + JSON . stringify ( command ) ) ;
2020-05-22 07:25:11 +03:00
if ( obj . sendAgentMessage ( command , user . _id , domain . id ) == false ) { delete obj . id ; parent . parent . debug ( 'relay' , 'Relay: Unable to contact this agent (' + obj . req . clientIp + ')' ) ; }
2019-05-07 04:44:23 +03:00
}
2018-10-16 20:52:05 +03:00
performRelay ( ) ;
} ) ;
return obj ;
2020-11-28 00:55:02 +03:00
} else if ( ( cookie != null ) && ( cookie . nid != null ) && ( typeof cookie . r == 'number' ) && ( typeof cookie . p == 'number' ) && ( typeof cookie . cf == 'number' ) && ( typeof cookie . gn == 'string' ) ) {
2020-08-08 01:51:28 +03:00
// We have routing instructions in the cookie, but first, check user access for this node.
parent . db . Get ( cookie . nid , function ( err , docs ) {
if ( docs . length == 0 ) { console . log ( 'ERR: Node not found' ) ; try { obj . close ( ) ; } catch ( e ) { } return ; } // Disconnect websocket
const node = docs [ 0 ] ;
// Check if this user has permission to manage this computer
if ( ( parent . GetNodeRights ( user , node . meshid , node . _id ) & 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 ) ; }
const rcookie = parent . parent . encodeCookie ( { ruserid : user . _id , nodeid : node . _id } , parent . parent . loginCookieEncryptionKey ) ;
2021-07-08 21:08:53 +03:00
const command = { nodeid : node . _id , action : 'msg' , type : 'tunnel' , userid : user . _id , value : '*/' + xdomain + 'meshrelay.ashx?p=' + cookie . p + '&id=' + obj . id + '&rauth=' + rcookie + '&nodeid=' + node . _id , soptions : { } , rights : cookie . r , guestname : cookie . gn , consent : cookie . cf , remoteaddr : cleanRemoteAddr ( obj . req . clientIp ) } ;
2021-04-09 23:27:21 +03:00
// Limit what this relay connection can do
if ( typeof cookie . p == 'number' ) {
var usages = [ ] ;
if ( cookie . p & 1 ) { usages . push ( 1 ) ; usages . push ( 6 ) ; usages . push ( 8 ) ; usages . push ( 9 ) ; } // Terminal
if ( cookie . p & 2 ) { usages . push ( 2 ) ; } // Desktop
if ( cookie . p & 4 ) { usages . push ( 5 ) ; usages . push ( 10 ) ; } // Files
command . soptions . usages = usages ;
}
2020-08-08 01:51:28 +03:00
if ( typeof domain . consentmessages == 'object' ) {
if ( typeof domain . consentmessages . title == 'string' ) { command . soptions . consentTitle = domain . consentmessages . title ; }
if ( typeof domain . consentmessages . desktop == 'string' ) { command . soptions . consentMsgDesktop = domain . consentmessages . desktop ; }
if ( typeof domain . consentmessages . terminal == 'string' ) { command . soptions . consentMsgTerminal = domain . consentmessages . terminal ; }
if ( typeof domain . consentmessages . files == 'string' ) { command . soptions . consentMsgFiles = domain . consentmessages . files ; }
}
if ( typeof domain . notificationmessages == 'object' ) {
if ( typeof domain . notificationmessages . title == 'string' ) { command . soptions . notifyTitle = domain . notificationmessages . title ; }
if ( typeof domain . notificationmessages . desktop == 'string' ) { command . soptions . notifyMsgDesktop = domain . notificationmessages . desktop ; }
if ( typeof domain . notificationmessages . terminal == 'string' ) { command . soptions . notifyMsgTerminal = domain . notificationmessages . terminal ; }
if ( typeof domain . notificationmessages . files == 'string' ) { command . soptions . notifyMsgFiles = domain . notificationmessages . files ; }
}
parent . parent . debug ( 'relay' , 'Relay: Sending agent tunnel command: ' + JSON . stringify ( command ) ) ;
if ( obj . sendAgentMessage ( command , user . _id , domain . id ) == false ) { delete obj . id ; parent . parent . debug ( 'relay' , 'Relay: Unable to contact this agent (' + obj . req . clientIp + ')' ) ; }
performRelay ( 0 ) ;
} ) ;
return obj ;
2018-10-16 20:52:05 +03:00
}
}
2020-04-30 12:02:23 +03:00
// If there is a recording quota, remove any old recordings if needed
function cleanUpRecordings ( ) {
2020-05-06 00:19:27 +03:00
if ( ( parent . cleanUpRecordingsActive !== true ) && domain . sessionrecording && ( ( typeof domain . sessionrecording . maxrecordings == 'number' ) || ( typeof domain . sessionrecording . maxrecordingsizemegabytes == 'number' ) ) ) {
parent . cleanUpRecordingsActive = true ;
setTimeout ( function ( ) {
var recPath = null , fs = require ( 'fs' ) ;
if ( domain . sessionrecording . filepath ) { recPath = domain . sessionrecording . filepath ; } else { recPath = parent . parent . recordpath ; }
fs . readdir ( recPath , function ( err , files ) {
if ( ( err != null ) || ( files == null ) ) { delete parent . cleanUpRecordingsActive ; return ; }
var recfiles = [ ] ;
for ( var i in files ) {
if ( files [ i ] . endsWith ( '.mcrec' ) ) {
var j = files [ i ] . indexOf ( '-' ) ;
if ( j > 0 ) {
var size = null ;
try { size = fs . statSync ( parent . parent . path . join ( recPath , files [ i ] ) ) . size } catch ( ex ) { }
if ( size != null ) { recfiles . push ( { n : files [ i ] , r : files [ i ] . substring ( j + 1 ) , s : size } ) ; }
}
}
2020-04-30 12:02:23 +03:00
}
2020-05-06 00:19:27 +03:00
recfiles . sort ( function ( a , b ) { if ( a . r < b . r ) return 1 ; if ( a . r > b . r ) return - 1 ; return 0 ; } ) ;
var totalFiles = 0 , totalSize = 0 ;
for ( var i in recfiles ) {
var overQuota = false ;
if ( ( typeof domain . sessionrecording . maxrecordings == 'number' ) && ( totalFiles >= domain . sessionrecording . maxrecordings ) ) { overQuota = true ; }
else if ( ( typeof domain . sessionrecording . maxrecordingsizemegabytes == 'number' ) && ( totalSize >= ( domain . sessionrecording . maxrecordingsizemegabytes * 1048576 ) ) ) { overQuota = true ; }
if ( overQuota ) { fs . unlinkSync ( parent . parent . path . join ( recPath , recfiles [ i ] . n ) ) ; }
totalFiles ++ ;
totalSize += recfiles [ i ] . s ;
}
delete parent . cleanUpRecordingsActive ;
} ) ;
} , 500 ) ;
2020-04-30 12:02:23 +03:00
}
}
2018-10-16 20:52:05 +03:00
// 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 19:27:45 +03:00
return obj ;
2018-08-30 22:05:23 +03:00
} ;
2019-08-06 06:59:24 +03: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 21:49:05 +03:00
TYPE 2 bytes , 1 = Header , 2 = Network Data , 3 = EndBlock
2019-08-06 06:59:24 +03: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 23:27:24 +03:00
a given size and timestamp . When looking at network traffic the flags are important :
2019-08-06 06:59:24 +03: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 .
2021-04-28 09:22:55 +03:00
* /
module . exports . CreateLocalRelay = function ( parent , ws , req , domain , user , cookie ) {
CreateLocalRelayEx ( parent , ws , req , domain , user , cookie ) ;
}
function CreateLocalRelayEx ( parent , ws , req , domain , user , cookie ) {
const net = require ( 'net' ) ;
var obj = { } ;
obj . id = Buffer . from ( parent . crypto . randomBytes ( 9 ) , 'binary' ) . toString ( 'base64' ) ;
obj . req = req ;
obj . ws = ws ;
obj . user = user ;
2021-05-05 12:24:23 +03:00
// Check the protocol in use
var protocolInUse = parseInt ( req . query . p ) ;
if ( typeof protocolInUse != 'number' ) { protocolInUse = 0 ; }
2021-04-28 09:22:55 +03:00
// If there is no authentication, drop this connection
2021-06-29 05:26:34 +03:00
if ( obj . user == null ) { try { ws . close ( ) ; parent . parent . debug ( 'relay' , 'LocalRelay: Connection with no authentication' ) ; } catch ( e ) { console . log ( e ) ; } return ; }
2021-04-28 09:22:55 +03:00
2021-04-28 10:10:26 +03:00
// Use cookie values when present
if ( cookie != null ) {
if ( cookie . nodeid ) { req . query . nodeid = cookie . nodeid ; }
if ( cookie . tcpport ) { req . query . tcpport = cookie . tcpport ; }
}
2021-04-28 09:22:55 +03:00
// Check for nodeid and tcpport
2021-06-29 05:26:34 +03:00
if ( ( req . query == null ) || ( req . query . nodeid == null ) || ( req . query . tcpport == null ) ) { try { ws . close ( ) ; parent . parent . debug ( 'relay' , 'LocalRelay: Connection with invalid arguments' ) ; } catch ( e ) { console . log ( e ) ; } return ; }
2021-04-28 09:22:55 +03:00
const tcpport = parseInt ( req . query . tcpport ) ;
2021-06-29 05:26:34 +03:00
if ( ( typeof tcpport != 'number' ) || ( tcpport < 1 ) || ( tcpport > 65535 ) ) { try { ws . close ( ) ; parent . parent . debug ( 'relay' , 'LocalRelay: Connection with invalid arguments' ) ; } catch ( e ) { console . log ( e ) ; } return ; }
2021-04-28 09:22:55 +03:00
var nodeidsplit = req . query . nodeid . split ( '/' ) ;
2021-06-29 05:26:34 +03:00
if ( ( nodeidsplit . length != 3 ) || ( nodeidsplit [ 0 ] != 'node' ) || ( nodeidsplit [ 1 ] != domain . id ) || ( nodeidsplit [ 2 ] . length < 10 ) ) { try { ws . close ( ) ; parent . parent . debug ( 'relay' , 'LocalRelay: Connection with invalid arguments' ) ; } catch ( e ) { console . log ( e ) ; } return ; }
2021-04-28 09:22:55 +03:00
obj . nodeid = req . query . nodeid ;
obj . tcpport = tcpport ;
// Relay session count (we may remove this in the future)
obj . relaySessionCounted = true ;
parent . relaySessionCount ++ ;
// Setup slow relay is requested. This will show down sending any data to this peer.
if ( ( req . query . slowrelay != null ) ) {
var sr = null ;
try { sr = parseInt ( req . query . slowrelay ) ; } catch ( ex ) { }
if ( ( typeof sr == 'number' ) && ( sr > 0 ) && ( sr < 1000 ) ) { obj . ws . slowRelay = sr ; }
}
// Hold traffic until we connect to the target
ws . _socket . pause ( ) ;
2021-05-05 12:24:23 +03:00
ws . _socket . bytesReadEx = 0 ;
ws . _socket . bytesWrittenEx = 0 ;
2021-04-28 09:22:55 +03: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 ;
const MESHRIGHT _REMOTEVIEW = 256 ;
// 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 ;
// Clean a IPv6 address that encodes a IPv4 address
function cleanRemoteAddr ( addr ) { if ( addr . startsWith ( '::ffff:' ) ) { return addr . substring ( 7 ) ; } else { return addr ; } }
2021-05-05 12:24:23 +03:00
// Perform data accounting
function dataAccounting ( ) {
const datain = ( ( obj . client . bytesRead - obj . client . bytesReadEx ) + ( ws . _socket . bytesRead - ws . _socket . bytesReadEx ) ) ;
const dataout = ( ( obj . client . bytesWritten - obj . client . bytesWrittenEx ) + ( ws . _socket . bytesWritten - ws . _socket . bytesWrittenEx ) ) ;
obj . client . bytesReadEx = obj . client . bytesRead ;
obj . client . bytesWrittenEx = obj . client . bytesWritten ;
ws . _socket . bytesReadEx = ws . _socket . bytesRead ;
ws . _socket . bytesWrittenEx = ws . _socket . bytesWritten ;
// Add to counters
if ( parent . trafficStats . localRelayIn [ protocolInUse ] ) { parent . trafficStats . localRelayIn [ protocolInUse ] += datain ; } else { parent . trafficStats . localRelayIn [ protocolInUse ] = datain ; }
if ( parent . trafficStats . localRelayOut [ protocolInUse ] ) { parent . trafficStats . localRelayOut [ protocolInUse ] += dataout ; } else { parent . trafficStats . localRelayOut [ protocolInUse ] = dataout ; }
}
2021-04-28 09:22:55 +03:00
// Disconnect
obj . close = function ( arg ) {
2021-05-05 10:18:41 +03:00
// If the web socket is already closed, stop here.
if ( obj . ws == null ) return ;
2021-05-05 12:24:23 +03:00
// Perform data accounting
dataAccounting ( ) ;
2021-05-05 10:18:41 +03:00
// Collect how many raw bytes where received and sent.
// We sum both the websocket and TCP client in this case.
var inTraffc = obj . ws . _socket . bytesRead , outTraffc = obj . ws . _socket . bytesWritten ;
if ( obj . client != null ) { inTraffc += obj . client . bytesRead ; outTraffc += obj . client . bytesWritten ; }
// Close the web socket
2021-06-29 05:26:34 +03:00
if ( ( arg == 1 ) || ( arg == null ) ) { try { obj . ws . close ( ) ; parent . parent . debug ( 'relay' , 'LocalRelay: Soft disconnect' ) ; } catch ( e ) { console . log ( e ) ; } } // Soft close, close the websocket
if ( arg == 2 ) { try { obj . ws . _socket . _parent . end ( ) ; parent . parent . debug ( 'relay' , 'LocalRelay: Hard disconnect' ) ; } catch ( e ) { console . log ( e ) ; } } // Hard close, close the TCP socket
2021-04-28 09:22:55 +03:00
// Update the relay session count
if ( obj . relaySessionCounted ) { parent . relaySessionCount -- ; delete obj . relaySessionCounted ; }
2021-05-05 10:18:41 +03:00
// Log the disconnection, traffic will be credited to the authenticated user
2021-04-28 09:22:55 +03:00
if ( obj . time ) {
2021-06-29 05:26:34 +03:00
var protocolStr = req . query . p ;
if ( req . query . p == 10 ) { protocolStr = 'RDP' ; }
else if ( req . query . p == 11 ) { protocolStr = 'SSH-TERM' ; }
else if ( req . query . p == 12 ) { protocolStr = 'VNC' ; }
else if ( req . query . p == 13 ) { protocolStr = 'SSH-FILES' ; }
var event = { etype : 'relay' , action : 'relaylog' , domain : domain . id , userid : obj . user . _id , username : obj . user . name , msgid : 121 , msgArgs : [ obj . id , protocolStr , obj . host , Math . floor ( ( Date . now ( ) - obj . time ) / 1000 ) ] , msg : 'Ended local relay session \"' + obj . id + '\", protocol ' + protocolStr + ' to ' + obj . host + ', ' + Math . floor ( ( Date . now ( ) - obj . time ) / 1000 ) + ' second(s)' , nodeid : obj . req . query . nodeid , protocol : req . query . p , in : inTraffc , out : outTraffc } ;
2021-04-28 09:22:55 +03:00
parent . parent . DispatchEvent ( [ '*' , user . _id ] , obj , event ) ;
}
// Aggressive cleanup
delete obj . ws ;
delete obj . req ;
delete obj . time ;
delete obj . nodeid ;
delete obj . meshid ;
delete obj . tcpport ;
2021-05-25 22:35:41 +03:00
if ( obj . expireTimer != null ) { clearTimeout ( obj . expireTimer ) ; delete obj . expireTimer ; }
2021-05-05 10:18:41 +03:00
if ( obj . client != null ) { obj . client . destroy ( ) ; delete obj . client ; } // Close the client socket
2021-04-28 09:22:55 +03:00
if ( obj . pingtimer != null ) { clearInterval ( obj . pingtimer ) ; delete obj . pingtimer ; }
if ( obj . pongtimer != null ) { clearInterval ( obj . pongtimer ) ; delete obj . pongtimer ; }
// Unsubscribe
if ( obj . pid != null ) { parent . parent . RemoveAllEventDispatch ( obj ) ; }
} ;
// Send a PING/PONG message
function sendPing ( ) { try { obj . ws . send ( '{"ctrlChannel":"102938","type":"ping"}' ) ; } catch ( ex ) { } }
function sendPong ( ) { try { obj . ws . send ( '{"ctrlChannel":"102938","type":"pong"}' ) ; } catch ( ex ) { } }
function performRelay ( ) {
ws . _socket . setKeepAlive ( true , 240000 ) ; // Set TCP keep alive
// Setup the agent PING/PONG timers unless requested not to
if ( obj . req . query . noping != 1 ) {
if ( ( typeof parent . parent . args . agentping == 'number' ) && ( obj . pingtimer == null ) ) { obj . pingtimer = setInterval ( sendPing , parent . parent . args . agentping * 1000 ) ; }
else if ( ( typeof parent . parent . args . agentpong == 'number' ) && ( obj . pongtimer == null ) ) { obj . pongtimer = setInterval ( sendPong , parent . parent . args . agentpong * 1000 ) ; }
}
parent . db . Get ( obj . nodeid , function ( err , docs ) {
if ( ( err != null ) || ( docs == null ) || ( docs . length != 1 ) ) { try { obj . close ( ) ; } catch ( e ) { } return ; } // Disconnect websocket
const node = docs [ 0 ] ;
obj . host = node . host ;
obj . meshid = node . meshid ;
// Check if this user has permission to manage this computer
if ( ( parent . GetNodeRights ( obj . user , node . meshid , node . _id ) & MESHRIGHT _REMOTECONTROL ) == 0 ) { console . log ( 'ERR: Access denied (2)' ) ; try { obj . close ( ) ; } catch ( e ) { } return ; }
// Setup TCP client
obj . client = new net . Socket ( ) ;
2021-05-05 12:24:23 +03:00
obj . client . bytesReadEx = 0 ;
obj . client . bytesWrittenEx = 0 ;
2021-04-28 10:10:26 +03:00
obj . client . connect ( obj . tcpport , node . host , function ( ) {
// Log the start of the connection
2021-06-29 05:26:34 +03:00
var protocolStr = req . query . p ;
if ( req . query . p == 10 ) { protocolStr = 'RDP' ; }
else if ( req . query . p == 11 ) { protocolStr = 'SSH-TERM' ; }
else if ( req . query . p == 12 ) { protocolStr = 'VNC' ; }
else if ( req . query . p == 13 ) { protocolStr = 'SSH-FILES' ; }
2021-04-28 10:10:26 +03:00
obj . time = Date . now ( ) ;
2021-06-29 05:26:34 +03:00
var event = { etype : 'relay' , action : 'relaylog' , domain : domain . id , userid : obj . user . _id , username : obj . user . name , msgid : 120 , msgArgs : [ obj . id , protocolStr , obj . host ] , msg : 'Started local relay session \"' + obj . id + '\", protocol ' + protocolStr + ' to ' + obj . host , nodeid : req . query . nodeid , protocol : req . query . p } ;
2021-04-28 10:10:26 +03:00
parent . parent . DispatchEvent ( [ '*' , obj . user . _id , obj . meshid , obj . nodeid ] , obj , event ) ;
2021-05-05 12:24:23 +03:00
// Count the session
if ( parent . trafficStats . localRelayCount [ protocolInUse ] ) { parent . trafficStats . localRelayCount [ protocolInUse ] += 1 ; } else { parent . trafficStats . localRelayCount [ protocolInUse ] = 1 ; }
2021-04-28 10:10:26 +03:00
// Start the session
ws . send ( 'c' ) ;
ws . _socket . resume ( ) ;
} ) ;
2021-05-05 12:24:23 +03:00
obj . client . on ( 'data' , function ( data ) {
// Perform data accounting
dataAccounting ( ) ;
// Perform relay
try { this . pause ( ) ; ws . send ( data , this . clientResume ) ; } catch ( ex ) { console . log ( ex ) ; }
} ) ;
2021-04-28 09:22:55 +03:00
obj . client . on ( 'close' , function ( ) { obj . close ( ) ; } ) ;
obj . client . on ( 'error' , function ( err ) { obj . close ( ) ; } ) ;
obj . client . clientResume = function ( ) { try { obj . client . resume ( ) ; } catch ( ex ) { console . log ( ex ) ; } } ;
} ) ;
}
ws . flushSink = function ( ) { try { ws . _socket . resume ( ) ; } catch ( ex ) { console . log ( ex ) ; } } ;
// When data is received from the mesh relay web socket
2021-07-02 23:09:38 +03:00
ws . on ( 'message' , function ( data ) { if ( typeof data != 'string' ) { try { ws . _socket . pause ( ) ; obj . client . write ( data , ws . flushSink ) ; } catch ( ex ) { } } } ) ; // Perform relay
2021-04-28 09:22:55 +03:00
// If error, close both sides of the relay.
ws . on ( 'error' , function ( err ) { parent . relaySessionErrorCount ++ ; obj . close ( ) ; } ) ;
// Relay web socket is closed
ws . on ( 'close' , function ( req ) { obj . close ( ) ; } ) ;
// If this is not an authenticated session, or the session does not have routing instructions, just go ahead an connect to existing session.
performRelay ( ) ;
return obj ;
} ;