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 ) {
2017-10-24 00:09:58 +03:00
var obj = { } ;
obj . db = db ;
obj . ws = ws ;
obj . args = args ;
2018-10-16 20:52:05 +03:00
obj . user = user ;
2017-10-24 00:09:58 +03:00
obj . parent = parent ;
obj . domain = domain ;
obj . common = parent . common ;
2018-04-04 03:07:48 +03:00
obj . fs = require ( 'fs' ) ;
obj . path = require ( 'path' ) ;
2018-09-27 00:58:55 +03:00
obj . serverStatsTimer = null ;
2017-10-24 00:09:58 +03:00
// Send a message to the user
2019-01-03 05:03:34 +03:00
//obj.send = function (data) { try { if (typeof data == 'string') { obj.ws.send(Buffer.from(data, 'binary')); } else { obj.ws.send(data); } } catch (e) { } }
2017-10-24 00:09:58 +03:00
// Disconnect this user
obj . close = function ( arg ) {
if ( ( arg == 1 ) || ( arg == null ) ) { try { obj . ws . close ( ) ; obj . parent . parent . debug ( 1 , 'Soft disconnect' ) ; } catch ( e ) { console . log ( e ) ; } } // Soft close, close the websocket
if ( arg == 2 ) { try { obj . ws . _socket . _parent . end ( ) ; obj . parent . parent . debug ( 1 , 'Hard disconnect' ) ; } catch ( e ) { console . log ( e ) ; } } // Hard close, close the TCP socket
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 ) {
if ( obj . common . validateArray ( meshpath , 1 ) == false ) return null ;
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 ] ;
var path = obj . parent . path . join ( obj . parent . filespath , domainx , rootfoldersplit [ 0 ] + "-" + rootfoldersplit [ 2 ] ) ;
for ( var i = 1 ; i < meshpath . length ; i ++ ) { if ( obj . common . IsFilenameValid ( meshpath [ i ] ) == false ) { path = null ; break ; } path += ( "/" + meshpath [ i ] ) ; }
return path ;
}
//
function copyFile ( src , dest , func , tag ) {
2018-08-30 22:05:23 +03:00
//var ss = obj.fs.createReadStream(src, { flags: 'rb' });
2018-04-04 03:07:48 +03:00
//var ds = obj.fs.createWriteStream(dest, { flags: 'wb' });
var ss = obj . fs . createReadStream ( src ) ;
var ds = obj . fs . createWriteStream ( dest ) ;
ss . fs = obj . fs ;
2018-08-30 22:05:23 +03:00
ss . pipe ( ds ) ;
2018-04-04 03:07:48 +03:00
ds . ss = ss ;
/ *
if ( ! this . _copyStreams ) { this . _copyStreams = { } ; this . _copyStreamID = 0 ; }
ss . id = this . _copyStreamID ++ ;
this . _copyStreams [ ss . id ] = ss ;
* /
2018-08-30 22:05:23 +03:00
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 ( ) { /*delete this.ss.fs._copyStreams[this.ss.id];*/ func ( tag ) ; } ) ;
}
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 ) {
if ( obj . common . validateString ( command . nodeid , 8 , 128 ) == false ) return false ;
var splitnodeid = command . nodeid . split ( '/' ) ;
// Check that we are in the same domain and the user has rights over this node.
if ( ( splitnodeid [ 0 ] == 'node' ) && ( splitnodeid [ 1 ] == domain . id ) ) {
// See if the node is connected
var agent = obj . parent . wsagents [ command . nodeid ] ;
if ( agent != null ) {
// Check if we have permission to send a message to that node
var rights = user . links [ agent . dbMeshKey ] ;
if ( ( rights != 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.
command . rights = rights . rights ; // Add user rights flags to the message
delete command . nodeid ; // Remove the nodeid since it's implyed.
try { agent . send ( JSON . stringify ( command ) ) ; } catch ( ex ) { }
}
} else {
// Check if a peer server is connected to this agent
var routing = obj . parent . parent . GetRoutingServerId ( command . nodeid , 1 ) ; // 1 = MeshAgent routing type
if ( routing != null ) {
// Check if we have permission to send a message to that node
var rights = user . links [ routing . meshid ] ;
if ( ( rights != null ) && ( ( rights . rights & 8 ) || ( rights . rights & 256 ) ) ) { // 8 is remote control permission
command . fromSessionid = ws . sessionId ; // Set the session id, required for responses.
command . rights = rights . rights ; // Add user rights flags to the message
obj . parent . parent . multiServer . DispatchMessageSingleServer ( command , routing . serverid ) ;
}
}
}
}
return true ;
}
2017-10-24 00:09:58 +03:00
try {
// Check if the user is logged in
if ( user == null ) { try { obj . ws . close ( ) ; } catch ( e ) { } return ; }
2018-10-16 20:52:05 +03:00
// Associate this websocket session with the web session
2018-11-07 06:41:21 +03:00
obj . ws . userid = req . session . userid ;
obj . 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.
require ( 'crypto' ) . randomBytes ( 20 , function ( err , randombuf ) {
obj . ws . sessionId = user . _id + '/' + randombuf . toString ( 'hex' ) ;
// Add this web socket session to session list
obj . parent . wssessions2 [ ws . sessionId ] = obj . ws ;
if ( ! obj . parent . wssessions [ user . _id ] ) { obj . parent . wssessions [ user . _id ] = [ ws ] ; } else { obj . parent . wssessions [ user . _id ] . push ( obj . ws ) ; }
if ( obj . parent . parent . multiServer == null ) {
obj . parent . parent . DispatchEvent ( [ '*' ] , obj , { action : 'wssessioncount' , username : user . name , count : obj . parent . wssessions [ user . _id ] . length , nolog : 1 , domain : obj . domain . id } ) ;
} else {
obj . parent . recountSessions ( obj . ws . sessionId ) ; // Recount sessions
}
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
if ( obj . parent . parent . multiServer != null ) { obj . parent . parent . multiServer . DispatchMessage ( { action : 'sessionStart' , sessionid : obj . ws . sessionId } ) ; }
// Handle events
obj . ws . HandleEvent = function ( source , event ) {
if ( ! event . domain || event . domain == obj . domain . id ) {
try {
if ( event == 'close' ) { try { delete req . session ; } catch ( ex ) { } obj . close ( ) ; }
else if ( event == 'resubscribe' ) { user . subscriptions = obj . parent . subscribe ( user . _id , ws ) ; }
else if ( event == 'updatefiles' ) { updateUserFiles ( user , ws , domain ) ; }
else { ws . send ( JSON . stringify ( { action : 'event' , event : event } ) ) ; }
} catch ( e ) { }
}
} ;
user . subscriptions = obj . parent . subscribe ( user . _id , ws ) ; // Subscribe to events
try { obj . ws . _socket . setKeepAlive ( true , 240000 ) ; } catch ( ex ) { } // Set TCP keep alive
// Send current server statistics
obj . SendServerStats = function ( ) {
obj . db . getStats ( function ( data ) {
var os = require ( 'os' ) ;
var stats = { action : 'serverstats' , totalmem : os . totalmem ( ) , freemem : os . freemem ( ) } ;
if ( obj . parent . parent . platform != 'win32' ) { stats . cpuavg = os . loadavg ( ) ; } // else { stats.cpuavg = [ 0.2435345, 0.523234234, 0.6435345345 ]; }
var serverStats = { "User Accounts" : Object . keys ( obj . parent . users ) . length , "Device Groups" : Object . keys ( obj . parent . meshes ) . length , "Connected Agents" : Object . keys ( obj . parent . wsagents ) . length , "Connected Users" : Object . keys ( obj . parent . wssessions2 ) . length } ;
if ( obj . parent . parent . mpsserver != null ) { serverStats [ 'Connected Intel® AMT' ] = Object . keys ( obj . parent . parent . mpsserver . ciraConnections ) . length ; }
stats . values = { "Server State" : serverStats , "Database" : { "Records" : data . total , "Users" : data . users , "Device Groups" : data . meshes , "Devices" : data . nodes , "Device NetInfo" : data . nodeInterfaces , "Device Power Event" : data . powerEvents , "Notes" : data . notes , "Connection Records" : data . connectEvents , "SMBios" : data . smbios } }
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
ws . on ( 'error' , function ( err ) { console . log ( err ) ; } ) ;
// If the web socket is closed
ws . on ( 'close' , function ( req ) {
obj . parent . parent . RemoveAllEventDispatch ( ws ) ;
if ( obj . serverStatsTimer != null ) { clearInterval ( obj . serverStatsTimer ) ; obj . serverStatsTimer = null ; }
if ( req . session && req . session . ws && req . session . ws == ws ) { delete req . session . ws ; }
if ( obj . parent . wssessions2 [ ws . sessionId ] ) { delete obj . parent . wssessions2 [ ws . sessionId ] ; }
if ( obj . parent . wssessions [ ws . userid ] ) {
var i = obj . parent . wssessions [ ws . userid ] . indexOf ( ws ) ;
if ( i >= 0 ) {
obj . parent . wssessions [ ws . userid ] . splice ( i , 1 ) ;
var user = obj . parent . users [ ws . userid ] ;
if ( user ) {
if ( obj . parent . parent . multiServer == null ) {
obj . parent . parent . DispatchEvent ( [ '*' ] , obj , { action : 'wssessioncount' , username : user . name , count : obj . parent . wssessions [ ws . userid ] . length , nolog : 1 , domain : obj . domain . id } ) ;
2018-09-27 00:58:55 +03:00
} else {
2019-01-05 04:59:13 +03:00
obj . parent . recountSessions ( ws . sessionId ) ; // Recount sessions
2018-09-27 00:58:55 +03:00
}
}
2019-01-05 04:59:13 +03:00
if ( obj . parent . wssessions [ ws . userid ] . length == 0 ) { delete obj . parent . wssessions [ ws . userid ] ; }
2018-09-27 00:58:55 +03:00
}
2019-01-05 04:59:13 +03:00
}
// If we have peer servers, inform them of the disconnected session
if ( obj . parent . parent . multiServer != null ) { obj . parent . parent . multiServer . DispatchMessage ( { action : 'sessionEnd' , sessionid : ws . sessionId } ) ; }
} ) ;
// Figure out the MPS port, use the alias if set
var mpsport = ( ( obj . args . mpsaliasport != null ) ? obj . args . mpsaliasport : obj . args . mpsport ) ;
var httpport = ( ( obj . args . aliasport != null ) ? obj . args . aliasport : obj . args . port ) ;
// Build server information object
2019-01-16 23:04:48 +03:00
var serverinfo = { name : obj . parent . certificates . CommonName , mpsname : obj . parent . certificates . AmtMpsName , mpsport : mpsport , mpspass : obj . args . mpspass , port : httpport , emailcheck : ( ( obj . parent . parent . mailserver != null ) && ( domain . auth != 'sspi' ) ) , domainauth : ( domain . auth == 'sspi' ) } ;
2019-01-05 04:59:13 +03:00
if ( obj . args . notls == true ) { serverinfo . https = false ; } else { serverinfo . https = true ; serverinfo . redirport = obj . args . redirport ; }
// 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
var userinfo = obj . common . Clone ( obj . parent . users [ user . _id ] ) ;
delete userinfo . hash ;
2019-01-16 00:51:07 +03:00
delete userinfo . passhint ;
delete userinfo . salt ;
delete userinfo . type ;
delete userinfo . domain ;
delete userinfo . subscriptions ;
delete userinfo . passtype ;
if ( userinfo . otpsecret ) { userinfo . otpsecret = 1 ; }
2019-01-05 04:59:13 +03:00
try { ws . send ( JSON . stringify ( { action : 'userinfo' , userinfo : userinfo } ) ) ; } catch ( ex ) { }
// 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 ; }
if ( obj . common . validateString ( command . action , 3 , 32 ) == false ) return ; // Action must be a string between 3 and 32 chars
switch ( command . action ) {
case 'ping' : { try { ws . send ( JSON . stringify ( { action : 'pong' } ) ) ; } catch ( ex ) { } break ; }
case 'serverstats' :
{
if ( ( user . siteadmin ) != 0 ) {
if ( obj . common . validateInt ( command . interval , 1000 , 1000000 ) == false ) {
// Clear the timer
if ( obj . serverStatsTimer != null ) { clearInterval ( obj . serverStatsTimer ) ; obj . serverStatsTimer = null ; }
2018-01-05 02:59:57 +03:00
} else {
2019-01-05 04:59:13 +03:00
// Set the timer
obj . SendServerStats ( ) ;
obj . serverStatsTimer = setInterval ( obj . SendServerStats , command . interval ) ;
2018-01-05 02:59:57 +03:00
}
2019-01-05 04:59:13 +03:00
}
break ;
}
case 'meshes' :
{
// Request a list of all meshes this user as rights to
var docs = [ ] ;
for ( i in user . links ) { if ( obj . parent . meshes [ i ] ) { docs . push ( obj . parent . meshes [ i ] ) ; } }
try { ws . send ( JSON . stringify ( { action : 'meshes' , meshes : docs , tag : command . tag } ) ) ; } catch ( ex ) { }
break ;
}
case 'nodes' :
{
var links = [ ] ;
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 ( obj . common . validateString ( meshid , 0 , 128 ) == false ) return ;
if ( meshid . split ( '/' ) . length == 0 ) { meshid = 'mesh/' + domain . id + '/' + command . meshid ; }
if ( user . links [ meshid ] != null ) { links . push ( meshid ) ; }
}
2017-10-24 00:09:58 +03:00
2019-01-05 04:59:13 +03:00
// Request a list of all nodes
obj . db . GetAllTypeNoTypeFieldMeshFiltered ( links , domain . id , 'node' , function ( err , docs ) {
var r = { } ;
for ( i in docs ) {
// Add the connection state
var state = obj . parent . parent . GetConnectivityState ( docs [ i ] . _id ) ;
if ( state ) {
docs [ i ] . conn = state . connectivity ;
docs [ i ] . pwr = state . powerState ;
if ( ( state . connectivity & 1 ) != 0 ) { var agent = obj . parent . wsagents [ docs [ i ] . _id ] ; if ( agent != null ) { docs [ i ] . agct = agent . connectTime ; } }
if ( ( state . connectivity & 2 ) != 0 ) { var cira = obj . parent . parent . mpsserver . ciraConnections [ docs [ i ] . _id ] ; if ( cira != null ) { docs [ i ] . cict = cira . tag . connectTime ; } }
}
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-01-05 04:59:13 +03:00
r [ meshid ] . push ( docs [ i ] ) ;
}
try { ws . send ( JSON . stringify ( { action : 'nodes' , nodes : r , tag : command . tag } ) ) ; } catch ( ex ) { }
} ) ;
break ;
}
case 'powertimeline' :
{
// Query the database for the power timeline for a given node
// The result is a compacted array: [ startPowerState, startTimeUTC, powerState ] + many[ deltaTime, powerState ]
if ( obj . common . validateString ( command . nodeid , 0 , 128 ) == false ) return ;
obj . db . getPowerTimeline ( command . nodeid , function ( err , docs ) {
if ( err == null && docs . length > 0 ) {
var timeline = [ ] , time = null , previousPower ;
for ( i in docs ) {
var doc = docs [ i ] ;
if ( time == null ) {
// First element
time = doc . time ;
if ( doc . oldPower ) { timeline . push ( doc . oldPower ) ; } else { timeline . push ( 0 ) ; }
timeline . push ( time ) ;
timeline . push ( doc . power ) ;
previousPower = doc . power ;
} else {
// Delta element
if ( ( previousPower != doc . power ) && ( ( doc . time - time ) > 60000 ) ) { // To boost speed, any blocks less than a minute get approximated.
// Create a new timeline
timeline . push ( doc . time - time ) ;
2017-10-24 00:09:58 +03:00
timeline . push ( doc . power ) ;
2019-01-05 04:59:13 +03:00
time = doc . time ;
2017-10-24 00:09:58 +03:00
previousPower = doc . power ;
} else {
2019-01-05 04:59:13 +03:00
// Extend the previous timeline
if ( ( timeline . length >= 6 ) && ( timeline [ timeline . length - 3 ] == doc . power ) ) { // We can merge the block with the previous block
timeline [ timeline . length - 4 ] += ( timeline [ timeline . length - 2 ] + ( doc . time - time ) ) ;
timeline . pop ( ) ;
timeline . pop ( ) ;
} else { // Extend the last block in the timeline
timeline [ timeline . length - 2 ] += ( doc . time - time ) ;
timeline [ timeline . length - 1 ] = doc . power ;
2017-10-24 00:09:58 +03:00
}
2019-01-05 04:59:13 +03:00
time = doc . time ;
previousPower = doc . power ;
2017-10-24 00:09:58 +03:00
}
}
}
2019-01-05 04:59:13 +03:00
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 = obj . 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 ) { } }
}
} ) ;
break ;
}
case 'lastconnect' :
{
if ( obj . common . validateString ( command . nodeid , 0 , 128 ) == false ) return ;
// Query the database for the last time this node connected
obj . 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 } ) ) ; } catch ( ex ) { } }
} ) ;
break ;
}
case 'files' :
{
// Send the full list of server files to the browser app
if ( ( user != null ) && ( user . siteadmin != null ) && ( user . siteadmin & 8 ) != 0 ) { updateUserFiles ( user , ws , domain ) ; }
break ;
}
case 'fileoperation' :
{
// Check permissions
if ( ( user . siteadmin & 8 ) != 0 ) {
// Perform a file operation (Create Folder, Delete Folder, Delete File...)
if ( obj . common . validateString ( command . fileop , 4 , 16 ) == false ) return ;
var sendUpdate = true , path = meshPathToRealPath ( command . path , user ) ; // This will also check access rights
if ( path == null ) break ;
if ( ( command . fileop == 'createfolder' ) && ( obj . common . IsFilenameValid ( command . newfolder ) == true ) ) { try { obj . fs . mkdirSync ( path + "/" + command . newfolder ) ; } catch ( e ) { } } // Create a new folder
else if ( command . fileop == 'delete' ) { // Delete
if ( obj . common . validateArray ( command . delfiles , 1 ) == false ) return ;
for ( i in command . delfiles ) {
if ( obj . common . IsFilenameValid ( command . delfiles [ i ] ) == true ) {
var fullpath = obj . path . join ( path , command . delfiles [ i ] ) ;
if ( command . rec == true ) {
deleteFolderRecursive ( fullpath ) ; // TODO, make this an async function
} else {
try { obj . fs . rmdirSync ( fullpath ) ; } catch ( e ) { try { obj . fs . unlinkSync ( fullpath ) ; } catch ( e ) { } }
2018-09-25 21:51:40 +03:00
}
}
2017-10-24 00:09:58 +03:00
}
}
2019-01-05 04:59:13 +03:00
else if ( ( command . fileop == 'rename' ) && ( obj . common . IsFilenameValid ( command . oldname ) == true ) && ( obj . common . IsFilenameValid ( command . newname ) == true ) ) { try { obj . fs . renameSync ( path + "/" + command . oldname , path + "/" + command . newname ) ; } catch ( e ) { } } // Rename
else if ( ( command . fileop == 'copy' ) || ( command . fileop == 'move' ) ) {
if ( obj . common . validateArray ( command . names , 1 ) == false ) return ;
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 ) {
var s = obj . path . join ( scpath , command . names [ i ] ) , d = obj . path . join ( path , command . names [ i ] ) ;
sendUpdate = false ;
2019-01-07 07:32:12 +03:00
copyFile ( s , d , function ( op ) { if ( op != null ) { obj . fs . unlink ( op , function ( err ) { obj . parent . parent . DispatchEvent ( [ user . _id ] , obj , 'updatefiles' ) ; } ) ; } else { obj . 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-01-05 04:59:13 +03:00
if ( sendUpdate == true ) { obj . parent . parent . DispatchEvent ( [ user . _id ] , obj , 'updatefiles' ) ; } // Fire an event causing this user to update this files
}
break ;
}
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
obj . db . GetEvents ( filter , domain . id , function ( err , docs ) { if ( err != null ) return ; try { ws . send ( JSON . stringify ( { action : 'events' , events : docs , user : command . user , tag : command . tag } ) ) ; } catch ( ex ) { } } ) ;
} else {
2018-01-05 02:59:57 +03:00
// Send the list of most recent events for this session, up to 'limit' count
2019-01-05 04:59:13 +03:00
obj . db . GetEventsWithLimit ( filter , domain . id , command . limit , function ( err , docs ) { if ( err != null ) return ; try { ws . send ( JSON . stringify ( { action : 'events' , events : docs , user : command . user , tag : command . tag } ) ) ; } catch ( ex ) { } } ) ;
}
} else if ( obj . common . validateString ( command . nodeid , 0 , 128 ) == true ) { // Device filtered events
// TODO: Check that the user has access to this nodeid
var limit = 10000 ;
if ( obj . common . validateInt ( command . limit , 1 , 60000 ) == true ) { limit = command . limit ; }
// Send the list of most recent events for this session, up to 'limit' count
obj . 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 ) { } } ) ;
} else {
// All events
var filter = user . subscriptions ;
if ( ( command . limit == null ) || ( typeof command . limit != 'number' ) ) {
// Send the list of all events for this session
obj . db . GetEvents ( filter , domain . id , function ( err , docs ) { 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
obj . db . GetEventsWithLimit ( filter , domain . id , command . limit , function ( err , docs ) { 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 'clearevents' :
{
// Delete all events
if ( user . siteadmin != 0xFFFFFFFF ) break ;
obj . db . RemoveAllEvents ( domain . id ) ;
obj . parent . parent . DispatchEvent ( [ '*' , 'server-global' ] , obj , { action : 'clearevents' , nolog : 1 , domain : domain . id } ) ;
break ;
}
case 'users' :
{
// Request a list of all users
if ( ( user . siteadmin & 2 ) == 0 ) break ;
var docs = [ ] ;
for ( i in obj . parent . users ) {
if ( ( obj . parent . users [ i ] . domain == domain . id ) && ( obj . parent . users [ i ] . name != '~' ) ) {
var userinfo = obj . common . Clone ( obj . parent . users [ i ] ) ;
delete userinfo . hash ;
delete userinfo . passhint ;
delete userinfo . salt ;
delete userinfo . type ;
delete userinfo . domain ;
delete userinfo . subscriptions ;
delete userinfo . passtype ;
2019-01-16 00:51:07 +03:00
delete userinfo . otpsecret ;
2019-01-05 04:59:13 +03:00
docs . push ( userinfo ) ;
}
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 ;
}
case 'changeemail' :
{
// Change the email address
2019-01-16 23:04:48 +03:00
if ( domain . auth == 'sspi' ) return ;
2019-01-05 04:59:13 +03:00
if ( obj . common . validateEmail ( command . email , 1 , 256 ) == false ) return ;
if ( obj . parent . users [ req . session . userid ] . email != command . email ) {
// Check if this email is already validated on a different account
obj . db . GetUserWithVerifiedEmail ( domain . id , command . email , function ( err , docs ) {
if ( docs . length > 0 ) {
// Notify the duplicate email error
try { ws . send ( JSON . stringify ( { action : 'msg' , type : 'notify' , value : 'Failed to change email address, another account already using: <b>' + EscapeHtml ( command . email ) + '</b>.' } ) ) ; } catch ( ex ) { }
} else {
// Update the user's email
var oldemail = user . email ;
user . email = command . email ;
user . emailVerified = false ;
obj . parent . db . SetUser ( user ) ;
// Event the change
var userinfo = obj . common . Clone ( user ) ;
2017-12-13 03:04:54 +03:00
delete userinfo . hash ;
delete userinfo . passhint ;
delete userinfo . salt ;
delete userinfo . type ;
delete userinfo . domain ;
delete userinfo . subscriptions ;
delete userinfo . passtype ;
2019-01-16 00:51:07 +03:00
delete userinfo . otpsecret ;
2019-01-05 04:59:13 +03:00
var message = { etype : 'user' , username : userinfo . name , account : userinfo , action : 'accountchange' , domain : domain . id } ;
if ( oldemail != null ) {
message . msg = 'Changed email of user ' + userinfo . name + ' from ' + oldemail + ' to ' + user . email ;
2018-05-17 01:49:12 +03:00
} else {
2019-01-05 04:59:13 +03:00
message . msg = 'Set email of user ' + userinfo . name + ' to ' + user . email ;
2018-05-17 01:49:12 +03:00
}
2019-01-05 04:59:13 +03:00
obj . parent . parent . DispatchEvent ( [ '*' , 'server-users' , user . _id ] , obj , message ) ;
2019-01-16 23:04:48 +03:00
// Send the verification email
if ( ( obj . parent . parent . mailserver != null ) && ( domain . auth != 'sspi' ) ) { obj . 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-01-16 23:04:48 +03:00
if ( domain . auth == 'sspi' ) return ;
2019-01-05 04:59:13 +03:00
if ( obj . common . validateString ( command . email , 3 , 1024 ) == false ) return ;
2019-01-16 23:04:48 +03:00
if ( ( obj . parent . parent . mailserver != null ) && ( obj . parent . users [ req . session . userid ] . email == command . email ) ) {
// Send the verification email
obj . 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 ;
if ( obj . parent . parent . multiServer == null ) {
// No peering, use simple session counting
for ( i in obj . parent . wssessions ) { if ( obj . parent . wssessions [ i ] [ 0 ] . domainid == domain . id ) { wssessions [ i ] = obj . parent . wssessions [ i ] . length ; } }
} else {
// We have peer servers, use more complex session counting
for ( i in obj . parent . sessionsCount ) { if ( i . split ( '/' ) [ 1 ] == domain . id ) { wssessions [ i ] = obj . 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
if ( ( user . siteadmin & 2 ) == 0 ) break ;
if ( obj . common . validateString ( command . userid , 1 , 2048 ) == false ) break ;
var delusersplit = command . userid . split ( '/' ) , deluserid = command . userid , deluser = obj . parent . users [ deluserid ] ;
if ( ( deluser == null ) || ( delusersplit . length != 3 ) || ( delusersplit [ 1 ] != domain . id ) ) break ; // Invalid domain, operation only valid for current domain
if ( ( deluser . siteadmin != null ) && ( deluser . siteadmin > 0 ) && ( user . siteadmin != 0xFFFFFFFF ) ) break ; // Need full admin to remote another administrator
// Remove all the mesh links to this user
if ( deluser . links != null ) {
for ( meshid in deluser . links ) {
// Get the mesh
mesh = obj . parent . meshes [ meshid ] ;
if ( mesh ) {
// Remove user from the mesh
if ( mesh . links [ deluser . _id ] != null ) { delete mesh . links [ deluser . _id ] ; obj . parent . db . Set ( mesh ) ; }
// Notify mesh change
change = 'Removed user ' + deluser . name + ' from group ' + mesh . name ;
obj . parent . parent . DispatchEvent ( [ '*' , mesh . _id , deluser . _id , user . _id ] , obj , { etype : 'mesh' , username : user . name , userid : user . _id , meshid : mesh . _id , name : mesh . name , mtype : mesh . mtype , desc : mesh . desc , action : 'meshchange' , links : mesh . links , msg : change , domain : domain . id } ) ;
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-01-05 04:59:13 +03:00
// Remove notes for this user
obj . db . Remove ( 'nt' + deluser . _id ) ;
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 {
var deluserpath = obj . parent . getServerRootFilePath ( deluser ) ;
if ( deluserpath != null ) { obj . parent . deleteFolderRec ( deluserpath ) ; }
} catch ( e ) { }
2017-10-24 00:09:58 +03:00
2019-01-05 04:59:13 +03:00
obj . db . Remove ( deluserid ) ;
delete obj . parent . users [ deluserid ] ;
obj . parent . parent . DispatchEvent ( [ '*' , 'server-users' ] , obj , { etype : 'user' , userid : deluserid , username : deluser . name , action : 'accountremove' , msg : 'Account removed' , domain : domain . id } ) ;
obj . parent . parent . DispatchEvent ( [ deluserid ] , obj , 'close' ) ;
2017-10-24 00:09:58 +03:00
2019-01-05 04:59:13 +03:00
break ;
}
case 'adduser' :
{
// Add a new user account
if ( ( user . siteadmin & 2 ) == 0 ) break ;
if ( obj . common . validateUsername ( command . username , 1 , 64 ) == false ) break ; // Username is between 1 and 64 characters, no spaces
if ( obj . common . validateString ( command . pass , 1 , 256 ) == false ) break ; // Password is between 1 and 256 characters
if ( obj . common . checkPasswordRequirements ( command . pass , domain . passwordrequirements ) == false ) break ; // Password does not meet requirements
if ( ( command . email != null ) && ( obj . common . validateEmail ( command . email , 1 , 256 ) == false ) ) break ; // Check if this is a valid email address
var newusername = command . username , newuserid = 'user/' + domain . id + '/' + command . username . toLowerCase ( ) ;
if ( newusername == '~' ) break ; // This is a reserved user name
if ( ! obj . parent . users [ newuserid ] ) {
var newuser = { type : 'user' , _id : newuserid , name : newusername , creation : Date . now ( ) , domain : domain . id } ;
if ( command . email != null ) { newuser . email = command . email ; } // Email
obj . parent . users [ newuserid ] = newuser ;
// Create a user, generate a salt and hash the password
require ( './pass' ) . hash ( command . pass , function ( err , salt , hash ) {
if ( err ) throw err ;
newuser . salt = salt ;
newuser . hash = hash ;
obj . db . SetUser ( newuser ) ;
var newuser2 = obj . common . Clone ( newuser ) ;
if ( newuser2 . subscriptions ) { delete newuser2 . subscriptions ; }
if ( newuser2 . salt ) { delete newuser2 . salt ; }
if ( newuser2 . hash ) { delete newuser2 . hash ; }
obj . parent . parent . DispatchEvent ( [ '*' , 'server-users' ] , obj , { etype : 'user' , username : newusername , account : newuser2 , action : 'accountcreate' , msg : 'Account created, email is ' + command . email , domain : domain . id } ) ;
} ) ;
2017-10-24 00:09:58 +03:00
}
2019-01-05 04:59:13 +03:00
break ;
}
case 'edituser' :
{
// Edit a user account, may involve changing email or administrator permissions
if ( ( ( user . siteadmin & 2 ) != 0 ) || ( user . name == command . name ) ) {
var chguserid = 'user/' + domain . id + '/' + command . name . toLowerCase ( ) , chguser = obj . parent . users [ chguserid ] ;
change = 0 ;
if ( chguser ) {
if ( obj . common . validateString ( command . email , 1 , 256 ) && ( chguser . email != command . email ) ) { chguser . email = command . email ; change = 1 ; }
if ( ( command . emailVerified === true || command . emailVerified === false ) && ( chguser . emailVerified != command . emailVerified ) ) { chguser . emailVerified = command . emailVerified ; change = 1 ; }
if ( ( obj . 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 ; }
if ( ( user . siteadmin == 0xFFFFFFFF ) && obj . common . validateInt ( command . siteadmin ) && ( chguser . siteadmin != command . siteadmin ) ) { chguser . siteadmin = command . siteadmin ; change = 1 ; }
if ( change == 1 ) {
obj . db . SetUser ( chguser ) ;
obj . parent . parent . DispatchEvent ( [ chguser . _id ] , obj , 'resubscribe' ) ;
var userinfo = obj . common . Clone ( chguser ) ;
delete userinfo . hash ;
delete userinfo . passhint ;
delete userinfo . salt ;
delete userinfo . type ;
delete userinfo . domain ;
delete userinfo . subscriptions ;
delete userinfo . passtype ;
2019-01-16 00:51:07 +03:00
delete userinfo . otpsecret ;
2019-01-05 04:59:13 +03:00
obj . parent . parent . DispatchEvent ( [ '*' , 'server-users' , user . _id , chguser . _id ] , obj , { etype : 'user' , username : user . name , account : userinfo , action : 'accountchange' , msg : 'Account changed: ' + command . name , domain : domain . id } ) ;
}
if ( ( chguser . siteadmin ) && ( chguser . siteadmin != 0xFFFFFFFF ) && ( chguser . siteadmin & 32 ) ) {
obj . parent . parent . DispatchEvent ( [ chguser . _id ] , obj , 'close' ) ; // Disconnect all this user's sessions
2017-10-24 00:09:58 +03:00
}
}
}
2019-01-05 04:59:13 +03:00
break ;
}
case 'changeuserpass' :
{
// Change a user's password
if ( user . siteadmin != 0xFFFFFFFF ) break ;
if ( obj . common . validateString ( command . user , 1 , 256 ) == false ) break ;
if ( obj . common . validateString ( command . pass , 1 , 256 ) == false ) break ;
if ( obj . common . checkPasswordRequirements ( command . pass , domain . passwordrequirements ) == false ) break ; // Password does not meet requirements
var chguserid = 'user/' + domain . id + '/' + command . user . toLowerCase ( ) , chguser = obj . parent . users [ chguserid ] ;
if ( chguser && chguser . salt ) {
// Compute the password hash & save it
require ( './pass' ) . hash ( command . pass , chguser . salt , function ( err , hash ) { if ( ! err ) { chguser . hash = hash ; obj . db . SetUser ( chguser ) ; } } ) ;
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 ;
if ( obj . common . validateString ( command . userid , 1 , 2048 ) == false ) break ;
if ( obj . common . validateString ( command . msg , 1 , 4096 ) == false ) break ;
// Create the notification message
var notification = { "action" : "msg" , "type" : "notify" , "value" : "<b>" + user . name + "</b>: " + EscapeHtml ( command . msg ) , "userid" : user . _id , "username" : user . name } ;
// Get the list of sessions for this user
var sessions = obj . parent . wssessions [ command . userid ] ;
if ( sessions != null ) { for ( i in sessions ) { try { sessions [ i ] . send ( JSON . stringify ( notification ) ) ; } catch ( ex ) { } } }
if ( obj . parent . parent . multiServer != null ) {
// TODO: Add multi-server support
}
break ;
}
case 'meshmessenger' :
{
// Send a notification message to a user
if ( ( user . siteadmin & 2 ) == 0 ) break ;
// Setup a user-to-user session
if ( obj . common . validateString ( command . userid , 1 , 2048 ) ) {
2018-04-06 02:45:56 +03:00
// Create the notification message
2019-01-05 04:59:13 +03:00
var notification = {
"action" : "msg" , "type" : "notify" , "value" : "<b>" + user . name + "</b>: Chat Request, Click here to accept." , "userid" : user . _id , "username" : user . name , "tag" : 'meshmessenger/' + encodeURIComponent ( command . userid ) + '/' + encodeURIComponent ( user . _id )
} ;
2018-04-06 02:45:56 +03:00
// Get the list of sessions for this user
var sessions = obj . 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
2018-12-02 10:41:57 +03:00
if ( obj . parent . parent . multiServer != null ) {
// TODO: Add multi-server support
}
}
2019-01-05 04:59:13 +03:00
// Setup a user-to-node session
if ( obj . common . validateString ( command . nodeid , 1 , 2048 ) ) {
if ( obj . 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-01-05 04:59:13 +03:00
// Create the server url
var httpsPort = ( ( obj . parent . args . aliasport == null ) ? obj . parent . args . port : obj . parent . args . aliasport ) ; // Use HTTPS alias port is specified
var xdomain = ( domain . dns == null ) ? domain . id : '' ;
if ( xdomain != '' ) xdomain += "/" ;
var url = "http" + ( obj . args . notls ? '' : 's' ) + "://" + obj . parent . getWebServerName ( domain ) + ":" + httpsPort + "/" + xdomain + "messenger?id=meshmessenger/" + encodeURIComponent ( command . nodeid ) + "/" + encodeURIComponent ( user . _id ) + "&title=" + encodeURIComponent ( user . name ) ;
2018-12-02 10:41:57 +03:00
2019-01-05 04:59:13 +03:00
// Create the notification message
routeCommandToNode ( { "action" : "openUrl" , "nodeid" : command . nodeid , "userid" : user . _id , "username" : user . name , "url" : url } ) ;
}
break ;
}
case 'serverversion' :
{
// Check the server version
if ( ( user . siteadmin & 16 ) == 0 ) break ;
obj . parent . parent . getLatestServerVersion ( function ( currentVersion , latestVersion ) { try { ws . send ( JSON . stringify ( { action : 'serverversion' , current : currentVersion , latest : latestVersion } ) ) ; } catch ( ex ) { } } ) ;
break ;
}
case 'serverupdate' :
{
// Perform server update
if ( ( user . siteadmin & 16 ) == 0 ) break ;
obj . parent . parent . performServerUpdate ( ) ;
break ;
}
case 'servererrors' :
{
// Load the server error log
if ( ( user . siteadmin & 16 ) == 0 ) break ;
2019-01-05 23:04:18 +03:00
obj . parent . parent . fs . readFile ( obj . 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-01-07 07:32:12 +03:00
obj . parent . parent . fs . unlink ( obj . parent . parent . getConfigFilePath ( 'mesherrors.txt' ) , function ( err ) { } ) ;
2019-01-05 04:59:13 +03:00
break ;
}
case 'createmesh' :
{
2019-01-16 23:04:48 +03:00
// In some situations, we need a verified email address to create a device group.
if ( ( obj . parent . parent . mailserver != null ) && ( domain . auth != 'sspi' ) && ( user . emailVerified !== true ) && ( user . siteadmin != 0xFFFFFFFF ) ) return ; // User must verify it's email first.
2019-01-05 04:59:13 +03:00
// Create mesh
if ( obj . common . validateString ( command . meshname , 1 , 64 ) == false ) break ; // Meshname is between 1 and 64 characters
if ( obj . common . validateString ( command . desc , 0 , 1024 ) == false ) break ; // Mesh description is between 0 and 1024 characters
// We only create Agent-less Intel AMT mesh (Type1), or Agent mesh (Type2)
if ( ( command . meshtype == 1 ) || ( command . meshtype == 2 ) ) {
// Create a type 1 agent-less Intel AMT mesh.
obj . parent . crypto . randomBytes ( 48 , function ( err , buf ) {
meshid = 'mesh/' + domain . id + '/' + buf . toString ( 'base64' ) . replace ( /\+/g , '@' ) . replace ( /\//g , '$' ) ;
var links = { } ;
links [ user . _id ] = { name : user . name , rights : 0xFFFFFFFF } ;
mesh = { type : 'mesh' , _id : meshid , name : command . meshname , mtype : command . meshtype , desc : command . desc , domain : domain . id , links : links } ;
obj . db . Set ( obj . common . escapeLinksFieldName ( mesh ) ) ;
obj . parent . meshes [ meshid ] = mesh ;
obj . parent . parent . AddEventDispatch ( [ meshid ] , ws ) ;
if ( user . links == null ) user . links = { } ;
user . links [ meshid ] = { rights : 0xFFFFFFFF } ;
user . subscriptions = obj . parent . subscribe ( user . _id , ws ) ;
obj . db . SetUser ( user ) ;
obj . parent . parent . DispatchEvent ( [ '*' , meshid , user . _id ] , obj , { etype : 'mesh' , username : user . name , meshid : meshid , name : command . meshname , mtype : command . meshtype , desc : command . desc , action : 'createmesh' , links : links , msg : 'Mesh created: ' + command . meshname , domain : domain . id } ) ;
} ) ;
}
break ;
}
case 'deletemesh' :
{
// Delete a mesh and all computers within it
if ( obj . common . validateString ( command . meshid , 1 , 1024 ) == false ) break ; // Check the meshid
obj . db . Get ( command . meshid , function ( err , meshes ) {
if ( meshes . length != 1 ) return ;
mesh = obj . common . unEscapeLinksFieldName ( meshes [ 0 ] ) ;
// Check if this user has rights to do this
if ( mesh . links [ user . _id ] == null || mesh . links [ user . _id ] . rights != 0xFFFFFFFF ) 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-01-05 04:59:13 +03:00
// Fire the removal event first, because after this, the event will not route
obj . parent . parent . DispatchEvent ( [ '*' , command . meshid ] , obj , { etype : 'mesh' , username : user . name , meshid : command . meshid , name : command . meshname , action : 'deletemesh' , msg : 'Mesh deleted: ' + command . meshname , domain : domain . id } ) ;
// Remove all user links to this mesh
for ( i in meshes ) {
var links = meshes [ i ] . links ;
for ( var j in links ) {
var xuser = obj . parent . users [ j ] ;
delete xuser . links [ meshes [ i ] . _id ] ;
obj . db . SetUser ( xuser ) ;
obj . parent . parent . DispatchEvent ( [ xuser . _id ] , obj , 'resubscribe' ) ;
2018-12-08 03:36:27 +03:00
}
}
2019-01-05 04:59:13 +03:00
// Delete all files on the server for this mesh
try {
var meshpath = obj . parent . getServerRootFilePath ( mesh ) ;
if ( meshpath != null ) { obj . parent . deleteFolderRec ( meshpath ) ; }
} catch ( e ) { }
2018-12-08 03:36:27 +03:00
2019-01-05 04:59:13 +03:00
obj . parent . parent . RemoveEventDispatchId ( command . meshid ) ; // Remove all subscriptions to this mesh
obj . db . RemoveMesh ( command . meshid ) ; // Remove mesh from database
delete obj . parent . meshes [ command . meshid ] ; // Remove mesh from memory
} ) ;
break ;
}
case 'editmesh' :
{
// Change the name or description of a mesh
if ( obj . common . validateString ( command . meshid , 1 , 1024 ) == false ) break ; // Check the meshid
mesh = obj . parent . meshes [ command . meshid ] ;
change = '' ;
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-01-05 04:59:13 +03:00
if ( ( obj . 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 ( ( obj . 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 ( ( obj . 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 ; }
if ( change != '' ) { obj . db . Set ( obj . common . escapeLinksFieldName ( mesh ) ) ; obj . parent . parent . DispatchEvent ( [ '*' , mesh . _id , user . _id ] , obj , { etype : 'mesh' , username : user . name , meshid : mesh . _id , name : mesh . name , mtype : mesh . mtype , desc : mesh . desc , flags : mesh . flags , action : 'meshchange' , links : mesh . links , msg : change , domain : domain . id } ) ; }
2018-09-19 05:41:59 +03:00
}
2019-01-05 04:59:13 +03:00
break ;
}
case 'addmeshuser' :
{
if ( obj . common . validateString ( command . meshid , 1 , 1024 ) == false ) break ; // Check the meshid
if ( obj . common . validateString ( command . username , 1 , 64 ) == false ) break ; // Username is between 1 and 64 characters
if ( obj . common . validateInt ( command . meshadmin ) == false ) break ; // Mesh rights must be an integer
// Check if the user exists
var newuserid = 'user/' + domain . id + '/' + command . username . toLowerCase ( ) , newuser = obj . parent . users [ newuserid ] ;
if ( newuser == null ) {
// TODO: Send error back, user not found.
2017-10-24 00:09:58 +03:00
break ;
}
2019-01-05 04:59:13 +03:00
// Get the mesh
mesh = obj . parent . meshes [ command . meshid ] ;
if ( mesh ) {
// Check if this user has rights to do this
if ( mesh . links [ user . _id ] == null || ( ( mesh . links [ user . _id ] . rights & 2 ) == 0 ) ) return ;
if ( ( command . meshid . split ( '/' ) . length != 3 ) || ( command . meshid . 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
// Add mesh to user
if ( newuser . links == null ) newuser . links = { } ;
newuser . links [ command . meshid ] = { rights : command . meshadmin } ;
obj . db . SetUser ( newuser ) ;
obj . parent . parent . DispatchEvent ( [ newuser . _id ] , obj , 'resubscribe' ) ;
2017-10-24 00:09:58 +03:00
2019-01-05 04:59:13 +03:00
// Add a user to the mesh
mesh . links [ newuserid ] = { name : newuser . name , rights : command . meshadmin } ;
obj . db . Set ( obj . common . escapeLinksFieldName ( mesh ) ) ;
2017-10-24 00:09:58 +03:00
2019-01-05 04:59:13 +03:00
// Notify mesh change
obj . parent . parent . DispatchEvent ( [ '*' , mesh . _id , user . _id , newuserid ] , obj , { 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 } ) ;
2017-10-24 00:09:58 +03:00
}
2019-01-05 04:59:13 +03:00
break ;
}
case 'removemeshuser' :
{
if ( obj . common . validateString ( command . userid , 1 , 1024 ) == false ) break ; // Check userid
if ( obj . common . validateString ( command . meshid , 1 , 1024 ) == false ) break ; // Check meshid
if ( ( command . userid . split ( '/' ) . length != 3 ) || ( command . userid . split ( '/' ) [ 1 ] != domain . id ) ) return ; // Invalid domain, operation only valid for current domain
// Get the mesh
mesh = obj . parent . meshes [ command . meshid ] ;
if ( mesh ) {
// Check if this user has rights to do this
if ( mesh . links [ user . _id ] == null || ( ( mesh . links [ user . _id ] . rights & 2 ) == 0 ) ) return ;
// 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 = obj . 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 ] ;
obj . db . Set ( deluser ) ;
obj . parent . parent . DispatchEvent ( [ deluser . _id ] , obj , 'resubscribe' ) ;
}
2017-10-24 00:09:58 +03:00
}
2019-01-05 04:59:13 +03:00
// Remove user from the mesh
if ( mesh . links [ command . userid ] != null ) {
delete mesh . links [ command . userid ] ;
2018-07-14 05:18:43 +03:00
obj . db . Set ( obj . common . escapeLinksFieldName ( mesh ) ) ;
2017-10-24 00:09:58 +03:00
// Notify mesh change
2019-01-05 04:59:13 +03:00
obj . parent . parent . DispatchEvent ( [ '*' , mesh . _id , user . _id , command . userid ] , obj , { 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 } ) ;
2017-10-24 00:09:58 +03:00
}
}
2019-01-05 04:59:13 +03:00
break ;
}
case 'addamtdevice' :
{
if ( obj . args . wanonly == true ) return ; // This is a WAN-only server, local Intel AMT computers can't be added
if ( obj . common . validateString ( command . meshid , 1 , 1024 ) == false ) break ; // Check meshid
if ( ( command . meshid . split ( '/' ) . length != 3 ) || ( command . meshid . split ( '/' ) [ 1 ] != domain . id ) ) return ; // Invalid domain, operation only valid for current domain
if ( obj . common . validateString ( command . devicename , 1 , 256 ) == false ) break ; // Check device name
if ( obj . common . validateString ( command . hostname , 1 , 256 ) == false ) break ; // Check hostname
if ( obj . common . validateString ( command . amtusername , 0 , 16 ) == false ) break ; // Check username
if ( obj . common . validateString ( command . amtpassword , 0 , 16 ) == false ) break ; // Check password
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
if ( ( obj . parent . parent . args . wanonly == true ) && ( command . hostname ) ) { delete command . hostname ; }
// Get the mesh
mesh = obj . parent . meshes [ command . meshid ] ;
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
obj . parent . crypto . randomBytes ( 48 , function ( err , buf ) {
// create the new node
nodeid = 'node/' + domain . id + '/' + buf . toString ( 'base64' ) . replace ( /\+/g , '@' ) . replace ( /\//g , '$' ) ;
var device = { type : 'node' , mtype : 1 , _id : nodeid , meshid : command . meshid , name : command . devicename , host : command . hostname , domain : domain . id , intelamt : { user : command . amtusername , pass : command . amtpassword , tls : command . amttls } } ;
obj . db . Set ( device ) ;
// Event the new node
var device2 = obj . common . Clone ( device ) ;
delete device2 . intelamt . pass ; // Remove the Intel AMT password before eventing this.
obj . parent . parent . DispatchEvent ( [ '*' , command . meshid ] , obj , { etype : 'node' , username : user . name , action : 'addnode' , node : device2 , msg : 'Added device ' + command . devicename + ' to mesh ' + mesh . name , domain : domain . id } ) ;
} ) ;
}
break ;
}
case 'scanamtdevice' :
{
if ( obj . args . wanonly == true ) return ; // This is a WAN-only server, this type of scanning is not allowed.
if ( obj . common . validateString ( command . range , 1 , 256 ) == false ) break ; // Check range string
// Ask the RMCP scanning to scan a range of IP addresses
if ( obj . parent . parent . amtScanner ) {
if ( obj . parent . parent . amtScanner . performRangeScan ( ws . userid , command . range ) == false ) {
obj . parent . parent . DispatchEvent ( [ '*' , ws . userid ] , 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 ;
}
case 'removedevices' :
{
if ( obj . 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 ] ;
if ( obj . common . validateString ( nodeid , 1 , 1024 ) == false ) break ; // Check nodeid
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
obj . db . Get ( nodeid , function ( err , nodes ) {
if ( nodes . length != 1 ) return ;
var node = nodes [ 0 ] ;
// Get the mesh for this device
mesh = obj . 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 & 4 ) == 0 ) ) return ;
// Delete this node including network interface information, events and timeline
obj . db . Remove ( node . _id ) ; // Remove node with that id
obj . db . Remove ( 'if' + node . _id ) ; // Remove interface information
obj . db . Remove ( 'nt' + node . _id ) ; // Remove notes
obj . db . Remove ( 'lc' + node . _id ) ; // Remove last connect time
obj . db . Remove ( 'sm' + node . _id ) ; // Remove SMBios data
obj . db . RemoveNode ( node . _id ) ; // Remove all entries with node:id
// Event node deletion
obj . parent . parent . DispatchEvent ( [ '*' , node . meshid ] , obj , { etype : 'node' , username : user . name , action : 'removenode' , nodeid : node . _id , msg : 'Removed device ' + node . name + ' from group ' + mesh . name , domain : domain . id } ) ;
// Disconnect all connections if needed
var state = obj . parent . parent . GetConnectivityState ( nodeid ) ;
if ( ( state != null ) && ( state . connectivity != null ) ) {
if ( ( state . connectivity & 1 ) != 0 ) { obj . parent . wsagents [ nodeid ] . close ( ) ; } // Disconnect mesh agent
if ( ( state . connectivity & 2 ) != 0 ) { obj . parent . parent . mpsserver . close ( obj . parent . parent . mpsserver . ciraConnections [ nodeid ] ) ; } // Disconnect CIRA connection
}
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' :
{
if ( obj . common . validateArray ( command . nodeids , 1 ) == false ) break ; // Check nodeid's
// 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 ( obj . 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
2017-10-24 00:09:58 +03:00
// Get the device
obj . db . Get ( nodeid , function ( err , nodes ) {
if ( nodes . length != 1 ) return ;
var node = nodes [ 0 ] ;
// Get the mesh for this device
2018-08-30 22:05:23 +03:00
mesh = obj . 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 ) ) {
// Get the device interface information
obj . db . Get ( 'if' + node . _id , function ( err , nodeifs ) {
if ( nodeifs . length == 1 ) {
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)
if ( obj . parent . parent . meshScanner != null ) { obj . parent . parent . meshScanner . wakeOnLan ( macs ) ; wakeActions ++ ; }
// 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
for ( i in obj . parent . wsagents ) {
var agent = obj . parent . wsagents [ i ] ;
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-01-05 04:59:13 +03:00
break ;
}
case 'poweraction' :
{
if ( obj . common . validateArray ( command . nodeids , 1 ) == false ) break ; // Check nodeid's
for ( i in command . nodeids ) {
nodeid = command . nodeids [ i ] ;
var powerActions = 0 ;
if ( obj . 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
obj . db . Get ( nodeid , function ( err , nodes ) {
if ( nodes . length != 1 ) return ;
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
mesh = obj . 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 & 8 ) != 0 ) ) { // "Remote Control permission"
// Get this device
var agent = obj . parent . wsagents [ node . _id ] ;
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' :
{
if ( obj . common . validateArray ( command . nodeids , 1 ) == false ) break ; // Check nodeid's
if ( obj . common . validateString ( command . title , 1 , 512 ) == false ) break ; // Check title
if ( obj . common . validateString ( command . msg , 1 , 4096 ) == false ) break ; // Check message
for ( i in command . nodeids ) {
nodeid = command . nodeids [ i ] ;
var powerActions = 0 ;
if ( obj . 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
obj . db . Get ( nodeid , function ( err , nodes ) {
if ( nodes . length != 1 ) return ;
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
mesh = obj . parent . meshes [ node . meshid ] ;
if ( mesh ) {
2017-10-24 00:09:58 +03:00
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"
2017-10-24 00:09:58 +03:00
2019-01-05 04:59:13 +03:00
// Get this device
var agent = obj . parent . wsagents [ node . _id ] ;
if ( agent != null ) {
// Send the power command
try { agent . send ( JSON . stringify ( { action : 'toast' , title : command . title , msg : command . msg } ) ) ; } catch ( ex ) { }
}
}
}
} ) ;
}
2017-10-24 00:09:58 +03:00
}
2019-01-05 04:59:13 +03:00
break ;
}
case 'getnetworkinfo' :
{
// Argument validation
if ( obj . common . validateString ( command . nodeid , 1 , 1024 ) == false ) break ; // Check nodeid
if ( ( command . nodeid . split ( '/' ) . length != 3 ) || ( command . nodeid . split ( '/' ) [ 1 ] != domain . id ) ) return ; // Invalid domain, operation only valid for current domain
// Get the device
obj . db . Get ( command . nodeid , function ( err , nodes ) {
if ( nodes . length != 1 ) { try { ws . send ( JSON . stringify ( { action : 'getnetworkinfo' , nodeid : command . nodeid , netif : null } ) ) ; } catch ( ex ) { } return ; }
var node = nodes [ 0 ] ;
// Get the mesh for this device
mesh = obj . 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 == 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
obj . db . Get ( 'if' + command . nodeid , function ( err , netinfos ) {
if ( netinfos . length != 1 ) { try { ws . send ( JSON . stringify ( { action : 'getnetworkinfo' , nodeid : command . nodeid , netif : null } ) ) ; } catch ( ex ) { } return ; }
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
if ( obj . common . validateString ( command . nodeid , 1 , 1024 ) == false ) break ; // Check nodeid
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
obj . db . Get ( command . nodeid , function ( err , nodes ) {
if ( nodes . length != 1 ) return ;
var node = nodes [ 0 ] ;
// Get the mesh for this device
mesh = obj . 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 & 4 ) == 0 ) ) return ;
2017-10-24 00:09:58 +03:00
2019-01-05 04:59:13 +03:00
// Ready the node change event
var changes = [ ] , event = { etype : 'node' , username : user . name , action : 'changenode' , nodeid : node . _id , domain : domain . id } ;
change = 0 ;
event . msg = ": " ;
// If we are in WAN-only mode, host is not used
if ( ( obj . parent . parent . args . wanonly == true ) && ( command . host ) ) { delete command . host ; }
// 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' ) ; }
if ( command . userloc && ( ( node . userloc == null ) || ( command . userloc [ 0 ] != node . userloc [ 0 ] ) || ( command . userloc [ 1 ] != node . userloc [ 1 ] ) ) ) {
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
var ok = true ;
if ( obj . common . validateString ( command . tags , 0 , 4096 ) == true ) { command . tags = command . tags . split ( ',' ) ; }
if ( obj . common . validateStrArray ( command . tags , 1 , 256 ) == true ) { var groupTags = command . tags ; for ( var i in groupTags ) { groupTags [ i ] = groupTags [ i ] . trim ( ) ; if ( ( groupTags [ i ] == '' ) || ( groupTags [ i ] . indexOf ( ',' ) >= 0 ) ) { ok = false ; } } }
if ( ok == true ) { groupTags . sort ( function ( a , b ) { return a . toLowerCase ( ) . localeCompare ( b . toLowerCase ( ) ) ; } ) ; node . tags = groupTags ; change = 1 ; }
} else if ( ( command . tags === '' ) && node . tags ) { delete node . tags ; change = 1 ; }
if ( change == 1 ) {
// Save the node
obj . db . Set ( node ) ;
// Event the node change
event . msg = 'Changed device ' + node . name + ' from group ' + mesh . name + ': ' + changes . join ( ', ' ) ;
var node2 = obj . common . Clone ( node ) ;
if ( node2 . intelamt && node2 . intelamt . pass ) delete node2 . intelamt . pass ; // Remove the Intel AMT password before eventing this.
event . node = node2 ;
obj . parent . parent . DispatchEvent ( [ '*' , node . meshid ] , obj , event ) ;
}
}
} ) ;
break ;
}
case 'uploadagentcore' :
{
if ( user . siteadmin != 0xFFFFFFFF ) break ;
if ( obj . common . validateString ( command . nodeid , 1 , 1024 ) == false ) break ; // Check nodeid
if ( command . path ) {
if ( obj . common . validateString ( command . path , 1 , 4096 ) == false ) break ; // Check path
if ( command . path == '*' ) {
// Update the server default core and send a core hash request
// Load default mesh agent core if present, then perform a core update
obj . parent . parent . updateMeshCore ( function ( ) { obj . parent . sendMeshAgentCore ( user , domain , command . nodeid , '*' ) ; } ) ;
2017-10-24 00:09:58 +03:00
} else {
2019-01-05 04:59:13 +03:00
// Send a mesh agent core to the mesh agent
var file = obj . parent . getServerFilePath ( user , domain , command . path ) ;
if ( file != null ) {
2019-01-05 23:04:18 +03:00
obj . parent . parent . fs . readFile ( file . fullpath , 'utf8' , function ( err , data ) {
if ( err != null ) {
2019-01-05 04:59:13 +03:00
data = obj . common . IntToStr ( 0 ) + data ; // Add the 4 bytes encoding type & flags (Set to 0 for raw)
obj . parent . sendMeshAgentCore ( user , domain , command . nodeid , data ) ;
}
} ) ;
}
2017-10-24 00:09:58 +03:00
}
2019-01-05 04:59:13 +03:00
} else {
// Clear the mesh agent core on the mesh agent
obj . parent . sendMeshAgentCore ( user , domain , command . nodeid , null ) ;
2017-10-24 00:09:58 +03:00
}
2019-01-05 04:59:13 +03:00
break ;
}
case 'agentdisconnect' :
{
// Force mesh agent disconnection
if ( obj . common . validateString ( command . nodeid , 1 , 1024 ) == false ) break ; // Check nodeid
if ( obj . common . validateInt ( command . disconnectMode ) == false ) break ; // Check disconnect mode
obj . parent . forceMeshAgentDisconnect ( user , domain , command . nodeid , command . disconnectMode ) ;
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
if ( obj . common . validateString ( command . nodeid , 1 , 1024 ) == false ) break ; // Check nodeid
obj . 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 (???)
if ( nodes . length == 1 ) {
meshlinks = user . links [ nodes [ 0 ] . meshid ] ;
if ( ( meshlinks ) && ( meshlinks . rights ) && ( meshlinks . rights & obj . parent . MESHRIGHT _REMOTECONTROL != 0 ) ) {
// 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
command . cookie = obj . parent . parent . encodeCookie ( cookieContent ) ;
try { ws . send ( JSON . stringify ( command ) ) ; } catch ( ex ) { }
}
}
} ) ;
break ;
}
case 'inviteAgent' :
{
if ( ( obj . parent . parent . mailserver == null ) || ( obj . args . lanonly == true ) ) return ; // This operation requires the email server
if ( ( obj . parent . parent . certificates . CommonName == null ) || ( obj . parent . parent . certificates . CommonName == 'un-configured' ) ) return ; // Server name must be configured
if ( obj . common . validateString ( command . meshid , 1 , 1024 ) == false ) break ; // Check meshid
if ( ( command . meshid . split ( '/' ) . length != 3 ) || ( command . meshid . split ( '/' ) [ 1 ] != domain . id ) ) return ; // Invalid domain, operation only valid for current domain
// Get the mesh
mesh = obj . parent . meshes [ command . meshid ] ;
if ( mesh ) {
if ( mesh . mtype != 2 ) return ; // This operation is only allowed for mesh type 2, agent mesh
// Check if this user has rights to do this
//if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 4) == 0)) return;
// Perform email invitation
obj . parent . parent . mailserver . sendAgentInviteMail ( domain , user . name , command . email , command . meshid , command . name , command . os , command . msg , command . flags ) ;
2017-10-24 00:09:58 +03:00
}
2019-01-05 04:59:13 +03:00
break ;
}
case 'setNotes' :
{
// Argument validation
if ( obj . common . validateString ( command . id , 1 , 1024 ) == false ) break ; // Check id
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
obj . 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 (???)
2017-10-24 00:09:58 +03:00
if ( nodes . length == 1 ) {
2018-08-30 22:05:23 +03:00
meshlinks = user . links [ nodes [ 0 ] . meshid ] ;
2019-01-05 04:59:13 +03:00
if ( ( meshlinks ) && ( meshlinks . rights ) && ( meshlinks . rights & obj . parent . MESHRIGHT _SETNOTES != 0 ) ) {
// Set the id's notes
if ( obj . common . validateString ( command . notes , 1 ) == false ) {
obj . db . Remove ( 'nt' + command . id ) ; // Delete the note for this node
} else {
obj . db . Set ( { _id : 'nt' + command . id , type : 'note' , value : command . notes } ) ; // Set the note for this node
}
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
mesh = obj . 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
if ( obj . common . validateString ( command . notes , 1 ) == false ) {
obj . db . Remove ( 'nt' + command . id ) ; // Delete the note for this node
} else {
2019-01-05 04:59:13 +03:00
obj . 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
if ( obj . common . validateString ( command . notes , 1 ) == false ) {
obj . db . Remove ( 'nt' + command . id ) ; // Delete the note for this node
} else {
obj . db . Set ( { _id : 'nt' + command . id , type : 'note' , value : command . notes } ) ; // Set the note for this user
}
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
const twoStepLoginSupported = ( ( domain . auth != 'sspi' ) && ( obj . parent . parent . certificates . CommonName != 'un-configured' ) && ( obj . args . lanonly !== true ) && ( obj . args . nousers !== true ) ) ;
if ( twoStepLoginSupported ) {
// Request a one time password to be setup
const otplib = require ( 'otplib' ) ;
const secret = otplib . authenticator . generateSecret ( ) ; // TODO: Check the random source of this value.
ws . send ( JSON . stringify ( { action : 'otpauth-request' , secret : secret , url : otplib . authenticator . keyuri ( user . name , obj . parent . certificates . CommonName , secret ) } ) ) ;
}
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
const twoStepLoginSupported = ( ( domain . auth != 'sspi' ) && ( obj . parent . parent . certificates . CommonName != 'un-configured' ) && ( obj . args . lanonly !== true ) && ( obj . args . nousers !== true ) ) ;
if ( twoStepLoginSupported ) {
// Perform the one time password setup
const otplib = require ( 'otplib' ) ;
otplib . authenticator . options = { window : 6 } ; // Set +/- 3 minute window
if ( otplib . authenticator . check ( command . token , command . secret ) === true ) {
// Token is valid, activate 2-step login on this account.
user . otpsecret = command . secret ;
obj . parent . db . SetUser ( user ) ;
ws . send ( JSON . stringify ( { action : 'otpauth-setup' , success : true } ) ) ; // Report success
// Notify change
var userinfo = obj . common . Clone ( user ) ;
delete userinfo . hash ;
delete userinfo . passhint ;
delete userinfo . salt ;
delete userinfo . type ;
delete userinfo . domain ;
delete userinfo . subscriptions ;
delete userinfo . passtype ;
if ( userinfo . otpsecret ) { userinfo . otpsecret = 1 ; }
try { ws . send ( JSON . stringify ( { action : 'userinfo' , userinfo : userinfo } ) ) ; } catch ( ex ) { }
} 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
const twoStepLoginSupported = ( ( domain . auth != 'sspi' ) && ( obj . parent . parent . certificates . CommonName != 'un-configured' ) && ( obj . args . lanonly !== true ) && ( obj . args . nousers !== true ) ) ;
if ( twoStepLoginSupported ) {
// Clear the one time password secret
if ( user . otpsecret ) {
delete user . otpsecret ;
obj . parent . db . SetUser ( user ) ;
// Notify change
var userinfo = obj . common . Clone ( user ) ;
delete userinfo . hash ;
delete userinfo . passhint ;
delete userinfo . salt ;
delete userinfo . type ;
delete userinfo . domain ;
delete userinfo . subscriptions ;
delete userinfo . passtype ;
if ( userinfo . otpsecret ) { userinfo . otpsecret = 1 ; }
try { ws . send ( JSON . stringify ( { action : 'userinfo' , userinfo : userinfo } ) ) ; } catch ( ex ) { }
ws . send ( JSON . stringify ( { action : 'otpauth-clear' , success : true } ) ) ; // Report success
} 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 ;
}
case 'getNotes' :
{
// Argument validation
if ( obj . common . validateString ( command . id , 1 , 1024 ) == false ) break ; // Check id
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
obj . db . Get ( command . id , function ( err , nodes ) {
if ( nodes . length != 1 ) return ;
var node = nodes [ 0 ] ;
2018-04-13 04:14:03 +03:00
// Get the mesh for this device
2019-01-05 04:59:13 +03:00
mesh = obj . 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
2018-04-16 21:57:42 +03:00
obj . db . Get ( 'nt' + command . id , function ( err , notes ) {
2018-09-25 21:51:40 +03:00
try {
if ( notes . length != 1 ) { ws . send ( JSON . stringify ( { action : 'getNotes' , id : command . id , notes : null } ) ) ; return ; }
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
mesh = obj . parent . meshes [ command . id ] ;
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
obj . db . Get ( 'nt' + command . id , function ( err , notes ) {
2018-09-25 21:51:40 +03:00
try {
if ( notes . length != 1 ) { ws . send ( JSON . stringify ( { action : 'getNotes' , id : command . id , notes : null } ) ) ; return ; }
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
obj . db . Get ( 'nt' + command . id , function ( err , notes ) {
try {
if ( notes . length != 1 ) { ws . send ( JSON . stringify ( { action : 'getNotes' , id : command . id , notes : null } ) ) ; return ; }
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-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
// Read the folder and all sub-folders and serialize that into json.
function readFilesRec ( path ) {
var r = { } , dir = obj . fs . readdirSync ( path ) ;
for ( var i in dir ) {
var f = { t : 3 , d : 111 } ;
2018-08-30 22:05:23 +03:00
var stat = obj . 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 ) {
if ( obj . fs . existsSync ( path ) ) {
obj . fs . readdirSync ( path ) . forEach ( function ( file , index ) {
var curPath = obj . path . join ( path , file ) ; ;
if ( obj . fs . lstatSync ( curPath ) . isDirectory ( ) ) { // recurse
deleteFolderRecursive ( curPath ) ;
} else { // delete file
obj . fs . unlinkSync ( curPath ) ;
}
} ) ;
obj . fs . rmdirSync ( path ) ;
}
} ;
2017-10-24 00:09:58 +03:00
function updateUserFiles ( user , ws , domain ) {
// 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 : { } } ;
2017-12-01 01:40:46 +03:00
files . filetree . f [ user . _id ] . maxbytes = obj . 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 {
files . filetree . f [ user . _id ] . f = readFilesRec ( obj . path . join ( obj . parent . filespath , domainx + "/user-" + usersplit [ 2 ] ) ) ;
} catch ( e ) {
// Got an error, try to create all the folders and try again...
try { obj . fs . mkdirSync ( obj . parent . filespath ) ; } catch ( e ) { }
try { obj . fs . mkdirSync ( obj . path . join ( obj . parent . filespath , domainx ) ) ; } catch ( e ) { }
try { obj . fs . mkdirSync ( obj . path . join ( obj . parent . filespath , domainx + "/user-" + usersplit [ 2 ] ) ) ; } catch ( e ) { }
try { obj . fs . mkdirSync ( obj . path . join ( obj . parent . filespath , domainx + "/user-" + usersplit [ 2 ] + "/Public" ) ) ; } catch ( e ) { }
try { files . filetree . f [ user . _id ] . f = readFilesRec ( obj . parent . path . join ( obj . parent . filespath , domainx + "/user-" + usersplit [ 2 ] ) ) ; } catch ( e ) { }
}
// Add files for each mesh
for ( var i in user . links ) {
if ( ( user . links [ i ] . rights & 32 ) != 0 ) { // Check that we have file permissions
var mesh = obj . parent . meshes [ i ] ;
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 : { } } ;
2017-12-01 01:40:46 +03:00
files . filetree . f [ mesh . _id ] . maxbytes = obj . parent . getQuota ( mesh . _id , domain ) ;
2017-10-24 00:09:58 +03:00
// Read all files recursively
try {
files . filetree . f [ mesh . _id ] . f = readFilesRec ( obj . parent . path . join ( _ _dirname , "files/" + domainx + "/mesh-" + meshsplit [ 2 ] ) ) ;
} catch ( e ) {
// Got an error, try to create all the folders and try again...
try { obj . fs . mkdirSync ( obj . parent . filespath ) ; } catch ( e ) { }
try { obj . fs . mkdirSync ( obj . parent . path . join ( obj . parent . filespath , domainx ) ) ; } catch ( e ) { }
try { obj . fs . mkdirSync ( obj . parent . path . join ( obj . parent . filespath , domainx + "/mesh-" + meshsplit [ 2 ] ) ) ; } catch ( e ) { }
try { files . filetree . f [ mesh . _id ] . f = readFilesRec ( obj . parent . path . join ( obj . parent . filespath , domainx + "/mesh-" + meshsplit [ 2 ] ) ) ; } catch ( e ) { }
}
}
}
}
// 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
2017-10-24 00:09:58 +03:00
return obj ;
2018-08-30 22:05:23 +03:00
} ;