2017-10-24 00:09:58 +03:00
/ * *
2018-01-04 23:15:21 +03:00
* @ description MeshCentral MeshAgent
2017-10-24 00:09:58 +03:00
* @ author Ylian Saint - Hilaire & Bryan Roe
2019-01-04 03:22:15 +03:00
* @ copyright Intel Corporation 2018 - 2019
2018-01-04 23:15:21 +03:00
* @ license Apache - 2.0
2017-10-24 00:09:58 +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
2017-10-24 00:09:58 +03:00
// Construct a MeshAgent object, called upon connection
2018-10-16 20:52:05 +03:00
module . exports . CreateMeshUser = function ( parent , db , ws , req , args , domain , user ) {
2019-03-10 01:28:08 +03:00
const fs = require ( 'fs' ) ;
const path = require ( 'path' ) ;
const common = parent . common ;
2019-04-09 21:18:09 +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 ;
2019-06-11 21:33:44 +03:00
const MESHRIGHT _REMOTEVIEWONLY = 256 ;
const MESHRIGHT _NOTERMINAL = 512 ;
const MESHRIGHT _NOFILES = 1024 ;
const MESHRIGHT _NOAMT = 2048 ;
const MESHRIGHT _DESKLIMITEDINPUT = 4096 ;
2019-10-16 22:57:29 +03:00
const MESHRIGHT _LIMITEVENTS = 8192 ;
const MESHRIGHT _CHATNOTIFY = 16384 ;
2019-10-24 23:13:18 +03:00
const MESHRIGHT _UNINSTALL = 32768 ;
2019-04-09 21:18:09 +03:00
// Site rights
const SITERIGHT _SERVERBACKUP = 1 ;
const SITERIGHT _MANAGEUSERS = 2 ;
const SITERIGHT _SERVERRESTORE = 4 ;
const SITERIGHT _FILEACCESS = 8 ;
const SITERIGHT _SERVERUPDATE = 16 ;
2019-06-30 21:34:27 +03:00
const SITERIGHT _LOCKED = 32 ; // 0x00000020
const SITERIGHT _NONEWGROUPS = 64 ; // 0x00000040
const SITERIGHT _NOMESHCMD = 128 ; // 0x00000080
2019-04-09 21:18:09 +03:00
2017-10-24 00:09:58 +03:00
var obj = { } ;
2018-10-16 20:52:05 +03:00
obj . user = user ;
2017-10-24 00:09:58 +03:00
obj . domain = domain ;
2019-10-24 23:41:35 +03:00
obj . ws = ws ;
2017-10-24 00:09:58 +03:00
2019-09-23 21:45:10 +03:00
// Server side Intel AMT stack
const WsmanComm = require ( './amt/amt-wsman-comm.js' ) ;
const Wsman = require ( './amt/amt-wsman.js' ) ;
const Amt = require ( './amt/amt.js' ) ;
2019-09-21 03:21:58 +03:00
2017-10-24 00:09:58 +03:00
// Send a message to the user
2019-03-10 01:28:08 +03:00
//obj.send = function (data) { try { if (typeof data == 'string') { ws.send(Buffer.from(data, 'binary')); } else { ws.send(data); } } catch (e) { } }
2017-10-24 00:09:58 +03:00
2019-09-01 05:40:50 +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 user
obj . close = function ( arg ) {
2019-08-23 01:31:39 +03:00
if ( ( arg == 1 ) || ( arg == null ) ) { try { ws . close ( ) ; parent . parent . debug ( 'user' , 'Soft disconnect' ) ; } catch ( e ) { console . log ( e ) ; } } // Soft close, close the websocket
if ( arg == 2 ) { try { ws . _socket . _parent . end ( ) ; parent . parent . debug ( 'user' , 'Hard disconnect' ) ; } catch ( e ) { console . log ( e ) ; } } // Hard close, close the TCP socket
2019-03-10 01:28:08 +03:00
// Perform cleanup
parent . parent . RemoveAllEventDispatch ( ws ) ;
if ( obj . serverStatsTimer != null ) { clearInterval ( obj . serverStatsTimer ) ; delete obj . serverStatsTimer ; }
if ( req . session && req . session . ws && req . session . ws == ws ) { delete req . session . ws ; }
if ( parent . wssessions2 [ ws . sessionId ] ) { delete parent . wssessions2 [ ws . sessionId ] ; }
2019-06-07 21:10:04 +03:00
if ( ( obj . user != null ) && ( parent . wssessions [ obj . user . _id ] ) ) {
2019-03-12 03:33:24 +03:00
var i = parent . wssessions [ obj . user . _id ] . indexOf ( ws ) ;
2019-03-10 01:28:08 +03:00
if ( i >= 0 ) {
2019-03-12 03:33:24 +03:00
parent . wssessions [ obj . user . _id ] . splice ( i , 1 ) ;
var user = parent . users [ obj . user . _id ] ;
2019-03-10 01:28:08 +03:00
if ( user ) {
if ( parent . parent . multiServer == null ) {
2019-05-04 22:55:46 +03:00
var targets = [ '*' , 'server-users' ] ;
if ( obj . user . groups ) { for ( var i in obj . user . groups ) { targets . push ( 'server-users:' + i ) ; } }
2019-07-30 02:35:48 +03:00
parent . parent . DispatchEvent ( targets , obj , { action : 'wssessioncount' , userid : user . _id , username : user . name , count : parent . wssessions [ obj . user . _id ] . length , nolog : 1 , domain : domain . id } ) ;
2019-03-10 01:28:08 +03:00
} else {
parent . recountSessions ( ws . sessionId ) ; // Recount sessions
}
}
2019-03-12 03:33:24 +03:00
if ( parent . wssessions [ obj . user . _id ] . length == 0 ) { delete parent . wssessions [ obj . user . _id ] ; }
2019-03-10 01:28:08 +03:00
}
}
// If we have peer servers, inform them of the disconnected session
if ( parent . parent . multiServer != null ) { parent . parent . multiServer . DispatchMessage ( { action : 'sessionEnd' , sessionid : ws . sessionId } ) ; }
// Aggressive cleanup
if ( obj . user ) { delete obj . user ; }
if ( obj . domain ) { delete obj . domain ; }
if ( ws . userid ) { delete ws . userid ; }
if ( ws . domainid ) { delete ws . domainid ; }
if ( ws . sessionId ) { delete ws . sessionId ; }
if ( ws . HandleEvent ) { delete ws . HandleEvent ; }
ws . removeAllListeners ( [ "message" , "close" , "error" ] ) ;
2018-08-30 22:05:23 +03:00
} ;
2017-10-24 00:09:58 +03:00
2018-04-04 03:07:48 +03:00
// Convert a mesh path array into a real path on the server side
2018-04-06 02:45:56 +03:00
function meshPathToRealPath ( meshpath , user ) {
2019-03-10 01:28:08 +03:00
if ( common . validateArray ( meshpath , 1 ) == false ) return null ;
2018-04-06 02:45:56 +03:00
var splitid = meshpath [ 0 ] . split ( '/' ) ;
if ( splitid [ 0 ] == 'user' ) {
// Check user access
if ( meshpath [ 0 ] != user . _id ) return null ; // Only allow own user folder
} else if ( splitid [ 0 ] == 'mesh' ) {
// Check mesh access
var meshrights = user . links [ meshpath [ 0 ] ] ;
2018-06-19 22:25:57 +03:00
if ( meshrights == null ) return null ; // No meth rights for this user
meshrights = meshrights . rights ; // Get the rights bit mask
2018-04-06 02:45:56 +03:00
if ( ( meshrights == null ) || ( ( meshrights & 32 ) == 0 ) ) return null ; // This user must have mesh rights to "server files"
} else return null ;
2018-04-04 03:07:48 +03:00
var rootfolder = meshpath [ 0 ] , rootfoldersplit = rootfolder . split ( '/' ) , domainx = 'domain' ;
if ( rootfoldersplit [ 1 ] . length > 0 ) domainx = 'domain-' + rootfoldersplit [ 1 ] ;
2019-10-31 00:30:34 +03:00
var path = parent . path . join ( parent . filespath , domainx , rootfoldersplit [ 0 ] + '-' + rootfoldersplit [ 2 ] ) ;
2019-03-10 01:28:08 +03:00
for ( var i = 1 ; i < meshpath . length ; i ++ ) { if ( common . IsFilenameValid ( meshpath [ i ] ) == false ) { path = null ; break ; } path += ( "/" + meshpath [ i ] ) ; }
2018-04-04 03:07:48 +03:00
return path ;
}
2019-10-31 00:30:34 +03:00
// Copy a file using the best technique available
2018-04-04 03:07:48 +03:00
function copyFile ( src , dest , func , tag ) {
2019-10-31 00:30:34 +03:00
if ( fs . copyFile ) {
// NodeJS v8.5 and higher
fs . copyFile ( src , dest , function ( err ) { func ( tag ) ; } )
} else {
// Older NodeJS
try {
var ss = fs . createReadStream ( src ) , ds = fs . createWriteStream ( dest ) ;
ss . on ( 'error' , function ( ) { func ( tag ) ; } ) ;
ds . on ( 'error' , function ( ) { func ( tag ) ; } ) ;
ss . pipe ( ds ) ;
ds . ss = ss ;
if ( arguments . length == 3 && typeof arguments [ 2 ] === 'function' ) { ds . on ( 'close' , arguments [ 2 ] ) ; }
else if ( arguments . length == 4 && typeof arguments [ 3 ] === 'function' ) { ds . on ( 'close' , arguments [ 3 ] ) ; }
ds . on ( 'close' , function ( ) { func ( tag ) ; } ) ;
} catch ( ex ) { }
}
2018-08-30 22:05:23 +03:00
}
2018-04-04 03:07:48 +03:00
2018-12-08 03:36:27 +03:00
// Route a command to a target node
function routeCommandToNode ( command ) {
2019-03-10 01:28:08 +03:00
if ( common . validateString ( command . nodeid , 8 , 128 ) == false ) return false ;
2018-12-08 03:36:27 +03:00
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 ] == domain . id ) ) {
// See if the node is connected
2019-03-10 01:28:08 +03:00
var agent = parent . wsagents [ command . nodeid ] ;
2018-12-08 03:36:27 +03:00
if ( agent != null ) {
// Check if we have permission to send a message to that node
var rights = user . links [ agent . dbMeshKey ] ;
2019-04-13 00:19:03 +03:00
var mesh = parent . meshes [ agent . dbMeshKey ] ;
if ( ( rights != null ) && ( mesh != null ) && ( ( rights . rights & 8 ) || ( rights . rights & 256 ) ) ) { // 8 is remote control permission, 256 is desktop read only
command . sessionid = ws . sessionId ; // Set the session id, required for responses
2018-12-08 03:36:27 +03:00
command . rights = rights . rights ; // Add user rights flags to the message
2019-04-13 00:19:03 +03: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-07-30 02:35:48 +03:00
command . userid = user . _id ; // Add user id
2019-09-01 05:40:50 +03:00
command . remoteaddr = cleanRemoteAddr ( req . ip ) ; // User's IP address
2019-04-13 00:19:03 +03:00
delete command . nodeid ; // Remove the nodeid since it's implied
2018-12-08 03:36:27 +03:00
try { agent . send ( JSON . stringify ( command ) ) ; } catch ( ex ) { }
}
} else {
// Check if a peer server is connected to this agent
2019-03-10 01:28:08 +03:00
var routing = parent . parent . GetRoutingServerId ( command . nodeid , 1 ) ; // 1 = MeshAgent routing type
2018-12-08 03:36:27 +03:00
if ( routing != null ) {
// Check if we have permission to send a message to that node
var rights = user . links [ routing . meshid ] ;
2019-08-13 00:58:06 +03:00
var mesh = parent . meshes [ routing . meshid ] ;
2019-04-13 00:19:03 +03:00
if ( ( rights != null ) && ( mesh != null ) && ( ( rights . rights & 8 ) || ( rights . rights & 256 ) ) ) { // 8 is remote control permission
command . fromSessionid = ws . sessionId ; // Set the session id, required for responses
2018-12-08 03:36:27 +03:00
command . rights = rights . rights ; // Add user rights flags to the message
2019-04-13 00:19:03 +03: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-07-30 02:35:48 +03:00
command . userid = user . _id ; // Add user id
2019-09-01 05:40:50 +03:00
command . remoteaddr = cleanRemoteAddr ( req . ip ) ; // User's IP address
2019-03-10 01:28:08 +03:00
parent . parent . multiServer . DispatchMessageSingleServer ( command , routing . serverid ) ;
2018-12-08 03:36:27 +03:00
}
}
}
}
return true ;
}
2019-02-08 02:00:10 +03:00
// Route a command to all targets in a mesh
function routeCommandToMesh ( meshid , command ) {
// Send the request to all peer servers
// TODO !!!!
// See if the node is connected
2019-03-10 01:28:08 +03:00
for ( var nodeid in parent . wsagents ) {
var agent = parent . wsagents [ nodeid ] ;
2019-02-08 02:00:10 +03:00
if ( agent . dbMeshKey == meshid ) { try { agent . send ( JSON . stringify ( command ) ) ; } catch ( ex ) { } }
}
return true ;
}
2017-10-24 00:09:58 +03:00
try {
// Check if the user is logged in
2019-03-10 01:28:08 +03:00
if ( user == null ) { try { ws . close ( ) ; } catch ( e ) { } return ; }
2017-10-24 00:09:58 +03:00
2019-02-12 01:41:15 +03:00
// Check if we have exceeded the user session limit
2019-02-13 06:23:40 +03:00
if ( ( typeof domain . limits . maxusersessions == 'number' ) || ( typeof domain . limits . maxsingleusersessions == 'number' ) ) {
2019-02-12 01:41:15 +03:00
// Count the number of user sessions for this domain
2019-02-13 06:23:40 +03:00
var domainUserSessionCount = 0 , selfUserSessionCount = 0 ;
2019-03-10 01:28:08 +03:00
for ( var i in parent . wssessions2 ) {
if ( parent . wssessions2 [ i ] . domainid == domain . id ) {
domainUserSessionCount ++ ; if ( parent . wssessions2 [ i ] . userid == user . _id ) { selfUserSessionCount ++ ; }
}
}
2019-02-12 01:41:15 +03:00
// Check if we have too many user sessions
2019-02-13 06:23:40 +03:00
if ( ( ( typeof domain . limits . maxusersessions == 'number' ) && ( domainUserSessionCount >= domain . limits . maxusersessions ) ) || ( ( typeof domain . limits . maxsingleusersessions == 'number' ) && ( selfUserSessionCount >= domain . limits . maxsingleusersessions ) ) ) {
2019-02-12 01:41:15 +03:00
ws . send ( JSON . stringify ( { action : 'stopped' , msg : 'Session count exceed' } ) ) ;
2019-03-10 01:28:08 +03:00
try { ws . close ( ) ; } catch ( e ) { }
2019-02-12 01:41:15 +03:00
return ;
}
}
2018-10-16 20:52:05 +03:00
// Associate this websocket session with the web session
2019-03-12 03:33:24 +03:00
ws . userid = user . _id ;
2019-03-10 01:28:08 +03:00
ws . domainid = domain . id ;
2018-10-16 20:52:05 +03:00
2019-01-05 04:59:13 +03:00
// Create a new session id for this user.
2019-03-10 01:28:08 +03:00
parent . crypto . randomBytes ( 20 , function ( err , randombuf ) {
ws . sessionId = user . _id + '/' + randombuf . toString ( 'hex' ) ;
2019-01-05 04:59:13 +03:00
// Add this web socket session to session list
2019-03-10 01:28:08 +03:00
parent . wssessions2 [ ws . sessionId ] = ws ;
if ( ! parent . wssessions [ user . _id ] ) { parent . wssessions [ user . _id ] = [ ws ] ; } else { parent . wssessions [ user . _id ] . push ( ws ) ; }
if ( parent . parent . multiServer == null ) {
2019-05-04 22:55:46 +03:00
var targets = [ '*' , 'server-users' ] ;
if ( obj . user . groups ) { for ( var i in obj . user . groups ) { targets . push ( 'server-users:' + i ) ; } }
2019-07-30 02:35:48 +03:00
parent . parent . DispatchEvent ( targets , obj , { action : 'wssessioncount' , userid : user . _id , username : user . name , count : parent . wssessions [ user . _id ] . length , nolog : 1 , domain : domain . id } ) ;
2019-01-05 04:59:13 +03:00
} else {
2019-03-10 01:28:08 +03:00
parent . recountSessions ( ws . sessionId ) ; // Recount sessions
2019-01-05 04:59:13 +03:00
}
2017-10-24 00:09:58 +03:00
2019-01-05 04:59:13 +03:00
// If we have peer servers, inform them of the new session
2019-03-10 01:28:08 +03:00
if ( parent . parent . multiServer != null ) { parent . parent . multiServer . DispatchMessage ( { action : 'sessionStart' , sessionid : ws . sessionId } ) ; }
2019-01-05 04:59:13 +03:00
// Handle events
2019-09-19 21:21:35 +03:00
ws . HandleEvent = function ( source , event , ids , id ) {
2019-03-10 01:28:08 +03:00
if ( ! event . domain || event . domain == domain . id ) {
2019-01-05 04:59:13 +03:00
try {
if ( event == 'close' ) { try { delete req . session ; } catch ( ex ) { } obj . close ( ) ; }
2019-03-10 01:28:08 +03:00
else if ( event == 'resubscribe' ) { user . subscriptions = parent . subscribe ( user . _id , ws ) ; }
2019-01-05 04:59:13 +03:00
else if ( event == 'updatefiles' ) { updateUserFiles ( user , ws , domain ) ; }
2019-09-19 21:21:35 +03:00
else {
// Because of the device group "Show Self Events Only", we need to do more checks here.
if ( id . startsWith ( 'mesh/' ) ) {
// Check if we have rights to get this message. If we have limited events on this mesh, don't send the event to the user.
var meshlink = obj . user . links [ id ] ;
if ( ( meshlink != null ) && ( ( meshlink . rights == 0xFFFFFFFF ) || ( ( meshlink . rights & 8192 ) == 0 ) || ( ids . indexOf ( user . _id ) >= 0 ) ) ) {
// We have the device group rights to see this event or we are directly targetted by the event
ws . send ( JSON . stringify ( { action : 'event' , event : event } ) ) ;
} else {
// Check if no other users are targeted by the event, if not, we can get this event.
var userTarget = false ;
for ( var i in ids ) { if ( ids [ i ] . startsWith ( 'user/' ) ) { userTarget = true ; } }
if ( userTarget == false ) { ws . send ( JSON . stringify ( { action : 'event' , event : event } ) ) ; }
}
} else {
// This is not a device group event, we can get this event.
ws . send ( JSON . stringify ( { action : 'event' , event : event } ) ) ;
}
}
2019-01-05 04:59:13 +03:00
} catch ( e ) { }
}
} ;
2019-03-10 01:28:08 +03:00
user . subscriptions = parent . subscribe ( user . _id , ws ) ; // Subscribe to events
try { ws . _socket . setKeepAlive ( true , 240000 ) ; } catch ( ex ) { } // Set TCP keep alive
2019-01-05 04:59:13 +03:00
// Send current server statistics
obj . SendServerStats = function ( ) {
2019-05-21 02:00:33 +03:00
// Take a look at server stats
2019-02-16 23:56:33 +03:00
var os = require ( 'os' ) ;
var stats = { action : 'serverstats' , totalmem : os . totalmem ( ) , freemem : os . freemem ( ) } ;
2019-03-10 01:28:08 +03:00
if ( parent . parent . platform != 'win32' ) { stats . cpuavg = os . loadavg ( ) ; } // else { stats.cpuavg = [ 0.2435345, 0.523234234, 0.6435345345 ]; }
2019-02-19 04:14:00 +03:00
var serverStats = {
2019-03-10 01:28:08 +03:00
"User Accounts" : Object . keys ( parent . users ) . length ,
"Device Groups" : Object . keys ( parent . meshes ) . length ,
"Agent Sessions" : Object . keys ( parent . wsagents ) . length ,
"Connected Users" : Object . keys ( parent . wssessions ) . length ,
"Users Sessions" : Object . keys ( parent . wssessions2 ) . length ,
"Relay Sessions" : parent . relaySessionCount ,
2019-04-29 06:31:08 +03:00
"Relay Count" : Object . keys ( parent . wsrelays ) . length
2019-02-19 04:14:00 +03:00
} ;
2019-03-10 01:28:08 +03:00
if ( parent . relaySessionErrorCount != 0 ) { serverStats [ 'Relay Errors' ] = parent . relaySessionErrorCount ; }
if ( parent . parent . mpsserver != null ) { serverStats [ 'Connected Intel® AMT' ] = Object . keys ( parent . parent . mpsserver . ciraConnections ) . length ; }
2019-05-21 02:00:33 +03:00
// Take a look at agent errors
var agentstats = parent . getAgentStats ( ) ;
var errorCounters = { } , errorCountersCount = 0 ;
if ( agentstats . meshDoesNotExistCount > 0 ) { errorCountersCount ++ ; errorCounters [ "Unknown Group" ] = agentstats . meshDoesNotExistCount ; }
if ( agentstats . invalidPkcsSignatureCount > 0 ) { errorCountersCount ++ ; errorCounters [ "Invalid PKCS signature" ] = agentstats . invalidPkcsSignatureCount ; }
if ( agentstats . invalidRsaSignatureCount > 0 ) { errorCountersCount ++ ; errorCounters [ "Invalid RSA siguature" ] = agentstats . invalidRsaSignatureCount ; }
if ( agentstats . invalidJsonCount > 0 ) { errorCountersCount ++ ; errorCounters [ "Invalid JSON" ] = agentstats . invalidJsonCount ; }
if ( agentstats . unknownAgentActionCount > 0 ) { errorCountersCount ++ ; errorCounters [ "Unknown Action" ] = agentstats . unknownAgentActionCount ; }
if ( agentstats . agentBadWebCertHashCount > 0 ) { errorCountersCount ++ ; errorCounters [ "Bad Web Certificate" ] = agentstats . agentBadWebCertHashCount ; }
2019-05-28 21:34:28 +03:00
if ( ( agentstats . agentBadSignature1Count + agentstats . agentBadSignature2Count ) > 0 ) { errorCountersCount ++ ; errorCounters [ "Bad Signature" ] = ( agentstats . agentBadSignature1Count + agentstats . agentBadSignature2Count ) ; }
2019-05-21 02:00:33 +03:00
if ( agentstats . agentMaxSessionHoldCount > 0 ) { errorCountersCount ++ ; errorCounters [ "Max Sessions Reached" ] = agentstats . agentMaxSessionHoldCount ; }
2019-05-28 21:34:28 +03:00
if ( ( agentstats . invalidDomainMeshCount + agentstats . invalidDomainMesh2Count ) > 0 ) { errorCountersCount ++ ; errorCounters [ "Unknown Device Group" ] = ( agentstats . invalidDomainMeshCount + agentstats . invalidDomainMesh2Count ) ; }
if ( ( agentstats . invalidMeshTypeCount + agentstats . invalidMeshType2Count ) > 0 ) { errorCountersCount ++ ; errorCounters [ "Invalid Device Group Type" ] = ( agentstats . invalidMeshTypeCount + agentstats . invalidMeshType2Count ) ; }
2019-06-15 02:58:03 +03:00
//if (agentstats.duplicateAgentCount > 0) { errorCountersCount++; errorCounters["Duplicate Agent"] = agentstats.duplicateAgentCount; }
2019-05-21 02:00:33 +03:00
// Send out the stats
2019-02-16 23:56:33 +03:00
stats . values = { "Server State" : serverStats }
2019-05-21 02:00:33 +03:00
if ( errorCountersCount > 0 ) { stats . values [ "Agent Error Counters" ] = errorCounters ; }
2019-02-16 23:56:33 +03:00
try { ws . send ( JSON . stringify ( stats ) ) ; } catch ( ex ) { }
2017-10-24 00:09:58 +03:00
}
2018-09-27 00:58:55 +03:00
2019-01-05 04:59:13 +03:00
// When data is received from the web socket
ws . on ( 'message' , processWebSocketData ) ;
// If error, do nothing
2019-03-10 01:28:08 +03:00
ws . on ( 'error' , function ( err ) { console . log ( err ) ; obj . close ( 0 ) ; } ) ;
2019-01-05 04:59:13 +03:00
// If the web socket is closed
2019-03-10 01:28:08 +03:00
ws . on ( 'close' , function ( req ) { obj . close ( 0 ) ; } ) ;
2019-01-05 04:59:13 +03:00
// Figure out the MPS port, use the alias if set
2019-03-10 01:28:08 +03:00
var mpsport = ( ( args . mpsaliasport != null ) ? args . mpsaliasport : args . mpsport ) ;
var httpport = ( ( args . aliasport != null ) ? args . aliasport : args . port ) ;
2019-01-05 04:59:13 +03:00
// Build server information object
2019-10-10 01:56:27 +03:00
var serverinfo = { name : domain . dns ? domain . dns : parent . certificates . CommonName , mpsname : parent . certificates . AmtMpsName , mpsport : mpsport , mpspass : args . mpspass , port : httpport , emailcheck : ( ( parent . parent . mailserver != null ) && ( domain . auth != 'sspi' ) && ( domain . auth != 'ldap' ) && ( args . lanonly != true ) && ( parent . certificates . CommonName != null ) && ( parent . certificates . CommonName . indexOf ( '.' ) != - 1 ) ) , domainauth : ( ( domain . auth == 'sspi' ) || ( domain . auth == 'ldap' ) ) , serverTime : Date . now ( ) } ;
2019-10-22 21:59:19 +03:00
serverinfo . languages = parent . renderLanguages ;
2019-06-23 08:06:50 +03:00
serverinfo . tlshash = Buffer . from ( parent . webCertificateHashs [ domain . id ] , 'binary' ) . toString ( 'hex' ) . toUpperCase ( ) ; // SHA384 of server HTTPS certificate
if ( ( parent . parent . config . domains [ domain . id ] . amtacmactivation != null ) && ( parent . parent . config . domains [ domain . id ] . amtacmactivation . acmmatch != null ) ) {
var matchingDomains = [ ] ;
for ( var i in parent . parent . config . domains [ domain . id ] . amtacmactivation . acmmatch ) {
var cn = parent . parent . config . domains [ domain . id ] . amtacmactivation . acmmatch [ i ] . cn ;
if ( ( cn != '*' ) && ( matchingDomains . indexOf ( cn ) == - 1 ) ) { matchingDomains . push ( cn ) ; }
}
if ( matchingDomains . length > 0 ) { serverinfo . amtAcmFqdn = matchingDomains ; }
}
2019-03-10 01:28:08 +03:00
if ( args . notls == true ) { serverinfo . https = false ; } else { serverinfo . https = true ; serverinfo . redirport = args . redirport ; }
2019-04-13 00:19:03 +03:00
if ( typeof domain . userconsentflags == 'number' ) { serverinfo . consent = domain . userconsentflags ; }
2019-05-31 19:58:23 +03:00
if ( ( typeof domain . usersessionidletimeout == 'number' ) && ( domain . usersessionidletimeout > 0 ) ) { serverinfo . timeout = ( domain . usersessionidletimeout * 60 * 1000 ) ; }
2019-01-05 04:59:13 +03:00
// Send server information
try { ws . send ( JSON . stringify ( { action : 'serverinfo' , serverinfo : serverinfo } ) ) ; } catch ( ex ) { }
// Send user information to web socket, this is the first thing we send
2019-03-10 01:28:08 +03:00
try { ws . send ( JSON . stringify ( { action : 'userinfo' , userinfo : parent . CloneSafeUser ( parent . users [ user . _id ] ) } ) ) ; } catch ( ex ) { }
2019-01-05 04:59:13 +03:00
2019-08-23 01:31:39 +03:00
// Send server tracing information
if ( user . siteadmin == 0xFFFFFFFF ) {
try { ws . send ( JSON . stringify ( { action : 'traceinfo' , traceSources : parent . parent . debugRemoteSources } ) ) ; } catch ( ex ) { }
}
2019-01-05 04:59:13 +03:00
// We are all set, start receiving data
ws . _socket . resume ( ) ;
} ) ;
} catch ( e ) { console . log ( e ) ; }
// Process incoming web socket data from the browser
function processWebSocketData ( msg ) {
var command , i = 0 , mesh = null , meshid = null , nodeid = null , meshlinks = null , change = 0 ;
try { command = JSON . parse ( msg . toString ( 'utf8' ) ) ; } catch ( e ) { return ; }
2019-03-10 01:28:08 +03:00
if ( common . validateString ( command . action , 3 , 32 ) == false ) return ; // Action must be a string between 3 and 32 chars
2019-01-05 04:59:13 +03:00
switch ( command . action ) {
case 'ping' : { try { ws . send ( JSON . stringify ( { action : 'pong' } ) ) ; } catch ( ex ) { } break ; }
2019-01-30 01:08:29 +03:00
case 'authcookie' :
{
// Renew the authentication cookie
2019-10-16 01:50:11 +03:00
try {
ws . send ( JSON . stringify ( {
action : 'authcookie' ,
cookie : parent . parent . encodeCookie ( { userid : user . _id , domainid : domain . id , ip : cleanRemoteAddr ( req . ip ) } , parent . parent . loginCookieEncryptionKey ) ,
rcookie : parent . parent . encodeCookie ( { ruserid : user . _id } , parent . parent . loginCookieEncryptionKey )
} ) ) ;
} catch ( ex ) { }
2019-01-30 01:08:29 +03:00
break ;
}
2019-04-23 23:42:54 +03:00
case 'logincookie' :
{
2019-04-27 21:53:06 +03:00
// If allowed, return a login cookie
if ( parent . parent . config . settings . allowlogintoken === true ) {
try { ws . send ( JSON . stringify ( { action : 'logincookie' , cookie : parent . parent . encodeCookie ( { u : user . _id , a : 3 } , parent . parent . loginCookieEncryptionKey ) } ) ) ; } catch ( ex ) { }
}
2019-04-23 23:42:54 +03:00
break ;
}
2019-03-26 05:59:04 +03:00
case 'servertimelinestats' :
{
if ( ( user . siteadmin & 21 ) == 0 ) return ; // Only site administrators with "site backup" or "site restore" or "site update" permissions can use this.
if ( common . validateInt ( command . hours , 0 , 24 * 30 ) == false ) return ;
db . GetServerStats ( command . hours , function ( err , docs ) {
if ( err == null ) { ws . send ( JSON . stringify ( { action : 'servertimelinestats' , events : docs } ) ) ; }
} ) ;
break ;
}
2019-01-05 04:59:13 +03:00
case 'serverstats' :
{
2019-03-26 05:59:04 +03:00
if ( ( user . siteadmin & 21 ) == 0 ) return ; // Only site administrators with "site backup" or "site restore" or "site update" permissions can use this.
if ( common . validateInt ( command . interval , 1000 , 1000000 ) == false ) {
// Clear the timer
if ( obj . serverStatsTimer != null ) { clearInterval ( obj . serverStatsTimer ) ; delete obj . serverStatsTimer ; }
} else {
// Set the timer
obj . SendServerStats ( ) ;
obj . serverStatsTimer = setInterval ( obj . SendServerStats , command . interval ) ;
2019-01-05 04:59:13 +03:00
}
break ;
}
case 'meshes' :
{
// Request a list of all meshes this user as rights to
var docs = [ ] ;
2019-06-25 04:56:14 +03:00
for ( i in user . links ) {
if ( ( parent . meshes [ i ] ) && ( parent . meshes [ i ] . deleted == null ) ) {
2019-10-11 21:16:36 +03:00
// Remove the Intel AMT password if present
docs . push ( parent . CloneSafeMesh ( parent . meshes [ i ] ) ) ;
2019-06-25 04:56:14 +03:00
}
}
2019-01-05 04:59:13 +03:00
try { ws . send ( JSON . stringify ( { action : 'meshes' , meshes : docs , tag : command . tag } ) ) ; } catch ( ex ) { }
break ;
}
case 'nodes' :
{
2019-07-05 23:28:41 +03:00
var links = [ ] , err = null ;
try {
if ( command . meshid == null ) {
// Request a list of all meshes this user as rights to
for ( i in user . links ) { links . push ( i ) ; }
} else {
// Request list of all nodes for one specific meshid
meshid = command . meshid ;
if ( common . validateString ( meshid , 0 , 128 ) == false ) { err = 'Invalid group id' ; } else {
if ( meshid . split ( '/' ) . length == 1 ) { meshid = 'mesh/' + domain . id + '/' + command . meshid ; }
if ( user . links [ meshid ] != null ) { links . push ( meshid ) ; } else { err = 'Invalid group id' ; }
}
}
} catch ( ex ) { err = 'Validation exception: ' + ex ; }
// Handle any errors
if ( err != null ) {
if ( command . responseid != null ) { try { ws . send ( JSON . stringify ( { action : 'nodes' , responseid : command . responseid , result : err } ) ) ; } catch ( ex ) { } }
break ;
2019-01-05 04:59:13 +03:00
}
2017-10-24 00:09:58 +03:00
2019-01-05 04:59:13 +03:00
// Request a list of all nodes
2019-03-10 01:28:08 +03:00
db . GetAllTypeNoTypeFieldMeshFiltered ( links , domain . id , 'node' , command . id , function ( err , docs ) {
2019-05-21 04:03:14 +03:00
if ( docs == null ) { docs = [ ] ; }
2019-01-05 04:59:13 +03:00
var r = { } ;
for ( i in docs ) {
2019-04-10 20:41:10 +03:00
// Remove any connectivity and power state information, that should not be in the database anyway.
// TODO: Find why these are sometimes saves in the db.
if ( docs [ i ] . conn != null ) { delete docs [ i ] . conn ; }
if ( docs [ i ] . pwr != null ) { delete docs [ i ] . pwr ; }
if ( docs [ i ] . agct != null ) { delete docs [ i ] . agct ; }
if ( docs [ i ] . cict != null ) { delete docs [ i ] . cict ; }
2019-01-05 04:59:13 +03:00
// Add the connection state
2019-03-10 01:28:08 +03:00
var state = parent . parent . GetConnectivityState ( docs [ i ] . _id ) ;
2019-01-05 04:59:13 +03:00
if ( state ) {
docs [ i ] . conn = state . connectivity ;
docs [ i ] . pwr = state . powerState ;
2019-03-10 01:28:08 +03:00
if ( ( state . connectivity & 1 ) != 0 ) { var agent = parent . wsagents [ docs [ i ] . _id ] ; if ( agent != null ) { docs [ i ] . agct = agent . connectTime ; } }
if ( ( state . connectivity & 2 ) != 0 ) { var cira = parent . parent . mpsserver . ciraConnections [ docs [ i ] . _id ] ; if ( cira != null ) { docs [ i ] . cict = cira . tag . connectTime ; } }
2019-01-05 04:59:13 +03:00
}
2017-12-13 03:04:54 +03:00
2019-01-05 04:59:13 +03:00
// Compress the meshid's
meshid = docs [ i ] . meshid ;
if ( ! r [ meshid ] ) { r [ meshid ] = [ ] ; }
delete docs [ i ] . meshid ;
2017-12-13 03:04:54 +03:00
2019-01-05 04:59:13 +03:00
// Remove Intel AMT credential if present
if ( docs [ i ] . intelamt != null && docs [ i ] . intelamt . pass != null ) { delete docs [ i ] . intelamt . pass ; }
2017-12-13 03:04:54 +03:00
2019-02-27 03:26:33 +03:00
// If GeoLocation not enabled, remove any node location information
if ( domain . geolocation != true ) {
if ( docs [ i ] . iploc != null ) { delete docs [ i ] . iploc ; }
if ( docs [ i ] . wifiloc != null ) { delete docs [ i ] . wifiloc ; }
if ( docs [ i ] . gpsloc != null ) { delete docs [ i ] . gpsloc ; }
if ( docs [ i ] . userloc != null ) { delete docs [ i ] . userloc ; }
}
2019-01-05 04:59:13 +03:00
r [ meshid ] . push ( docs [ i ] ) ;
}
2019-07-05 23:28:41 +03:00
try { ws . send ( JSON . stringify ( { action : 'nodes' , responseid : command . responseid , nodes : r , tag : command . tag } ) ) ; } catch ( ex ) { }
2019-01-05 04:59:13 +03:00
} ) ;
break ;
}
case 'powertimeline' :
{
2019-09-25 22:10:41 +03:00
// Perform pre-validation
if ( common . validateString ( command . nodeid , 0 , 128 ) == false ) break ;
var snode = command . nodeid . split ( '/' ) ;
if ( ( snode . length != 3 ) || ( snode [ 1 ] != domain . id ) ) break ;
2019-09-24 02:46:26 +03:00
2019-09-25 22:10:41 +03:00
// Check that we have permissions for this node.
if ( obj . user . links == null ) return ;
db . Get ( command . nodeid , function ( err , nodes ) {
if ( nodes == null || nodes . length != 1 ) return ;
const node = nodes [ 0 ] ;
var meshlink = obj . user . links [ node . meshid ] ;
if ( ( meshlink != null ) && ( meshlink . rights != 0 ) ) {
// Query the database for the power timeline for a given node
// The result is a compacted array: [ startPowerState, startTimeUTC, powerState ] + many[ deltaTime, powerState ]
db . getPowerTimeline ( command . nodeid , function ( err , docs ) {
if ( ( err == null ) && ( docs != null ) && ( docs . length > 0 ) ) {
var timeline = [ ] , time = null , previousPower ;
for ( i in docs ) {
var doc = docs [ i ] , j = parseInt ( i ) ;
doc . time = Date . parse ( doc . time ) ;
if ( time == null ) { // First element
// Skip all starting power 0 events.
if ( ( doc . power == 0 ) && ( ( doc . oldPower == null ) || ( doc . oldPower == 0 ) ) ) continue ;
time = doc . time ;
if ( doc . oldPower ) { timeline . push ( doc . oldPower , time / 1000 , doc . power ) ; } else { timeline . push ( 0 , time / 1000 , doc . power ) ; }
} else if ( previousPower != doc . power ) { // Delta element
// If this event is of a short duration (2 minutes or less), skip it.
if ( ( docs . length > ( j + 1 ) ) && ( ( Date . parse ( docs [ j + 1 ] . time ) - doc . time ) < 120000 ) ) continue ;
timeline . push ( ( doc . time - time ) / 1000 , doc . power ) ;
time = doc . time ;
}
previousPower = doc . power ;
}
try { ws . send ( JSON . stringify ( { action : 'powertimeline' , nodeid : command . nodeid , timeline : timeline , tag : command . tag } ) ) ; } catch ( ex ) { }
} else {
// No records found, send current state if we have it
var state = parent . parent . GetConnectivityState ( command . nodeid ) ;
if ( state != null ) { try { ws . send ( JSON . stringify ( { action : 'powertimeline' , nodeid : command . nodeid , timeline : [ state . powerState , Date . now ( ) , state . powerState ] , tag : command . tag } ) ) ; } catch ( ex ) { } }
2017-10-24 00:09:58 +03:00
}
2019-09-25 22:10:41 +03:00
} ) ;
2019-01-05 04:59:13 +03:00
}
} ) ;
break ;
}
2019-09-24 02:46:26 +03:00
case 'getsysinfo' :
{
2019-09-25 22:10:41 +03:00
// Perform pre-validation
2019-09-24 02:46:26 +03:00
if ( common . validateString ( command . nodeid , 0 , 128 ) == false ) break ;
var snode = command . nodeid . split ( '/' ) ;
if ( ( snode . length != 3 ) || ( snode [ 1 ] != domain . id ) ) break ;
2019-09-25 22:10:41 +03:00
// Check that we have permissions for this node.
if ( obj . user . links == null ) return ;
db . Get ( command . nodeid , function ( err , nodes ) {
if ( nodes == null || nodes . length != 1 ) return ;
const node = nodes [ 0 ] ;
var meshlink = obj . user . links [ node . meshid ] ;
if ( ( meshlink != null ) && ( meshlink . rights != 0 ) ) {
// Query the database system information
db . Get ( 'si' + command . nodeid , function ( err , docs ) {
if ( ( docs != null ) && ( docs . length > 0 ) ) {
var doc = docs [ 0 ] ;
doc . action = 'getsysinfo' ;
doc . nodeid = command . nodeid ;
doc . tag = command . tag ;
delete doc . type ;
delete doc . domain ;
delete doc . _id ;
try { ws . send ( JSON . stringify ( doc ) ) ; } catch ( ex ) { }
} else {
try { ws . send ( JSON . stringify ( { action : 'getsysinfo' , nodeid : command . nodeid , tag : command . tag , noinfo : true } ) ) ; } catch ( ex ) { }
}
} ) ;
2019-09-24 02:46:26 +03:00
}
} ) ;
break ;
}
2019-01-05 04:59:13 +03:00
case 'lastconnect' :
{
2019-09-25 22:10:41 +03:00
// Perform pre-validation
2019-03-10 01:28:08 +03:00
if ( common . validateString ( command . nodeid , 0 , 128 ) == false ) return ;
2019-09-24 02:46:26 +03:00
var snode = command . nodeid . split ( '/' ) ;
if ( ( snode . length != 3 ) || ( snode [ 1 ] != domain . id ) ) break ;
2019-01-05 04:59:13 +03:00
2019-09-25 22:10:41 +03:00
// Check that we have permissions for this node.
if ( obj . user . links == null ) return ;
db . Get ( command . nodeid , function ( err , nodes ) {
if ( nodes == null || nodes . length != 1 ) return ;
const node = nodes [ 0 ] ;
var meshlink = obj . user . links [ node . meshid ] ;
if ( ( meshlink != null ) && ( meshlink . rights != 0 ) ) {
// Query the database for the last time this node connected
db . Get ( 'lc' + command . nodeid , function ( err , docs ) {
if ( ( docs != null ) && ( docs . length > 0 ) ) {
try { ws . send ( JSON . stringify ( { action : 'lastconnect' , nodeid : command . nodeid , time : docs [ 0 ] . time , addr : docs [ 0 ] . addr } ) ) ; } catch ( ex ) { }
}
} ) ;
2019-09-24 02:46:26 +03:00
}
2019-01-05 04:59:13 +03:00
} ) ;
break ;
}
case 'files' :
{
// Send the full list of server files to the browser app
2019-02-19 09:20:25 +03:00
updateUserFiles ( user , ws , domain ) ;
2019-01-05 04:59:13 +03:00
break ;
}
case 'fileoperation' :
{
// Check permissions
if ( ( user . siteadmin & 8 ) != 0 ) {
// Perform a file operation (Create Folder, Delete Folder, Delete File...)
2019-03-10 01:28:08 +03:00
if ( common . validateString ( command . fileop , 4 , 16 ) == false ) return ;
2019-01-05 04:59:13 +03:00
var sendUpdate = true , path = meshPathToRealPath ( command . path , user ) ; // This will also check access rights
if ( path == null ) break ;
2019-03-10 01:28:08 +03:00
if ( ( command . fileop == 'createfolder' ) && ( common . IsFilenameValid ( command . newfolder ) == true ) ) {
2019-02-20 02:38:27 +03:00
// Create a new folder
2019-10-31 00:30:34 +03:00
try { fs . mkdirSync ( path + '/' + command . newfolder ) ; } catch ( ex ) {
try { fs . mkdirSync ( path ) ; } catch ( ex ) { }
try { fs . mkdirSync ( path + '/' + command . newfolder ) ; } catch ( ex ) { }
2019-02-20 02:38:27 +03:00
}
2019-10-25 11:16:00 +03:00
}
2019-02-20 02:38:27 +03:00
else if ( command . fileop == 'delete' ) {
// Delete a file
2019-03-10 01:28:08 +03:00
if ( common . validateArray ( command . delfiles , 1 ) == false ) return ;
2019-01-05 04:59:13 +03:00
for ( i in command . delfiles ) {
2019-03-10 01:28:08 +03:00
if ( common . IsFilenameValid ( command . delfiles [ i ] ) == true ) {
2019-03-20 19:53:44 +03:00
var fullpath = parent . path . join ( path , command . delfiles [ i ] ) ;
2019-01-05 04:59:13 +03:00
if ( command . rec == true ) {
2019-10-31 00:30:34 +03:00
try { deleteFolderRecursive ( fullpath ) ; } catch ( ex ) { } // TODO, make this an async function
2019-01-05 04:59:13 +03:00
} else {
2019-10-31 00:30:34 +03:00
try { fs . rmdirSync ( fullpath ) ; } catch ( ex ) { try { fs . unlinkSync ( fullpath ) ; } catch ( xe ) { } }
2018-09-25 21:51:40 +03:00
}
}
2017-10-24 00:09:58 +03:00
}
2019-02-19 09:20:25 +03:00
2019-02-20 02:38:27 +03:00
// If we deleted something in the mesh root folder and the entire mesh folder is empty, remove it.
if ( command . path . length == 1 ) {
try {
if ( command . path [ 0 ] . startsWith ( 'mesh//' ) ) {
path = meshPathToRealPath ( [ command . path [ 0 ] ] , user ) ;
2019-03-10 01:28:08 +03:00
fs . readdir ( path , function ( err , dir ) { if ( ( err == null ) && ( dir . length == 0 ) ) { fs . rmdir ( path , function ( err ) { } ) ; } } ) ;
2019-02-20 02:38:27 +03:00
}
} catch ( ex ) { }
}
}
2019-03-10 01:28:08 +03:00
else if ( ( command . fileop == 'rename' ) && ( common . IsFilenameValid ( command . oldname ) == true ) && ( common . IsFilenameValid ( command . newname ) == true ) ) {
2019-02-20 02:38:27 +03:00
// Rename
2019-03-10 01:28:08 +03:00
try { fs . renameSync ( path + "/" + command . oldname , path + "/" + command . newname ) ; } catch ( e ) { }
2017-10-24 00:09:58 +03:00
}
2019-01-05 04:59:13 +03:00
else if ( ( command . fileop == 'copy' ) || ( command . fileop == 'move' ) ) {
2019-03-10 01:28:08 +03:00
if ( common . validateArray ( command . names , 1 ) == false ) return ;
2019-01-05 04:59:13 +03:00
var scpath = meshPathToRealPath ( command . scpath , user ) ; // This will also check access rights
if ( scpath == null ) break ;
// TODO: Check quota if this is a copy!!!!!!!!!!!!!!!!
for ( i in command . names ) {
2019-03-20 19:53:44 +03:00
var s = parent . path . join ( scpath , command . names [ i ] ) , d = parent . path . join ( path , command . names [ i ] ) ;
2019-01-05 04:59:13 +03:00
sendUpdate = false ;
2019-03-10 01:28:08 +03:00
copyFile ( s , d , function ( op ) { if ( op != null ) { fs . unlink ( op , function ( err ) { parent . parent . DispatchEvent ( [ user . _id ] , obj , 'updatefiles' ) ; } ) ; } else { parent . parent . DispatchEvent ( [ user . _id ] , obj , 'updatefiles' ) ; } } , ( ( command . fileop == 'move' ) ? s : null ) ) ;
2018-04-17 01:37:41 +03:00
}
2019-01-05 04:59:13 +03:00
}
2018-04-17 01:37:41 +03:00
2019-03-10 01:28:08 +03:00
if ( sendUpdate == true ) { parent . parent . DispatchEvent ( [ user . _id ] , obj , 'updatefiles' ) ; } // Fire an event causing this user to update this files
2019-01-05 04:59:13 +03:00
}
break ;
}
2019-02-05 05:06:01 +03:00
case 'serverconsole' :
{
// This is a server console message, only process this if full administrator
if ( user . siteadmin != 0xFFFFFFFF ) break ;
var r = '' ;
2019-03-10 01:53:46 +03:00
var cmdargs = splitArgs ( command . value ) ;
if ( cmdargs . length == 0 ) break ;
const cmd = cmdargs [ 0 ] . toLowerCase ( ) ;
cmdargs = parseArgs ( cmdargs ) ;
2019-02-05 05:06:01 +03:00
switch ( cmd ) {
case 'help' : {
2019-10-25 11:16:00 +03:00
r = 'Available commands: help, info, versions, args, resetserver, showconfig, usersessions, tasklimiter, setmaxtasks, cores,\r\n'
2019-05-18 01:44:01 +03:00
r += 'migrationagents, agentstats, webstats, mpsstats, swarmstats, acceleratorsstats, updatecheck, serverupdate, nodeconfig,\r\n' ;
2019-07-11 00:27:38 +03:00
r += 'heapdump, relays, autobackup, backupconfig, dupagents, dispatchtable.' ;
break ;
}
case 'dispatchtable' : {
r = '' ;
for ( var i in parent . parent . eventsDispatch ) {
r += ( i + ', ' + parent . parent . eventsDispatch [ i ] . length + '\r\n' ) ;
}
2019-05-22 00:19:32 +03:00
break ;
}
case 'dupagents' : {
for ( var i in parent . duplicateAgentsLog ) { r += JSON . stringify ( parent . duplicateAgentsLog [ i ] ) + '\r\n' ; }
if ( r == '' ) { r = 'No duplicate agents in log.' ; }
2019-05-02 01:02:03 +03:00
break ;
}
case 'agentstats' : {
var stats = parent . getAgentStats ( ) ;
for ( var i in stats ) {
if ( typeof stats [ i ] == 'object' ) { r += ( i + ': ' + JSON . stringify ( stats [ i ] ) + '\r\n' ) ; } else { r += ( i + ': ' + stats [ i ] + '\r\n' ) ; }
}
break ;
}
case 'webstats' : {
var stats = parent . getStats ( ) ;
for ( var i in stats ) {
if ( typeof stats [ i ] == 'object' ) { r += ( i + ': ' + JSON . stringify ( stats [ i ] ) + '\r\n' ) ; } else { r += ( i + ': ' + stats [ i ] + '\r\n' ) ; }
}
break ;
}
case 'acceleratorsstats' : {
var stats = parent . parent . certificateOperations . getAcceleratorStats ( ) ;
for ( var i in stats ) {
if ( typeof stats [ i ] == 'object' ) { r += ( i + ': ' + JSON . stringify ( stats [ i ] ) + '\r\n' ) ; } else { r += ( i + ': ' + stats [ i ] + '\r\n' ) ; }
}
break ;
}
case 'mpsstats' : {
var stats = parent . parent . mpsserver . getStats ( ) ;
for ( var i in stats ) {
if ( typeof stats [ i ] == 'object' ) { r += ( i + ': ' + JSON . stringify ( stats [ i ] ) + '\r\n' ) ; } else { r += ( i + ': ' + stats [ i ] + '\r\n' ) ; }
}
break ;
}
case 'serverupdate' : {
r = 'Performing server update...' ;
2019-07-18 23:11:08 +03:00
if ( parent . parent . performServerUpdate ( ) == false ) { r = 'Server self-update not possible.' ; }
2019-05-02 01:02:03 +03:00
break ;
}
2019-07-18 01:57:42 +03:00
case 'print' : {
console . log ( cmdargs [ "_" ] [ 0 ] ) ;
break ;
}
2019-05-02 01:02:03 +03:00
case 'updatecheck' : {
parent . parent . getLatestServerVersion ( function ( currentVer , newVer , error ) {
var r2 = 'Current Version: ' + currentVer + '\r\n' ;
if ( newVer != null ) { r2 += 'Available Version: ' + newVer + '\r\n' ; }
2019-05-03 04:26:43 +03:00
if ( error != null ) { r2 += 'Exception: ' + error + '\r\n' ; }
2019-05-02 01:02:03 +03:00
try { ws . send ( JSON . stringify ( { action : 'serverconsole' , value : r2 , tag : command . tag } ) ) ; } catch ( ex ) { }
} ) ;
r = 'Checking server update...' ;
2019-03-06 20:15:32 +03:00
break ;
}
case 'info' : {
var info = process . memoryUsage ( ) ;
2019-10-25 11:16:00 +03:00
info . dbType = [ 'None' , 'NeDB' , 'MongoJS' , 'MongoDB' ] [ parent . db . databaseType ] ;
2019-05-29 03:25:23 +03:00
if ( parent . db . databaseType == 3 ) { info . dbChangeStream = parent . db . changeStream ; }
2019-03-06 20:15:32 +03:00
try { info . platform = process . platform ; } catch ( ex ) { }
try { info . arch = process . arch ; } catch ( ex ) { }
try { info . pid = process . pid ; } catch ( ex ) { }
try { info . uptime = process . uptime ( ) ; } catch ( ex ) { }
try { info . version = process . version ; } catch ( ex ) { }
try { info . cpuUsage = process . cpuUsage ( ) ; } catch ( ex ) { }
r = JSON . stringify ( info , null , 4 ) ;
break ;
}
case 'nodeconfig' : {
r = JSON . stringify ( process . config , null , 4 ) ;
break ;
}
case 'versions' : {
r = JSON . stringify ( process . versions , null , 4 ) ;
2019-02-05 05:06:01 +03:00
break ;
}
case 'args' : {
2019-03-10 01:53:46 +03:00
r = cmd + ': ' + JSON . stringify ( cmdargs ) ;
2019-02-05 05:06:01 +03:00
break ;
}
case 'usersessions' : {
2019-03-10 01:28:08 +03:00
for ( var i in parent . wssessions ) {
r += ( i + ', ' + parent . wssessions [ i ] . length + ' session' + ( ( parent . wssessions [ i ] . length > 1 ) ? 'a' : '' ) + '.<br />' ) ;
for ( var j in parent . wssessions [ i ] ) {
var addr = parent . wssessions [ i ] [ j ] . _socket . remoteAddress ;
2019-02-05 05:06:01 +03:00
if ( addr . startsWith ( '::ffff:' ) ) { addr = addr . substring ( 7 ) ; }
2019-03-10 01:28:08 +03:00
r += ' ' + addr + ' --> ' + parent . wssessions [ i ] [ j ] . sessionId + '.<br />' ;
2019-02-05 05:06:01 +03:00
}
}
break ;
}
case 'resetserver' : {
console . log ( 'Server restart...' ) ;
process . exit ( 0 ) ;
break ;
}
2019-02-26 01:35:08 +03:00
case 'tasklimiter' : {
2019-03-10 01:28:08 +03:00
if ( parent . parent . taskLimiter != null ) {
2019-02-26 01:35:08 +03:00
//var obj = { maxTasks: maxTasks, maxTaskTime: (maxTaskTime * 1000), nextTaskId: 0, currentCount: 0, current: {}, pending: [[], [], []], timer: null };
2019-03-10 01:28:08 +03:00
const tl = parent . parent . taskLimiter ;
2019-02-26 03:07:27 +03:00
r += 'MaxTasks: ' + tl . maxTasks + ', NextTaskId: ' + tl . nextTaskId + '<br />' ;
r += 'MaxTaskTime: ' + ( tl . maxTaskTime / 1000 ) + ' seconds, Timer: ' + ( tl . timer != null ) + '<br />' ;
2019-02-26 01:35:08 +03:00
var c = [ ] ;
for ( var i in tl . current ) { c . push ( i ) ; }
2019-02-26 03:07:27 +03:00
r += 'Current (' + tl . currentCount + '): [' + c . join ( ', ' ) + ']<br />' ;
2019-02-26 01:35:08 +03:00
r += 'Pending (High/Med/Low): ' + tl . pending [ 0 ] . length + ', ' + tl . pending [ 1 ] . length + ', ' + tl . pending [ 2 ] . length + '<br />' ;
2019-02-26 03:07:27 +03:00
}
break ;
}
case 'setmaxtasks' : {
2019-03-10 01:53:46 +03:00
if ( ( cmdargs [ "_" ] . length != 1 ) || ( parseInt ( cmdargs [ "_" ] [ 0 ] ) < 1 ) || ( parseInt ( cmdargs [ "_" ] [ 0 ] ) > 1000 ) ) {
2019-02-26 03:07:27 +03:00
r = 'Usage: setmaxtasks [1 to 1000]' ;
} else {
2019-03-10 01:53:46 +03:00
parent . parent . taskLimiter . maxTasks = parseInt ( cmdargs [ "_" ] [ 0 ] ) ;
2019-03-10 01:28:08 +03:00
r = 'MaxTasks set to ' + parent . parent . taskLimiter . maxTasks + '.' ;
2019-02-26 01:35:08 +03:00
}
break ;
}
case 'cores' : {
2019-03-10 01:28:08 +03:00
if ( parent . parent . defaultMeshCores != null ) { for ( var i in parent . parent . defaultMeshCores ) { r += i + ': ' + parent . parent . defaultMeshCores [ i ] . length + ' bytes<br />' ; } }
2019-02-26 01:35:08 +03:00
break ;
}
2019-02-05 05:06:01 +03:00
case 'showconfig' : {
2019-02-11 03:04:36 +03:00
// Make a copy of the configuration and hide any secrets
2019-03-10 01:28:08 +03:00
var config = common . Clone ( parent . parent . config ) ;
2019-02-06 06:07:12 +03:00
if ( config . settings ) {
if ( config . settings . configkey ) { config . settings . configkey = '(present)' ; }
if ( config . settings . sessionkey ) { config . settings . sessionkey = '(present)' ; }
if ( config . settings . dbencryptkey ) { config . settings . dbencryptkey = '(present)' ; }
}
2019-02-11 03:04:36 +03:00
if ( config . domains ) {
for ( var i in config . domains ) {
if ( config . domains [ i ] . yubikey && config . domains [ i ] . yubikey . secret ) { config . domains [ i ] . yubikey . secret = '(present)' ; }
}
}
2019-10-25 11:16:00 +03:00
2019-02-06 06:07:12 +03:00
r = JSON . stringify ( removeAllUnderScore ( config ) , null , 4 ) ;
2019-02-05 05:06:01 +03:00
break ;
}
2019-02-28 05:48:50 +03:00
case 'migrationagents' : {
2019-03-10 01:28:08 +03:00
if ( parent . parent . swarmserver == null ) {
2019-02-28 05:48:50 +03:00
r = 'Swarm server not running.' ;
} else {
2019-03-10 01:28:08 +03:00
for ( var i in parent . parent . swarmserver . migrationAgents ) {
var arch = parent . parent . swarmserver . migrationAgents [ i ] ;
2019-03-08 09:47:27 +03:00
for ( var j in arch ) { var agent = arch [ j ] ; r += 'Arch ' + agent . arch + ', Ver ' + agent . ver + ', Size ' + ( ( agent . binary == null ) ? 0 : agent . binary . length ) + '<br />' ; }
2019-02-28 05:48:50 +03:00
}
}
break ;
}
case 'swarmstats' : {
2019-03-10 01:28:08 +03:00
if ( parent . parent . swarmserver == null ) {
2019-02-28 05:48:50 +03:00
r = 'Swarm server not running.' ;
} else {
2019-03-10 01:28:08 +03:00
for ( var i in parent . parent . swarmserver . stats ) {
if ( typeof parent . parent . swarmserver . stats [ i ] == 'object' ) {
2019-05-02 01:02:03 +03:00
r += i + ': ' + JSON . stringify ( parent . parent . swarmserver . stats [ i ] ) + '\r\n' ;
2019-02-28 05:48:50 +03:00
} else {
2019-05-02 01:02:03 +03:00
r += i + ': ' + parent . parent . swarmserver . stats [ i ] + '\r\n' ;
2019-02-28 05:48:50 +03:00
}
}
}
break ;
}
2019-03-08 06:56:24 +03:00
case 'heapdump' : {
var heapdump = null ;
try { heapdump = require ( 'heapdump' ) ; } catch ( ex ) { }
if ( heapdump == null ) {
r = 'Heapdump module not installed, run "npm install heapdump".' ;
} else {
heapdump . writeSnapshot ( function ( err , filename ) {
if ( err != null ) {
try { ws . send ( JSON . stringify ( { action : 'serverconsole' , value : 'Unable to write heapdump: ' + err } ) ) ; } catch ( ex ) { }
} else {
try { ws . send ( JSON . stringify ( { action : 'serverconsole' , value : 'Wrote heapdump at ' + filename } ) ) ; } catch ( ex ) { }
}
} ) ;
}
break ;
}
2019-04-29 06:31:08 +03:00
case 'relays' : {
for ( var i in parent . wsrelays ) {
r += 'id: ' + i + ', state: ' + parent . wsrelays [ i ] . state ;
2019-09-01 05:40:50 +03:00
if ( parent . wsrelays [ i ] . peer1 != null ) { r += ', peer1: ' + cleanRemoteAddr ( parent . wsrelays [ i ] . peer1 . req . ip ) ; }
if ( parent . wsrelays [ i ] . peer2 != null ) { r += ', peer2: ' + cleanRemoteAddr ( parent . wsrelays [ i ] . peer2 . req . ip ) ; }
2019-04-29 06:31:08 +03:00
r += '<br />' ;
}
if ( r == '' ) { r = 'No relays.' ; }
break ;
}
2019-05-18 01:44:01 +03:00
case 'autobackup' : {
var backupResult = parent . db . performBackup ( ) ;
if ( backupResult == 0 ) { r = 'Starting auto-backup...' ; } else { r = 'Backup alreay in progress.' ; }
break ;
2019-06-08 02:44:00 +03:00
}
case 'backupconfig' : {
r = parent . db . getBackupConfig ( ) ;
break ;
2019-05-18 01:44:01 +03:00
}
2019-02-05 05:06:01 +03:00
default : { // This is an unknown command, return an error message
r = 'Unknown command \"' + cmd + '\", type \"help\" for list of avaialble commands.' ;
break ;
}
}
if ( r != '' ) { try { ws . send ( JSON . stringify ( { action : 'serverconsole' , value : r , tag : command . tag } ) ) ; } catch ( ex ) { } }
break ;
}
2019-01-05 04:59:13 +03:00
case 'msg' :
{
// Route this command to a target node
routeCommandToNode ( command ) ;
break ;
}
case 'events' :
{
// User filtered events
if ( ( command . user != null ) && ( ( user . siteadmin & 2 ) != 0 ) ) { // SITERIGHT_MANAGEUSERS
// TODO: Add the meshes command.user has access to (???)
var filter = [ 'user/' + domain . id + '/' + command . user . toLowerCase ( ) ] ;
if ( ( command . limit == null ) || ( typeof command . limit != 'number' ) ) {
// Send the list of all events for this session
2019-03-10 01:28:08 +03:00
db . GetUserEvents ( filter , domain . id , command . user , function ( err , docs ) {
2019-02-19 01:32:55 +03:00
if ( err != null ) return ;
try { ws . send ( JSON . stringify ( { action : 'events' , events : docs , user : command . user , tag : command . tag } ) ) ; } catch ( ex ) { }
} ) ;
2019-01-05 04:59:13 +03:00
} else {
2018-01-05 02:59:57 +03:00
// Send the list of most recent events for this session, up to 'limit' count
2019-03-10 01:28:08 +03:00
db . GetUserEventsWithLimit ( filter , domain . id , command . user , command . limit , function ( err , docs ) {
2019-02-19 01:32:55 +03:00
if ( err != null ) return ;
try { ws . send ( JSON . stringify ( { action : 'events' , events : docs , user : command . user , tag : command . tag } ) ) ; } catch ( ex ) { }
} ) ;
2019-01-05 04:59:13 +03:00
}
2019-03-10 01:28:08 +03:00
} else if ( common . validateString ( command . nodeid , 0 , 128 ) == true ) { // Device filtered events
2019-09-18 22:05:33 +03:00
// Check that the user has access to this nodeid
if ( obj . user . links == null ) return ;
db . Get ( command . nodeid , function ( err , nodes ) {
2019-09-26 01:09:40 +03:00
if ( ( nodes == null ) || ( nodes . length != 1 ) ) return ;
2019-09-18 22:05:33 +03:00
const node = nodes [ 0 ] ;
var meshlink = obj . user . links [ node . meshid ] ;
if ( ( meshlink != null ) && ( meshlink . rights != 0 ) ) {
// Put a limit on the number of returned entries if present
var limit = 10000 ;
if ( common . validateInt ( command . limit , 1 , 60000 ) == true ) { limit = command . limit ; }
if ( ( meshlink . rights & 8192 ) != 0 ) {
// Send the list of most recent events for this nodeid that only apply to us, up to 'limit' count
db . GetNodeEventsSelfWithLimit ( command . nodeid , domain . id , user . _id , limit , function ( err , docs ) {
if ( err != null ) return ;
try { ws . send ( JSON . stringify ( { action : 'events' , events : docs , nodeid : command . nodeid , tag : command . tag } ) ) ; } catch ( ex ) { }
} ) ;
} else {
// Send the list of most recent events for this nodeid, up to 'limit' count
db . GetNodeEventsWithLimit ( command . nodeid , domain . id , limit , function ( err , docs ) {
if ( err != null ) return ;
try { ws . send ( JSON . stringify ( { action : 'events' , events : docs , nodeid : command . nodeid , tag : command . tag } ) ) ; } catch ( ex ) { }
} ) ;
}
}
2019-02-19 01:32:55 +03:00
} ) ;
2019-01-05 04:59:13 +03:00
} else {
2019-09-18 22:05:33 +03:00
// Create a filter for device groups
if ( obj . user . links == null ) return ;
2019-01-05 04:59:13 +03:00
// All events
2019-09-18 22:05:33 +03:00
var exGroupFilter2 = [ ] , filter = [ ] , filter2 = user . subscriptions ;
// Remove MeshID's that we do not have rights to see events for
for ( var link in obj . user . links ) { if ( ( ( obj . user . links [ link ] . rights & 8192 ) != 0 ) && ( ( obj . user . links [ link ] . rights != 0xFFFFFFFF ) ) ) { exGroupFilter2 . push ( link ) ; } }
for ( var i in filter2 ) { if ( exGroupFilter2 . indexOf ( filter2 [ i ] ) == - 1 ) { filter . push ( filter2 [ i ] ) ; } }
2019-01-05 04:59:13 +03:00
if ( ( command . limit == null ) || ( typeof command . limit != 'number' ) ) {
// Send the list of all events for this session
2019-03-10 01:28:08 +03:00
db . GetEvents ( filter , domain . id , function ( err , docs ) {
2019-02-19 01:32:55 +03:00
if ( err != null ) return ;
try { ws . send ( JSON . stringify ( { action : 'events' , events : docs , user : command . user , tag : command . tag } ) ) ; } catch ( ex ) { }
} ) ;
2018-04-17 01:37:41 +03:00
} else {
2019-01-05 04:59:13 +03:00
// Send the list of most recent events for this session, up to 'limit' count
2019-03-10 01:28:08 +03:00
db . GetEventsWithLimit ( filter , domain . id , command . limit , function ( err , docs ) {
2019-02-19 01:32:55 +03:00
if ( err != null ) return ;
try { ws . send ( JSON . stringify ( { action : 'events' , events : docs , user : command . user , tag : command . tag } ) ) ; } catch ( ex ) { }
} ) ;
2018-01-05 02:59:57 +03:00
}
2017-10-24 00:09:58 +03:00
}
2019-01-05 04:59:13 +03:00
break ;
}
case 'users' :
{
// Request a list of all users
if ( ( user . siteadmin & 2 ) == 0 ) break ;
var docs = [ ] ;
2019-03-10 01:28:08 +03:00
for ( i in parent . users ) {
if ( ( parent . users [ i ] . domain == domain . id ) && ( parent . users [ i ] . name != '~' ) ) {
2019-04-17 03:32:18 +03:00
// If we are part of a user group, we can only see other members of our own group
if ( ( user . groups == null ) || ( user . groups . length == 0 ) || ( ( parent . users [ i ] . groups != null ) && ( findOne ( parent . users [ i ] . groups , user . groups ) ) ) ) {
docs . push ( parent . CloneSafeUser ( parent . users [ i ] ) ) ;
}
2019-01-05 04:59:13 +03:00
}
2017-10-24 00:09:58 +03:00
}
2019-01-05 04:59:13 +03:00
try { ws . send ( JSON . stringify ( { action : 'users' , users : docs , tag : command . tag } ) ) ; } catch ( ex ) { }
break ;
}
2019-10-22 21:59:19 +03:00
case 'changelang' :
{
if ( common . validateString ( command . lang , 1 , 6 ) == false ) return ;
// Always lowercase the email address
command . lang = command . lang . toLowerCase ( ) ;
// Update the user's email
var oldlang = user . lang ;
if ( command . lang == '*' ) { delete user . lang ; } else { user . lang = command . lang ; }
parent . db . SetUser ( user ) ;
// Event the change
var message = { etype : 'user' , userid : user . _id , username : user . name , account : parent . CloneSafeUser ( user ) , action : 'accountchange' , domain : domain . id } ;
if ( db . changeStream ) { message . noact = 1 ; } // If DB change stream is active, don't use this event to change the user. Another event will come.
message . msg = 'Changed language of user ' + user . name + ' from ' + ( oldlang ? oldlang : 'default' ) + ' to ' + ( user . lang ? user . lang : 'default' ) ;
var targets = [ '*' , 'server-users' , user . _id ] ;
if ( user . groups ) { for ( var i in user . groups ) { targets . push ( 'server-users:' + i ) ; } }
parent . parent . DispatchEvent ( targets , obj , message ) ;
break ;
}
2019-01-05 04:59:13 +03:00
case 'changeemail' :
{
2019-07-09 01:59:44 +03:00
// If the email is the username, this command is not allowed.
if ( domain . usernameisemail ) return ;
2019-04-17 03:32:18 +03:00
// Change our own email address
2019-04-11 23:41:51 +03:00
if ( ( domain . auth == 'sspi' ) || ( domain . auth == 'ldap' ) ) return ;
2019-07-15 20:24:31 +03:00
if ( common . validateEmail ( command . email , 1 , 1024 ) == false ) return ;
// Always lowercase the email address
command . email = command . email . toLowerCase ( ) ;
2019-08-26 22:20:24 +03:00
if ( obj . user . email != command . email ) {
2019-01-05 04:59:13 +03:00
// Check if this email is already validated on a different account
2019-03-10 01:28:08 +03:00
db . GetUserWithVerifiedEmail ( domain . id , command . email , function ( err , docs ) {
2019-05-21 04:03:14 +03:00
if ( ( docs != null ) && ( docs . length > 0 ) ) {
2019-01-05 04:59:13 +03:00
// Notify the duplicate email error
2019-04-30 01:31:11 +03:00
try { ws . send ( JSON . stringify ( { action : 'msg' , type : 'notify' , title : 'Account Settings' , tag : 'ServerNotify' , value : 'Failed to change email address, another account already using: <b>' + EscapeHtml ( command . email ) + '</b>.' } ) ) ; } catch ( ex ) { }
2019-01-05 04:59:13 +03:00
} else {
// Update the user's email
var oldemail = user . email ;
user . email = command . email ;
user . emailVerified = false ;
2019-03-10 01:28:08 +03:00
parent . db . SetUser ( user ) ;
2019-01-05 04:59:13 +03:00
// Event the change
2019-07-30 02:35:48 +03:00
var message = { etype : 'user' , userid : user . _id , username : user . name , account : parent . CloneSafeUser ( user ) , action : 'accountchange' , domain : domain . id } ;
2019-05-29 03:25:23 +03:00
if ( db . changeStream ) { message . noact = 1 ; } // If DB change stream is active, don't use this event to change the user. Another event will come.
2019-01-05 04:59:13 +03:00
if ( oldemail != null ) {
2019-02-06 06:07:12 +03:00
message . msg = 'Changed email of user ' + user . name + ' from ' + oldemail + ' to ' + user . email ;
2018-05-17 01:49:12 +03:00
} else {
2019-02-06 06:07:12 +03:00
message . msg = 'Set email of user ' + user . name + ' to ' + user . email ;
2018-05-17 01:49:12 +03:00
}
2019-04-17 03:32:18 +03:00
var targets = [ '*' , 'server-users' , user . _id ] ;
if ( user . groups ) { for ( var i in user . groups ) { targets . push ( 'server-users:' + i ) ; } }
parent . parent . DispatchEvent ( targets , obj , message ) ;
2019-01-16 23:04:48 +03:00
// Send the verification email
2019-04-11 23:41:51 +03:00
if ( parent . parent . mailserver != null ) { parent . parent . mailserver . sendAccountCheckMail ( domain , user . name , user . email ) ; }
2019-01-05 04:59:13 +03:00
}
} ) ;
2017-12-13 03:04:54 +03:00
}
2019-01-05 04:59:13 +03:00
break ;
}
case 'verifyemail' :
{
// Send a account email verification email
2019-04-11 23:41:51 +03:00
if ( ( domain . auth == 'sspi' ) || ( domain . auth == 'ldap' ) ) return ;
2019-03-10 01:28:08 +03:00
if ( common . validateString ( command . email , 3 , 1024 ) == false ) return ;
2019-07-15 20:24:31 +03:00
// Always lowercase the email address
command . email = command . email . toLowerCase ( ) ;
2019-08-26 22:20:24 +03:00
if ( ( parent . parent . mailserver != null ) && ( obj . user . email . toLowerCase ( ) == command . email ) ) {
2019-01-16 23:04:48 +03:00
// Send the verification email
2019-03-10 01:28:08 +03:00
parent . parent . mailserver . sendAccountCheckMail ( domain , user . name , user . email ) ;
2017-12-13 03:04:54 +03:00
}
2019-01-05 04:59:13 +03:00
break ;
}
case 'wssessioncount' :
{
// Request a list of all web socket user session count
var wssessions = { } ;
if ( ( user . siteadmin & 2 ) == 0 ) break ;
2019-03-10 01:28:08 +03:00
if ( parent . parent . multiServer == null ) {
2019-01-05 04:59:13 +03:00
// No peering, use simple session counting
2019-04-17 03:32:18 +03:00
for ( i in parent . wssessions ) {
if ( parent . wssessions [ i ] [ 0 ] . domainid == domain . id ) {
if ( ( user . groups == null ) || ( user . groups . length == 0 ) ) {
// No user groups, count everything
wssessions [ i ] = parent . wssessions [ i ] . length ;
} else {
// Only count if session is for a user in our user groups
var sessionUser = parent . users [ parent . wssessions [ i ] [ 0 ] . userid ] ;
if ( ( sessionUser != null ) && findOne ( sessionUser . groups , user . groups ) ) {
wssessions [ i ] = parent . wssessions [ i ] . length ;
}
}
}
}
2019-01-05 04:59:13 +03:00
} else {
// We have peer servers, use more complex session counting
2019-04-17 03:32:18 +03:00
for ( i in parent . sessionsCount ) {
if ( i . split ( '/' ) [ 1 ] == domain . id ) {
if ( ( user . groups == null ) || ( user . groups . length == 0 ) ) {
// No user groups, count everything
wssessions [ i ] = parent . sessionsCount [ i ] ;
} else {
// Only count if session is for a user in our user groups
var sessionUser = parent . users [ i ] ;
if ( ( sessionUser != null ) && findOne ( sessionUser . groups , user . groups ) ) {
wssessions [ i ] = parent . sessionsCount [ i ] ;
}
}
}
}
2017-10-24 00:09:58 +03:00
}
2019-01-05 04:59:13 +03:00
try { ws . send ( JSON . stringify ( { action : 'wssessioncount' , wssessions : wssessions , tag : command . tag } ) ) ; } catch ( ex ) { } // wssessions is: userid --> count
break ;
}
case 'deleteuser' :
{
// Delete a user account
2019-06-30 21:34:27 +03:00
var err = null , delusersplit , deluserid , deluser ;
try {
if ( ( user . siteadmin & 2 ) == 0 ) { err = 'Permission denied' ; }
else if ( common . validateString ( command . userid , 1 , 2048 ) == false ) { err = 'Invalid userid' ; }
else {
delusersplit = command . userid . split ( '/' ) ;
deluserid = command . userid ;
deluser = parent . users [ deluserid ] ;
if ( deluser == null ) { err = 'User does not exists' ; }
else if ( ( delusersplit . length != 3 ) || ( delusersplit [ 1 ] != domain . id ) ) { err = 'Invalid domain' ; } // Invalid domain, operation only valid for current domain
else if ( ( deluser . siteadmin != null ) && ( deluser . siteadmin > 0 ) && ( user . siteadmin != 0xFFFFFFFF ) ) { err = 'Permission denied' ; } // Need full admin to remote another administrator
else if ( ( user . groups != null ) && ( user . groups . length > 0 ) && ( ( deluser . groups == null ) || ( findOne ( deluser . groups , user . groups ) == false ) ) ) { err = 'Invalid user group' ; } // Can only perform this operation on other users of our group.
}
} catch ( ex ) { err = 'Validation exception: ' + ex ; }
// Handle any errors
if ( err != null ) {
if ( command . responseid != null ) { try { ws . send ( JSON . stringify ( { action : 'deleteuser' , responseid : command . responseid , result : err } ) ) ; } catch ( ex ) { } }
break ;
}
2019-01-05 04:59:13 +03:00
// Remove all the mesh links to this user
if ( deluser . links != null ) {
for ( meshid in deluser . links ) {
// Get the mesh
2019-03-10 01:28:08 +03:00
mesh = parent . meshes [ meshid ] ;
2019-01-05 04:59:13 +03:00
if ( mesh ) {
// Remove user from the mesh
2019-07-11 00:27:38 +03:00
if ( mesh . links [ deluser . _id ] != null ) { delete mesh . links [ deluser . _id ] ; parent . db . Set ( common . escapeLinksFieldName ( mesh ) ) ; }
2019-01-05 04:59:13 +03:00
// Notify mesh change
change = 'Removed user ' + deluser . name + ' from group ' + mesh . name ;
2019-10-12 01:14:38 +03:00
var event = { etype : 'mesh' , userid : user . _id , username : user . name , meshid : mesh . _id , name : mesh . name , mtype : mesh . mtype , desc : mesh . desc , action : 'meshchange' , links : mesh . links , msg : change , domain : domain . id } ;
2019-05-29 03:25:23 +03:00
if ( db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the mesh. Another event will come.
parent . parent . DispatchEvent ( [ '*' , mesh . _id , deluser . _id , user . _id ] , obj , event ) ;
2018-05-17 23:37:50 +03:00
}
}
2019-01-05 04:59:13 +03:00
}
2018-05-17 23:37:50 +03:00
2019-04-30 01:31:11 +03:00
db . Remove ( 'ws' + deluser . _id ) ; // Remove user web state
db . Remove ( 'nt' + deluser . _id ) ; // Remove notes for this user
2018-05-17 23:37:50 +03:00
2019-01-05 04:59:13 +03:00
// Delete all files on the server for this account
try {
2019-03-10 01:28:08 +03:00
var deluserpath = parent . getServerRootFilePath ( deluser ) ;
if ( deluserpath != null ) { parent . deleteFolderRec ( deluserpath ) ; }
2019-01-05 04:59:13 +03:00
} catch ( e ) { }
2017-10-24 00:09:58 +03:00
2019-03-10 01:28:08 +03:00
db . Remove ( deluserid ) ;
delete parent . users [ deluserid ] ;
2019-04-17 03:32:18 +03:00
var targets = [ '*' , 'server-users' ] ;
if ( deluser . groups ) { for ( var i in deluser . groups ) { targets . push ( 'server-users:' + i ) ; } }
parent . parent . DispatchEvent ( targets , obj , { etype : 'user' , userid : deluserid , username : deluser . name , action : 'accountremove' , msg : 'Account removed' , domain : domain . id } ) ;
2019-03-10 01:28:08 +03:00
parent . parent . DispatchEvent ( [ deluserid ] , obj , 'close' ) ;
2017-10-24 00:09:58 +03:00
2019-06-30 21:34:27 +03:00
if ( command . responseid != null ) { try { ws . send ( JSON . stringify ( { action : 'deleteuser' , responseid : command . responseid , result : 'ok' } ) ) ; } catch ( ex ) { } }
2019-02-15 01:44:38 +03:00
break ;
}
case 'userbroadcast' :
{
2019-06-30 21:34:27 +03:00
var err = null ;
try {
// Broadcast a message to all currently connected users.
if ( ( user . siteadmin & 2 ) == 0 ) { err = 'Permission denied' ; }
else if ( common . validateString ( command . msg , 1 , 512 ) == false ) { err = 'Message is too long' ; } // Notification message is between 1 and 256 characters
} catch ( ex ) { err = 'Validation exception: ' + ex ; }
// Handle any errors
if ( err != null ) {
if ( command . responseid != null ) { try { ws . send ( JSON . stringify ( { action : 'userbroadcast' , responseid : command . responseid , result : err } ) ) ; } catch ( ex ) { } }
break ;
}
2019-02-15 01:44:38 +03:00
// Create the notification message
2019-04-30 01:31:11 +03:00
var notification = { action : "msg" , type : "notify" , domain : domain . id , "value" : command . msg , "title" : user . name , icon : 0 , tag : "broadcast" } ;
2019-02-15 01:44:38 +03:00
// Send the notification on all user sessions for this server
2019-03-18 05:34:52 +03:00
for ( var i in parent . wssessions2 ) {
try {
2019-04-17 03:32:18 +03:00
if ( parent . wssessions2 [ i ] . domainid == domain . id ) {
if ( ( user . groups == null ) || ( user . groups . length == 0 ) ) {
// We are part of no user groups, send to everyone.
parent . wssessions2 [ i ] . send ( JSON . stringify ( notification ) ) ;
} else {
// We are part of user groups, only send to sessions of users in our groups.
var sessionUser = parent . users [ parent . wssessions2 [ i ] . userid ] ;
if ( ( sessionUser != null ) && findOne ( sessionUser . groups , user . groups ) ) {
parent . wssessions2 [ i ] . send ( JSON . stringify ( notification ) ) ;
}
}
}
2019-03-18 05:34:52 +03:00
} catch ( ex ) { }
}
2019-02-15 01:44:38 +03:00
// TODO: Notify all sessions on other peers.
2019-06-30 21:34:27 +03:00
if ( command . responseid != null ) { try { ws . send ( JSON . stringify ( { action : 'userbroadcast' , responseid : command . responseid , result : 'ok' } ) ) ; } catch ( ex ) { } }
2019-05-15 00:39:26 +03:00
break ;
}
case 'adduserbatch' :
{
// Add many new user accounts
if ( ( user . siteadmin & 2 ) == 0 ) break ;
2019-05-15 03:14:43 +03:00
if ( ( domain . auth == 'sspi' ) || ( domain . auth == 'ldap' ) ) break ;
2019-05-15 00:39:26 +03:00
if ( ! Array . isArray ( command . users ) ) break ;
var userCount = 0 ;
for ( var i in command . users ) {
2019-07-09 01:59:44 +03:00
if ( domain . usernameisemail ) { if ( command . users [ i ] . email ) { command . users [ i ] . user = command . users [ i ] . email ; } else { command . users [ i ] . email = command . users [ i ] . user ; } } // If the email is the username, set this here.
if ( common . validateUsername ( command . users [ i ] . user , 1 , 256 ) == false ) break ; // Username is between 1 and 64 characters, no spaces
2019-05-15 00:39:26 +03:00
if ( ( command . users [ i ] . user == '~' ) || ( command . users [ i ] . user . indexOf ( '/' ) >= 0 ) ) break ; // This is a reserved user name
if ( common . validateString ( command . users [ i ] . pass , 1 , 256 ) == false ) break ; // Password is between 1 and 256 characters
if ( common . checkPasswordRequirements ( command . users [ i ] . pass , domain . passwordrequirements ) == false ) break ; // Password does not meet requirements
2019-07-15 20:24:31 +03:00
if ( ( command . users [ i ] . email != null ) && ( common . validateEmail ( command . users [ i ] . email , 1 , 1024 ) == false ) ) break ; // Check if this is a valid email address
2019-05-15 00:39:26 +03:00
userCount ++ ;
}
// Check if we exceed the maximum number of user accounts
db . isMaxType ( domain . limits . maxuseraccounts + userCount , 'user' , domain . id , function ( maxExceed ) {
if ( maxExceed ) {
// Account count exceed, do notification
// Create the notification message
var notification = { action : "msg" , type : "notify" , value : "Account limit reached." , title : "Server Limit" , userid : user . _id , username : user . name , domain : domain . id } ;
// Get the list of sessions for this user
var sessions = parent . wssessions [ user . _id ] ;
if ( sessions != null ) { for ( i in sessions ) { try { if ( sessions [ i ] . domainid == domain . id ) { sessions [ i ] . send ( JSON . stringify ( notification ) ) ; } } catch ( ex ) { } } }
// TODO: Notify all sessions on other peers.
} else {
for ( var i in command . users ) {
// Check if this is an existing user
var newuserid = 'user/' + domain . id + '/' + command . users [ i ] . user . toLowerCase ( ) ;
var newuser = { type : 'user' , _id : newuserid , name : command . users [ i ] . user , creation : Math . floor ( Date . now ( ) / 1000 ) , domain : domain . id } ;
if ( domain . newaccountsrights ) { newuser . siteadmin = domain . newaccountsrights ; }
2019-07-15 20:24:31 +03:00
if ( command . users [ i ] . email != null ) { newuser . email = command . users [ i ] . email . toLowerCase ( ) ; if ( command . users [ i ] . emailVerified === true ) { newuser . emailVerified = true ; } } // Email, always lowercase
2019-05-15 00:39:26 +03:00
if ( command . users [ i ] . resetNextLogin === true ) { newuser . passchange = - 1 ; } else { newuser . passchange = Math . floor ( Date . now ( ) / 1000 ) ; }
2019-07-28 22:16:30 +03:00
if ( user . groups ) { newuser . groups = user . groups ; } // New accounts are automatically part of our groups (Realms).
2019-05-15 00:39:26 +03:00
if ( parent . users [ newuserid ] == null ) {
parent . users [ newuserid ] = newuser ;
// Create a user, generate a salt and hash the password
require ( './pass' ) . hash ( command . users [ i ] . pass , function ( err , salt , hash , newuser ) {
if ( err ) throw err ;
newuser . salt = salt ;
newuser . hash = hash ;
db . SetUser ( newuser ) ;
2019-05-30 00:36:14 +03:00
var event , targets = [ '*' , 'server-users' ] ;
2019-05-15 00:39:26 +03:00
if ( newuser . groups ) { for ( var i in newuser . groups ) { targets . push ( 'server-users:' + i ) ; } }
if ( newuser . email == null ) {
2019-07-30 02:35:48 +03:00
event = { etype : 'user' , userid : newuser . _id , username : newuser . name , account : parent . CloneSafeUser ( newuser ) , action : 'accountcreate' , msg : 'Account created, username is ' + newuser . name , domain : domain . id } ;
2019-05-15 00:39:26 +03:00
} else {
2019-07-30 02:35:48 +03:00
event = { etype : 'user' , userid : newuser . _id , username : newuser . name , account : parent . CloneSafeUser ( newuser ) , action : 'accountcreate' , msg : 'Account created, email is ' + newuser . email , domain : domain . id } ;
2019-05-15 00:39:26 +03:00
}
2019-05-30 00:36:14 +03:00
if ( parent . db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to create the user. Another event will come.
parent . parent . DispatchEvent ( targets , obj , event ) ;
2019-05-15 00:39:26 +03:00
} , newuser ) ;
}
}
}
} ) ;
2019-01-05 04:59:13 +03:00
break ;
}
case 'adduser' :
{
2019-07-09 01:59:44 +03:00
// If the email is the username, set this here.
if ( domain . usernameisemail ) { if ( command . email ) { command . username = command . email ; } else { command . email = command . username ; } }
2019-07-23 02:00:43 +03:00
// Randomize the password if needed
if ( command . randomPassword === true ) { command . pass = getRandomPassword ( ) ; }
2019-01-05 04:59:13 +03:00
// Add a new user account
2019-06-30 21:34:27 +03:00
var err = null , newusername , newuserid ;
try {
if ( ( user . siteadmin & 2 ) == 0 ) { err = 'Permission denied' ; }
2019-07-02 02:46:05 +03:00
else if ( ( domain . auth == 'sspi' ) || ( domain . auth == 'ldap' ) ) { err = 'Unable to add user in this mode' ; }
2019-07-09 01:59:44 +03:00
else if ( common . validateUsername ( command . username , 1 , 256 ) == false ) { err = 'Invalid username' ; } // Username is between 1 and 64 characters, no spaces
2019-06-30 21:34:27 +03:00
else if ( common . validateString ( command . pass , 1 , 256 ) == false ) { err = 'Invalid password' ; } // Password is between 1 and 256 characters
else if ( command . username . indexOf ( '/' ) >= 0 ) { err = 'Invalid username' ; } // Usernames can't have '/'
2019-07-23 02:00:43 +03:00
else if ( ( command . randomPassword !== true ) && ( common . checkPasswordRequirements ( command . pass , domain . passwordrequirements ) == false ) ) { err = 'Invalid password' ; } // Password does not meet requirements
2019-07-15 20:24:31 +03:00
else if ( ( command . email != null ) && ( common . validateEmail ( command . email , 1 , 1024 ) == false ) ) { err = 'Invalid email' ; } // Check if this is a valid email address
2019-06-30 21:34:27 +03:00
else {
newusername = command . username ;
newuserid = 'user/' + domain . id + '/' + command . username . toLowerCase ( ) ;
if ( newusername == '~' ) { err = 'Invalid username' ; } // This is a reserved user name
else if ( command . siteadmin != null ) {
if ( ( typeof command . siteadmin != 'number' ) || ( Number . isInteger ( command . siteadmin ) == false ) ) { err = 'Invalid site permissions' ; } // Check permissions
else if ( ( user . siteadmin != 0xFFFFFFFF ) && ( ( command . siteadmin & ( 0xFFFFFFFF - 224 ) ) != 0 ) ) { err = 'Invalid site permissions' ; }
}
if ( parent . users [ newuserid ] ) { err = 'User already exists' ; } // Account already exists
}
} catch ( ex ) { err = 'Validation exception' ; }
// Handle any errors
if ( err != null ) {
if ( command . responseid != null ) { try { ws . send ( JSON . stringify ( { action : 'adduser' , responseid : command . responseid , result : err } ) ) ; } catch ( ex ) { } }
break ;
}
2019-02-11 07:13:03 +03:00
// Check if we exceed the maximum number of user accounts
2019-03-10 01:28:08 +03:00
db . isMaxType ( domain . limits . maxuseraccounts , 'user' , domain . id , function ( maxExceed ) {
2019-02-11 07:13:03 +03:00
if ( maxExceed ) {
// Account count exceed, do notification
2019-06-30 21:34:27 +03:00
if ( command . responseid != null ) {
// Respond privately if requested
try { ws . send ( JSON . stringify ( { action : 'adduser' , responseid : command . responseid , result : 'maxUsersExceed' } ) ) ; } catch ( ex ) { }
} else {
// Create the notification message
var notification = { action : "msg" , type : "notify" , value : "Account limit reached." , title : "Server Limit" , userid : user . _id , username : user . name , domain : domain . id } ;
2019-02-11 07:13:03 +03:00
2019-06-30 21:34:27 +03:00
// Get the list of sessions for this user
var sessions = parent . wssessions [ user . _id ] ;
if ( sessions != null ) { for ( i in sessions ) { try { if ( sessions [ i ] . domainid == domain . id ) { sessions [ i ] . send ( JSON . stringify ( notification ) ) ; } } catch ( ex ) { } } }
// TODO: Notify all sessions on other peers.
}
2019-02-11 07:13:03 +03:00
} else {
2019-06-30 21:34:27 +03:00
// Create a new user
2019-02-11 07:13:03 +03:00
var newuser = { type : 'user' , _id : newuserid , name : newusername , creation : Math . floor ( Date . now ( ) / 1000 ) , domain : domain . id } ;
2019-06-30 21:34:27 +03:00
if ( command . siteadmin != null ) { newuser . siteadmin = command . siteadmin ; }
else if ( domain . newaccountsrights ) { newuser . siteadmin = domain . newaccountsrights ; }
2019-07-15 20:24:31 +03:00
if ( command . email != null ) { newuser . email = command . email . toLowerCase ( ) ; if ( command . emailVerified === true ) { newuser . emailVerified = true ; } } // Email
2019-03-01 03:17:22 +03:00
if ( command . resetNextLogin === true ) { newuser . passchange = - 1 ; } else { newuser . passchange = Math . floor ( Date . now ( ) / 1000 ) ; }
2019-07-28 22:16:30 +03:00
if ( user . groups ) { newuser . groups = user . groups ; } // New accounts are automatically part of our groups (Realms).
2019-10-25 11:16:00 +03:00
2019-03-10 01:28:08 +03:00
parent . users [ newuserid ] = newuser ;
2019-03-01 03:17:22 +03:00
2019-02-11 07:13:03 +03:00
// Create a user, generate a salt and hash the password
2019-05-15 00:39:26 +03:00
require ( './pass' ) . hash ( command . pass , function ( err , salt , hash , tag ) {
2019-06-30 21:34:27 +03:00
if ( err == null ) {
newuser . salt = salt ;
newuser . hash = hash ;
db . SetUser ( newuser ) ;
var event , targets = [ '*' , 'server-users' ] ;
if ( newuser . groups ) { for ( var i in newuser . groups ) { targets . push ( 'server-users:' + i ) ; } }
if ( command . email == null ) {
2019-07-30 02:35:48 +03:00
event = { etype : 'user' , userid : newuser . _id , username : newusername , account : parent . CloneSafeUser ( newuser ) , action : 'accountcreate' , msg : 'Account created, username is ' + command . username , domain : domain . id } ;
2019-06-30 21:34:27 +03:00
} else {
2019-07-30 02:35:48 +03:00
event = { etype : 'user' , userid : newuser . _id , username : newusername , account : parent . CloneSafeUser ( newuser ) , action : 'accountcreate' , msg : 'Account created, email is ' + command . email . toLowerCase ( ) , domain : domain . id } ;
2019-06-30 21:34:27 +03:00
}
if ( parent . db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to create the user. Another event will come.
parent . parent . DispatchEvent ( targets , obj , event ) ;
2019-07-23 02:00:43 +03:00
// Perform email invitation
if ( ( command . emailInvitation == true ) && ( command . emailVerified == true ) && command . email && parent . parent . mailserver ) {
parent . parent . mailserver . sendAccountInviteMail ( domain , user . name , newusername , command . email . toLowerCase ( ) , command . pass ) ;
}
2019-06-30 21:34:27 +03:00
// OK Response
if ( command . responseid != null ) { try { ws . send ( JSON . stringify ( { action : 'adduser' , responseid : command . responseid , result : 'ok' } ) ) ; } catch ( ex ) { } }
2019-05-15 00:39:26 +03:00
} else {
2019-06-30 21:34:27 +03:00
if ( command . responseid != null ) { try { ws . send ( JSON . stringify ( { action : 'adduser' , responseid : command . responseid , result : 'passwordHashError' } ) ) ; } catch ( ex ) { } }
2019-05-15 00:39:26 +03:00
}
} , 0 ) ;
2019-02-11 07:13:03 +03:00
}
} ) ;
2019-01-05 04:59:13 +03:00
break ;
}
case 'edituser' :
{
// Edit a user account, may involve changing email or administrator permissions
2019-04-16 01:05:09 +03:00
if ( ( ( user . siteadmin & 2 ) != 0 ) || ( user . _id == command . id ) ) {
var chguser = parent . users [ command . id ] ;
2019-01-05 04:59:13 +03:00
change = 0 ;
if ( chguser ) {
2019-05-04 22:55:46 +03:00
// If the target user is admin and we are not admin, no changes can be made.
if ( ( chguser . siteadmin == 0xFFFFFFFF ) && ( user . siteadmin != 0xFFFFFFFF ) ) return ;
// Can only perform this operation on other users of our group.
if ( user . siteadmin != 0xFFFFFFFF ) {
if ( ( user . groups != null ) && ( user . groups . length > 0 ) && ( ( chguser . groups == null ) || ( findOne ( chguser . groups , user . groups ) == false ) ) ) return ;
}
2019-07-09 01:59:44 +03:00
// Validate and change email
if ( domain . usernameisemail !== true ) {
2019-07-15 20:24:31 +03:00
if ( common . validateString ( command . email , 1 , 1024 ) && ( chguser . email != command . email ) ) { chguser . email = command . email . toLowerCase ( ) ; change = 1 ; }
2019-07-09 01:59:44 +03:00
}
2019-05-04 22:55:46 +03:00
// Make changes
2019-01-05 04:59:13 +03:00
if ( ( command . emailVerified === true || command . emailVerified === false ) && ( chguser . emailVerified != command . emailVerified ) ) { chguser . emailVerified = command . emailVerified ; change = 1 ; }
2019-03-10 01:28:08 +03:00
if ( ( common . validateInt ( command . quota , 0 ) || command . quota == null ) && ( command . quota != chguser . quota ) ) { chguser . quota = command . quota ; if ( chguser . quota == null ) { delete chguser . quota ; } change = 1 ; }
2019-05-04 22:55:46 +03:00
// Site admins can change any server rights, user managers can only change AccountLock, NoMeshCmd and NoNewGroups
var chgusersiteadmin = chguser . siteadmin ? chguser . siteadmin : 0 ;
if ( ( ( user . siteadmin == 0xFFFFFFFF ) || ( ( user . siteadmin & 2 ) && ( ( ( chgusersiteadmin ^ command . siteadmin ) & 0xFFFFFF1F ) == 0 ) ) ) && common . validateInt ( command . siteadmin ) && ( chguser . siteadmin != command . siteadmin ) ) { chguser . siteadmin = command . siteadmin ; change = 1 ; }
2019-04-13 03:56:49 +03:00
2019-04-17 03:32:18 +03:00
// Went sending a notification about a group change, we need to send to all the previous and new groups.
var allTargetGroups = chguser . groups ;
2019-05-04 22:55:46 +03:00
if ( ( Array . isArray ( command . groups ) ) && ( ( user . _id != command . id ) || ( user . siteadmin == 0xFFFFFFFF ) ) ) {
2019-04-13 03:56:49 +03:00
if ( command . groups . length == 0 ) {
2019-04-16 01:05:09 +03:00
// Remove the user groups
2019-04-13 03:56:49 +03:00
if ( chguser . groups != null ) { delete chguser . groups ; change = 1 ; }
} else {
2019-04-16 01:05:09 +03:00
// Arrange the user groups
var groups2 = [ ] ;
for ( var i in command . groups ) {
if ( typeof command . groups [ i ] == 'string' ) {
var gname = command . groups [ i ] . trim ( ) . toLowerCase ( ) ;
if ( ( gname . length > 0 ) && ( gname . length <= 64 ) && ( groups2 . indexOf ( gname ) == - 1 ) ) { groups2 . push ( gname ) ; }
}
}
groups2 . sort ( ) ;
2019-07-28 22:16:30 +03:00
// Set the user groups (Realms)
2019-04-16 01:05:09 +03:00
if ( chguser . groups != groups2 ) { chguser . groups = groups2 ; change = 1 ; }
2019-04-17 03:32:18 +03:00
// Add any missing groups in the target list
if ( allTargetGroups == null ) { allTargetGroups = [ ] ; }
for ( var i in groups2 ) { if ( allTargetGroups . indexOf ( i ) == - 1 ) { allTargetGroups . push ( i ) ; } }
2019-04-13 03:56:49 +03:00
}
}
2019-01-05 04:59:13 +03:00
if ( change == 1 ) {
2019-04-16 01:05:09 +03:00
// Update the user
2019-03-10 01:28:08 +03:00
db . SetUser ( chguser ) ;
parent . parent . DispatchEvent ( [ chguser . _id ] , obj , 'resubscribe' ) ;
2019-04-17 03:32:18 +03:00
var targets = [ '*' , 'server-users' , user . _id , chguser . _id ] ;
if ( allTargetGroups ) { for ( var i in allTargetGroups ) { targets . push ( 'server-users:' + i ) ; } }
2019-07-30 02:35:48 +03:00
var event = { etype : 'user' , userid : user . _id , username : user . name , account : parent . CloneSafeUser ( chguser ) , action : 'accountchange' , msg : 'Account changed: ' + chguser . name , domain : domain . id } ;
2019-05-29 03:25:23 +03:00
if ( db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the user. Another event will come.
parent . parent . DispatchEvent ( targets , obj , event ) ;
2019-01-05 04:59:13 +03:00
}
if ( ( chguser . siteadmin ) && ( chguser . siteadmin != 0xFFFFFFFF ) && ( chguser . siteadmin & 32 ) ) {
2019-04-16 01:05:09 +03:00
// If the user is locked out of this account, disconnect now
2019-03-10 01:28:08 +03:00
parent . parent . DispatchEvent ( [ chguser . _id ] , obj , 'close' ) ; // Disconnect all this user's sessions
2017-10-24 00:09:58 +03:00
}
}
}
2019-07-22 21:25:18 +03:00
break ;
}
case 'changemeshnotify' :
{
var err = null ;
try {
// Change the current user's notification flags for a meshid
if ( common . validateString ( command . meshid , 1 , 1024 ) == false ) { err = 'Invalid group identifier' ; } // Check the meshid
else if ( command . meshid . indexOf ( '/' ) == - 1 ) { command . meshid = 'mesh/' + domain . id + '/' + command . meshid ; }
if ( common . validateInt ( command . notify ) == false ) { err = 'Invalid notification flags' ; }
if ( ( user . links == null ) || ( user . links [ command . meshid ] == null ) ) { err = 'Incorrect group identifier' ; }
} catch ( ex ) { err = 'Validation exception: ' + ex ; }
// Handle any errors
if ( err != null ) { if ( command . responseid != null ) { try { ws . send ( JSON . stringify ( { action : 'changemeshnotify' , responseid : command . responseid , result : err } ) ) ; } catch ( ex ) { } } break ; }
// Change the notification
if ( command . notify == 0 ) {
delete user . links [ command . meshid ] . notify ;
} else {
user . links [ command . meshid ] . notify = command . notify ;
}
// Save the user
parent . db . SetUser ( user ) ;
// Notify change
var targets = [ '*' , 'server-users' , user . _id ] ;
if ( user . groups ) { for ( var i in user . groups ) { targets . push ( 'server-users:' + i ) ; } }
2019-07-30 02:35:48 +03:00
var event = { etype : 'user' , userid : user . _id , username : user . name , account : parent . CloneSafeUser ( user ) , action : 'accountchange' , msg : 'Mesh notification change.' , domain : domain . id } ;
2019-07-22 21:25:18 +03:00
if ( db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the user. Another event will come.
parent . parent . DispatchEvent ( targets , obj , event ) ;
2019-01-05 04:59:13 +03:00
break ;
}
2019-02-13 06:23:40 +03:00
case 'changepassword' :
{
// Change our own password
2019-03-10 01:28:08 +03:00
if ( common . validateString ( command . oldpass , 1 , 256 ) == false ) break ;
if ( common . validateString ( command . newpass , 1 , 256 ) == false ) break ;
if ( ( command . hint != null ) && ( common . validateString ( command . hint , 0 , 256 ) == false ) ) break ;
if ( common . checkPasswordRequirements ( command . newpass , domain . passwordrequirements ) == false ) break ; // Password does not meet requirements
2019-02-13 06:23:40 +03:00
// Start by checking the old password
2019-03-10 01:28:08 +03:00
parent . checkUserPassword ( domain , user , command . oldpass , function ( result ) {
2019-02-13 06:23:40 +03:00
if ( result == true ) {
// Update the password
2019-05-15 00:39:26 +03:00
require ( './pass' ) . hash ( command . newpass , function ( err , salt , hash , tag ) {
2019-02-13 06:23:40 +03:00
if ( err ) {
// Send user notification of error
2019-04-30 01:31:11 +03:00
displayNotificationMessage ( 'Error, password not changed.' , 'Account Settings' , 'ServerNotify' ) ;
2019-02-13 06:23:40 +03:00
} else {
// Change the password
2019-03-01 03:17:22 +03:00
if ( ( domain . passwordrequirements != null ) && ( domain . passwordrequirements . hint === true ) && ( command . hint != null ) ) {
var hint = command . hint ;
if ( hint . length > 250 ) { hint = hint . substring ( 0 , 250 ) ; }
user . passhint = hint ;
}
2019-02-13 06:23:40 +03:00
user . salt = salt ;
user . hash = hash ;
user . passchange = Math . floor ( Date . now ( ) / 1000 ) ;
delete user . passtype ;
2019-03-10 01:28:08 +03:00
db . SetUser ( user ) ;
2019-04-17 03:32:18 +03:00
var targets = [ '*' , 'server-users' ] ;
if ( user . groups ) { for ( var i in user . groups ) { targets . push ( 'server-users:' + i ) ; } }
2019-07-30 02:35:48 +03:00
var event = { etype : 'user' , userid : user . _id , username : user . name , account : parent . CloneSafeUser ( user ) , action : 'accountchange' , msg : 'Account password changed: ' + user . name , domain : domain . id } ;
2019-05-29 03:25:23 +03:00
if ( db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the user. Another event will come.
parent . parent . DispatchEvent ( targets , obj , event ) ;
2019-02-13 06:23:40 +03:00
// Send user notification of password change
2019-04-30 01:31:11 +03:00
displayNotificationMessage ( 'Password changed.' , 'Account Settings' , 'ServerNotify' ) ;
2019-02-13 06:23:40 +03:00
}
2019-05-15 00:39:26 +03:00
} , 0 ) ;
2019-02-13 06:23:40 +03:00
} else {
// Send user notification of error
2019-04-30 01:31:11 +03:00
displayNotificationMessage ( 'Current password not correct.' , 'Account Settings' , 'ServerNotify' ) ;
2019-02-13 06:23:40 +03:00
}
} ) ;
break ;
}
2019-01-05 04:59:13 +03:00
case 'changeuserpass' :
{
// Change a user's password
if ( user . siteadmin != 0xFFFFFFFF ) break ;
2019-04-17 03:32:18 +03:00
if ( common . validateString ( command . userid , 1 , 256 ) == false ) break ;
2019-05-22 00:19:32 +03:00
if ( common . validateString ( command . pass , 0 , 256 ) == false ) break ;
2019-03-10 01:28:08 +03:00
if ( ( command . hint != null ) && ( common . validateString ( command . hint , 0 , 256 ) == false ) ) break ;
2019-02-11 07:13:03 +03:00
if ( typeof command . removeMultiFactor != 'boolean' ) break ;
2019-05-22 00:19:32 +03:00
if ( ( command . pass != '' ) && ( common . checkPasswordRequirements ( command . pass , domain . passwordrequirements ) == false ) ) break ; // Password does not meet requirements
2019-02-11 07:13:03 +03:00
2019-04-17 03:32:18 +03:00
var chguser = parent . users [ command . userid ] ;
2019-02-13 06:23:40 +03:00
if ( chguser ) {
2019-04-17 03:32:18 +03:00
// Can only perform this operation on other users of our group.
if ( ( user . groups != null ) && ( user . groups . length > 0 ) && ( ( chguser . groups == null ) || ( findOne ( chguser . groups , user . groups ) == false ) ) ) break ;
2019-01-05 04:59:13 +03:00
// Compute the password hash & save it
2019-05-15 00:39:26 +03:00
require ( './pass' ) . hash ( command . pass , function ( err , salt , hash , tag ) {
2019-02-11 07:13:03 +03:00
if ( ! err ) {
2019-05-22 00:19:32 +03:00
if ( command . pass != '' ) { chguser . salt = salt ; chguser . hash = hash ; }
2019-03-01 03:17:22 +03:00
if ( ( domain . passwordrequirements != null ) && ( domain . passwordrequirements . hint === true ) && ( command . hint != null ) ) {
var hint = command . hint ;
if ( hint . length > 250 ) { hint = hint . substring ( 0 , 250 ) ; }
chguser . passhint = hint ;
}
if ( command . resetNextLogin === true ) { chguser . passchange = - 1 ; } else { chguser . passchange = Math . floor ( Date . now ( ) / 1000 ) ; }
2019-02-13 06:23:40 +03:00
delete chguser . passtype ; // Remove the password type if one was present.
2019-02-11 07:13:03 +03:00
if ( command . removeMultiFactor == true ) {
2019-02-28 05:48:50 +03:00
if ( chguser . otpsecret ) { delete chguser . otpsecret ; }
if ( chguser . otphkeys ) { delete chguser . otphkeys ; }
if ( chguser . otpkeys ) { delete chguser . otpkeys ; }
2019-02-11 07:13:03 +03:00
}
2019-03-10 01:28:08 +03:00
db . SetUser ( chguser ) ;
2019-04-17 03:32:18 +03:00
var targets = [ '*' , 'server-users' , user . _id , chguser . _id ] ;
if ( chguser . groups ) { for ( var i in chguser . groups ) { targets . push ( 'server-users:' + i ) ; } }
2019-07-30 02:35:48 +03:00
var event = { etype : 'user' , userid : user . _id , username : user . name , account : parent . CloneSafeUser ( chguser ) , action : 'accountchange' , msg : 'Changed account credentials.' , domain : domain . id } ;
2019-05-29 03:25:23 +03:00
if ( db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the user. Another event will come.
parent . parent . DispatchEvent ( targets , obj , event ) ;
2019-02-13 06:23:40 +03:00
} else {
// Report that the password change failed
// TODO
2019-02-11 07:13:03 +03:00
}
2019-05-15 00:39:26 +03:00
} , 0 ) ;
2018-04-16 08:22:44 +03:00
}
2019-01-05 04:59:13 +03:00
break ;
}
case 'notifyuser' :
{
// Send a notification message to a user
if ( ( user . siteadmin & 2 ) == 0 ) break ;
2019-03-10 01:28:08 +03:00
if ( common . validateString ( command . userid , 1 , 2048 ) == false ) break ;
if ( common . validateString ( command . msg , 1 , 4096 ) == false ) break ;
2019-01-05 04:59:13 +03:00
2019-04-17 03:32:18 +03:00
// Can only perform this operation on other users of our group.
var chguser = parent . users [ command . userid ] ;
if ( chguser == null ) break ; // This user does not exists
if ( ( user . groups != null ) && ( user . groups . length > 0 ) && ( ( chguser . groups == null ) || ( findOne ( chguser . groups , user . groups ) == false ) ) ) break ;
2019-01-05 04:59:13 +03:00
// Create the notification message
2019-04-30 04:48:51 +03:00
var notification = { action : "msg" , type : "notify" , value : command . msg , title : user . name , icon : 8 , userid : user . _id , username : user . name } ;
2019-01-05 04:59:13 +03:00
// Get the list of sessions for this user
2019-03-10 01:28:08 +03:00
var sessions = parent . wssessions [ command . userid ] ;
2019-01-05 04:59:13 +03:00
if ( sessions != null ) { for ( i in sessions ) { try { sessions [ i ] . send ( JSON . stringify ( notification ) ) ; } catch ( ex ) { } } }
2019-03-10 01:28:08 +03:00
if ( parent . parent . multiServer != null ) {
2019-01-05 04:59:13 +03:00
// TODO: Add multi-server support
}
break ;
}
case 'meshmessenger' :
{
// Setup a user-to-user session
2019-03-10 01:28:08 +03:00
if ( common . validateString ( command . userid , 1 , 2048 ) ) {
2019-10-16 02:26:24 +03:00
// Send a notification message to a user
if ( ( user . siteadmin & 2 ) == 0 ) break ;
2018-04-06 02:45:56 +03:00
2019-04-17 03:32:18 +03:00
// Can only perform this operation on other users of our group.
var chguser = parent . users [ command . userid ] ;
if ( chguser == null ) break ; // This user does not exists
if ( ( user . groups != null ) && ( user . groups . length > 0 ) && ( ( chguser . groups == null ) || ( findOne ( chguser . groups , user . groups ) == false ) ) ) break ;
2018-04-06 02:45:56 +03:00
// Create the notification message
2019-01-05 04:59:13 +03:00
var notification = {
2019-04-30 01:31:11 +03:00
"action" : "msg" , "type" : "notify" , "value" : "Chat Request, Click here to accept." , "title" : user . name , "userid" : user . _id , "username" : user . name , "tag" : 'meshmessenger/' + encodeURIComponent ( command . userid ) + '/' + encodeURIComponent ( user . _id )
2019-01-05 04:59:13 +03:00
} ;
2018-04-06 02:45:56 +03:00
// Get the list of sessions for this user
2019-03-10 01:28:08 +03:00
var sessions = parent . wssessions [ command . userid ] ;
2018-10-11 23:29:50 +03:00
if ( sessions != null ) { for ( i in sessions ) { try { sessions [ i ] . send ( JSON . stringify ( notification ) ) ; } catch ( ex ) { } } }
2018-04-06 02:45:56 +03:00
2019-03-10 01:28:08 +03:00
if ( parent . parent . multiServer != null ) {
2018-12-02 10:41:57 +03:00
// TODO: Add multi-server support
}
}
2019-01-05 04:59:13 +03:00
// Setup a user-to-node session
2019-03-10 01:28:08 +03:00
if ( common . validateString ( command . nodeid , 1 , 2048 ) ) {
if ( args . lanonly == true ) { return ; } // User-to-device chat is not support in LAN-only mode yet. We need the agent to replace the IP address of the server??
2018-12-02 10:41:57 +03:00
2019-10-16 02:26:24 +03:00
// Get the device
db . Get ( command . nodeid , function ( err , nodes ) {
if ( ( nodes == null ) || ( nodes . length != 1 ) ) return ;
var node = nodes [ 0 ] ;
2018-12-02 10:41:57 +03:00
2019-10-16 02:26:24 +03:00
// Get the mesh for this device
mesh = parent . meshes [ node . meshid ] ;
if ( mesh ) {
// Check if this user has rights to do this
2019-10-16 22:57:29 +03:00
if ( mesh . links [ user . _id ] == null || ( ( mesh . links [ user . _id ] . rights & MESHRIGHT _CHATNOTIFY ) == 0 ) ) return ;
2019-10-16 02:26:24 +03:00
// Create the server url
var httpsPort = ( ( args . aliasport == null ) ? args . port : args . aliasport ) ; // Use HTTPS alias port is specified
var xdomain = ( domain . dns == null ) ? domain . id : '' ;
if ( xdomain != '' ) xdomain += "/" ;
var url = "http" + ( args . notls ? '' : 's' ) + "://" + parent . getWebServerName ( domain ) + ":" + httpsPort + "/" + xdomain + "messenger?id=meshmessenger/" + encodeURIComponent ( command . nodeid ) + "/" + encodeURIComponent ( user . _id ) + "&title=" + encodeURIComponent ( user . name ) ;
// Create the notification message
routeCommandToNode ( { "action" : "openUrl" , "nodeid" : command . nodeid , "userid" : user . _id , "username" : user . name , "url" : url } ) ;
}
} ) ;
2019-01-05 04:59:13 +03:00
}
break ;
}
case 'serverversion' :
{
// Check the server version
if ( ( user . siteadmin & 16 ) == 0 ) break ;
2019-03-10 01:28:08 +03:00
parent . parent . getLatestServerVersion ( function ( currentVersion , latestVersion ) { try { ws . send ( JSON . stringify ( { action : 'serverversion' , current : currentVersion , latest : latestVersion } ) ) ; } catch ( ex ) { } } ) ;
2019-01-05 04:59:13 +03:00
break ;
}
case 'serverupdate' :
{
// Perform server update
if ( ( user . siteadmin & 16 ) == 0 ) break ;
2019-03-10 01:28:08 +03:00
parent . parent . performServerUpdate ( ) ;
2019-01-05 04:59:13 +03:00
break ;
}
case 'servererrors' :
{
// Load the server error log
if ( ( user . siteadmin & 16 ) == 0 ) break ;
2019-03-10 01:28:08 +03:00
fs . readFile ( parent . parent . getConfigFilePath ( 'mesherrors.txt' ) , 'utf8' , function ( err , data ) { try { ws . send ( JSON . stringify ( { action : 'servererrors' , data : data } ) ) ; } catch ( ex ) { } } ) ;
2019-01-05 04:59:13 +03:00
break ;
}
case 'serverclearerrorlog' :
{
// Clear the server error log
if ( ( user . siteadmin & 16 ) == 0 ) break ;
2019-03-10 01:28:08 +03:00
fs . unlink ( parent . parent . getConfigFilePath ( 'mesherrors.txt' ) , function ( err ) { } ) ;
2019-01-05 04:59:13 +03:00
break ;
}
case 'createmesh' :
{
2019-06-30 21:34:27 +03:00
var err = null ;
try {
// Check if we have new group restriction
if ( ( user . siteadmin != 0xFFFFFFFF ) && ( ( user . siteadmin & 64 ) != 0 ) ) { err = 'Permission denied' ; }
// In some situations, we need a verified email address to create a device group.
else if ( ( parent . parent . mailserver != null ) && ( domain . auth != 'sspi' ) && ( domain . auth != 'ldap' ) && ( user . emailVerified !== true ) && ( user . siteadmin != 0xFFFFFFFF ) ) { err = 'Email verification required' ; } // User must verify it's email first.
// Create mesh
else if ( common . validateString ( command . meshname , 1 , 64 ) == false ) { err = 'Invalid group name' ; } // Meshname is between 1 and 64 characters
else if ( ( command . desc != null ) && ( common . validateString ( command . desc , 0 , 1024 ) == false ) ) { err = 'Invalid group description' ; } // Mesh description is between 0 and 1024 characters
else if ( ( command . meshtype != 1 ) && ( command . meshtype != 2 ) ) { err = 'Invalid group type' ; }
} catch ( ex ) { err = 'Validation exception: ' + ex ; }
// Handle any errors
if ( err != null ) {
if ( command . responseid != null ) { try { ws . send ( JSON . stringify ( { action : 'createmesh' , responseid : command . responseid , result : err } ) ) ; } catch ( ex ) { } }
break ;
}
2019-01-05 04:59:13 +03:00
// We only create Agent-less Intel AMT mesh (Type1), or Agent mesh (Type2)
2019-06-30 21:34:27 +03:00
parent . crypto . randomBytes ( 48 , function ( err , buf ) {
// Create new device group identifier
meshid = 'mesh/' + domain . id + '/' + buf . toString ( 'base64' ) . replace ( /\+/g , '@' ) . replace ( /\//g , '$' ) ;
// Create the new device group
var links = { } ;
links [ user . _id ] = { name : user . name , rights : 4294967295 } ;
mesh = { type : 'mesh' , _id : meshid , name : command . meshname , mtype : command . meshtype , desc : command . desc , domain : domain . id , links : links } ;
db . Set ( common . escapeLinksFieldName ( mesh ) ) ;
parent . meshes [ meshid ] = mesh ;
parent . parent . AddEventDispatch ( [ meshid ] , ws ) ;
// Change the user to make him administration of the new device group
if ( user . links == null ) user . links = { } ;
user . links [ meshid ] = { rights : 4294967295 } ;
user . subscriptions = parent . subscribe ( user . _id , ws ) ;
db . SetUser ( user ) ;
// Event the user change
var targets = [ '*' , 'server-users' , user . _id ] ;
if ( user . groups ) { for ( var i in user . groups ) { targets . push ( 'server-users:' + i ) ; } }
2019-07-30 02:35:48 +03:00
var event = { etype : 'user' , userid : user . _id , username : user . name , account : parent . CloneSafeUser ( user ) , action : 'accountchange' , domain : domain . id , nolog : 1 } ;
2019-06-30 21:34:27 +03:00
if ( db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the user. Another event will come.
parent . parent . DispatchEvent ( targets , obj , event ) ;
// Event the device group creation
2019-10-12 01:14:38 +03:00
var event = { etype : 'mesh' , userid : user . _id , username : user . name , meshid : meshid , name : command . meshname , mtype : command . meshtype , desc : command . desc , action : 'createmesh' , links : links , msg : 'Device group created: ' + command . meshname , domain : domain . id } ;
2019-06-30 21:34:27 +03:00
parent . parent . DispatchEvent ( [ '*' , meshid , user . _id ] , obj , event ) ; // Even if DB change stream is active, this event must be acted upon.
2019-10-12 01:39:15 +03:00
try { ws . send ( JSON . stringify ( { action : 'createmesh' , responseid : command . responseid , result : 'ok' , meshid : meshid , links : links } ) ) ; } catch ( ex ) { }
2019-06-30 21:34:27 +03:00
} ) ;
2019-01-05 04:59:13 +03:00
break ;
}
case 'deletemesh' :
{
2019-06-30 21:34:27 +03:00
var err = null ;
try {
// Delete a mesh and all computers within it
if ( common . validateString ( command . meshid , 1 , 1024 ) == false ) { err = 'Invalid group identifier' ; } // Check the meshid
2019-07-05 23:46:14 +03:00
else if ( command . meshid . indexOf ( '/' ) == - 1 ) { command . meshid = 'mesh/' + domain . id + '/' + command . meshid ; }
2019-06-30 21:34:27 +03:00
} catch ( ex ) { err = 'Validation exception: ' + ex ; }
// Handle any errors
2019-07-11 00:27:38 +03:00
if ( err != null ) { if ( command . responseid != null ) { try { ws . send ( JSON . stringify ( { action : 'deletemesh' , responseid : command . responseid , result : err } ) ) ; } catch ( ex ) { } } break ; }
2019-06-30 21:34:27 +03:00
2019-07-11 00:27:38 +03:00
// Get the device group reference we are going to delete
var mesh = parent . meshes [ command . meshid ] ;
if ( mesh == null ) { if ( command . responseid != null ) { try { ws . send ( JSON . stringify ( { action : 'deletemesh' , responseid : command . responseid , result : 'Unknown device group' } ) ) ; } catch ( ex ) { } } return ; }
2019-01-05 04:59:13 +03:00
2019-07-11 00:27:38 +03:00
// Check if this user has rights to do this
var err = null ;
if ( mesh . links [ user . _id ] == null || mesh . links [ user . _id ] . rights != 0xFFFFFFFF ) { err = 'Access denied' ; }
if ( ( command . meshid . split ( '/' ) . length != 3 ) || ( command . meshid . split ( '/' ) [ 1 ] != domain . id ) ) { err = 'Invalid group' ; } // Invalid domain, operation only valid for current domain
2018-12-08 03:36:27 +03:00
2019-07-11 00:27:38 +03:00
// Handle any errors
if ( err != null ) { if ( command . responseid != null ) { try { ws . send ( JSON . stringify ( { action : 'deletemesh' , responseid : command . responseid , result : err } ) ) ; } catch ( ex ) { } } return ; }
// Fire the removal event first, because after this, the event will not route
2019-07-30 04:21:52 +03:00
var event = { etype : 'mesh' , userid : user . _id , username : user . name , meshid : command . meshid , name : command . meshname , action : 'deletemesh' , msg : 'Device group deleted: ' + command . meshname , domain : domain . id } ;
2019-07-11 00:27:38 +03:00
parent . parent . DispatchEvent ( [ '*' , command . meshid ] , obj , event ) ; // Even if DB change stream is active, this event need to be acted on.
// Remove all user links to this mesh
for ( var j in mesh . links ) {
var xuser = parent . users [ j ] ;
if ( xuser && xuser . links ) {
delete xuser . links [ mesh . _id ] ;
db . SetUser ( xuser ) ;
parent . parent . DispatchEvent ( [ xuser . _id ] , obj , 'resubscribe' ) ;
2018-12-08 03:36:27 +03:00
}
2019-07-11 00:27:38 +03:00
}
2018-12-08 03:36:27 +03:00
2019-07-11 00:27:38 +03:00
// Delete all files on the server for this mesh
try {
var meshpath = parent . getServerRootFilePath ( mesh ) ;
if ( meshpath != null ) { parent . deleteFolderRec ( meshpath ) ; }
} catch ( e ) { }
2018-12-08 03:36:27 +03:00
2019-07-11 00:27:38 +03:00
parent . parent . RemoveEventDispatchId ( command . meshid ) ; // Remove all subscriptions to this mesh
2019-02-19 01:32:55 +03:00
2019-07-11 00:27:38 +03:00
// Mark the mesh as deleted
mesh . deleted = new Date ( ) ; // Mark the time this mesh was deleted, we can expire it at some point.
db . Set ( common . escapeLinksFieldName ( mesh ) ) ; // We don't really delete meshes because if a device connects to is again, we will un-delete it.
2019-02-19 01:32:55 +03:00
2019-07-11 00:27:38 +03:00
// Delete all devices attached to this mesh in the database
db . RemoveMeshDocuments ( command . meshid ) ;
2019-06-30 21:34:27 +03:00
2019-07-11 00:27:38 +03:00
if ( command . responseid != null ) { try { ws . send ( JSON . stringify ( { action : 'deletemesh' , responseid : command . responseid , result : 'ok' } ) ) ; } catch ( ex ) { } }
2019-01-05 04:59:13 +03:00
break ;
}
case 'editmesh' :
{
// Change the name or description of a mesh
2019-03-10 01:28:08 +03:00
if ( common . validateString ( command . meshid , 1 , 1024 ) == false ) break ; // Check the meshid
mesh = parent . meshes [ command . meshid ] ;
2019-01-05 04:59:13 +03:00
change = '' ;
2019-04-13 00:19:03 +03:00
2019-01-05 04:59:13 +03:00
if ( mesh ) {
// Check if this user has rights to do this
if ( mesh . links [ user . _id ] == null || ( ( mesh . links [ user . _id ] . rights & 1 ) == 0 ) ) return ;
if ( ( command . meshid . split ( '/' ) . length != 3 ) || ( command . meshid . split ( '/' ) [ 1 ] != domain . id ) ) return ; // Invalid domain, operation only valid for current domain
2018-12-08 03:36:27 +03:00
2019-03-10 01:28:08 +03:00
if ( ( common . validateString ( command . meshname , 1 , 64 ) == true ) && ( command . meshname != mesh . name ) ) { change = 'Group name changed from "' + mesh . name + '" to "' + command . meshname + '"' ; mesh . name = command . meshname ; }
if ( ( common . validateString ( command . desc , 0 , 1024 ) == true ) && ( command . desc != mesh . desc ) ) { if ( change != '' ) change += ' and description changed' ; else change += 'Group "' + mesh . name + '" description changed' ; mesh . desc = command . desc ; }
if ( ( common . validateInt ( command . flags ) == true ) && ( command . flags != mesh . flags ) ) { if ( change != '' ) change += ' and flags changed' ; else change += 'Group "' + mesh . name + '" flags changed' ; mesh . flags = command . flags ; }
2019-04-13 00:19:03 +03:00
if ( ( common . validateInt ( command . consent ) == true ) && ( command . consent != mesh . consent ) ) { if ( change != '' ) change += ' and consent changed' ; else change += 'Group "' + mesh . name + '" consent changed' ; mesh . consent = command . consent ; }
2019-05-29 03:25:23 +03:00
if ( change != '' ) {
db . Set ( common . escapeLinksFieldName ( mesh ) ) ;
2019-10-12 01:14:38 +03:00
var event = { etype : 'mesh' , userid : user . _id , username : user . name , meshid : mesh . _id , name : mesh . name , mtype : mesh . mtype , desc : mesh . desc , flags : mesh . flags , consent : mesh . consent , action : 'meshchange' , links : mesh . links , msg : change , domain : domain . id } ;
2019-05-29 03:25:23 +03:00
if ( db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the mesh. Another event will come.
parent . parent . DispatchEvent ( [ '*' , mesh . _id , user . _id ] , obj , event ) ;
}
2018-09-19 05:41:59 +03:00
}
2019-01-05 04:59:13 +03:00
break ;
}
case 'addmeshuser' :
{
2019-07-02 00:44:26 +03:00
var err = null ;
try {
if ( common . validateString ( command . meshid , 1 , 1024 ) == false ) { err = 'Invalid groupid' ; } // Check the meshid
else if ( common . validateInt ( command . meshadmin ) == false ) { err = 'Invalid group rights' ; } // Mesh rights must be an integer
else if ( common . validateStrArray ( command . usernames , 1 , 64 ) == false ) { err = 'Invalid usernames' ; } // Username is between 1 and 64 characters
else {
if ( command . meshid . indexOf ( '/' ) == - 1 ) { command . meshid = 'mesh/' + domain . id + '/' + command . meshid ; }
mesh = parent . meshes [ command . meshid ] ;
if ( mesh == null ) { err = 'Unknown group' ; }
else if ( mesh . links [ user . _id ] == null || ( ( mesh . links [ user . _id ] . rights & 2 ) == 0 ) ) { err = 'Permission denied' ; }
else if ( ( command . meshid . split ( '/' ) . length != 3 ) || ( command . meshid . split ( '/' ) [ 1 ] != domain . id ) ) { err = 'Invalid domain' ; } // Invalid domain, operation only valid for current domain
}
} catch ( ex ) { err = 'Validation exception: ' + ex ; }
2017-10-24 00:09:58 +03:00
2019-07-02 00:44:26 +03:00
// Handle any errors
if ( err != null ) {
if ( command . responseid != null ) { try { ws . send ( JSON . stringify ( { action : 'addmeshuser' , responseid : command . responseid , result : err } ) ) ; } catch ( ex ) { } }
break ;
}
2017-10-24 00:09:58 +03:00
2019-07-02 00:44:26 +03:00
var unknownUsers = [ ] , removedCount = 0 , failCount = 0 ;
for ( var i in command . usernames ) {
// Check if the user exists
var newuserid = 'user/' + domain . id + '/' + command . usernames [ i ] . toLowerCase ( ) , newuser = parent . users [ newuserid ] ;
2019-10-26 20:57:40 +03:00
if ( newuserid == obj . user . _id ) { continue ; } // Can't add or modify self
2019-07-02 00:44:26 +03:00
if ( newuser != null ) {
// Add mesh to user
if ( newuser . links == null ) newuser . links = { } ;
2019-07-23 23:37:12 +03:00
if ( newuser . links [ command . meshid ] ) { newuser . links [ command . meshid ] . rights = command . meshadmin ; } else { newuser . links [ command . meshid ] = { rights : command . meshadmin } ; }
2019-07-02 00:44:26 +03:00
db . SetUser ( newuser ) ;
parent . parent . DispatchEvent ( [ newuser . _id ] , obj , 'resubscribe' ) ;
// Add a user to the mesh
mesh . links [ newuserid ] = { userid : newuser . id , name : newuser . name , rights : command . meshadmin } ;
db . Set ( common . escapeLinksFieldName ( mesh ) ) ;
2019-05-30 22:40:10 +03:00
2019-07-02 00:44:26 +03:00
// Notify mesh change
2019-10-12 01:14:38 +03:00
var event = { etype : 'mesh' , username : newuser . name , userid : command . userid , meshid : mesh . _id , name : mesh . name , mtype : mesh . mtype , desc : mesh . desc , action : 'meshchange' , links : mesh . links , msg : 'Added user ' + newuser . name + ' to mesh ' + mesh . name , domain : domain . id } ;
2019-07-02 00:44:26 +03:00
if ( db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the mesh. Another event will come.
parent . parent . DispatchEvent ( [ '*' , mesh . _id , user . _id , newuserid ] , obj , event ) ;
removedCount ++ ;
} else {
unknownUsers . push ( command . usernames [ i ] ) ;
failCount ++ ;
2019-05-30 22:40:10 +03:00
}
2019-07-02 00:44:26 +03:00
}
2017-10-24 00:09:58 +03:00
2019-07-02 00:44:26 +03:00
if ( unknownUsers . length > 0 ) {
// Send error back, user not found.
2019-10-25 11:16:00 +03:00
displayNotificationMessage ( 'User' + ( ( unknownUsers . length > 1 ) ? 's' : '' ) + ' ' + EscapeHtml ( unknownUsers . join ( ', ' ) ) + ' not found.' , 'Device Group' , 'ServerNotify' ) ;
2017-10-24 00:09:58 +03:00
}
2019-07-02 00:44:26 +03:00
if ( command . responseid != null ) { try { ws . send ( JSON . stringify ( { action : 'addmeshuser' , responseid : command . responseid , result : 'ok' , removed : removedCount , failed : failCount } ) ) ; } catch ( ex ) { } }
2019-01-05 04:59:13 +03:00
break ;
}
case 'removemeshuser' :
{
2019-07-02 00:44:26 +03:00
var err = null ;
try {
2019-10-26 20:57:40 +03:00
if ( common . validateString ( command . userid , 1 , 1024 ) == false ) { err = "Invalid userid" ; } // Check userid
if ( common . validateString ( command . meshid , 1 , 1024 ) == false ) { err = "Invalid groupid" ; } // Check meshid
2019-07-02 00:44:26 +03:00
if ( command . userid . indexOf ( '/' ) == - 1 ) { command . userid = 'user/' + domain . id + '/' + command . userid ; }
2019-10-26 20:57:40 +03:00
if ( command . userid == obj . user . _id ) { err = "Can't remove self" ; } // Can't add of modify self
if ( ( command . userid . split ( '/' ) . length != 3 ) || ( command . userid . split ( '/' ) [ 1 ] != domain . id ) ) { err = "Invalid userid" ; } // Invalid domain, operation only valid for current domain
2019-07-02 00:44:26 +03:00
else {
if ( command . meshid . indexOf ( '/' ) == - 1 ) { command . meshid = 'mesh/' + domain . id + '/' + command . meshid ; }
mesh = parent . meshes [ command . meshid ] ;
2019-10-26 20:57:40 +03:00
if ( mesh == null ) { err = "Unknown device group" ; }
else if ( mesh . links [ user . _id ] == null || ( ( mesh . links [ user . _id ] . rights & 2 ) == 0 ) ) { err = "Permission denied" ; }
else if ( ( command . meshid . split ( '/' ) . length != 3 ) || ( command . meshid . split ( '/' ) [ 1 ] != domain . id ) ) { err = "Invalid domain" ; } // Invalid domain, operation only valid for current domain
2019-07-02 00:44:26 +03:00
}
2019-10-26 20:57:40 +03:00
} catch ( ex ) { err = "Validation exception: " + ex ; }
2019-01-05 04:59:13 +03:00
2019-07-02 00:44:26 +03:00
// Handle any errors
if ( err != null ) {
if ( command . responseid != null ) { try { ws . send ( JSON . stringify ( { action : 'addmeshuser' , responseid : command . responseid , result : err } ) ) ; } catch ( ex ) { } }
break ;
}
2019-01-05 04:59:13 +03:00
2019-07-02 00:44:26 +03:00
// Check if the user exists - Just in case we need to delete a mesh right for a non-existant user, we do it this way. Technically, it's not possible, but just in case.
var deluserid = command . userid , deluser = parent . users [ deluserid ] ;
if ( deluser != null ) {
// Remove mesh from user
if ( deluser . links != null && deluser . links [ command . meshid ] != null ) {
var delmeshrights = deluser . links [ command . meshid ] . rights ;
if ( ( delmeshrights == 0xFFFFFFFF ) && ( mesh . links [ deluserid ] . rights != 0xFFFFFFFF ) ) return ; // A non-admin can't kick out an admin
delete deluser . links [ command . meshid ] ;
db . Set ( deluser ) ;
parent . parent . DispatchEvent ( [ deluser . _id ] , obj , 'resubscribe' ) ;
2017-10-24 00:09:58 +03:00
}
2019-07-02 00:44:26 +03:00
}
2017-10-24 00:09:58 +03:00
2019-07-02 00:44:26 +03:00
// Remove user from the mesh
if ( mesh . links [ command . userid ] != null ) {
delete mesh . links [ command . userid ] ;
db . Set ( common . escapeLinksFieldName ( mesh ) ) ;
2017-10-24 00:09:58 +03:00
2019-07-02 00:44:26 +03:00
// Notify mesh change
var event ;
if ( deluser != null ) {
2019-10-12 01:14:38 +03:00
event = { etype : 'mesh' , username : user . name , userid : deluser . name , meshid : mesh . _id , name : mesh . name , mtype : mesh . mtype , desc : mesh . desc , action : 'meshchange' , links : mesh . links , msg : 'Removed user ' + deluser . name + ' from group ' + mesh . name , domain : domain . id } ;
2019-07-02 00:44:26 +03:00
} else {
2019-10-12 01:14:38 +03:00
event = { etype : 'mesh' , username : user . name , userid : ( deluserid . split ( '/' ) [ 2 ] ) , meshid : mesh . _id , name : mesh . name , mtype : mesh . mtype , desc : mesh . desc , action : 'meshchange' , links : mesh . links , msg : 'Removed user ' + ( deluserid . split ( '/' ) [ 2 ] ) + ' from group ' + mesh . name , domain : domain . id } ;
2017-10-24 00:09:58 +03:00
}
2019-07-02 00:44:26 +03:00
parent . parent . DispatchEvent ( [ '*' , mesh . _id , user . _id , command . userid ] , obj , event ) ;
if ( command . responseid != null ) { try { ws . send ( JSON . stringify ( { action : 'removemeshuser' , responseid : command . responseid , result : 'ok' } ) ) ; } catch ( ex ) { } }
} else {
if ( command . responseid != null ) { try { ws . send ( JSON . stringify ( { action : 'removemeshuser' , responseid : command . responseid , result : 'User not in group' } ) ) ; } catch ( ex ) { } }
2017-10-24 00:09:58 +03:00
}
2019-01-05 04:59:13 +03:00
break ;
}
2019-02-08 02:00:10 +03:00
case 'meshamtpolicy' :
{
// Change a mesh Intel AMT policy
2019-03-10 01:28:08 +03:00
if ( common . validateString ( command . meshid , 1 , 1024 ) == false ) break ; // Check the meshid
if ( common . validateObject ( command . amtpolicy ) == false ) break ; // Check the amtpolicy
2019-06-13 05:40:27 +03:00
if ( common . validateInt ( command . amtpolicy . type , 0 , 3 ) == false ) break ; // Check the amtpolicy.type
2019-02-08 02:00:10 +03:00
if ( command . amtpolicy . type === 2 ) {
2019-03-10 01:28:08 +03:00
if ( common . validateString ( command . amtpolicy . password , 0 , 32 ) == false ) break ; // Check the amtpolicy.password
2019-06-25 04:56:14 +03:00
if ( ( command . amtpolicy . badpass != null ) && common . validateInt ( command . amtpolicy . badpass , 0 , 1 ) == false ) break ; // Check the amtpolicy.badpass
2019-03-10 01:28:08 +03:00
if ( common . validateInt ( command . amtpolicy . cirasetup , 0 , 2 ) == false ) break ; // Check the amtpolicy.cirasetup
2019-06-13 05:40:27 +03:00
} else if ( command . amtpolicy . type === 3 ) {
if ( common . validateString ( command . amtpolicy . password , 0 , 32 ) == false ) break ; // Check the amtpolicy.password
if ( common . validateInt ( command . amtpolicy . cirasetup , 0 , 2 ) == false ) break ; // Check the amtpolicy.cirasetup
2019-02-08 02:00:10 +03:00
}
2019-03-10 01:28:08 +03:00
mesh = parent . meshes [ command . meshid ] ;
2019-02-08 02:00:10 +03:00
change = '' ;
if ( mesh ) {
// Check if this user has rights to do this
2019-04-13 00:19:03 +03:00
if ( ( mesh . links [ user . _id ] == null ) || ( ( mesh . links [ user . _id ] . rights & 1 ) == 0 ) ) return ;
2019-02-08 02:00:10 +03:00
if ( ( command . meshid . split ( '/' ) . length != 3 ) || ( command . meshid . split ( '/' ) [ 1 ] != domain . id ) ) return ; // Invalid domain, operation only valid for current domain
// TODO: Check if this is a change from the existing policy
// Perform the Intel AMT policy change
change = 'Intel AMT policy change' ;
var amtpolicy = { type : command . amtpolicy . type } ;
if ( command . amtpolicy . type === 2 ) { amtpolicy = { type : command . amtpolicy . type , password : command . amtpolicy . password , badpass : command . amtpolicy . badpass , cirasetup : command . amtpolicy . cirasetup } ; }
2019-06-13 05:40:27 +03:00
else if ( command . amtpolicy . type === 3 ) { amtpolicy = { type : command . amtpolicy . type , password : command . amtpolicy . password , cirasetup : command . amtpolicy . cirasetup } ; }
2019-02-08 02:00:10 +03:00
mesh . amt = amtpolicy ;
2019-03-10 01:28:08 +03:00
db . Set ( common . escapeLinksFieldName ( mesh ) ) ;
2019-10-11 21:16:36 +03:00
var amtpolicy2 = Object . assign ( { } , amtpolicy ) ; // Shallow clone
2019-06-25 04:56:14 +03:00
delete amtpolicy2 . password ;
2019-10-12 01:14:38 +03:00
var event = { etype : 'mesh' , userid : user . _id , username : user . name , meshid : mesh . _id , amt : amtpolicy2 , action : 'meshchange' , links : mesh . links , msg : change , domain : domain . id } ;
2019-05-29 03:25:23 +03:00
if ( db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the mesh. Another event will come.
parent . parent . DispatchEvent ( [ '*' , mesh . _id , user . _id ] , obj , event ) ;
2019-02-08 02:00:10 +03:00
// Send new policy to all computers on this mesh
2019-03-11 07:40:25 +03:00
//routeCommandToMesh(command.meshid, { action: 'amtPolicy', amtPolicy: amtpolicy });
// See if the node is connected
for ( var nodeid in parent . wsagents ) {
const agent = parent . wsagents [ nodeid ] ;
if ( agent . dbMeshKey == command . meshid ) { agent . sendUpdatedIntelAmtPolicy ( amtpolicy ) ; }
}
2019-02-08 02:00:10 +03:00
}
break ;
}
2019-01-05 04:59:13 +03:00
case 'addamtdevice' :
{
2019-03-10 01:28:08 +03:00
if ( args . wanonly == true ) return ; // This is a WAN-only server, local Intel AMT computers can't be added
if ( common . validateString ( command . meshid , 1 , 1024 ) == false ) break ; // Check meshid
2019-01-05 04:59:13 +03:00
if ( ( command . meshid . split ( '/' ) . length != 3 ) || ( command . meshid . split ( '/' ) [ 1 ] != domain . id ) ) return ; // Invalid domain, operation only valid for current domain
2019-03-10 01:28:08 +03:00
if ( common . validateString ( command . devicename , 1 , 256 ) == false ) break ; // Check device name
if ( common . validateString ( command . hostname , 1 , 256 ) == false ) break ; // Check hostname
if ( common . validateString ( command . amtusername , 0 , 16 ) == false ) break ; // Check username
if ( common . validateString ( command . amtpassword , 0 , 16 ) == false ) break ; // Check password
2019-01-05 04:59:13 +03:00
if ( command . amttls == '0' ) { command . amttls = 0 ; } else if ( command . amttls == '1' ) { command . amttls = 1 ; } // Check TLS flag
if ( ( command . amttls != 1 ) && ( command . amttls != 0 ) ) break ;
// If we are in WAN-only mode, hostname is not used
2019-03-10 01:53:46 +03:00
if ( ( args . wanonly == true ) && ( command . hostname ) ) { delete command . hostname ; }
2019-01-05 04:59:13 +03:00
// Get the mesh
2019-03-10 01:28:08 +03:00
mesh = parent . meshes [ command . meshid ] ;
2019-01-05 04:59:13 +03:00
if ( mesh ) {
if ( mesh . mtype != 1 ) return ; // This operation is only allowed for mesh type 1, Intel AMT agentless mesh.
// Check if this user has rights to do this
if ( mesh . links [ user . _id ] == null || ( ( mesh . links [ user . _id ] . rights & 4 ) == 0 ) ) return ;
// Create a new nodeid
2019-03-10 01:28:08 +03:00
parent . crypto . randomBytes ( 48 , function ( err , buf ) {
2019-01-05 04:59:13 +03:00
// create the new node
nodeid = 'node/' + domain . id + '/' + buf . toString ( 'base64' ) . replace ( /\+/g , '@' ) . replace ( /\//g , '$' ) ;
2019-06-25 04:56:14 +03:00
var device = { type : 'node' , _id : nodeid , meshid : command . meshid , name : command . devicename , host : command . hostname , domain : domain . id , intelamt : { user : command . amtusername , pass : command . amtpassword , tls : command . amttls } } ;
2019-03-10 01:28:08 +03:00
db . Set ( device ) ;
2019-01-05 04:59:13 +03:00
// Event the new node
2019-10-11 21:16:36 +03:00
parent . parent . DispatchEvent ( [ '*' , command . meshid ] , obj , { etype : 'node' , userid : user . _id , username : user . name , action : 'addnode' , node : parent . CloneSafeNode ( device ) , msg : 'Added device ' + command . devicename + ' to mesh ' + mesh . name , domain : domain . id } ) ;
2019-01-05 04:59:13 +03:00
} ) ;
}
break ;
}
case 'scanamtdevice' :
{
2019-03-10 01:28:08 +03:00
if ( args . wanonly == true ) return ; // This is a WAN-only server, this type of scanning is not allowed.
if ( common . validateString ( command . range , 1 , 256 ) == false ) break ; // Check range string
2019-01-05 04:59:13 +03:00
// Ask the RMCP scanning to scan a range of IP addresses
2019-03-10 01:28:08 +03:00
if ( parent . parent . amtScanner ) {
2019-03-12 03:33:24 +03:00
if ( parent . parent . amtScanner . performRangeScan ( user . _id , command . range ) == false ) {
parent . parent . DispatchEvent ( [ '*' , user . _id ] , obj , { action : 'scanamtdevice' , range : command . range , results : null , nolog : 1 } ) ;
2017-10-24 00:09:58 +03:00
}
}
2019-01-05 04:59:13 +03:00
break ;
}
2019-02-12 05:02:07 +03:00
case 'changeDeviceMesh' :
{
2019-03-10 01:28:08 +03:00
if ( common . validateStrArray ( command . nodeids , 1 , 256 ) == false ) break ; // Check nodeid strings
if ( common . validateString ( command . meshid , 1 , 256 ) == false ) break ; // Check target meshid string
2019-02-12 05:02:07 +03:00
2019-02-19 04:50:10 +03:00
// For each nodeid, change the group
for ( var i = 0 ; i < command . nodeids . length ; i ++ ) {
2019-03-10 01:28:08 +03:00
db . Get ( command . nodeids [ i ] , function ( err , nodes ) {
2019-09-26 01:09:40 +03:00
if ( ( nodes == null ) || ( nodes . length != 1 ) ) return ;
2019-03-08 06:56:24 +03:00
const node = nodes [ 0 ] ;
2019-02-19 04:50:10 +03:00
// Check if already in the right mesh
if ( node . meshid == command . meshid ) return ;
// Make sure both source and target mesh are the same type
2019-03-10 01:28:08 +03:00
try { if ( parent . meshes [ node . meshid ] . mtype != parent . meshes [ command . meshid ] . mtype ) return ; } catch ( e ) { return ; } ;
2019-02-19 04:50:10 +03:00
// Make sure that we have rights on both source and destination mesh
2019-03-08 06:56:24 +03:00
const sourceMeshRights = user . links [ node . meshid ] . rights ;
const targetMeshRights = user . links [ command . meshid ] . rights ;
2019-02-19 04:50:10 +03:00
if ( ( ( sourceMeshRights & 4 ) == 0 ) || ( ( targetMeshRights & 4 ) == 0 ) ) return ;
// Perform the switch, start by saving the node with the new meshid.
2019-03-08 06:56:24 +03:00
const oldMeshId = node . meshid ;
2019-02-19 04:50:10 +03:00
node . meshid = command . meshid ;
2019-03-10 01:28:08 +03:00
db . Set ( node ) ;
2019-02-19 04:50:10 +03:00
// If the device is connected on this server, switch it now.
2019-03-10 01:28:08 +03:00
var agentSession = parent . wsagents [ node . _id ] ;
2019-03-08 06:56:24 +03:00
if ( agentSession != null ) {
agentSession . dbMeshKey = command . meshid ; // Switch the agent mesh
agentSession . meshid = command . meshid . split ( '/' ) [ 2 ] ; // Switch the agent mesh
agentSession . sendUpdatedIntelAmtPolicy ( ) ; // Send the new Intel AMT policy
}
2019-02-19 04:50:10 +03:00
2019-10-10 01:56:27 +03:00
// If any MQTT sessions are connected on this server, switch it now.
if ( parent . parent . mqttbroker != null ) { parent . parent . mqttbroker . changeDeviceMesh ( node . _id , command . meshid ) ; }
2019-10-11 02:07:32 +03:00
// If any CIRA sessions are connected on this server, switch it now.
if ( parent . parent . mpsserver != null ) { parent . parent . mpsserver . changeDeviceMesh ( node . _id , command . meshid ) ; }
2019-02-19 04:50:10 +03:00
// Add the connection state
2019-03-10 01:28:08 +03:00
const state = parent . parent . GetConnectivityState ( node . _id ) ;
2019-02-19 04:50:10 +03:00
if ( state ) {
node . conn = state . connectivity ;
node . pwr = state . powerState ;
2019-03-10 01:28:08 +03:00
if ( ( state . connectivity & 1 ) != 0 ) { var agent = parent . wsagents [ node . _id ] ; if ( agent != null ) { node . agct = agent . connectTime ; } }
if ( ( state . connectivity & 2 ) != 0 ) { var cira = parent . parent . mpsserver . ciraConnections [ node . _id ] ; if ( cira != null ) { node . cict = cira . tag . connectTime ; } }
2019-02-19 04:50:10 +03:00
}
// Event the node change
2019-03-10 01:28:08 +03:00
var newMesh = parent . meshes [ command . meshid ] ;
2019-07-30 02:35:48 +03:00
var event = { etype : 'node' , userid : user . _id , username : user . name , action : 'nodemeshchange' , nodeid : node . _id , node : node , oldMeshId : oldMeshId , newMeshId : command . meshid , msg : 'Moved device ' + node . name + ' to group ' + newMesh . name , domain : domain . id } ;
2019-05-29 03:25:23 +03:00
if ( db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the mesh. Another event will come.
parent . parent . DispatchEvent ( [ '*' , oldMeshId , command . meshid ] , obj , event ) ;
2019-02-19 04:50:10 +03:00
} ) ;
}
2019-02-12 05:02:07 +03:00
break ;
}
2019-01-05 04:59:13 +03:00
case 'removedevices' :
{
2019-03-10 01:28:08 +03:00
if ( common . validateArray ( command . nodeids , 1 ) == false ) break ; // Check nodeid's
2017-10-24 00:09:58 +03:00
2019-01-05 04:59:13 +03:00
for ( i in command . nodeids ) {
nodeid = command . nodeids [ i ] ;
2019-03-10 01:28:08 +03:00
if ( common . validateString ( nodeid , 1 , 1024 ) == false ) break ; // Check nodeid
2019-01-05 04:59:13 +03:00
if ( ( nodeid . split ( '/' ) . length != 3 ) || ( nodeid . split ( '/' ) [ 1 ] != domain . id ) ) return ; // Invalid domain, operation only valid for current domain
2017-10-24 00:09:58 +03:00
2019-01-05 04:59:13 +03:00
// Get the device
2019-03-10 01:28:08 +03:00
db . Get ( nodeid , function ( err , nodes ) {
2019-05-21 04:03:14 +03:00
if ( ( nodes == null ) || ( nodes . length != 1 ) ) return ;
2019-01-05 04:59:13 +03:00
var node = nodes [ 0 ] ;
// Get the mesh for this device
2019-03-10 01:28:08 +03:00
mesh = parent . meshes [ node . meshid ] ;
2019-01-05 04:59:13 +03:00
if ( mesh ) {
// Check if this user has rights to do this
if ( mesh . links [ user . _id ] == null || ( ( mesh . links [ user . _id ] . rights & 4 ) == 0 ) ) return ;
// Delete this node including network interface information, events and timeline
2019-03-10 01:28:08 +03:00
db . Remove ( node . _id ) ; // Remove node with that id
db . Remove ( 'if' + node . _id ) ; // Remove interface information
db . Remove ( 'nt' + node . _id ) ; // Remove notes
db . Remove ( 'lc' + node . _id ) ; // Remove last connect time
2019-08-07 03:58:29 +03:00
db . Remove ( 'si' + node . _id ) ; // Remove system information
2019-03-10 01:28:08 +03:00
db . RemoveSMBIOS ( node . _id ) ; // Remove SMBios data
db . RemoveAllNodeEvents ( node . _id ) ; // Remove all events for this node
db . removeAllPowerEventsForNode ( node . _id ) ; // Remove all power events for this node
2019-04-10 01:51:05 +03:00
db . Get ( 'ra' + obj . dbNodeKey , function ( err , nodes ) {
2019-05-30 00:36:14 +03:00
if ( ( nodes != null ) && ( nodes . length == 1 ) ) { db . Remove ( 'da' + nodes [ 0 ] . daid ) ; } // Remove diagnostic agent to real agent link
db . Remove ( 'ra' + node . _id ) ; // Remove real agent to diagnostic agent link
2019-04-10 01:51:05 +03:00
} ) ;
2019-01-05 04:59:13 +03:00
// Event node deletion
2019-07-30 02:35:48 +03:00
var event = { etype : 'node' , userid : user . _id , username : user . name , action : 'removenode' , nodeid : node . _id , msg : 'Removed device ' + node . name + ' from group ' + mesh . name , domain : domain . id } ;
2019-05-30 00:36:14 +03:00
// TODO: We can't use the changeStream for node delete because we will not know the meshid the device was in.
//if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to remove the node. Another event will come.
parent . parent . DispatchEvent ( [ '*' , node . meshid ] , obj , event ) ;
2019-01-05 04:59:13 +03:00
// Disconnect all connections if needed
2019-03-10 01:28:08 +03:00
var state = parent . parent . GetConnectivityState ( nodeid ) ;
2019-01-05 04:59:13 +03:00
if ( ( state != null ) && ( state . connectivity != null ) ) {
2019-03-10 01:28:08 +03:00
if ( ( state . connectivity & 1 ) != 0 ) { parent . wsagents [ nodeid ] . close ( ) ; } // Disconnect mesh agent
if ( ( state . connectivity & 2 ) != 0 ) { parent . parent . mpsserver . close ( parent . parent . mpsserver . ciraConnections [ nodeid ] ) ; } // Disconnect CIRA connection
2019-01-05 04:59:13 +03:00
}
2017-10-24 00:09:58 +03:00
}
2019-01-05 04:59:13 +03:00
} ) ;
2017-10-24 00:09:58 +03:00
}
2019-01-05 04:59:13 +03:00
break ;
}
case 'wakedevices' :
{
2019-03-10 01:28:08 +03:00
if ( common . validateArray ( command . nodeids , 1 ) == false ) break ; // Check nodeid's
2019-01-05 04:59:13 +03:00
// TODO: We can optimize this a lot.
// - We should get a full list of all MAC's to wake first.
// - We should try to only have one agent per subnet (using Gateway MAC) send a wake-on-lan.
for ( i in command . nodeids ) {
nodeid = command . nodeids [ i ] ;
var wakeActions = 0 ;
2019-03-10 01:28:08 +03:00
if ( common . validateString ( nodeid , 1 , 1024 ) == false ) break ; // Check nodeid
2019-01-05 04:59:13 +03:00
if ( ( nodeid . split ( '/' ) . length == 3 ) && ( nodeid . split ( '/' ) [ 1 ] == domain . id ) ) { // Validate the domain, operation only valid for current domain
2017-10-24 00:09:58 +03:00
// Get the device
2019-03-10 01:28:08 +03:00
db . Get ( nodeid , function ( err , nodes ) {
2019-05-21 04:03:14 +03:00
if ( ( nodes == null ) || ( nodes . length != 1 ) ) return ;
2017-10-24 00:09:58 +03:00
var node = nodes [ 0 ] ;
// Get the mesh for this device
2019-03-10 01:28:08 +03:00
mesh = parent . meshes [ node . meshid ] ;
2017-10-24 00:09:58 +03:00
if ( mesh ) {
2019-01-05 04:59:13 +03:00
2017-10-24 00:09:58 +03:00
// Check if this user has rights to do this
2019-01-05 04:59:13 +03:00
if ( mesh . links [ user . _id ] != null && ( ( mesh . links [ user . _id ] . rights & 64 ) != 0 ) ) {
2019-10-08 04:11:15 +03:00
// If this device is connected on MQTT, send a wake action.
if ( parent . parent . mqttbroker != null ) { parent . parent . mqttbroker . publish ( node . _id , 'powerAction' , 'wake' ) ; }
2019-01-05 04:59:13 +03:00
// Get the device interface information
2019-03-10 01:28:08 +03:00
db . Get ( 'if' + node . _id , function ( err , nodeifs ) {
2019-05-21 04:03:14 +03:00
if ( ( nodeifs != null ) && ( nodeifs . length == 1 ) ) {
2019-01-05 04:59:13 +03:00
var nodeif = nodeifs [ 0 ] ;
var macs = [ ] ;
for ( var i in nodeif . netif ) { if ( nodeif . netif [ i ] . mac ) { macs . push ( nodeif . netif [ i ] . mac ) ; } }
// Have the server send a wake-on-lan packet (Will not work in WAN-only)
2019-03-10 01:28:08 +03:00
if ( parent . parent . meshScanner != null ) { parent . parent . meshScanner . wakeOnLan ( macs ) ; wakeActions ++ ; }
2019-01-05 04:59:13 +03:00
// Get the list of mesh this user as access to
var targetMeshes = [ ] ;
for ( i in user . links ) { targetMeshes . push ( i ) ; }
// Go thru all the connected agents and send wake-on-lan on all the ones in the target mesh list
2019-03-10 01:28:08 +03:00
for ( i in parent . wsagents ) {
var agent = parent . wsagents [ i ] ;
2019-01-05 04:59:13 +03:00
if ( ( targetMeshes . indexOf ( agent . dbMeshKey ) >= 0 ) && ( agent . authenticated == 2 ) ) {
//console.log('Asking agent ' + agent.dbNodeKey + ' to wake ' + macs.join(','));
try { agent . send ( JSON . stringify ( { action : 'wakeonlan' , macs : macs } ) ) ; } catch ( ex ) { }
wakeActions ++ ;
}
}
}
} ) ;
2017-10-24 00:09:58 +03:00
}
}
} ) ;
}
2019-01-05 04:59:13 +03:00
// Confirm we may be doing something (TODO)
try { ws . send ( JSON . stringify ( { action : 'wakedevices' } ) ) ; } catch ( ex ) { }
2017-10-24 00:09:58 +03:00
}
2019-10-24 23:13:18 +03:00
break ;
}
case 'uninstallagent' :
{
if ( common . validateArray ( command . nodeids , 1 ) == false ) break ; // Check nodeid's
for ( i in command . nodeids ) {
nodeid = command . nodeids [ i ] ;
if ( common . validateString ( nodeid , 1 , 1024 ) == false ) break ; // Check nodeid
if ( ( nodeid . split ( '/' ) . length == 3 ) && ( nodeid . split ( '/' ) [ 1 ] == domain . id ) ) { // Validate the domain, operation only valid for current domain
// Get the device
db . Get ( nodeid , function ( err , nodes ) {
if ( ( nodes == null ) || ( nodes . length != 1 ) ) return ;
var node = nodes [ 0 ] ;
// Get the mesh for this device
mesh = parent . meshes [ node . meshid ] ;
if ( mesh ) {
// Check if this user has rights to do this
if ( mesh . links [ user . _id ] != null && ( ( mesh . links [ user . _id ] . rights & MESHRIGHT _UNINSTALL ) != 0 ) ) {
// Send uninstall command to connected agent
var agent = parent . wsagents [ node . _id ] ;
if ( agent != null ) {
//console.log('Asking agent ' + agent.dbNodeKey + ' to uninstall.');
try { agent . send ( JSON . stringify ( { action : 'uninstallagent' } ) ) ; } catch ( ex ) { }
}
}
}
} ) ;
}
}
2019-01-05 04:59:13 +03:00
break ;
}
case 'poweraction' :
{
2019-03-10 01:28:08 +03:00
if ( common . validateArray ( command . nodeids , 1 ) == false ) break ; // Check nodeid's
2019-10-08 04:11:15 +03:00
if ( common . validateInt ( command . actiontype , 2 , 4 ) == false ) break ; // Check actiontype
2019-01-05 04:59:13 +03:00
for ( i in command . nodeids ) {
nodeid = command . nodeids [ i ] ;
var powerActions = 0 ;
2019-03-10 01:28:08 +03:00
if ( common . validateString ( nodeid , 1 , 1024 ) == false ) break ; // Check nodeid
2019-01-05 04:59:13 +03:00
if ( ( nodeid . split ( '/' ) . length == 3 ) && ( nodeid . split ( '/' ) [ 1 ] == domain . id ) ) { // Validate the domain, operation only valid for current domain
// Get the device
2019-03-10 01:28:08 +03:00
db . Get ( nodeid , function ( err , nodes ) {
2019-05-21 04:03:14 +03:00
if ( ( nodes == null ) || ( nodes . length != 1 ) ) return ;
2019-01-05 04:59:13 +03:00
var node = nodes [ 0 ] ;
2017-10-24 00:09:58 +03:00
2019-01-05 04:59:13 +03:00
// Get the mesh for this device
2019-03-10 01:28:08 +03:00
mesh = parent . meshes [ node . meshid ] ;
2019-01-05 04:59:13 +03:00
if ( mesh ) {
2019-10-08 04:11:15 +03:00
// If this device is connected on MQTT, send a power action.
if ( parent . parent . mqttbroker != null ) { parent . parent . mqttbroker . publish ( nodeid , 'powerAction' , [ '' , '' , 'poweroff' , 'reset' , 'sleep' ] [ command . actiontype ] ) ; }
2019-01-05 04:59:13 +03:00
// Check if this user has rights to do this
if ( mesh . links [ user . _id ] != null && ( ( mesh . links [ user . _id ] . rights & 8 ) != 0 ) ) { // "Remote Control permission"
// Get this device
2019-03-10 01:28:08 +03:00
var agent = parent . wsagents [ node . _id ] ;
2019-01-05 04:59:13 +03:00
if ( agent != null ) {
// Send the power command
try { agent . send ( JSON . stringify ( { action : 'poweraction' , actiontype : command . actiontype } ) ) ; } catch ( ex ) { }
powerActions ++ ;
2018-04-20 04:19:15 +03:00
}
}
2019-01-05 04:59:13 +03:00
}
} ) ;
2018-04-20 04:19:15 +03:00
}
2019-01-05 04:59:13 +03:00
// Confirm we may be doing something (TODO)
try { ws . send ( JSON . stringify ( { action : 'poweraction' } ) ) ; } catch ( ex ) { }
2018-04-20 04:19:15 +03:00
}
2019-01-05 04:59:13 +03:00
break ;
}
case 'toast' :
{
2019-03-10 01:28:08 +03:00
if ( common . validateArray ( command . nodeids , 1 ) == false ) break ; // Check nodeid's
if ( common . validateString ( command . title , 1 , 512 ) == false ) break ; // Check title
if ( common . validateString ( command . msg , 1 , 4096 ) == false ) break ; // Check message
2019-01-05 04:59:13 +03:00
for ( i in command . nodeids ) {
nodeid = command . nodeids [ i ] ;
var powerActions = 0 ;
2019-03-10 01:28:08 +03:00
if ( common . validateString ( nodeid , 1 , 1024 ) == false ) break ; // Check nodeid
2019-01-05 04:59:13 +03:00
if ( ( nodeid . split ( '/' ) . length == 3 ) && ( nodeid . split ( '/' ) [ 1 ] == domain . id ) ) { // Validate the domain, operation only valid for current domain
// Get the device
2019-03-10 01:28:08 +03:00
db . Get ( nodeid , function ( err , nodes ) {
2019-05-21 04:03:14 +03:00
if ( ( nodes == null ) || ( nodes . length != 1 ) ) return ;
2019-01-05 04:59:13 +03:00
var node = nodes [ 0 ] ;
2017-10-24 00:09:58 +03:00
2019-01-05 04:59:13 +03:00
// Get the mesh for this device
2019-03-10 01:28:08 +03:00
mesh = parent . meshes [ node . meshid ] ;
2019-01-05 04:59:13 +03:00
if ( mesh ) {
// Check if this user has rights to do this
2019-10-16 22:57:29 +03:00
if ( mesh . links [ user . _id ] != null && ( ( mesh . links [ user . _id ] . rights & MESHRIGHT _CHATNOTIFY ) != 0 ) ) {
2019-01-05 04:59:13 +03:00
// Get this device
2019-03-10 01:28:08 +03:00
var agent = parent . wsagents [ node . _id ] ;
2019-01-05 04:59:13 +03:00
if ( agent != null ) {
// Send the power command
2019-07-30 02:35:48 +03:00
try { agent . send ( JSON . stringify ( { action : 'toast' , title : command . title , msg : command . msg , sessionid : ws . sessionId , username : user . name , userid : user . _id } ) ) ; } catch ( ex ) { }
2019-01-05 04:59:13 +03:00
}
}
}
} ) ;
}
2017-10-24 00:09:58 +03:00
}
2019-01-05 04:59:13 +03:00
break ;
}
case 'getnetworkinfo' :
{
// Argument validation
2019-03-10 01:28:08 +03:00
if ( common . validateString ( command . nodeid , 1 , 1024 ) == false ) break ; // Check nodeid
2019-01-05 04:59:13 +03:00
if ( ( command . nodeid . split ( '/' ) . length != 3 ) || ( command . nodeid . split ( '/' ) [ 1 ] != domain . id ) ) return ; // Invalid domain, operation only valid for current domain
// Get the device
2019-03-10 01:28:08 +03:00
db . Get ( command . nodeid , function ( err , nodes ) {
2019-05-21 04:03:14 +03:00
if ( ( nodes == null ) || ( nodes . length != 1 ) ) { try { ws . send ( JSON . stringify ( { action : 'getnetworkinfo' , nodeid : command . nodeid , netif : null } ) ) ; } catch ( ex ) { } return ; }
2019-01-05 04:59:13 +03:00
var node = nodes [ 0 ] ;
// Get the mesh for this device
2019-03-10 01:28:08 +03:00
mesh = parent . meshes [ node . meshid ] ;
2019-01-05 04:59:13 +03:00
if ( mesh ) {
// Check if this user has rights to do this
if ( mesh . links [ user . _id ] == null || ( mesh . links [ user . _id ] . rights == 0 ) ) { try { ws . send ( JSON . stringify ( { action : 'getnetworkinfo' , nodeid : command . nodeid , netif : null } ) ) ; } catch ( ex ) { } return ; }
2017-10-24 00:09:58 +03:00
2019-01-05 04:59:13 +03:00
// Get network information about this node
2019-03-10 01:28:08 +03:00
db . Get ( 'if' + command . nodeid , function ( err , netinfos ) {
2019-05-21 04:03:14 +03:00
if ( ( netinfos == null ) || ( netinfos . length != 1 ) ) { try { ws . send ( JSON . stringify ( { action : 'getnetworkinfo' , nodeid : command . nodeid , netif : null } ) ) ; } catch ( ex ) { } return ; }
2019-01-05 04:59:13 +03:00
var netinfo = netinfos [ 0 ] ;
try { ws . send ( JSON . stringify ( { action : 'getnetworkinfo' , nodeid : command . nodeid , updateTime : netinfo . updateTime , netif : netinfo . netif } ) ) ; } catch ( ex ) { }
} ) ;
}
} ) ;
break ;
}
case 'changedevice' :
{
// Argument validation
2019-03-10 01:28:08 +03:00
if ( common . validateString ( command . nodeid , 1 , 1024 ) == false ) break ; // Check nodeid
2019-01-05 04:59:13 +03:00
if ( ( command . nodeid . split ( '/' ) . length != 3 ) || ( command . nodeid . split ( '/' ) [ 1 ] != domain . id ) ) return ; // Invalid domain, operation only valid for current domain
if ( ( command . userloc ) && ( command . userloc . length != 2 ) && ( command . userloc . length != 0 ) ) return ;
// Change the device
2019-03-10 01:28:08 +03:00
db . Get ( command . nodeid , function ( err , nodes ) {
2019-05-21 04:03:14 +03:00
if ( ( nodes == null ) || ( nodes . length != 1 ) ) return ;
2019-01-05 04:59:13 +03:00
var node = nodes [ 0 ] ;
// Get the mesh for this device
2019-03-10 01:28:08 +03:00
mesh = parent . meshes [ node . meshid ] ;
2019-01-05 04:59:13 +03:00
if ( mesh ) {
// Check if this user has rights to do this
if ( mesh . links [ user . _id ] == null || ( ( mesh . links [ user . _id ] . rights & 4 ) == 0 ) ) return ;
2017-10-24 00:09:58 +03:00
2019-01-05 04:59:13 +03:00
// Ready the node change event
2019-07-30 02:35:48 +03:00
var changes = [ ] , event = { etype : 'node' , userid : user . _id , username : user . name , action : 'changenode' , nodeid : node . _id , domain : domain . id } ;
2019-01-05 04:59:13 +03:00
change = 0 ;
event . msg = ": " ;
// If we are in WAN-only mode, host is not used
2019-03-10 01:53:46 +03:00
if ( ( args . wanonly == true ) && ( command . host ) ) { delete command . host ; }
2019-01-05 04:59:13 +03:00
// Look for a change
if ( command . icon && ( command . icon != node . icon ) ) { change = 1 ; node . icon = command . icon ; changes . push ( 'icon' ) ; }
if ( command . name && ( command . name != node . name ) ) { change = 1 ; node . name = command . name ; changes . push ( 'name' ) ; }
if ( command . host && ( command . host != node . host ) ) { change = 1 ; node . host = command . host ; changes . push ( 'host' ) ; }
2019-02-27 03:26:33 +03:00
if ( domain . geolocation && command . userloc && ( ( node . userloc == null ) || ( command . userloc [ 0 ] != node . userloc [ 0 ] ) || ( command . userloc [ 1 ] != node . userloc [ 1 ] ) ) ) {
2019-01-05 04:59:13 +03:00
change = 1 ;
if ( ( command . userloc . length == 0 ) && ( node . userloc ) ) {
delete node . userloc ;
changes . push ( 'location removed' ) ;
} else {
command . userloc . push ( ( Math . floor ( ( new Date ( ) ) / 1000 ) ) ) ;
node . userloc = command . userloc . join ( ',' ) ;
changes . push ( 'location' ) ;
2017-10-24 00:09:58 +03:00
}
}
2019-01-05 04:59:13 +03:00
if ( command . desc != null && ( command . desc != node . desc ) ) { change = 1 ; node . desc = command . desc ; changes . push ( 'description' ) ; }
if ( command . intelamt != null ) {
if ( ( command . intelamt . user != null ) && ( command . intelamt . pass != undefined ) && ( ( command . intelamt . user != node . intelamt . user ) || ( command . intelamt . pass != node . intelamt . pass ) ) ) { change = 1 ; node . intelamt . user = command . intelamt . user ; node . intelamt . pass = command . intelamt . pass ; changes . push ( 'Intel AMT credentials' ) ; }
if ( command . intelamt . tls && ( command . intelamt . tls != node . intelamt . tls ) ) { change = 1 ; node . intelamt . tls = command . intelamt . tls ; changes . push ( 'Intel AMT TLS' ) ; }
2017-10-24 00:09:58 +03:00
}
2019-01-05 04:59:13 +03:00
if ( command . tags ) { // Node grouping tag, this is a array of strings that can't be empty and can't contain a comma
2019-04-16 01:05:09 +03:00
var ok = true , group2 = [ ] ;
2019-03-10 01:28:08 +03:00
if ( common . validateString ( command . tags , 0 , 4096 ) == true ) { command . tags = command . tags . split ( ',' ) ; }
2019-04-16 01:05:09 +03:00
for ( var i in command . tags ) { var tname = command . tags [ i ] . trim ( ) ; if ( ( tname . length > 0 ) && ( tname . length < 64 ) && ( group2 . indexOf ( tname ) == - 1 ) ) { group2 . push ( tname ) ; } }
group2 . sort ( ) ;
if ( node . tags != group2 ) { node . tags = group2 ; change = 1 ; }
2019-01-05 04:59:13 +03:00
} else if ( ( command . tags === '' ) && node . tags ) { delete node . tags ; change = 1 ; }
if ( change == 1 ) {
// Save the node
2019-03-10 01:28:08 +03:00
db . Set ( node ) ;
2019-01-05 04:59:13 +03:00
2019-05-29 03:25:23 +03:00
// Event the node change. Only do this if the database will not do it.
2019-01-05 04:59:13 +03:00
event . msg = 'Changed device ' + node . name + ' from group ' + mesh . name + ': ' + changes . join ( ', ' ) ;
2019-10-13 21:50:28 +03:00
event . node = parent . CloneSafeNode ( node ) ;
2019-05-29 03:25:23 +03:00
if ( db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the node. Another event will come.
2019-09-18 22:05:33 +03:00
parent . parent . DispatchEvent ( [ '*' , node . meshid , user . _id ] , obj , event ) ;
2019-01-05 04:59:13 +03:00
}
}
} ) ;
break ;
}
case 'uploadagentcore' :
{
2019-03-10 01:28:08 +03:00
if ( common . validateString ( command . nodeid , 1 , 1024 ) == false ) break ; // Check nodeid
if ( common . validateString ( command . type , 1 , 40 ) == false ) break ; // Check path
2019-02-16 23:56:33 +03:00
// Change the device
2019-03-10 01:28:08 +03:00
db . Get ( command . nodeid , function ( err , nodes ) {
2019-05-21 04:03:14 +03:00
if ( ( nodes == null ) || ( nodes . length != 1 ) ) return ;
2019-02-16 23:56:33 +03:00
var node = nodes [ 0 ] ;
// Get the mesh for this device
2019-03-10 01:28:08 +03:00
mesh = parent . meshes [ node . meshid ] ;
2019-02-16 23:56:33 +03:00
if ( mesh ) {
// Check if this user has rights to do this
if ( mesh . links [ user . _id ] == null || ( ( ( mesh . links [ user . _id ] . rights & 16 ) == 0 ) && ( user . siteadmin != 0xFFFFFFFF ) ) ) { return ; }
if ( command . type == 'default' ) {
// Send the default core to the agent
2019-03-10 01:28:08 +03:00
parent . parent . updateMeshCore ( function ( ) { parent . sendMeshAgentCore ( user , domain , command . nodeid , 'default' ) ; } ) ;
2019-02-16 23:56:33 +03:00
} else if ( command . type == 'clear' ) {
// Clear the mesh agent core on the mesh agent
2019-03-10 01:28:08 +03:00
parent . sendMeshAgentCore ( user , domain , command . nodeid , 'clear' ) ;
2019-02-16 23:56:33 +03:00
} else if ( command . type == 'recovery' ) {
// Send the recovery core to the agent
2019-03-10 01:28:08 +03:00
parent . sendMeshAgentCore ( user , domain , command . nodeid , 'recovery' ) ;
} else if ( ( command . type == 'custom' ) && ( common . validateString ( command . path , 1 , 2048 ) == true ) ) {
2019-02-16 23:56:33 +03:00
// Send a mesh agent core to the mesh agent
2019-03-10 01:28:08 +03:00
var file = parent . getServerFilePath ( user , domain , command . path ) ;
2019-02-16 23:56:33 +03:00
if ( file != null ) {
2019-03-10 01:28:08 +03:00
fs . readFile ( file . fullpath , 'utf8' , function ( err , data ) {
2019-02-16 23:56:33 +03:00
if ( err != null ) {
2019-03-10 01:28:08 +03:00
data = common . IntToStr ( 0 ) + data ; // Add the 4 bytes encoding type & flags (Set to 0 for raw)
parent . sendMeshAgentCore ( user , domain , command . nodeid , 'custom' , data ) ;
2019-02-16 23:56:33 +03:00
}
} ) ;
2019-01-22 01:05:50 +03:00
}
2019-02-16 23:56:33 +03:00
}
2019-01-22 01:05:50 +03:00
}
2019-02-16 23:56:33 +03:00
} ) ;
2019-01-05 04:59:13 +03:00
break ;
}
case 'agentdisconnect' :
{
2019-03-10 01:28:08 +03:00
if ( common . validateString ( command . nodeid , 1 , 1024 ) == false ) break ; // Check nodeid
if ( common . validateInt ( command . disconnectMode ) == false ) return ; // Check disconnect mode
2019-02-16 23:56:33 +03:00
// Change the device
2019-03-10 01:28:08 +03:00
db . Get ( command . nodeid , function ( err , nodes ) {
2019-05-21 04:03:14 +03:00
if ( ( nodes == null ) || ( nodes . length != 1 ) ) return ;
2019-02-16 23:56:33 +03:00
var node = nodes [ 0 ] ;
// Get the mesh for this device
2019-03-10 01:28:08 +03:00
mesh = parent . meshes [ node . meshid ] ;
2019-02-16 23:56:33 +03:00
if ( mesh ) {
// Check if this user has rights to do this
if ( mesh . links [ user . _id ] == null || ( ( ( mesh . links [ user . _id ] . rights & 16 ) == 0 ) && ( user . siteadmin != 0xFFFFFFFF ) ) ) return ;
// Force mesh agent disconnection
2019-03-10 01:28:08 +03:00
parent . forceMeshAgentDisconnect ( user , domain , command . nodeid , command . disconnectMode ) ;
2019-02-16 23:56:33 +03:00
}
} ) ;
2019-01-05 04:59:13 +03:00
break ;
}
case 'close' :
{
// Close the web socket session
2019-01-05 23:04:18 +03:00
if ( obj . req . session && obj . req . session . ws && obj . req . session . ws == ws ) { delete obj . req . session . ws ; }
2019-01-05 04:59:13 +03:00
try { ws . close ( ) ; } catch ( e ) { }
break ;
}
case 'getcookie' :
{
// Check if this user has rights on this nodeid
2019-03-10 01:28:08 +03:00
if ( common . validateString ( command . nodeid , 1 , 1024 ) == false ) break ; // Check nodeid
db . Get ( command . nodeid , function ( err , nodes ) { // TODO: Make a NodeRights(user) method that also does not do a db call if agent is connected (???)
2019-05-21 04:03:14 +03:00
if ( ( nodes == null ) || ( nodes . length == 1 ) ) {
2019-01-05 04:59:13 +03:00
meshlinks = user . links [ nodes [ 0 ] . meshid ] ;
2019-04-09 21:18:09 +03:00
if ( ( meshlinks ) && ( meshlinks . rights ) && ( ( meshlinks . rights & MESHRIGHT _REMOTECONTROL ) != 0 ) ) {
2019-01-05 04:59:13 +03:00
// Add a user authentication cookie to a url
var cookieContent = { userid : user . _id , domainid : user . domain } ;
if ( command . nodeid ) { cookieContent . nodeid = command . nodeid ; }
if ( command . tcpaddr ) { cookieContent . tcpaddr = command . tcpaddr ; } // Indicates the browser want to agent to TCP connect to a remote address
if ( command . tcpport ) { cookieContent . tcpport = command . tcpport ; } // Indicates the browser want to agent to TCP connect to a remote port
2019-04-17 20:03:23 +03:00
command . cookie = parent . parent . encodeCookie ( cookieContent , parent . parent . loginCookieEncryptionKey ) ;
2019-01-05 04:59:13 +03:00
try { ws . send ( JSON . stringify ( command ) ) ; } catch ( ex ) { }
}
}
} ) ;
break ;
}
case 'inviteAgent' :
{
2019-07-05 23:28:41 +03:00
var err = null , mesh = null ;
2019-10-25 11:16:00 +03:00
try {
2019-07-05 23:28:41 +03:00
if ( ( parent . parent . mailserver == null ) || ( args . lanonly == true ) ) { err = 'Unsupported feature' ; } // This operation requires the email server
else if ( ( parent . parent . certificates . CommonName == null ) || ( parent . parent . certificates . CommonName . indexOf ( '.' ) == - 1 ) ) { err = 'Unsupported feature' ; } // Server name must be configured
else if ( common . validateString ( command . meshid , 1 , 1024 ) == false ) { err = 'Invalid group identifier' ; } // Check meshid
else {
if ( command . meshid . split ( '/' ) . length == 1 ) { command . meshid = 'mesh/' + domain . id + '/' + command . meshid ; }
if ( ( command . meshid . split ( '/' ) . length != 3 ) || ( command . meshid . split ( '/' ) [ 1 ] != domain . id ) ) { err = 'Invalid group identifier' ; } // Invalid domain, operation only valid for current domain
else if ( common . validateString ( command . email , 4 , 1024 ) == false ) { err = 'Invalid email' ; } // Check email
else if ( command . email . split ( '@' ) . length != 2 ) { err = 'Invalid email' ; } // Check email
else {
mesh = parent . meshes [ command . meshid ] ;
if ( mesh == null ) { err = 'Unknown device group' ; } // Check if the group exists
else if ( mesh . mtype != 2 ) { err = 'Invalid group type' ; } // Check if this is the correct group type
else if ( mesh . links [ user . _id ] == null ) { err = 'Not allowed' ; } // Check if this user has rights to do this
}
}
} catch ( ex ) { err = 'Validation exception: ' + ex ; }
2019-01-05 04:59:13 +03:00
2019-07-05 23:28:41 +03:00
// Handle any errors
if ( err != null ) {
if ( command . responseid != null ) { try { ws . send ( JSON . stringify ( { action : 'inviteAgent' , responseid : command . responseid , result : err } ) ) ; } catch ( ex ) { } }
break ;
}
2019-01-05 04:59:13 +03:00
2019-07-05 23:28:41 +03:00
// Perform email invitation
2019-07-15 20:24:31 +03:00
parent . parent . mailserver . sendAgentInviteMail ( domain , user . name , command . email . toLowerCase ( ) , command . meshid , command . name , command . os , command . msg , command . flags , command . expire ) ;
2019-01-05 04:59:13 +03:00
2019-07-05 23:28:41 +03:00
// Send a response if needed
if ( command . responseid != null ) { try { ws . send ( JSON . stringify ( { action : 'inviteAgent' , responseid : command . responseid , result : 'ok' } ) ) ; } catch ( ex ) { } }
2019-10-17 00:46:41 +03:00
break ;
}
case 'setDeviceEvent' :
{
// Argument validation
if ( common . validateString ( command . msg , 1 , 4096 ) == false ) break ; // Check event
if ( common . validateString ( command . nodeid , 1 , 1024 ) == false ) break ; // Check nodeid
var splitid = command . nodeid . split ( '/' ) ;
if ( ( splitid . length != 3 ) || ( splitid [ 1 ] != domain . id ) ) return ; // Invalid domain, operation only valid for current domain
var idtype = splitid [ 0 ] ;
if ( ( idtype != 'node' ) ) return ;
// Check if this user has rights on this id to set notes
db . Get ( command . nodeid , function ( err , nodes ) {
if ( ( nodes == null ) || ( nodes . length == 1 ) ) {
meshlinks = user . links [ nodes [ 0 ] . meshid ] ;
if ( ( meshlinks ) && ( meshlinks . rights ) && ( meshlinks . rights != 0 ) ) {
// Add an event for this device
var targets = [ '*' , 'server-users' , user . _id , nodes [ 0 ] . meshid ] ;
var event = { etype : 'node' , userid : user . _id , username : user . name , nodeid : nodes [ 0 ] . _id , action : 'manual' , msg : decodeURIComponent ( command . msg ) , domain : domain . id } ;
parent . parent . DispatchEvent ( targets , obj , event ) ;
}
}
} ) ;
2019-01-05 04:59:13 +03:00
break ;
}
case 'setNotes' :
{
// Argument validation
2019-03-10 01:28:08 +03:00
if ( common . validateString ( command . id , 1 , 1024 ) == false ) break ; // Check id
2019-01-05 04:59:13 +03:00
var splitid = command . id . split ( '/' ) ;
if ( ( splitid . length != 3 ) || ( splitid [ 1 ] != domain . id ) ) return ; // Invalid domain, operation only valid for current domain
var idtype = splitid [ 0 ] ;
if ( ( idtype != 'user' ) && ( idtype != 'mesh' ) && ( idtype != 'node' ) ) return ;
if ( idtype == 'node' ) {
// Check if this user has rights on this id to set notes
2019-03-10 01:28:08 +03:00
db . Get ( command . id , function ( err , nodes ) { // TODO: Make a NodeRights(user) method that also does not do a db call if agent is connected (???)
2019-05-21 04:03:14 +03:00
if ( ( nodes == null ) || ( nodes . length == 1 ) ) {
2018-08-30 22:05:23 +03:00
meshlinks = user . links [ nodes [ 0 ] . meshid ] ;
2019-10-15 00:15:59 +03:00
if ( ( meshlinks ) && ( meshlinks . rights ) && ( ( meshlinks . rights & MESHRIGHT _SETNOTES ) != 0 ) ) {
2019-01-05 04:59:13 +03:00
// Set the id's notes
2019-03-10 01:28:08 +03:00
if ( common . validateString ( command . notes , 1 ) == false ) {
db . Remove ( 'nt' + command . id ) ; // Delete the note for this node
2019-01-05 04:59:13 +03:00
} else {
2019-03-10 01:28:08 +03:00
db . Set ( { _id : 'nt' + command . id , type : 'note' , value : command . notes } ) ; // Set the note for this node
2019-01-05 04:59:13 +03:00
}
2017-10-24 00:09:58 +03:00
}
}
} ) ;
2019-01-05 04:59:13 +03:00
} else if ( idtype == 'mesh' ) {
// Get the mesh for this device
2019-03-10 01:28:08 +03:00
mesh = parent . meshes [ command . id ] ;
2018-01-24 01:15:59 +03:00
if ( mesh ) {
// Check if this user has rights to do this
2019-01-05 04:59:13 +03:00
if ( ( mesh . links [ user . _id ] == null ) || ( ( mesh . links [ user . _id ] . rights & 1 ) == 0 ) ) { return ; } // Must have rights to edit the mesh
2018-01-24 01:15:59 +03:00
2018-04-16 21:57:42 +03:00
// Set the id's notes
2019-03-10 01:28:08 +03:00
if ( common . validateString ( command . notes , 1 ) == false ) {
db . Remove ( 'nt' + command . id ) ; // Delete the note for this node
2018-04-16 21:57:42 +03:00
} else {
2019-03-10 01:28:08 +03:00
db . Set ( { _id : 'nt' + command . id , type : 'note' , value : command . notes } ) ; // Set the note for this mesh
2018-04-16 21:57:42 +03:00
}
}
2019-01-05 04:59:13 +03:00
} else if ( ( idtype == 'user' ) && ( ( user . siteadmin & 2 ) != 0 ) ) {
// Set the id's notes
2019-03-10 01:28:08 +03:00
if ( common . validateString ( command . notes , 1 ) == false ) {
db . Remove ( 'nt' + command . id ) ; // Delete the note for this node
2019-01-05 04:59:13 +03:00
} else {
2019-04-17 03:32:18 +03:00
// Can only perform this operation on other users of our group.
var chguser = parent . users [ command . id ] ;
if ( chguser == null ) break ; // This user does not exists
if ( ( user . groups != null ) && ( user . groups . length > 0 ) && ( ( chguser . groups == null ) || ( findOne ( chguser . groups , user . groups ) == false ) ) ) break ;
2019-03-10 01:28:08 +03:00
db . Set ( { _id : 'nt' + command . id , type : 'note' , value : command . notes } ) ; // Set the note for this user
2019-01-05 04:59:13 +03:00
}
2018-04-13 04:14:03 +03:00
}
2019-01-16 00:51:07 +03:00
break ;
}
case 'otpauth-request' :
{
2019-01-16 05:21:03 +03:00
// Check is 2-step login is supported
2019-08-23 03:05:21 +03:00
const twoStepLoginSupported = ( ( parent . parent . config . settings . no2factorauth !== true ) && ( domain . auth != 'sspi' ) && ( parent . parent . certificates . CommonName . indexOf ( '.' ) != - 1 ) && ( args . nousers !== true ) ) ;
2019-01-16 05:21:03 +03:00
if ( twoStepLoginSupported ) {
// Request a one time password to be setup
2019-03-25 21:32:16 +03:00
var otplib = null ;
try { otplib = require ( 'otplib' ) ; } catch ( ex ) { }
if ( otplib == null ) { break ; }
2019-01-16 05:21:03 +03:00
const secret = otplib . authenticator . generateSecret ( ) ; // TODO: Check the random source of this value.
2019-03-10 01:28:08 +03:00
ws . send ( JSON . stringify ( { action : 'otpauth-request' , secret : secret , url : otplib . authenticator . keyuri ( user . name , parent . certificates . CommonName , secret ) } ) ) ;
2019-01-16 05:21:03 +03:00
}
2019-01-16 00:51:07 +03:00
break ;
}
case 'otpauth-setup' :
{
2019-01-16 05:21:03 +03:00
// Check is 2-step login is supported
2019-08-23 03:05:21 +03:00
const twoStepLoginSupported = ( ( parent . parent . config . settings . no2factorauth !== true ) && ( domain . auth != 'sspi' ) && ( parent . parent . certificates . CommonName . indexOf ( '.' ) != - 1 ) && ( args . nousers !== true ) ) ;
2019-01-16 05:21:03 +03:00
if ( twoStepLoginSupported ) {
// Perform the one time password setup
2019-03-25 21:32:16 +03:00
var otplib = null ;
try { otplib = require ( 'otplib' ) ; } catch ( ex ) { }
if ( otplib == null ) { break ; }
2019-02-11 03:04:36 +03:00
otplib . authenticator . options = { window : 2 } ; // Set +/- 1 minute window
2019-01-16 05:21:03 +03:00
if ( otplib . authenticator . check ( command . token , command . secret ) === true ) {
// Token is valid, activate 2-step login on this account.
user . otpsecret = command . secret ;
2019-03-10 01:28:08 +03:00
parent . db . SetUser ( user ) ;
2019-01-16 05:21:03 +03:00
ws . send ( JSON . stringify ( { action : 'otpauth-setup' , success : true } ) ) ; // Report success
2019-02-11 07:13:03 +03:00
// Notify change
2019-04-17 03:32:18 +03:00
var targets = [ '*' , 'server-users' , user . _id ] ;
if ( user . groups ) { for ( var i in user . groups ) { targets . push ( 'server-users:' + i ) ; } }
2019-07-30 02:35:48 +03:00
var event = { etype : 'user' , userid : user . _id , username : user . name , account : parent . CloneSafeUser ( user ) , action : 'accountchange' , msg : 'Added authentication application.' , domain : domain . id } ;
2019-05-29 03:25:23 +03:00
if ( db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the user. Another event will come.
parent . parent . DispatchEvent ( targets , obj , event ) ;
2019-01-16 05:21:03 +03:00
} else {
ws . send ( JSON . stringify ( { action : 'otpauth-setup' , success : false } ) ) ; // Report fail
}
2019-01-16 00:51:07 +03:00
}
break ;
}
case 'otpauth-clear' :
{
2019-01-16 05:21:03 +03:00
// Check is 2-step login is supported
2019-08-23 03:05:21 +03:00
const twoStepLoginSupported = ( ( parent . parent . config . settings . no2factorauth !== true ) && ( domain . auth != 'sspi' ) && ( parent . parent . certificates . CommonName . indexOf ( '.' ) != - 1 ) && ( args . nousers !== true ) ) ;
2019-01-16 05:21:03 +03:00
if ( twoStepLoginSupported ) {
// Clear the one time password secret
if ( user . otpsecret ) {
delete user . otpsecret ;
2019-03-10 01:28:08 +03:00
parent . db . SetUser ( user ) ;
2019-02-11 07:13:03 +03:00
ws . send ( JSON . stringify ( { action : 'otpauth-clear' , success : true } ) ) ; // Report success
2019-01-16 05:21:03 +03:00
// Notify change
2019-04-17 03:32:18 +03:00
var targets = [ '*' , 'server-users' , user . _id ] ;
if ( user . groups ) { for ( var i in user . groups ) { targets . push ( 'server-users:' + i ) ; } }
2019-07-30 02:35:48 +03:00
var event = { etype : 'user' , userid : user . _id , username : user . name , account : parent . CloneSafeUser ( user ) , action : 'accountchange' , msg : 'Removed authentication application.' , domain : domain . id } ;
2019-05-29 03:25:23 +03:00
if ( db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the user. Another event will come.
parent . parent . DispatchEvent ( targets , obj , event ) ;
2019-01-16 05:21:03 +03:00
} else {
ws . send ( JSON . stringify ( { action : 'otpauth-clear' , success : false } ) ) ; // Report fail
}
2019-01-16 00:51:07 +03:00
}
2019-01-05 04:59:13 +03:00
break ;
}
2019-02-06 06:07:12 +03:00
case 'otpauth-getpasswords' :
{
// Check is 2-step login is supported
2019-08-23 03:05:21 +03:00
const twoStepLoginSupported = ( ( parent . parent . config . settings . no2factorauth !== true ) && ( domain . auth != 'sspi' ) && ( parent . parent . certificates . CommonName . indexOf ( '.' ) != - 1 ) && ( args . nousers !== true ) ) ;
2019-02-08 07:06:01 +03:00
if ( twoStepLoginSupported == false ) break ;
2019-02-06 06:07:12 +03:00
// Perform a sub-action
2019-09-24 20:34:33 +03:00
var actionTaken = false , actionText = null ;
2019-02-06 06:07:12 +03:00
if ( command . subaction == 1 ) { // Generate a new set of tokens
2019-02-06 07:01:01 +03:00
var randomNumbers = [ ] , v ;
for ( var i = 0 ; i < 10 ; i ++ ) { do { v = getRandomEightDigitInteger ( ) ; } while ( randomNumbers . indexOf ( v ) >= 0 ) ; randomNumbers . push ( v ) ; }
2019-02-06 06:07:12 +03:00
user . otpkeys = { keys : [ ] } ;
for ( var i = 0 ; i < 10 ; i ++ ) { user . otpkeys . keys [ i ] = { p : randomNumbers [ i ] , u : true } }
actionTaken = true ;
2019-09-24 20:34:33 +03:00
actionText = 'New 2FA backup codes generated.' ;
2019-02-06 06:07:12 +03:00
} else if ( command . subaction == 2 ) { // Clear all tokens
actionTaken = ( user . otpkeys != null ) ;
2019-10-03 23:32:54 +03:00
delete user . otpkeys ;
2019-09-24 20:34:33 +03:00
if ( actionTaken ) { actionText = '2FA backup codes cleared.' ; }
2019-02-06 06:07:12 +03:00
}
// Save the changed user
2019-03-10 01:28:08 +03:00
if ( actionTaken ) { parent . db . SetUser ( user ) ; }
2019-02-06 06:07:12 +03:00
// Return one time passwords for this user
2019-02-08 09:30:33 +03:00
if ( user . otpsecret || ( ( user . otphkeys != null ) && ( user . otphkeys . length > 0 ) ) ) {
ws . send ( JSON . stringify ( { action : 'otpauth-getpasswords' , passwords : user . otpkeys ? user . otpkeys . keys : null } ) ) ;
}
2019-02-11 03:04:36 +03:00
2019-02-11 07:13:03 +03:00
// Notify change
2019-09-24 20:34:33 +03:00
if ( actionText != null ) {
var targets = [ '*' , 'server-users' , user . _id ] ;
if ( user . groups ) { for ( var i in user . groups ) { targets . push ( 'server-users:' + i ) ; } }
var event = { etype : 'user' , userid : user . _id , username : user . name , account : parent . CloneSafeUser ( user ) , action : 'accountchange' , msg : actionText , domain : domain . id } ;
if ( db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the user. Another event will come.
parent . parent . DispatchEvent ( targets , obj , event ) ;
}
2019-02-08 07:06:01 +03:00
break ;
}
case 'otp-hkey-get' :
{
// Check is 2-step login is supported
2019-08-23 03:05:21 +03:00
const twoStepLoginSupported = ( ( parent . parent . config . settings . no2factorauth !== true ) && ( domain . auth != 'sspi' ) && ( parent . parent . certificates . CommonName . indexOf ( '.' ) != - 1 ) && ( args . nousers !== true ) ) ;
2019-02-08 07:06:01 +03:00
if ( twoStepLoginSupported == false ) break ;
// Send back the list of keys we have, just send the list of names and index
var hkeys = [ ] ;
2019-02-08 20:24:00 +03:00
if ( user . otphkeys != null ) { for ( var i = 0 ; i < user . otphkeys . length ; i ++ ) { hkeys . push ( { i : user . otphkeys [ i ] . keyIndex , name : user . otphkeys [ i ] . name , type : user . otphkeys [ i ] . type } ) ; } }
2019-02-08 07:06:01 +03:00
ws . send ( JSON . stringify ( { action : 'otp-hkey-get' , keys : hkeys } ) ) ;
break ;
}
case 'otp-hkey-remove' :
{
// Check is 2-step login is supported
2019-08-23 03:05:21 +03:00
const twoStepLoginSupported = ( ( parent . parent . config . settings . no2factorauth !== true ) && ( domain . auth != 'sspi' ) && ( parent . parent . certificates . CommonName . indexOf ( '.' ) != - 1 ) && ( args . nousers !== true ) ) ;
2019-02-08 07:06:01 +03:00
if ( twoStepLoginSupported == false || command . index == null ) break ;
// Remove a key
var foundAtIndex = - 1 ;
if ( user . otphkeys != null ) { for ( var i = 0 ; i < user . otphkeys . length ; i ++ ) { if ( user . otphkeys [ i ] . keyIndex == command . index ) { foundAtIndex = i ; } } }
if ( foundAtIndex != - 1 ) {
user . otphkeys . splice ( foundAtIndex , 1 ) ;
2019-03-10 01:28:08 +03:00
parent . db . SetUser ( user ) ;
2019-02-08 07:06:01 +03:00
}
2019-02-08 09:30:33 +03:00
2019-02-11 07:13:03 +03:00
// Notify change
2019-04-17 03:32:18 +03:00
var targets = [ '*' , 'server-users' , user . _id ] ;
if ( user . groups ) { for ( var i in user . groups ) { targets . push ( 'server-users:' + i ) ; } }
2019-07-30 02:35:48 +03:00
var event = { etype : 'user' , userid : user . _id , username : user . name , account : parent . CloneSafeUser ( user ) , action : 'accountchange' , msg : 'Removed security key.' , domain : domain . id } ;
2019-05-29 03:25:23 +03:00
if ( db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the user. Another event will come.
parent . parent . DispatchEvent ( targets , obj , event ) ;
2019-02-08 07:06:01 +03:00
break ;
}
case 'otp-hkey-yubikey-add' :
{
// Yubico API id and signature key can be requested from https://upgrade.yubico.com/getapikey/
2019-03-25 21:32:16 +03:00
var yubikeyotp = null ;
try { yubikeyotp = require ( 'yubikeyotp' ) ; } catch ( ex ) { }
2019-02-08 07:06:01 +03:00
// Check is 2-step login is supported
2019-08-23 03:05:21 +03:00
const twoStepLoginSupported = ( ( parent . parent . config . settings . no2factorauth !== true ) && ( domain . auth != 'sspi' ) && ( parent . parent . certificates . CommonName . indexOf ( '.' ) != - 1 ) && ( args . nousers !== true ) ) ;
2019-03-25 21:32:16 +03:00
if ( ( yubikeyotp == null ) || ( twoStepLoginSupported == false ) || ( typeof command . otp != 'string' ) ) {
2019-02-15 01:44:38 +03:00
ws . send ( JSON . stringify ( { action : 'otp-hkey-yubikey-add' , result : false , name : command . name } ) ) ;
break ;
}
// Check if Yubikey support is present or OTP no exactly 44 in length
if ( ( typeof domain . yubikey != 'object' ) || ( typeof domain . yubikey . id != 'string' ) || ( typeof domain . yubikey . secret != 'string' ) || ( command . otp . length != 44 ) ) {
ws . send ( JSON . stringify ( { action : 'otp-hkey-yubikey-add' , result : false , name : command . name } ) ) ;
break ;
}
2019-02-08 07:06:01 +03:00
2019-02-15 01:44:38 +03:00
// TODO: Check if command.otp is modhex encoded, reject if not.
2019-02-08 07:06:01 +03:00
2019-02-08 20:24:00 +03:00
// Query the YubiKey server to validate the OTP
2019-02-08 07:06:01 +03:00
var request = { otp : command . otp , id : domain . yubikey . id , key : domain . yubikey . secret , timestamp : true }
if ( domain . yubikey . proxy ) { request . requestParams = { proxy : domain . yubikey . proxy } ; }
yubikeyotp . verifyOTP ( request , function ( err , results ) {
2019-02-15 01:44:38 +03:00
if ( ( results != null ) && ( results . status == 'OK' ) ) {
2019-03-10 01:28:08 +03:00
var keyIndex = parent . crypto . randomBytes ( 4 ) . readUInt32BE ( 0 ) ;
2019-02-08 20:24:00 +03:00
var keyId = command . otp . substring ( 0 , 12 ) ;
if ( user . otphkeys == null ) { user . otphkeys = [ ] ; }
// Check if this key was already registered, if so, remove it.
var foundAtIndex = - 1 ;
for ( var i = 0 ; i < user . otphkeys . length ; i ++ ) { if ( user . otphkeys [ i ] . keyid == keyId ) { foundAtIndex = i ; } }
if ( foundAtIndex != - 1 ) { user . otphkeys . splice ( foundAtIndex , 1 ) ; }
// Add the new key and notify
user . otphkeys . push ( { name : command . name , type : 2 , keyid : keyId , keyIndex : keyIndex } ) ;
2019-03-10 01:28:08 +03:00
parent . db . SetUser ( user ) ;
2019-02-08 20:24:00 +03:00
ws . send ( JSON . stringify ( { action : 'otp-hkey-yubikey-add' , result : true , name : command . name , index : keyIndex } ) ) ;
// Notify change TODO: Should be done on all sessions/servers for this user.
2019-04-17 03:32:18 +03:00
var targets = [ '*' , 'server-users' , user . _id ] ;
if ( user . groups ) { for ( var i in user . groups ) { targets . push ( 'server-users:' + i ) ; } }
2019-07-30 02:35:48 +03:00
var event = { etype : 'user' , userid : user . _id , username : user . name , account : parent . CloneSafeUser ( user ) , action : 'accountchange' , msg : 'Added security key.' , domain : domain . id } ;
2019-05-29 03:25:23 +03:00
if ( db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the user. Another event will come.
parent . parent . DispatchEvent ( targets , obj , event ) ;
2019-02-08 20:24:00 +03:00
} else {
ws . send ( JSON . stringify ( { action : 'otp-hkey-yubikey-add' , result : false , name : command . name } ) ) ;
}
2019-02-08 07:06:01 +03:00
} ) ;
2019-02-06 06:07:12 +03:00
break ;
}
2019-03-23 08:33:53 +03:00
case 'webauthn-startregister' :
{
// Check is 2-step login is supported
2019-08-23 03:05:21 +03:00
const twoStepLoginSupported = ( ( parent . parent . config . settings . no2factorauth !== true ) && ( domain . auth != 'sspi' ) && ( parent . parent . certificates . CommonName . indexOf ( '.' ) != - 1 ) && ( args . nousers !== true ) ) ;
2019-05-17 00:55:07 +03:00
if ( ( twoStepLoginSupported == false ) || ( command . name == null ) ) break ;
// Send the registration request
var registrationOptions = parent . webauthn . generateRegistrationChallenge ( "Anonymous Service" , { id : Buffer ( user . _id , 'binary' ) . toString ( 'base64' ) , name : user . _id , displayName : user . _id . split ( '/' ) [ 2 ] } ) ;
obj . webAuthnReqistrationRequest = { action : 'webauthn-startregister' , keyname : command . name , request : registrationOptions } ;
ws . send ( JSON . stringify ( obj . webAuthnReqistrationRequest ) ) ;
2019-03-23 08:33:53 +03:00
break ;
}
case 'webauthn-endregister' :
{
2019-08-23 03:05:21 +03:00
const twoStepLoginSupported = ( ( parent . parent . config . settings . no2factorauth !== true ) && ( domain . auth != 'sspi' ) && ( parent . parent . certificates . CommonName . indexOf ( '.' ) != - 1 ) && ( args . nousers !== true ) ) ;
if ( ( twoStepLoginSupported == false ) || ( obj . webAuthnReqistrationRequest == null ) ) return ;
2019-03-23 08:33:53 +03:00
2019-03-23 23:28:17 +03:00
// Figure out the origin
var httpport = ( ( args . aliasport != null ) ? args . aliasport : args . port ) ;
var origin = "https://" + ( domain . dns ? domain . dns : parent . certificates . CommonName ) ;
if ( httpport != 443 ) { origin += ':' + httpport ; }
2019-05-17 00:55:07 +03:00
// Use internal WebAuthn module to check the response
var regResult = null ;
try { regResult = parent . webauthn . verifyAuthenticatorAttestationResponse ( command . response . response ) ; } catch ( ex ) { regResult = { verified : false , error : ex } ; }
if ( regResult . verified === true ) {
2019-03-25 08:48:06 +03:00
// Since we are registering a WebAuthn/FIDO2 key, remove all U2F keys (Type 1).
var otphkeys2 = [ ] ;
2019-04-23 02:37:56 +03:00
if ( user . otphkeys && Array . isArray ( user . otphkeys ) ) { for ( var i = 0 ; i < user . otphkeys . length ; i ++ ) { if ( user . otphkeys [ i ] . type != 1 ) { otphkeys2 . push ( user . otphkeys [ i ] ) ; } } }
2019-03-25 08:48:06 +03:00
user . otphkeys = otphkeys2 ;
2019-03-23 23:28:17 +03:00
// Add the new WebAuthn/FIDO2 keys
2019-03-23 08:33:53 +03:00
var keyIndex = parent . crypto . randomBytes ( 4 ) . readUInt32BE ( 0 ) ;
if ( user . otphkeys == null ) { user . otphkeys = [ ] ; }
2019-05-17 00:55:07 +03:00
user . otphkeys . push ( { name : obj . webAuthnReqistrationRequest . keyname , type : 3 , publicKey : regResult . authrInfo . publicKey , counter : regResult . authrInfo . counter , keyIndex : keyIndex , keyId : regResult . authrInfo . keyId } ) ;
2019-03-23 08:33:53 +03:00
parent . db . SetUser ( user ) ;
ws . send ( JSON . stringify ( { action : 'otp-hkey-setup-response' , result : true , name : command . name , index : keyIndex } ) ) ;
// Notify change
2019-04-17 03:32:18 +03:00
var targets = [ '*' , 'server-users' , user . _id ] ;
if ( user . groups ) { for ( var i in user . groups ) { targets . push ( 'server-users:' + i ) ; } }
2019-07-30 02:35:48 +03:00
var event = { etype : 'user' , userid : user . _id , username : user . name , account : parent . CloneSafeUser ( user ) , action : 'accountchange' , msg : 'Added security key.' , domain : domain . id } ;
2019-05-29 03:25:23 +03:00
if ( db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the user. Another event will come.
parent . parent . DispatchEvent ( targets , obj , event ) ;
2019-05-17 00:55:07 +03:00
} else {
//console.log('webauthn-endregister-error', regResult.error);
ws . send ( JSON . stringify ( { action : 'otp-hkey-setup-response' , result : false , error : regResult . error , name : command . name , index : keyIndex } ) ) ;
}
2019-03-23 08:33:53 +03:00
delete obj . hardwareKeyRegistrationRequest ;
break ;
}
2019-02-17 08:16:39 +03:00
case 'getClip' : {
2019-03-10 01:28:08 +03:00
if ( common . validateString ( command . nodeid , 1 , 1024 ) == false ) break ; // Check nodeid
2019-02-17 08:16:39 +03:00
// Get the device
2019-03-10 01:28:08 +03:00
db . Get ( command . nodeid , function ( err , nodes ) {
2019-05-21 04:03:14 +03:00
if ( ( nodes == null ) || ( nodes . length != 1 ) ) return ;
2019-02-17 08:16:39 +03:00
var node = nodes [ 0 ] ;
// Get the mesh for this device
2019-03-10 01:28:08 +03:00
mesh = parent . meshes [ node . meshid ] ;
2019-02-17 08:16:39 +03:00
if ( mesh ) {
// Check if this user has "remote" rights to do this
if ( ( mesh . links [ user . _id ] == null ) || ( ( mesh . links [ user . _id ] . rights & 16 ) == 0 ) ) return ;
// Ask for clipboard data from agent
2019-03-10 01:28:08 +03:00
var agent = parent . wsagents [ node . _id ] ;
2019-02-17 08:16:39 +03:00
if ( agent != null ) { try { agent . send ( JSON . stringify ( { action : 'getClip' } ) ) ; } catch ( ex ) { } }
}
} ) ;
break ;
}
case 'setClip' : {
2019-03-10 01:28:08 +03:00
if ( common . validateString ( command . nodeid , 1 , 1024 ) == false ) break ; // Check nodeid
if ( common . validateString ( command . data , 1 , 65535 ) == false ) break ; // Check
2019-02-17 08:16:39 +03:00
// Get the device
2019-03-10 01:28:08 +03:00
db . Get ( command . nodeid , function ( err , nodes ) {
2019-05-21 04:03:14 +03:00
if ( ( nodes == null ) || ( nodes . length != 1 ) ) return ;
2019-02-17 08:16:39 +03:00
var node = nodes [ 0 ] ;
// Get the mesh for this device
2019-03-10 01:28:08 +03:00
mesh = parent . meshes [ node . meshid ] ;
2019-02-17 08:16:39 +03:00
if ( mesh ) {
// Check if this user has "remote" rights to do this
if ( ( mesh . links [ user . _id ] == null ) || ( ( mesh . links [ user . _id ] . rights & 16 ) == 0 ) ) return ;
// Send clipboard data to the agent
2019-03-10 01:28:08 +03:00
var agent = parent . wsagents [ node . _id ] ;
2019-02-17 08:16:39 +03:00
if ( agent != null ) { try { agent . send ( JSON . stringify ( { action : 'setClip' , data : command . data } ) ) ; } catch ( ex ) { } }
}
} ) ;
break ;
}
2019-04-30 01:31:11 +03:00
case 'userWebState' : {
if ( common . validateString ( command . state , 1 , 10000 ) == false ) break ; // Check state size, no more than 10k
2019-10-26 00:41:14 +03:00
command . state = parent . filterUserWebState ( command . state ) ; // Filter the state to remove anything bad
2019-04-30 01:31:11 +03:00
db . Set ( { _id : 'ws' + user . _id , state : command . state } ) ;
parent . parent . DispatchEvent ( [ user . _id ] , obj , { action : 'userWebState' , nolog : 1 , domain : domain . id , state : command . state } ) ;
break ;
}
2019-01-05 04:59:13 +03:00
case 'getNotes' :
{
// Argument validation
2019-03-10 01:28:08 +03:00
if ( common . validateString ( command . id , 1 , 1024 ) == false ) break ; // Check id
2019-01-05 04:59:13 +03:00
var splitid = command . id . split ( '/' ) ;
if ( ( splitid . length != 3 ) || ( splitid [ 1 ] != domain . id ) ) return ; // Invalid domain, operation only valid for current domain
var idtype = splitid [ 0 ] ;
if ( ( idtype != 'user' ) && ( idtype != 'mesh' ) && ( idtype != 'node' ) ) return ;
if ( idtype == 'node' ) {
// Get the device
2019-03-10 01:28:08 +03:00
db . Get ( command . id , function ( err , nodes ) {
2019-05-21 04:03:14 +03:00
if ( ( nodes == null ) || ( nodes . length != 1 ) ) return ;
2019-01-05 04:59:13 +03:00
var node = nodes [ 0 ] ;
2018-04-13 04:14:03 +03:00
// Get the mesh for this device
2019-03-10 01:28:08 +03:00
mesh = parent . meshes [ node . meshid ] ;
2018-04-13 04:14:03 +03:00
if ( mesh ) {
// Check if this user has rights to do this
2019-01-05 04:59:13 +03:00
if ( mesh . links [ user . _id ] == null || ( mesh . links [ user . _id ] . rights == 0 ) ) { return ; }
2018-04-13 04:14:03 +03:00
// Get the notes about this node
2019-03-10 01:28:08 +03:00
db . Get ( 'nt' + command . id , function ( err , notes ) {
2018-09-25 21:51:40 +03:00
try {
2019-05-21 04:03:14 +03:00
if ( ( notes == null ) || ( notes . length != 1 ) ) { ws . send ( JSON . stringify ( { action : 'getNotes' , id : command . id , notes : null } ) ) ; return ; }
2018-09-25 21:51:40 +03:00
ws . send ( JSON . stringify ( { action : 'getNotes' , id : command . id , notes : notes [ 0 ] . value } ) ) ;
} catch ( ex ) { }
2018-04-13 04:14:03 +03:00
} ) ;
}
2019-01-05 04:59:13 +03:00
} ) ;
} else if ( idtype == 'mesh' ) {
// Get the mesh for this device
2019-03-10 01:28:08 +03:00
mesh = parent . meshes [ command . id ] ;
2019-01-05 04:59:13 +03:00
if ( mesh ) {
// Check if this user has rights to do this
if ( mesh . links [ user . _id ] == null || ( ( mesh . links [ user . _id ] . rights & 1 ) == 0 ) ) { return ; } // Must have rights to edit the mesh
2018-04-16 21:57:42 +03:00
// Get the notes about this node
2019-03-10 01:28:08 +03:00
db . Get ( 'nt' + command . id , function ( err , notes ) {
2018-09-25 21:51:40 +03:00
try {
2019-05-21 04:03:14 +03:00
if ( ( notes == null ) || ( notes . length != 1 ) ) { ws . send ( JSON . stringify ( { action : 'getNotes' , id : command . id , notes : null } ) ) ; return ; }
2018-09-25 21:51:40 +03:00
ws . send ( JSON . stringify ( { action : 'getNotes' , id : command . id , notes : notes [ 0 ] . value } ) ) ;
} catch ( ex ) { }
2018-04-16 21:57:42 +03:00
} ) ;
}
2019-01-05 04:59:13 +03:00
} else if ( ( idtype == 'user' ) && ( ( user . siteadmin & 2 ) != 0 ) ) {
// Get the notes about this node
2019-03-10 01:28:08 +03:00
db . Get ( 'nt' + command . id , function ( err , notes ) {
2019-01-05 04:59:13 +03:00
try {
2019-05-21 04:03:14 +03:00
if ( ( notes == null ) || ( notes . length != 1 ) ) { ws . send ( JSON . stringify ( { action : 'getNotes' , id : command . id , notes : null } ) ) ; return ; }
2019-01-05 04:59:13 +03:00
ws . send ( JSON . stringify ( { action : 'getNotes' , id : command . id , notes : notes [ 0 ] . value } ) ) ;
} catch ( ex ) { }
} ) ;
2018-04-13 04:14:03 +03:00
}
2017-10-24 00:09:58 +03:00
2019-01-05 04:59:13 +03:00
break ;
2017-10-24 00:09:58 +03:00
}
2019-06-03 23:15:38 +03:00
case 'createInviteLink' : {
2019-09-23 21:45:10 +03:00
var err = null ;
if ( common . validateString ( command . meshid , 8 , 128 ) == false ) { err = 'Invalid group id' ; } // Check the meshid
else if ( common . validateInt ( command . expire , 0 , 99999 ) == false ) { err = 'Invalid expire time' ; } // Check the expire time in hours
else if ( common . validateInt ( command . flags , 0 , 256 ) == false ) { err = 'Invalid flags' ; } ; // Check the flags
if ( command . meshid . split ( '/' ) . length == 1 ) { command . meshid = 'mesh/' + domain . id + '/' + command . meshid ; }
var smesh = command . meshid . split ( '/' ) ;
if ( ( smesh . length != 3 ) || ( smesh [ 0 ] != 'mesh' ) || ( smesh [ 1 ] != domain . id ) ) { err = 'Invalid group id' ; }
2019-09-25 21:37:58 +03:00
var serverName = parent . getWebServerName ( domain ) ;
2019-09-23 21:45:10 +03:00
// Handle any errors
if ( err != null ) {
if ( command . responseid != null ) { try { ws . send ( JSON . stringify ( { action : 'createInviteLink' , responseid : command . responseid , result : err } ) ) ; } catch ( ex ) { } }
break ;
}
2019-07-02 00:44:26 +03:00
mesh = parent . meshes [ command . meshid ] ;
2019-09-23 21:45:10 +03:00
if ( mesh == null ) { if ( command . responseid != null ) { try { ws . send ( JSON . stringify ( { action : 'createInviteLink' , responseid : command . responseid , result : 'Unable to find this device group id' } ) ) ; } catch ( ex ) { } } break ; }
2019-06-08 03:11:56 +03:00
const inviteCookie = parent . parent . encodeCookie ( { a : 4 , mid : command . meshid , f : command . flags , expire : command . expire * 60 } , parent . parent . invitationLinkEncryptionKey ) ;
2019-09-23 21:45:10 +03:00
if ( inviteCookie == null ) { if ( command . responseid != null ) { try { ws . send ( JSON . stringify ( { action : 'createInviteLink' , responseid : command . responseid , result : 'Unable to generate invitation cookie' } ) ) ; } catch ( ex ) { } } break ; }
// Create the server url
var httpsPort = ( ( args . aliasport == null ) ? args . port : args . aliasport ) ; // Use HTTPS alias port is specified
var xdomain = ( domain . dns == null ) ? domain . id : '' ;
if ( xdomain != '' ) xdomain += "/" ;
2019-09-25 21:37:58 +03:00
var url = "http" + ( args . notls ? '' : 's' ) + "://" + serverName + ":" + httpsPort + "/" + xdomain + "agentinvite?c=" + inviteCookie ;
2019-09-26 22:58:35 +03:00
if ( serverName . split ( '.' ) == 1 ) { url = "/" + xdomain + "agentinvite?c=" + inviteCookie ; }
2019-09-23 21:45:10 +03:00
ws . send ( JSON . stringify ( { action : 'createInviteLink' , meshid : command . meshid , url : url , expire : command . expire , cookie : inviteCookie , responseid : command . responseid , tag : command . tag } ) ) ;
2019-06-03 23:15:38 +03:00
break ;
}
2019-08-23 01:31:39 +03:00
case 'traceinfo' : {
if ( ( user . siteadmin == 0xFFFFFFFF ) && ( typeof command . traceSources == 'object' ) ) {
parent . parent . debugRemoteSources = command . traceSources ;
parent . parent . DispatchEvent ( [ '*' ] , obj , { action : 'traceinfo' , userid : user . _id , username : user . name , traceSources : command . traceSources , nolog : 1 , domain : domain . id } ) ;
}
break ;
}
2019-10-08 21:08:41 +03:00
case 'sendmqttmsg' : {
if ( common . validateArray ( command . nodeids , 1 ) == false ) { err = 'Invalid nodeids' ; } ; // Check nodeid's
if ( common . validateString ( command . topic , 1 , 64 ) == false ) { err = 'Invalid topic' ; } // Check the topic
if ( common . validateString ( command . msg , 1 , 4096 ) == false ) { err = 'Invalid msg' ; } // Check the message
// Handle any errors
if ( err != null ) {
if ( command . responseid != null ) { try { ws . send ( JSON . stringify ( { action : 'sendmqttmsg' , responseid : command . responseid , result : err } ) ) ; } catch ( ex ) { } }
break ;
}
// TODO: We can optimize this a lot.
// - We should get a full list of all MAC's to wake first.
// - We should try to only have one agent per subnet (using Gateway MAC) send a wake-on-lan.
for ( i in command . nodeids ) {
nodeid = command . nodeids [ i ] ;
var wakeActions = 0 ;
if ( common . validateString ( nodeid , 1 , 1024 ) == false ) break ; // Check nodeid
if ( ( nodeid . split ( '/' ) . length == 3 ) && ( nodeid . split ( '/' ) [ 1 ] == domain . id ) ) { // Validate the domain, operation only valid for current domain
// Get the device
db . Get ( nodeid , function ( err , nodes ) {
if ( ( nodes == null ) || ( nodes . length != 1 ) ) return ;
var node = nodes [ 0 ] ;
// Get the mesh for this device
mesh = parent . meshes [ node . meshid ] ;
if ( mesh ) {
// Check if this user has rights to do this
if ( mesh . links [ user . _id ] != null && ( ( mesh . links [ user . _id ] . rights & 64 ) != 0 ) ) {
// If this device is connected on MQTT, send a wake action.
if ( parent . parent . mqttbroker != null ) { parent . parent . mqttbroker . publish ( node . _id , command . topic , command . msg ) ; }
}
}
} ) ;
}
}
break ;
}
2019-10-06 00:24:40 +03:00
case 'getmqttlogin' : {
var err = null ;
if ( parent . parent . mqttbroker == null ) { err = 'MQTT not supported on this server' ; }
if ( common . validateString ( command . nodeid , 1 , 1024 ) == false ) { err = 'Invalid nodeid' ; } // Check the nodeid
// Handle any errors
if ( err != null ) { if ( command . responseid != null ) { try { ws . send ( JSON . stringify ( { action : 'getmqttlogin' , responseid : command . responseid , result : err } ) ) ; } catch ( ex ) { } } break ; }
var nodeid = command . nodeid ;
if ( ( nodeid . split ( '/' ) . length == 3 ) && ( nodeid . split ( '/' ) [ 1 ] == domain . id ) ) { // Validate the domain, operation only valid for current domain
// Get the device
db . Get ( nodeid , function ( err , nodes ) {
if ( ( nodes == null ) || ( nodes . length != 1 ) ) { if ( command . responseid != null ) { try { ws . send ( JSON . stringify ( { action : 'getmqttlogin' , responseid : command . responseid , result : 'Invalid node id' } ) ) ; } catch ( ex ) { } return ; } }
var node = nodes [ 0 ] ;
2019-10-25 11:16:00 +03:00
2019-10-06 00:24:40 +03:00
// Get the device group for this node
var mesh = parent . meshes [ node . meshid ] ;
if ( mesh ) {
// Check if this user has rights to do this
if ( ( mesh . links [ user . _id ] != null ) && ( mesh . links [ user . _id ] . rights == 0xFFFFFFFF ) ) {
var token = parent . parent . mqttbroker . generateLogin ( mesh . _id , node . _id ) ;
var r = { action : 'getmqttlogin' , responseid : command . responseid , nodeid : node . _id , user : token . user , pass : token . pass } ;
const serverName = parent . getWebServerName ( domain ) ;
// Add MPS URL
if ( parent . parent . mpsserver != null ) {
r . mpsCertHashSha384 = parent . parent . certificateOperations . getCertHash ( parent . parent . mpsserver . certificates . mps . cert ) ;
r . mpsCertHashSha1 = parent . parent . certificateOperations . getCertHashSha1 ( parent . parent . mpsserver . certificates . mps . cert ) ;
r . mpsUrl = 'mqtts://' + serverName + ':' + ( ( args . mpsaliasport != null ) ? args . mpsaliasport : args . mpsport ) + '/' ;
}
// Add WS URL
var xdomain = ( domain . dns == null ) ? domain . id : '' ;
if ( xdomain != '' ) xdomain += "/" ;
var httpsPort = ( ( args . aliasport == null ) ? args . port : args . aliasport ) ; // Use HTTPS alias port is specified
r . wsUrl = "ws" + ( args . notls ? '' : 's' ) + "://" + serverName + ":" + httpsPort + "/" + xdomain + "mqtt.ashx" ;
r . wsTrustedCert = parent . isTrustedCert ( domain ) ;
try { ws . send ( JSON . stringify ( r ) ) ; } catch ( ex ) { }
} else {
if ( command . responseid != null ) { try { ws . send ( JSON . stringify ( { action : 'getmqttlogin' , responseid : command . responseid , result : 'Unable to perform this operation' } ) ) ; } catch ( ex ) { } }
}
}
} ) ;
}
break ;
}
2019-09-21 03:21:58 +03:00
case 'amt' : {
if ( common . validateString ( command . nodeid , 1 , 1024 ) == false ) break ; // Check nodeid
if ( common . validateInt ( command . mode , 0 , 3 ) == false ) break ; // Check connection mode
2019-09-23 21:45:10 +03:00
// Validate if communication mode is possible
2019-10-25 11:16:00 +03:00
if ( command . mode == null || command . mode == 0 ) {
2019-09-23 21:45:10 +03:00
break ; //unsupported
2019-09-21 03:21:58 +03:00
} else if ( command . mode == 1 ) {
var state = parent . parent . GetConnectivityState ( command . nodeid ) ;
2019-10-25 11:16:00 +03:00
if ( ( state == null ) || ( state . connectivity & 4 ) == 0 ) break ;
2019-09-21 03:21:58 +03:00
} else if ( command . mode == 2 ) {
if ( parent . parent . mpsserver . ciraConnections [ command . nodeid ] == null ) break ;
} else if ( command . mode == 3 ) {
if ( parent . parent . apfserver . apfConnections [ command . nodeid ] == null ) break ;
}
var nodeid = command . nodeid ;
if ( ( nodeid . split ( '/' ) . length == 3 ) && ( nodeid . split ( '/' ) [ 1 ] == domain . id ) ) { // Validate the domain, operation only valid for current domain
// Get the device
db . Get ( nodeid , function ( err , nodes ) {
if ( ( nodes == null ) || ( nodes . length != 1 ) ) return ;
var node = nodes [ 0 ] ;
// Get the mesh for this device
2019-09-23 21:45:10 +03:00
var mesh = parent . meshes [ node . meshid ] ;
2019-09-21 03:21:58 +03:00
if ( mesh ) {
// Check if this user has rights to do this
if ( mesh . links [ user . _id ] != null && ( ( mesh . links [ user . _id ] . rights & 8 ) != 0 ) ) { // "Remote Control permission"
handleAmtCommand ( command , node ) ;
}
}
} ) ;
}
break ;
}
2019-10-09 08:22:01 +03:00
case 'plugin' : {
2019-10-25 11:16:00 +03:00
if ( parent . parent . pluginHandler == null ) break ; // If the plugin's are not supported, reject this command.
2019-10-09 08:22:01 +03:00
command . userid = user . _id ;
if ( command . routeToNode === true ) {
2019-10-25 11:16:00 +03:00
routeCommandToNode ( command ) ;
2019-10-09 08:22:01 +03:00
} else {
2019-10-25 11:16:00 +03:00
try {
var pluginHandler = require ( './pluginHandler.js' ) . pluginHandler ( parent . parent ) ;
pluginHandler . plugins [ command . plugin ] . serveraction ( command , obj , parent ) ;
} catch ( e ) { console . log ( 'Error loading plugin handler (' + e + ')' ) ; }
2019-10-09 08:22:01 +03:00
}
2019-10-25 11:16:00 +03:00
break ;
2019-10-09 08:22:01 +03:00
}
2019-01-05 04:59:13 +03:00
default : {
// Unknown user action
console . log ( 'Unknown action from user ' + user . name + ': ' + command . action + '.' ) ;
break ;
2017-10-24 00:09:58 +03:00
}
2019-01-05 04:59:13 +03:00
}
}
2017-10-24 00:09:58 +03:00
2019-02-13 06:23:40 +03:00
// Display a notification message for this session only.
2019-04-30 01:31:11 +03:00
function displayNotificationMessage ( msg , title , tag ) { ws . send ( JSON . stringify ( { "action" : "msg" , "type" : "notify" , "value" : msg , "title" : title , "userid" : user . _id , "username" : user . name , "tag" : tag } ) ) ; }
2019-02-13 06:23:40 +03:00
2017-10-24 00:09:58 +03:00
// Read the folder and all sub-folders and serialize that into json.
function readFilesRec ( path ) {
2019-03-10 01:28:08 +03:00
var r = { } , dir = fs . readdirSync ( path ) ;
2017-10-24 00:09:58 +03:00
for ( var i in dir ) {
var f = { t : 3 , d : 111 } ;
2019-03-10 01:28:08 +03:00
var stat = fs . statSync ( path + '/' + dir [ i ] ) ;
2017-10-24 00:09:58 +03:00
if ( ( stat . mode & 0x004000 ) == 0 ) { f . s = stat . size ; f . d = stat . mtime . getTime ( ) ; } else { f . t = 2 ; f . f = readFilesRec ( path + '/' + dir [ i ] ) ; }
r [ dir [ i ] ] = f ;
}
return r ;
}
2018-09-25 21:51:40 +03:00
// Delete a directory with a files and directories within it
// TODO, make this an async function
function deleteFolderRecursive ( path ) {
2019-03-10 01:28:08 +03:00
if ( fs . existsSync ( path ) ) {
fs . readdirSync ( path ) . forEach ( function ( file , index ) {
2019-03-20 19:53:44 +03:00
var curPath = parent . path . join ( path , file ) ; ;
2019-03-10 01:28:08 +03:00
if ( fs . lstatSync ( curPath ) . isDirectory ( ) ) { // recurse
2018-09-25 21:51:40 +03:00
deleteFolderRecursive ( curPath ) ;
} else { // delete file
2019-03-10 01:28:08 +03:00
fs . unlinkSync ( curPath ) ;
2018-09-25 21:51:40 +03:00
}
} ) ;
2019-03-10 01:28:08 +03:00
fs . rmdirSync ( path ) ;
2018-09-25 21:51:40 +03:00
}
} ;
2017-10-24 00:09:58 +03:00
function updateUserFiles ( user , ws , domain ) {
2019-02-19 09:20:25 +03:00
if ( ( user == null ) || ( user . siteadmin == null ) || ( ( user . siteadmin & 8 ) == 0 ) ) return ;
2017-10-24 00:09:58 +03:00
// Request the list of server files
var files = { action : 'files' , filetree : { n : 'Root' , f : { } } } ;
// Add user files
files . filetree . f [ user . _id ] = { t : 1 , n : 'My Files' , f : { } } ;
2019-03-10 01:28:08 +03:00
files . filetree . f [ user . _id ] . maxbytes = parent . getQuota ( user . _id , domain ) ;
2017-10-24 00:09:58 +03:00
var usersplit = user . _id . split ( '/' ) , domainx = 'domain' ;
if ( usersplit [ 1 ] . length > 0 ) domainx = 'domain-' + usersplit [ 1 ] ;
// Read all files recursively
try {
2019-03-20 19:53:44 +03:00
files . filetree . f [ user . _id ] . f = readFilesRec ( parent . path . join ( parent . filespath , domainx + "/user-" + usersplit [ 2 ] ) ) ;
2017-10-24 00:09:58 +03:00
} catch ( e ) {
2019-02-19 09:20:25 +03:00
// TODO: We may want to fake this file structure until it's needed.
2017-10-24 00:09:58 +03:00
// Got an error, try to create all the folders and try again...
2019-03-10 01:28:08 +03:00
try { fs . mkdirSync ( parent . filespath ) ; } catch ( e ) { }
2019-03-20 19:53:44 +03:00
try { fs . mkdirSync ( parent . path . join ( parent . filespath , domainx ) ) ; } catch ( e ) { }
try { fs . mkdirSync ( parent . path . join ( parent . filespath , domainx + "/user-" + usersplit [ 2 ] ) ) ; } catch ( e ) { }
try { fs . mkdirSync ( parent . path . join ( parent . filespath , domainx + "/user-" + usersplit [ 2 ] + "/Public" ) ) ; } catch ( e ) { }
try { files . filetree . f [ user . _id ] . f = readFilesRec ( parent . path . join ( parent . filespath , domainx + "/user-" + usersplit [ 2 ] ) ) ; } catch ( e ) { }
2017-10-24 00:09:58 +03:00
}
// Add files for each mesh
for ( var i in user . links ) {
if ( ( user . links [ i ] . rights & 32 ) != 0 ) { // Check that we have file permissions
2019-03-10 01:28:08 +03:00
var mesh = parent . meshes [ i ] ;
2017-10-24 00:09:58 +03:00
if ( mesh ) {
var meshsplit = mesh . _id . split ( '/' ) ;
2018-06-19 22:25:57 +03:00
files . filetree . f [ mesh . _id ] = { t : 4 , n : mesh . name , f : { } } ;
2019-03-10 01:28:08 +03:00
files . filetree . f [ mesh . _id ] . maxbytes = parent . getQuota ( mesh . _id , domain ) ;
2017-10-24 00:09:58 +03:00
// Read all files recursively
try {
2019-03-20 19:53:44 +03:00
files . filetree . f [ mesh . _id ] . f = readFilesRec ( parent . path . join ( parent . filespath , domainx + "/mesh-" + meshsplit [ 2 ] ) ) ;
2017-10-24 00:09:58 +03:00
} catch ( e ) {
2019-02-19 09:20:25 +03:00
files . filetree . f [ mesh . _id ] . f = { } ; // Got an error, return empty folder. We will create the folder only when needed.
2017-10-24 00:09:58 +03:00
}
}
}
}
// Respond
2018-09-25 21:51:40 +03:00
try { ws . send ( JSON . stringify ( files ) ) ; } catch ( ex ) { }
2017-10-24 00:09:58 +03:00
}
2017-12-13 05:23:26 +03:00
function EscapeHtml ( x ) { if ( typeof x == "string" ) return x . replace ( /&/g , '&' ) . replace ( />/g , '>' ) . replace ( /</g , '<' ) . replace ( /"/g , '"' ) . replace ( /'/g , ''' ) ; if ( typeof x == "boolean" ) return x ; if ( typeof x == "number" ) return x ; }
2018-08-30 22:05:23 +03:00
//function EscapeHtmlBreaks(x) { if (typeof x == "string") return x.replace(/&/g, '&').replace(/>/g, '>').replace(/</g, '<').replace(/"/g, '"').replace(/'/g, ''').replace(/\r/g, '<br />').replace(/\n/g, '').replace(/\t/g, ' '); if (typeof x == "boolean") return x; if (typeof x == "number") return x; }
2017-12-13 05:23:26 +03:00
2019-02-05 05:06:01 +03:00
// Split a string taking into account the quoats. Used for command line parsing
function splitArgs ( str ) { var myArray = [ ] , myRegexp = /[^\s"]+|"([^"]*)"/gi ; do { var match = myRegexp . exec ( str ) ; if ( match != null ) { myArray . push ( match [ 1 ] ? match [ 1 ] : match [ 0 ] ) ; } } while ( match != null ) ; return myArray ; }
function toNumberIfNumber ( x ) { if ( ( typeof x == 'string' ) && ( + parseInt ( x ) === x ) ) { x = parseInt ( x ) ; } return x ; }
2019-02-06 06:07:12 +03:00
function removeAllUnderScore ( obj ) {
if ( typeof obj != 'object' ) return obj ;
for ( var i in obj ) { if ( i . startsWith ( '_' ) ) { delete obj [ i ] ; } else if ( typeof obj [ i ] == 'object' ) { removeAllUnderScore ( obj [ i ] ) ; } }
return obj ;
}
2019-02-06 07:01:01 +03:00
// Generate a 8 digit integer with even random probability for each value.
function getRandomEightDigitInteger ( ) {
var bigInt ;
2019-03-10 01:28:08 +03:00
do { bigInt = parent . crypto . randomBytes ( 4 ) . readUInt32BE ( 0 ) ; } while ( bigInt >= 4200000000 ) ;
2019-02-06 07:01:01 +03:00
return bigInt % 100000000 ;
}
2019-02-05 05:06:01 +03:00
// Parse arguments string array into an object
function parseArgs ( argv ) {
var results = { '_' : [ ] } , current = null ;
for ( var i = 1 , len = argv . length ; i < len ; i ++ ) {
var x = argv [ i ] ;
if ( x . length > 2 && x [ 0 ] == '-' && x [ 1 ] == '-' ) {
if ( current != null ) { results [ current ] = true ; }
current = x . substring ( 2 ) ;
} else {
if ( current != null ) { results [ current ] = toNumberIfNumber ( x ) ; current = null ; } else { results [ '_' ] . push ( toNumberIfNumber ( x ) ) ; }
}
}
if ( current != null ) { results [ current ] = true ; }
return results ;
}
2019-04-17 03:32:18 +03:00
// Return true if at least one element of arr2 is in arr1
function findOne ( arr1 , arr2 ) { if ( ( arr1 == null ) || ( arr2 == null ) ) return false ; return arr2 . some ( function ( v ) { return arr1 . indexOf ( v ) >= 0 ; } ) ; } ;
2019-07-23 02:00:43 +03:00
function getRandomPassword ( ) { return Buffer . from ( parent . crypto . randomBytes ( 9 ) , 'binary' ) . toString ( 'base64' ) . split ( '/' ) . join ( '@' ) ; }
2019-04-29 06:31:08 +03:00
2019-09-21 03:21:58 +03:00
function handleAmtCommand ( cmd , node ) {
2019-09-23 21:45:10 +03:00
if ( cmd == null ) return ;
2019-09-21 03:21:58 +03:00
var host = cmd . nodeid ;
2019-09-23 21:45:10 +03:00
if ( cmd . mode == 1 ) { host = node . host ; }
2019-09-21 03:21:58 +03:00
var tlsoptions = null ;
2019-09-23 21:45:10 +03:00
var wsman = new Wsman ( WsmanComm , host , node . intelamt . tls ? 16993 : 16992 , node . intelamt . user , node . intelamt . pass ,
node . intelamt . tls , tlsoptions , parent . parent , cmd . mode ) ;
2019-09-21 03:21:58 +03:00
var amt = new Amt ( wsman ) ;
switch ( cmd . command ) {
case "Get-GeneralSettings" : {
2019-09-23 21:45:10 +03:00
amt . Get ( "AMT_GeneralSettings" , function ( obj , name , response , status ) {
if ( status == 200 ) {
var resp = { action : 'amt' , nodeid : cmd . nodeid , command : 'Get-GeneralSettings' , value : response . Body }
2019-09-21 03:21:58 +03:00
ws . send ( JSON . stringify ( resp ) ) ;
} else {
2019-09-23 21:45:10 +03:00
ws . send ( JSON . stringify ( { "error" : error } ) ) ;
2019-09-21 03:21:58 +03:00
}
} ) ;
break ;
}
default : {
2019-09-23 21:45:10 +03:00
// Do nothing
2019-09-21 03:21:58 +03:00
}
}
}
2017-10-24 00:09:58 +03:00
return obj ;
2018-08-30 22:05:23 +03:00
} ;