2017-08-28 12:27:45 -04:00
/ * *
2018-01-04 15:15:21 -05:00
* @ description MeshCentral web server
2017-08-28 12:27:45 -04:00
* @ author Ylian Saint - Hilaire
2020-01-03 16:21:23 -05:00
* @ copyright Intel Corporation 2018 - 2020
2018-01-04 15:15:21 -05:00
* @ license Apache - 2.0
2017-08-28 12:27:45 -04:00
* @ version v0 . 0.1
* /
2018-08-30 15:05:23 -04:00
/*jslint node: true */
/*jshint node: true */
/*jshint strict:false */
/*jshint -W097 */
/*jshint esversion: 6 */
2019-12-27 01:53:01 -05:00
'use strict' ;
2017-08-28 12:27:45 -04:00
/ *
class SerialTunnel extends require ( 'stream' ) . Duplex {
constructor ( options ) { super ( options ) ; this . forwardwrite = null ; }
updateBuffer ( chunk ) { this . push ( chunk ) ; }
_write ( chunk , encoding , callback ) { if ( this . forwardwrite != null ) { this . forwardwrite ( chunk ) ; } else { console . err ( "Failed to fwd _write." ) ; } if ( callback ) callback ( ) ; } // Pass data written to forward
_read ( size ) { } // Push nothing, anything to read should be pushed from updateBuffer()
}
* /
// Older NodeJS does not support the keyword "class", so we do without using this syntax
// TODO: Validate that it's the same as above and that it works.
function SerialTunnel ( options ) {
var obj = new require ( 'stream' ) . Duplex ( options ) ;
obj . forwardwrite = null ;
2018-08-30 15:05:23 -04:00
obj . updateBuffer = function ( chunk ) { this . push ( chunk ) ; } ;
obj . _write = function ( chunk , encoding , callback ) { if ( obj . forwardwrite != null ) { obj . forwardwrite ( chunk ) ; } else { console . err ( "Failed to fwd _write." ) ; } if ( callback ) callback ( ) ; } ; // Pass data written to forward
obj . _read = function ( size ) { } ; // Push nothing, anything to read should be pushed from updateBuffer()
2017-08-28 12:27:45 -04:00
return obj ;
}
// ExpressJS login sample
// https://github.com/expressjs/express/blob/master/examples/auth/index.js
// Polyfill startsWith/endsWith for older NodeJS
if ( ! String . prototype . startsWith ) { String . prototype . startsWith = function ( searchString , position ) { position = position || 0 ; return this . substr ( position , searchString . length ) === searchString ; } ; }
if ( ! String . prototype . endsWith ) { String . prototype . endsWith = function ( searchString , position ) { var subjectString = this . toString ( ) ; if ( typeof position !== 'number' || ! isFinite ( position ) || Math . floor ( position ) !== position || position > subjectString . length ) { position = subjectString . length ; } position -= searchString . length ; var lastIndex = subjectString . lastIndexOf ( searchString , position ) ; return lastIndex !== - 1 && lastIndex === position ; } ; }
2019-01-02 21:34:50 -05:00
// Construct a HTTP server object
2018-08-22 19:18:01 -04:00
module . exports . CreateWebServer = function ( parent , db , args , certificates ) {
2019-10-04 15:18:56 -04:00
var obj = { } , i = 0 ;
2017-08-28 12:27:45 -04:00
2018-01-09 23:13:41 -05:00
// Modules
2017-08-28 12:27:45 -04:00
obj . fs = require ( 'fs' ) ;
obj . net = require ( 'net' ) ;
obj . tls = require ( 'tls' ) ;
obj . path = require ( 'path' ) ;
obj . bodyParser = require ( 'body-parser' ) ;
2018-08-22 19:18:01 -04:00
obj . session = require ( 'cookie-session' ) ;
2017-08-28 12:27:45 -04:00
obj . exphbs = require ( 'express-handlebars' ) ;
obj . crypto = require ( 'crypto' ) ;
obj . common = require ( './common.js' ) ;
obj . express = require ( 'express' ) ;
obj . meshAgentHandler = require ( './meshagent.js' ) ;
2018-08-30 15:05:23 -04:00
obj . meshRelayHandler = require ( './meshrelay.js' ) ;
2019-04-24 14:38:28 -04:00
obj . meshIderHandler = require ( './amt/amt-ider.js' ) ;
2018-08-30 15:05:23 -04:00
obj . meshUserHandler = require ( './meshuser.js' ) ;
2017-08-28 12:27:45 -04:00
obj . interceptor = require ( './interceptor' ) ;
2019-02-26 18:49:44 -05:00
const constants = ( obj . crypto . constants ? obj . crypto . constants : require ( 'constants' ) ) ; // require('constants') is deprecated in Node 11.10, use require('crypto').constants instead.
2018-05-24 00:43:44 -04:00
2019-03-23 16:28:17 -04:00
// Setup WebAuthn / FIDO2
2019-12-27 01:53:01 -05:00
obj . webauthn = require ( './webauthn.js' ) . CreateWebAuthnModule ( ) ;
2019-03-23 01:33:53 -04:00
2017-08-28 12:27:45 -04:00
// Variables
obj . parent = parent ;
obj . filespath = parent . filespath ;
obj . db = db ;
obj . app = obj . express ( ) ;
obj . app . use ( require ( 'compression' ) ( ) ) ;
obj . tlsServer = null ;
2018-08-30 15:05:23 -04:00
obj . tcpServer = null ;
2017-08-28 12:27:45 -04:00
obj . certificates = certificates ;
obj . args = args ;
2019-12-27 01:53:01 -05:00
obj . users = { } ; // UserID --> User
obj . meshes = { } ; // MeshID --> Mesh (also called device group)
2020-01-02 03:30:14 -05:00
obj . userGroups = { } ; // UGrpID --> User Group
2019-12-27 01:53:01 -05:00
obj . userAllowedIp = args . userallowedip ; // List of allowed IP addresses for users
obj . agentAllowedIp = args . agentallowedip ; // List of allowed IP addresses for agents
obj . agentBlockedIp = args . agentblockedip ; // List of blocked IP addresses for agents
2018-08-30 15:05:23 -04:00
obj . tlsSniCredentials = null ;
2018-01-02 19:52:49 -05:00
obj . dnsDomains = { } ;
2019-02-18 20:14:00 -05:00
obj . relaySessionCount = 0 ;
obj . relaySessionErrorCount = 0 ;
2020-01-10 14:19:23 -05:00
obj . blockedUsers = 0 ;
obj . blockedAgents = 0 ;
2019-10-21 18:59:20 -04:00
obj . renderPages = null ;
2019-10-22 14:59:19 -04:00
obj . renderLanguages = [ ] ;
2018-01-02 19:52:49 -05:00
2017-10-23 17:09:58 -04:00
// Mesh Rights
const MESHRIGHT _EDITMESH = 1 ;
const MESHRIGHT _MANAGEUSERS = 2 ;
const MESHRIGHT _MANAGECOMPUTERS = 4 ;
const MESHRIGHT _REMOTECONTROL = 8 ;
const MESHRIGHT _AGENTCONSOLE = 16 ;
const MESHRIGHT _SERVERFILES = 32 ;
const MESHRIGHT _WAKEDEVICE = 64 ;
2018-04-12 21:14:03 -04:00
const MESHRIGHT _SETNOTES = 128 ;
2017-10-23 17:09:58 -04:00
// Site rights
const SITERIGHT _SERVERBACKUP = 1 ;
const SITERIGHT _MANAGEUSERS = 2 ;
const SITERIGHT _SERVERRESTORE = 4 ;
const SITERIGHT _FILEACCESS = 8 ;
const SITERIGHT _SERVERUPDATE = 16 ;
2018-04-13 18:26:57 -04:00
const SITERIGHT _LOCKED = 32 ;
2017-10-23 17:09:58 -04:00
2018-01-02 19:52:49 -05:00
// Setup SSPI authentication if needed
if ( ( obj . parent . platform == 'win32' ) && ( obj . args . nousers != true ) && ( obj . parent . config != null ) && ( obj . parent . config . domains != null ) ) {
2018-08-30 15:05:23 -04:00
for ( i in obj . parent . config . domains ) { if ( obj . parent . config . domains [ i ] . auth == 'sspi' ) { var nodeSSPI = require ( 'node-sspi' ) ; obj . parent . config . domains [ i ] . sspi = new nodeSSPI ( { retrieveGroups : true , offerBasic : false } ) ; } }
2018-01-02 19:52:49 -05:00
}
2017-08-28 12:27:45 -04:00
// Perform hash on web certificate and agent certificate
2018-11-30 19:42:58 -05:00
obj . webCertificateHash = parent . certificateOperations . getPublicKeyHashBinary ( obj . certificates . web . cert ) ;
2018-01-02 19:52:49 -05:00
obj . webCertificateHashs = { '' : obj . webCertificateHash } ;
2019-01-02 21:03:34 -05:00
obj . webCertificateHashBase64 = Buffer . from ( obj . webCertificateHash , 'binary' ) . toString ( 'base64' ) . replace ( /\+/g , '@' ) . replace ( /\//g , '$' ) ;
2018-11-30 19:42:58 -05:00
obj . webCertificateFullHash = parent . certificateOperations . getCertHashBinary ( obj . certificates . web . cert ) ;
obj . webCertificateFullHashs = { '' : obj . webCertificateFullHash } ;
obj . agentCertificateHashHex = parent . certificateOperations . getPublicKeyHash ( obj . certificates . agent . cert ) ;
2019-01-02 21:03:34 -05:00
obj . agentCertificateHashBase64 = Buffer . from ( obj . agentCertificateHashHex , 'hex' ) . toString ( 'base64' ) . replace ( /\+/g , '@' ) . replace ( /\//g , '$' ) ;
2017-10-18 19:28:05 -04:00
obj . agentCertificateAsn1 = parent . certificateOperations . forge . asn1 . toDer ( parent . certificateOperations . forge . pki . certificateToAsn1 ( parent . certificateOperations . forge . pki . certificateFromPem ( parent . certificates . agent . cert ) ) ) . getBytes ( ) ;
2018-10-31 19:03:09 -04:00
// Compute the hash of all of the web certificates for each domain
for ( var i in obj . parent . config . domains ) {
if ( obj . parent . config . domains [ i ] . certhash != null ) {
// If the web certificate hash is provided, use it.
2019-01-02 21:03:34 -05:00
obj . webCertificateHashs [ i ] = obj . webCertificateFullHashs [ i ] = Buffer . from ( obj . parent . config . domains [ i ] . certhash , 'hex' ) . toString ( 'binary' ) ;
if ( obj . parent . config . domains [ i ] . certkeyhash != null ) { obj . webCertificateHashs [ i ] = Buffer . from ( obj . parent . config . domains [ i ] . certkeyhash , 'hex' ) . toString ( 'binary' ) ; }
2018-10-31 19:03:09 -04:00
} else if ( ( obj . parent . config . domains [ i ] . dns != null ) && ( obj . parent . config . domains [ i ] . certs != null ) ) {
// If the domain has a different DNS name, use a different certificate hash.
2018-11-30 19:42:58 -05:00
// Hash the full certificate
obj . webCertificateFullHashs [ i ] = parent . certificateOperations . getCertHashBinary ( obj . parent . config . domains [ i ] . certs . cert ) ;
2018-10-31 19:03:09 -04:00
try {
2018-11-30 19:42:58 -05:00
// Decode a RSA certificate and hash the public key.
obj . webCertificateHashs [ i ] = parent . certificateOperations . getPublicKeyHashBinary ( obj . parent . config . domains [ i ] . certs . cert ) ;
2018-10-31 19:03:09 -04:00
} catch ( ex ) {
2018-11-30 19:42:58 -05:00
// This may be a ECDSA certificate, hash the entire cert.
obj . webCertificateHashs [ i ] = obj . webCertificateFullHashs [ i ] ;
2018-10-31 19:03:09 -04:00
}
2019-03-14 15:28:58 -04:00
} else if ( ( obj . parent . config . domains [ i ] . dns != null ) && ( obj . certificates . dns [ i ] != null ) ) {
// If this domain has a DNS and a matching DNS cert, use it. This case works for wildcard certs.
obj . webCertificateFullHashs [ i ] = parent . certificateOperations . getCertHashBinary ( obj . certificates . dns [ i ] . cert ) ;
obj . webCertificateHashs [ i ] = parent . certificateOperations . getPublicKeyHashBinary ( obj . certificates . dns [ i ] . cert ) ;
2019-04-04 17:47:52 -04:00
} else if ( i != '' ) {
// For any other domain, use the default cert.
obj . webCertificateFullHashs [ i ] = obj . webCertificateFullHashs [ '' ] ;
obj . webCertificateHashs [ i ] = obj . webCertificateHashs [ '' ] ;
2018-10-31 19:03:09 -04:00
}
}
// If we are running the legacy swarm server, compute the hash for that certificate
2017-11-07 20:05:22 -05:00
if ( parent . certificates . swarmserver != null ) {
obj . swarmCertificateAsn1 = parent . certificateOperations . forge . asn1 . toDer ( parent . certificateOperations . forge . pki . certificateToAsn1 ( parent . certificateOperations . forge . pki . certificateFromPem ( parent . certificates . swarmserver . cert ) ) ) . getBytes ( ) ;
obj . swarmCertificateHash384 = parent . certificateOperations . forge . pki . getPublicKeyFingerprint ( parent . certificateOperations . forge . pki . certificateFromPem ( obj . certificates . swarmserver . cert ) . publicKey , { md : parent . certificateOperations . forge . md . sha384 . create ( ) , encoding : 'binary' } ) ;
obj . swarmCertificateHash256 = parent . certificateOperations . forge . pki . getPublicKeyFingerprint ( parent . certificateOperations . forge . pki . certificateFromPem ( obj . certificates . swarmserver . cert ) . publicKey , { md : parent . certificateOperations . forge . md . sha256 . create ( ) , encoding : 'binary' } ) ;
}
2017-08-28 12:27:45 -04:00
2018-10-31 19:03:09 -04:00
// Main lists
2019-10-29 19:17:29 -04:00
obj . wsagents = { } ; // NodeId --> Agent
obj . wsagentsWithBadWebCerts = { } ; // NodeId --> Agent
2019-01-22 22:34:55 -05:00
obj . wsagentsDisconnections = { } ;
obj . wsagentsDisconnectionsTimer = null ;
2019-05-21 17:19:32 -04:00
obj . duplicateAgentsLog = { } ;
2019-10-29 19:17:29 -04:00
obj . wssessions = { } ; // UserId --> Array Of Sessions
obj . wssessions2 = { } ; // "UserId + SessionRnd" --> Session (Note that the SessionId is the UserId + / + SessionRnd)
obj . wsPeerSessions = { } ; // ServerId --> Array Of "UserId + SessionRnd"
obj . wsPeerSessions2 = { } ; // "UserId + SessionRnd" --> ServerId
obj . wsPeerSessions3 = { } ; // ServerId --> UserId --> [ SessionId ]
obj . sessionsCount = { } ; // Merged session counters, used when doing server peering. UserId --> SessionCount
obj . wsrelays = { } ; // Id -> Relay
obj . wsPeerRelays = { } ; // Id -> { ServerId, Time }
var tlsSessionStore = { } ; // Store TLS session information for quick resume.
var tlsSessionStoreCount = 0 ; // Number of cached TLS session information in store.
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
// Setup randoms
2017-10-15 02:22:19 -04:00
obj . crypto . randomBytes ( 48 , function ( err , buf ) { obj . httpAuthRandom = buf ; } ) ;
2017-08-28 12:27:45 -04:00
obj . crypto . randomBytes ( 16 , function ( err , buf ) { obj . httpAuthRealm = buf . toString ( 'hex' ) ; } ) ;
2017-10-15 02:22:19 -04:00
obj . crypto . randomBytes ( 48 , function ( err , buf ) { obj . relayRandom = buf ; } ) ;
2017-08-28 12:27:45 -04:00
2020-04-04 22:29:20 -04:00
// Get non-english web pages and emails
2019-10-21 18:59:20 -04:00
getRenderList ( ) ;
2020-04-04 22:29:20 -04:00
getEmailLanguageList ( ) ;
2019-10-21 18:59:20 -04:00
2018-01-02 19:52:49 -05:00
// Setup DNS domain TLS SNI credentials
{
var dnscount = 0 ;
obj . tlsSniCredentials = { } ;
2018-08-30 15:05:23 -04:00
for ( i in obj . certificates . dns ) { if ( obj . parent . config . domains [ i ] . dns != null ) { obj . dnsDomains [ obj . parent . config . domains [ i ] . dns . toLowerCase ( ) ] = obj . parent . config . domains [ i ] ; obj . tlsSniCredentials [ obj . parent . config . domains [ i ] . dns ] = obj . tls . createSecureContext ( obj . certificates . dns [ i ] ) . context ; dnscount ++ ; } }
2018-01-15 00:01:06 -05:00
if ( dnscount > 0 ) { obj . tlsSniCredentials [ '' ] = obj . tls . createSecureContext ( { cert : obj . certificates . web . cert , key : obj . certificates . web . key , ca : obj . certificates . web . ca } ) . context ; } else { obj . tlsSniCredentials = null ; }
2018-01-02 19:52:49 -05:00
}
2018-12-11 20:52:37 -05:00
function TlsSniCallback ( name , cb ) {
var c = obj . tlsSniCredentials [ name ] ;
if ( c != null ) {
cb ( null , c ) ;
} else {
cb ( null , obj . tlsSniCredentials [ '' ] ) ;
}
}
2018-01-02 19:52:49 -05:00
2019-12-27 01:53:01 -05: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 15:05:23 -04: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-08-28 12:27:45 -04:00
// Fetch all users from the database, keep this in memory
obj . db . GetAllType ( 'user' , function ( err , docs ) {
2020-03-26 22:33:13 -04:00
obj . common . unEscapeAllLinksFieldName ( docs ) ;
2018-08-30 15:05:23 -04:00
var domainUserCount = { } , i = 0 ;
for ( i in parent . config . domains ) { domainUserCount [ i ] = 0 ; }
for ( i in docs ) { var u = obj . users [ docs [ i ] . _id ] = docs [ i ] ; domainUserCount [ u . domain ] ++ ; }
for ( i in parent . config . domains ) {
2017-08-28 12:27:45 -04:00
if ( domainUserCount [ i ] == 0 ) {
2018-12-02 02:41:57 -05:00
// If newaccounts is set to no new accounts, but no accounts exists, temporarly allow account creation.
2019-04-23 16:42:54 -04:00
//if ((parent.config.domains[i].newaccounts === 0) || (parent.config.domains[i].newaccounts === false)) { parent.config.domains[i].newaccounts = 2; }
2017-08-28 12:27:45 -04:00
console . log ( 'Server ' + ( ( i == '' ) ? '' : ( i + ' ' ) ) + 'has no users, next new account will be site administrator.' ) ;
}
}
2018-05-24 00:43:44 -04:00
2020-01-02 03:30:14 -05:00
// Fetch all device groups (meshes) from the database, keep this in memory
2020-03-30 20:47:04 -04:00
// As we load things in memory, we will also be doing some cleaning up.
// We will not save any clean up in the database right now, instead it will be saved next time there is a change.
2019-01-22 18:40:08 -05:00
obj . db . GetAllType ( 'mesh' , function ( err , docs ) {
obj . common . unEscapeAllLinksFieldName ( docs ) ;
2019-10-04 15:18:56 -04:00
for ( var i in docs ) { obj . meshes [ docs [ i ] . _id ] = docs [ i ] ; } // Get all meshes, including deleted ones.
2019-01-22 18:40:08 -05:00
2020-01-02 03:30:14 -05:00
// Fetch all user groups from the database, keep this in memory
obj . db . GetAllType ( 'ugrp' , function ( err , docs ) {
obj . common . unEscapeAllLinksFieldName ( docs ) ;
2020-03-30 20:47:04 -04:00
// Perform user group link cleanup
for ( var i in docs ) {
const ugrp = docs [ i ] ;
if ( ugrp . links != null ) {
for ( var j in ugrp . links ) {
if ( j . startsWith ( 'user/' ) && ( obj . users [ j ] == null ) ) { delete ugrp . links [ j ] ; } // User group has a link to a user that does not exist
else if ( j . startsWith ( 'mesh/' ) && ( ( obj . meshes [ j ] == null ) || ( obj . meshes [ j ] . deleted != null ) ) ) { delete ugrp . links [ j ] ; } // User has a link to a device group that does not exist
}
}
obj . userGroups [ docs [ i ] . _id ] = docs [ i ] ; // Get all user groups
}
// Perform device group link cleanup
for ( var i in obj . meshes ) {
const mesh = obj . meshes [ i ] ;
if ( mesh . links != null ) {
for ( var j in mesh . links ) {
if ( j . startsWith ( 'ugrp/' ) && ( obj . userGroups [ j ] == null ) ) { delete mesh . links [ j ] ; } // Device group has a link to a user group that does not exist
else if ( j . startsWith ( 'user/' ) && ( obj . users [ j ] == null ) ) { delete mesh . links [ j ] ; } // Device group has a link to a user that does not exist
}
}
}
// Perform user link cleanup
for ( var i in obj . users ) {
const user = obj . users [ i ] ;
if ( user . links != null ) {
for ( var j in user . links ) {
if ( j . startsWith ( 'ugrp/' ) && ( obj . userGroups [ j ] == null ) ) { delete user . links [ j ] ; } // User has a link to a user group that does not exist
else if ( j . startsWith ( 'mesh/' ) && ( ( obj . meshes [ j ] == null ) || ( obj . meshes [ j ] . deleted != null ) ) ) { delete user . links [ j ] ; } // User has a link to a device group that does not exist
//else if (j.startsWith('node/') && (obj.nodes[j] == null)) { delete user.links[j]; } // TODO
}
//if (Object.keys(user.links).length == 0) { delete user.links; }
}
}
// We loaded the users, device groups and user group state, start the server
2020-01-02 03:30:14 -05:00
serverStart ( ) ;
} ) ;
2019-01-22 18:40:08 -05:00
} ) ;
} ) ;
2017-08-28 12:27:45 -04:00
2020-03-30 20:47:04 -04:00
// Clean up a device, used before saving it in the database
obj . cleanDevice = function ( device ) {
// Check device links, if a link points to an unknown user, remove it.
if ( device . links != null ) {
for ( var j in device . links ) {
2020-04-13 01:51:57 -04:00
if ( ( obj . users [ j ] == null ) && ( obj . userGroups [ j ] == null ) ) {
2020-03-30 20:47:04 -04:00
delete device . links [ j ] ;
if ( Object . keys ( device . links ) . length == 0 ) { delete device . links ; }
}
}
}
return device ;
}
2019-05-01 18:02:03 -04:00
// Return statistics about this web server
obj . getStats = function ( ) {
return {
users : Object . keys ( obj . users ) . length ,
meshes : Object . keys ( obj . meshes ) . length ,
dnsDomains : Object . keys ( obj . dnsDomains ) . length ,
relaySessionCount : obj . relaySessionCount ,
relaySessionErrorCount : obj . relaySessionErrorCount ,
wsagents : Object . keys ( obj . wsagents ) . length ,
wsagentsDisconnections : Object . keys ( obj . wsagentsDisconnections ) . length ,
wsagentsDisconnectionsTimer : Object . keys ( obj . wsagentsDisconnectionsTimer ) . length ,
wssessions : Object . keys ( obj . wssessions ) . length ,
wssessions2 : Object . keys ( obj . wssessions2 ) . length ,
wsPeerSessions : Object . keys ( obj . wsPeerSessions ) . length ,
wsPeerSessions2 : Object . keys ( obj . wsPeerSessions2 ) . length ,
wsPeerSessions3 : Object . keys ( obj . wsPeerSessions3 ) . length ,
sessionsCount : Object . keys ( obj . sessionsCount ) . length ,
wsrelays : Object . keys ( obj . wsrelays ) . length ,
wsPeerRelays : Object . keys ( obj . wsPeerRelays ) . length ,
2020-01-10 14:19:23 -05:00
tlsSessionStore : Object . keys ( tlsSessionStore ) . length ,
blockedUsers : obj . blockedUsers ,
blockedAgents : obj . blockedAgents
2019-05-01 18:02:03 -04:00
} ;
}
// Agent counters
obj . agentStats = {
createMeshAgentCount : 0 ,
2019-05-24 18:11:00 -04:00
agentClose : 0 ,
agentBinaryUpdate : 0 ,
2019-05-01 18:02:03 -04:00
coreIsStableCount : 0 ,
verifiedAgentConnectionCount : 0 ,
clearingCoreCount : 0 ,
updatingCoreCount : 0 ,
recoveryCoreIsStableCount : 0 ,
meshDoesNotExistCount : 0 ,
invalidPkcsSignatureCount : 0 ,
invalidRsaSignatureCount : 0 ,
invalidJsonCount : 0 ,
unknownAgentActionCount : 0 ,
agentBadWebCertHashCount : 0 ,
agentBadSignature1Count : 0 ,
agentBadSignature2Count : 0 ,
agentMaxSessionHoldCount : 0 ,
invalidDomainMeshCount : 0 ,
invalidMeshTypeCount : 0 ,
invalidDomainMesh2Count : 0 ,
invalidMeshType2Count : 0 ,
2019-06-05 18:24:07 -04:00
duplicateAgentCount : 0 ,
maxDomainDevicesReached : 0
2019-05-01 18:02:03 -04:00
}
obj . getAgentStats = function ( ) { return obj . agentStats ; }
2017-08-28 12:27:45 -04:00
// Authenticate the user
obj . authenticate = function ( name , pass , domain , fn ) {
2019-01-15 21:21:03 -05:00
if ( ( typeof ( name ) != 'string' ) || ( typeof ( pass ) != 'string' ) || ( typeof ( domain ) != 'object' ) ) { fn ( new Error ( 'invalid fields' ) ) ; return ; }
2017-08-28 12:27:45 -04:00
if ( ! module . parent ) console . log ( 'authenticating %s:%s:%s' , domain . id , name , pass ) ;
2019-04-11 16:41:51 -04:00
if ( domain . auth == 'ldap' ) {
2019-04-11 17:52:23 -04:00
if ( domain . ldapoptions . url == 'test' ) {
// Fake LDAP login
var xxuser = domain . ldapoptions [ name . toLowerCase ( ) ] ;
if ( xxuser == null ) {
fn ( new Error ( 'invalid password' ) ) ;
return ;
2019-04-11 16:41:51 -04:00
} else {
2019-04-15 18:05:09 -04:00
var username = xxuser [ 'displayName' ] ;
if ( domain . ldapusername ) { username = xxuser [ domain . ldapusername ] ; }
2019-04-11 17:52:23 -04:00
var shortname = null ;
2019-04-15 18:05:09 -04:00
if ( domain . ldapuserbinarykey ) {
// Use a binary key as the userid
if ( xxuser [ domain . ldapuserbinarykey ] ) { shortname = Buffer . from ( xxuser [ domain . ldapuserbinarykey ] , 'binary' ) . toString ( 'hex' ) ; }
} else if ( domain . ldapuserkey ) {
// Use a string key as the userid
if ( xxuser [ domain . ldapuserkey ] ) { shortname = xxuser [ domain . ldapuserkey ] ; }
} else {
// Use the default key as the userid
if ( xxuser . objectSid ) { shortname = Buffer . from ( xxuser . objectSid , 'binary' ) . toString ( 'hex' ) . toLowerCase ( ) ; }
else if ( xxuser . objectGUID ) { shortname = Buffer . from ( xxuser . objectGUID , 'binary' ) . toString ( 'hex' ) . toLowerCase ( ) ; }
else if ( xxuser . name ) { shortname = xxuser . name ; }
else if ( xxuser . cn ) { shortname = xxuser . cn ; }
}
if ( username == null ) { fn ( new Error ( 'no user name' ) ) ; return ; }
if ( shortname == null ) { fn ( new Error ( 'no user identifier' ) ) ; return ; }
2019-04-11 17:52:23 -04:00
var userid = 'user/' + domain . id + '/' + shortname ;
var user = obj . users [ userid ] ;
2019-04-15 18:05:09 -04:00
2019-04-11 17:52:23 -04:00
if ( user == null ) {
2019-04-15 18:05:09 -04:00
// Create a new user
var user = { type : 'user' , _id : userid , name : username , creation : Math . floor ( Date . now ( ) / 1000 ) , login : Math . floor ( Date . now ( ) / 1000 ) , domain : domain . id } ;
2019-05-12 22:14:24 -04:00
if ( domain . newaccountsrights ) { user . siteadmin = domain . newaccountsrights ; }
2019-04-11 17:52:23 -04:00
var usercount = 0 ;
for ( var i in obj . users ) { if ( obj . users [ i ] . domain == domain . id ) { usercount ++ ; } }
2019-06-27 01:13:56 -04:00
if ( usercount == 0 ) { user . siteadmin = 4294967295 ; /*if (domain.newaccounts === 2) { delete domain.newaccounts; }*/ } // If this is the first user, give the account site admin.
2019-04-11 17:52:23 -04:00
obj . users [ user . _id ] = user ;
obj . db . SetUser ( user ) ;
2019-05-29 17:36:14 -04:00
var event = { etype : 'user' , userid : userid , username : username , account : obj . CloneSafeUser ( user ) , action : 'accountcreate' , msg : 'Account created, name is ' + name , domain : domain . id } ;
if ( obj . db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to create the user. Another event will come.
obj . parent . DispatchEvent ( [ '*' , 'server-users' ] , obj , event ) ;
2019-04-11 17:52:23 -04:00
return fn ( null , user . _id ) ;
} else {
// This is an existing user
2019-04-15 18:05:09 -04:00
// If the display username has changes, update it.
if ( user . name != username ) {
user . name = username ;
2019-05-28 20:25:23 -04:00
obj . db . SetUser ( user ) ;
2019-07-29 19:35:48 -04:00
var event = { etype : 'user' , userid : userid , username : user . name , account : obj . CloneSafeUser ( user ) , action : 'accountchange' , msg : 'Changed account display name to ' + username , domain : domain . id } ;
2019-05-28 20:25:23 -04:00
if ( obj . 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 . DispatchEvent ( [ '*' , 'server-users' , user . _id ] , obj , event ) ;
2019-04-15 18:05:09 -04:00
}
// If user is locker out, block here.
2019-04-11 17:52:23 -04:00
if ( ( user . siteadmin ) && ( user . siteadmin != 0xFFFFFFFF ) && ( user . siteadmin & 32 ) != 0 ) { fn ( 'locked' ) ; return ; }
return fn ( null , user . _id ) ;
}
2019-04-11 16:41:51 -04:00
}
2019-04-11 17:52:23 -04:00
} else {
// LDAP login
var LdapAuth = require ( 'ldapauth-fork' ) ;
var ldap = new LdapAuth ( domain . ldapoptions ) ;
ldap . authenticate ( name , pass , function ( err , xxuser ) {
try { ldap . close ( ) ; } catch ( ex ) { console . log ( ex ) ; } // Close the LDAP object
if ( err ) { fn ( new Error ( 'invalid password' ) ) ; return ; }
var shortname = null ;
2019-04-15 18:05:09 -04:00
var username = xxuser [ 'displayName' ] ;
if ( domain . ldapusername ) { username = xxuser [ domain . ldapusername ] ; }
if ( domain . ldapuserbinarykey ) {
// Use a binary key as the userid
if ( xxuser [ domain . ldapuserbinarykey ] ) { shortname = Buffer . from ( xxuser [ domain . ldapuserbinarykey ] , 'binary' ) . toString ( 'hex' ) . toLowerCase ( ) ; }
} else if ( domain . ldapuserkey ) {
// Use a string key as the userid
if ( xxuser [ domain . ldapuserkey ] ) { shortname = xxuser [ domain . ldapuserkey ] ; }
} else {
// Use the default key as the userid
if ( xxuser . objectSid ) { shortname = Buffer . from ( xxuser . objectSid , 'binary' ) . toString ( 'hex' ) . toLowerCase ( ) ; }
else if ( xxuser . objectGUID ) { shortname = Buffer . from ( xxuser . objectGUID , 'binary' ) . toString ( 'hex' ) . toLowerCase ( ) ; }
else if ( xxuser . name ) { shortname = xxuser . name ; }
else if ( xxuser . cn ) { shortname = xxuser . cn ; }
}
if ( username == null ) { fn ( new Error ( 'no user name' ) ) ; return ; }
if ( shortname == null ) { fn ( new Error ( 'no user identifier' ) ) ; return ; }
2019-04-11 17:52:23 -04:00
var userid = 'user/' + domain . id + '/' + shortname ;
var user = obj . users [ userid ] ;
2019-04-15 18:05:09 -04:00
2019-04-11 17:52:23 -04:00
if ( user == null ) {
// This user does not exist, create a new account.
var user = { type : 'user' , _id : userid , name : shortname , creation : Math . floor ( Date . now ( ) / 1000 ) , login : Math . floor ( Date . now ( ) / 1000 ) , domain : domain . id } ;
2019-05-12 22:14:24 -04:00
if ( domain . newaccountsrights ) { user . siteadmin = domain . newaccountsrights ; }
2019-04-11 17:52:23 -04:00
var usercount = 0 ;
for ( var i in obj . users ) { if ( obj . users [ i ] . domain == domain . id ) { usercount ++ ; } }
2019-06-27 01:13:56 -04:00
if ( usercount == 0 ) { user . siteadmin = 4294967295 ; /*if (domain.newaccounts === 2) { delete domain.newaccounts; }*/ } // If this is the first user, give the account site admin.
2019-04-11 17:52:23 -04:00
obj . users [ user . _id ] = user ;
obj . db . SetUser ( user ) ;
2019-07-29 19:35:48 -04:00
var event = { etype : 'user' , userid : user . _id , username : user . name , account : obj . CloneSafeUser ( user ) , action : 'accountcreate' , msg : 'Account created, name is ' + name , domain : domain . id } ;
2019-05-29 17:36:14 -04:00
if ( obj . db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to create the user. Another event will come.
obj . parent . DispatchEvent ( [ '*' , 'server-users' ] , obj , event ) ;
2019-04-11 17:52:23 -04:00
return fn ( null , user . _id ) ;
} else {
// This is an existing user
2019-04-15 18:05:09 -04:00
// If the display username has changes, update it.
if ( user . name != username ) {
user . name = username ;
2019-05-28 20:25:23 -04:00
obj . db . SetUser ( user ) ;
2019-07-29 19:35:48 -04:00
var event = { etype : 'user' , userid : user . _id , username : user . name , account : obj . CloneSafeUser ( user ) , action : 'accountchange' , msg : 'Changed account display name to ' + username , domain : domain . id } ;
2019-05-28 20:25:23 -04:00
if ( obj . 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 . DispatchEvent ( [ '*' , 'server-users' , user . _id ] , obj , event ) ;
2019-04-15 18:05:09 -04:00
}
// If user is locker out, block here.
2019-04-11 17:52:23 -04:00
if ( ( user . siteadmin ) && ( user . siteadmin != 0xFFFFFFFF ) && ( user . siteadmin & 32 ) != 0 ) { fn ( 'locked' ) ; return ; }
return fn ( null , user . _id ) ;
}
} ) ;
}
2017-08-28 12:27:45 -04:00
} else {
2019-04-11 16:41:51 -04:00
// Regular login
var user = obj . users [ 'user/' + domain . id + '/' + name . toLowerCase ( ) ] ;
// Query the db for the given username
if ( ! user ) { fn ( new Error ( 'cannot find user' ) ) ; return ; }
// Apply the same algorithm to the POSTed password, applying the hash against the pass / salt, if there is a match we found the user
if ( user . salt == null ) {
fn ( new Error ( 'invalid password' ) ) ;
2017-11-02 21:44:27 -04:00
} else {
2019-04-11 16:41:51 -04:00
if ( user . passtype != null ) {
// IIS default clear or weak password hashing (SHA-1)
require ( './pass' ) . iishash ( user . passtype , pass , user . salt , function ( err , hash ) {
if ( err ) return fn ( err ) ;
if ( hash == user . hash ) {
// Update the password to the stronger format.
2019-05-14 17:39:26 -04:00
require ( './pass' ) . hash ( pass , function ( err , salt , hash , tag ) { if ( err ) throw err ; user . salt = salt ; user . hash = hash ; delete user . passtype ; obj . db . SetUser ( user ) ; } , 0 ) ;
2019-04-11 16:41:51 -04:00
if ( ( user . siteadmin ) && ( user . siteadmin != 0xFFFFFFFF ) && ( user . siteadmin & 32 ) != 0 ) { fn ( 'locked' ) ; return ; }
return fn ( null , user . _id ) ;
}
fn ( new Error ( 'invalid password' ) , null , user . passhint ) ;
} ) ;
} else {
// Default strong password hashing (pbkdf2 SHA384)
2019-05-14 17:39:26 -04:00
require ( './pass' ) . hash ( pass , user . salt , function ( err , hash , tag ) {
2019-04-11 16:41:51 -04:00
if ( err ) return fn ( err ) ;
if ( hash == user . hash ) {
if ( ( user . siteadmin ) && ( user . siteadmin != 0xFFFFFFFF ) && ( user . siteadmin & 32 ) != 0 ) { fn ( 'locked' ) ; return ; }
return fn ( null , user . _id ) ;
}
fn ( new Error ( 'invalid password' ) , null , user . passhint ) ;
2019-05-14 17:39:26 -04:00
} , 0 ) ;
2019-04-11 16:41:51 -04:00
}
2017-11-02 21:44:27 -04:00
}
2017-08-28 12:27:45 -04:00
}
2018-08-30 15:05:23 -04:00
} ;
2017-11-02 21:44:27 -04:00
2017-08-28 12:27:45 -04:00
/ *
obj . restrict = function ( req , res , next ) {
console . log ( 'restrict' , req . url ) ;
var domain = getDomain ( req ) ;
if ( req . session . userid ) {
next ( ) ;
} else {
2019-11-17 17:33:29 -05:00
req . session . messageid = 111 ; // Access denied.
2017-08-28 12:27:45 -04:00
res . redirect ( domain . url + 'login' ) ;
}
2018-08-30 15:05:23 -04:00
} ;
2017-08-28 12:27:45 -04:00
* /
2019-01-30 16:43:42 -05:00
// Check if the source IP address is in the IP list, return false if not.
function checkIpAddressEx ( req , res , ipList , closeIfThis ) {
2017-09-27 15:43:20 -04:00
try {
2019-01-05 17:15:14 -05:00
var ip ;
if ( req . connection ) { // HTTP(S) request
ip = req . ip ;
2020-01-10 14:19:23 -05:00
2019-01-30 16:43:42 -05:00
if ( ip ) { for ( var i = 0 ; i < ipList . length ; i ++ ) { if ( require ( 'ipcheck' ) . match ( ip , ipList [ i ] ) ) { if ( closeIfThis === true ) { res . sendStatus ( 401 ) ; } return true ; } } }
if ( closeIfThis === false ) { res . sendStatus ( 401 ) ; }
2019-01-05 17:15:14 -05:00
} else if ( req . _socket ) { // WebSocket request
ip = req . _socket . remoteAddress ;
// If a trusted reverse-proxy is sending us the remote IP address, use it.
// This is not done automatically for web socket like it's done for HTTP requests.
2019-10-07 14:38:01 -04:00
if ( ( obj . args . trustedproxy ) && ( res . headers [ 'x-forwarded-for' ] ) && ( ( obj . args . trustedproxy === true ) || ( obj . args . trustedproxy === ip ) || ( ( '::ffff:' ) + obj . args . trustedproxy === ip ) ) ) { ip = res . headers [ 'x-forwarded-for' ] ; }
else if ( ( obj . args . tlsoffload ) && ( res . headers [ 'x-forwarded-for' ] ) && ( ( obj . args . tlsoffload === true ) || ( obj . args . tlsoffload === ip ) || ( ( '::ffff:' ) + obj . args . tlsoffload === ip ) ) ) { ip = res . headers [ 'x-forwarded-for' ] ; }
2019-01-05 17:15:14 -05:00
2019-01-30 16:43:42 -05:00
if ( ip ) { for ( var i = 0 ; i < ipList . length ; i ++ ) { if ( require ( 'ipcheck' ) . match ( ip , ipList [ i ] ) ) { if ( closeIfThis === true ) { try { req . close ( ) ; } catch ( e ) { } } return true ; } } }
if ( closeIfThis === false ) { try { req . close ( ) ; } catch ( e ) { } }
2019-10-04 15:18:56 -04:00
}
2019-01-30 16:43:42 -05:00
} catch ( e ) { console . log ( e ) ; } // Should never happen
2017-09-27 15:43:20 -04:00
return false ;
}
2017-10-02 17:12:29 -04:00
// Check if the source IP address is allowed, return domain if allowed
2020-03-26 15:06:34 -04:00
// If there is a fail and null is returned, the request or connection is closed already.
2019-01-05 15:04:18 -05:00
function checkUserIpAddress ( req , res ) {
2020-01-10 14:19:23 -05:00
if ( ( parent . config . settings . userblockedip != null ) && ( checkIpAddressEx ( req , res , parent . config . settings . userblockedip , true ) == true ) ) { obj . blockedUsers ++ ; return null ; }
if ( ( parent . config . settings . userallowedip != null ) && ( checkIpAddressEx ( req , res , parent . config . settings . userallowedip , false ) == false ) ) { obj . blockedUsers ++ ; return null ; }
2019-01-05 15:04:18 -05:00
const domain = ( req . url ? getDomain ( req ) : getDomain ( res ) ) ;
2020-03-26 15:06:34 -04:00
if ( domain == null ) { parent . debug ( 'web' , 'handleRootRequest: invalid domain.' ) ; try { res . sendStatus ( 404 ) ; } catch ( ex ) { } return ; }
2020-01-10 14:19:23 -05:00
if ( ( domain . userblockedip != null ) && ( checkIpAddressEx ( req , res , domain . userblockedip , true ) == true ) ) { obj . blockedUsers ++ ; return null ; }
if ( ( domain . userallowedip != null ) && ( checkIpAddressEx ( req , res , domain . userallowedip , false ) == false ) ) { obj . blockedUsers ++ ; return null ; }
2019-01-30 16:43:42 -05:00
return domain ;
}
// Check if the source IP address is allowed, return domain if allowed
2020-03-26 15:06:34 -04:00
// If there is a fail and null is returned, the request or connection is closed already.
2019-01-30 16:43:42 -05:00
function checkAgentIpAddress ( req , res ) {
2020-03-26 15:06:34 -04:00
if ( ( parent . config . settings . agentblockedip != null ) && ( checkIpAddressEx ( req , res , parent . config . settings . agentblockedip , null ) == true ) ) { obj . blockedAgents ++ ; return null ; }
if ( ( parent . config . settings . agentallowedip != null ) && ( checkIpAddressEx ( req , res , parent . config . settings . agentallowedip , null ) == false ) ) { obj . blockedAgents ++ ; return null ; }
2019-01-30 16:43:42 -05:00
const domain = ( req . url ? getDomain ( req ) : getDomain ( res ) ) ;
2020-01-10 14:19:23 -05:00
if ( ( domain . agentblockedip != null ) && ( checkIpAddressEx ( req , res , domain . agentblockedip , null ) == true ) ) { obj . blockedAgents ++ ; return null ; }
if ( ( domain . agentallowedip != null ) && ( checkIpAddressEx ( req , res , domain . agentallowedip , null ) == false ) ) { obj . blockedAgents ++ ; return null ; }
2017-10-02 17:12:29 -04:00
return domain ;
}
2017-08-28 12:27:45 -04:00
// Return the current domain of the request
2020-03-26 15:06:34 -04:00
// Request or connection says open regardless of the response
2017-08-28 12:27:45 -04:00
function getDomain ( req ) {
2018-01-09 23:13:41 -05:00
if ( req . xdomain != null ) { return req . xdomain ; } // Domain already set for this request, return it.
2020-02-18 15:02:42 -05:00
if ( req . headers . host != null ) { var d = obj . dnsDomains [ req . headers . host . split ( ':' ) [ 0 ] . toLowerCase ( ) ] ; if ( d != null ) return d ; } // If this is a DNS name domain, return it here.
2017-08-28 12:27:45 -04:00
var x = req . url . split ( '/' ) ;
if ( x . length < 2 ) return parent . config . domains [ '' ] ;
2018-08-30 15:05:23 -04:00
var y = parent . config . domains [ x [ 1 ] . toLowerCase ( ) ] ;
2019-01-04 18:29:27 -05:00
if ( ( y != null ) && ( y . dns == null ) ) { return parent . config . domains [ x [ 1 ] . toLowerCase ( ) ] ; }
2017-08-28 12:27:45 -04:00
return parent . config . domains [ '' ] ;
}
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
function handleLogoutRequest ( req , res ) {
2019-01-05 15:04:18 -05:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 15:06:34 -04:00
if ( domain == null ) { return ; }
if ( domain . auth == 'sspi' ) { parent . debug ( 'web' , 'handleLogoutRequest: failed checks.' ) ; res . sendStatus ( 404 ) ; return ; }
2019-11-25 17:30:23 -05:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2019-01-16 15:04:48 -05:00
2017-08-28 12:27:45 -04:00
res . set ( { 'Cache-Control' : 'no-cache, no-store, must-revalidate' , 'Pragma' : 'no-cache' , 'Expires' : '0' } ) ;
// Destroy the user's session to log them out will be re-created next request
if ( req . session . userid ) {
2018-08-30 15:05:23 -04:00
var user = obj . users [ req . session . userid ] ;
2019-07-29 19:35:48 -04:00
if ( user != null ) { obj . parent . DispatchEvent ( [ '*' ] , obj , { etype : 'user' , userid : user . _id , username : user . name , action : 'logout' , msg : 'Account logout' , domain : domain . id } ) ; }
2017-08-28 12:27:45 -04:00
}
2018-08-22 19:18:01 -04:00
req . session = null ;
2019-12-27 01:53:01 -05:00
if ( req . query . key != null ) { res . redirect ( domain . url + '?key=' + req . query . key ) ; } else { res . redirect ( domain . url ) ; }
2019-08-23 14:51:48 -04:00
parent . debug ( 'web' , 'handleLogoutRequest: success.' ) ;
2017-08-28 12:27:45 -04:00
}
2018-05-24 00:43:44 -04:00
2019-02-08 01:30:33 -05:00
// Return true if this user has 2-step auth active
2019-11-28 23:57:34 -05:00
function checkUserOneTimePasswordRequired ( domain , user , req ) {
// Check if we can skip 2nd factor auth because of the source IP address
if ( ( req != null ) && ( req . ip != null ) && ( domain . passwordrequirements != null ) && ( domain . passwordrequirements . skip2factor != null ) ) {
for ( var i in domain . passwordrequirements . skip2factor ) { if ( require ( 'ipcheck' ) . match ( req . ip , domain . passwordrequirements . skip2factor [ i ] ) === true ) return false ; }
}
2019-11-29 01:29:22 -05:00
// Check if a 2nd factor cookie is present
if ( typeof req . headers . cookie == 'string' ) {
const cookies = req . headers . cookie . split ( '; ' ) ;
for ( var i in cookies ) {
if ( cookies [ i ] . startsWith ( 'twofactor=' ) ) {
var twoFactorCookie = obj . parent . decodeCookie ( decodeURIComponent ( cookies [ i ] . substring ( 10 ) ) , obj . parent . loginCookieEncryptionKey , ( 30 * 24 * 60 ) ) ; // 30 day timeout
2020-01-26 13:05:27 -05:00
if ( ( twoFactorCookie != null ) && ( obj . args . cookieipcheck !== false ) && ( ( twoFactorCookie . ip == null ) || ( twoFactorCookie . ip === cleanRemoteAddr ( req . ip ) ) ) && ( twoFactorCookie . userid == user . _id ) ) { return false ; }
2019-11-29 01:29:22 -05:00
}
}
}
2019-11-28 23:57:34 -05:00
// Check if a 2nd factor is present
2020-03-20 21:51:28 -04:00
return ( ( parent . config . settings . no2factorauth !== true ) && ( ( user . otpsecret != null ) || ( ( user . email != null ) && ( user . emailVerified == true ) && ( parent . mailserver != null ) && ( user . otpekey != null ) ) || ( ( user . otphkeys != null ) && ( user . otphkeys . length > 0 ) ) ) ) ;
2019-02-08 01:30:33 -05:00
}
// Check the 2-step auth token
2019-02-10 19:04:36 -05:00
function checkUserOneTimePassword ( req , domain , user , token , hwtoken , func ) {
2019-08-23 18:45:36 -04:00
parent . debug ( 'web' , 'checkUserOneTimePassword()' ) ;
2019-03-26 17:11:51 -04:00
const twoStepLoginSupported = ( ( domain . auth != 'sspi' ) && ( obj . parent . certificates . CommonName . indexOf ( '.' ) != - 1 ) && ( obj . args . nousers !== true ) && ( parent . config . settings . no2factorauth !== true ) ) ;
2019-08-23 18:45:36 -04:00
if ( twoStepLoginSupported == false ) { parent . debug ( 'web' , 'checkUserOneTimePassword: not supported.' ) ; func ( true ) ; return ; } ;
2019-02-08 01:30:33 -05:00
2020-03-13 23:39:21 -04:00
// Check if we can use OTP tokens with email
var otpemail = ( parent . mailserver != null ) ;
2020-03-14 18:03:50 -04:00
if ( ( typeof domain . passwordrequirements == 'object' ) && ( domain . passwordrequirements . email2factor == false ) ) { otpemail = false ; }
2020-03-13 23:39:21 -04:00
// Check email key
if ( ( otpemail ) && ( user . otpekey != null ) && ( user . otpekey . d != null ) && ( user . otpekey . k === token ) ) {
var deltaTime = ( Date . now ( ) - user . otpekey . d ) ;
if ( ( deltaTime > 0 ) && ( deltaTime < 300000 ) ) { // Allow 5 minutes to use the email token (10000 * 60 * 5).
user . otpekey = { } ;
obj . db . SetUser ( user ) ;
parent . debug ( 'web' , 'checkUserOneTimePassword: success (email).' ) ;
func ( true ) ;
return ;
}
}
2019-03-23 15:14:53 -04:00
// Check hardware key
2019-02-10 19:04:36 -05:00
if ( user . otphkeys && ( user . otphkeys . length > 0 ) && ( typeof ( hwtoken ) == 'string' ) && ( hwtoken . length > 0 ) ) {
2019-03-23 15:14:53 -04:00
var authResponse = null ;
try { authResponse = JSON . parse ( hwtoken ) ; } catch ( ex ) { }
2019-05-16 17:55:07 -04:00
if ( ( authResponse != null ) && ( authResponse . clientDataJSON ) ) {
// Get all WebAuthn keys
var webAuthnKeys = [ ] ;
for ( var i = 0 ; i < user . otphkeys . length ; i ++ ) { if ( user . otphkeys [ i ] . type == 3 ) { webAuthnKeys . push ( user . otphkeys [ i ] ) ; } }
if ( webAuthnKeys . length > 0 ) {
// Decode authentication response
var clientAssertionResponse = { response : { } } ;
clientAssertionResponse . id = authResponse . id ;
clientAssertionResponse . rawId = Buffer . from ( authResponse . id , 'base64' ) ;
clientAssertionResponse . response . authenticatorData = Buffer . from ( authResponse . authenticatorData , 'base64' ) ;
clientAssertionResponse . response . clientDataJSON = Buffer . from ( authResponse . clientDataJSON , 'base64' ) ;
clientAssertionResponse . response . signature = Buffer . from ( authResponse . signature , 'base64' ) ;
clientAssertionResponse . response . userHandle = Buffer . from ( authResponse . userHandle , 'base64' ) ;
// Look for the key with clientAssertionResponse.id
var webAuthnKey = null ;
for ( var i = 0 ; i < webAuthnKeys . length ; i ++ ) { if ( webAuthnKeys [ i ] . keyId == clientAssertionResponse . id ) { webAuthnKey = webAuthnKeys [ i ] ; } }
// If we found a valid key to use, let's validate the response
if ( webAuthnKey != null ) {
// Figure out the origin
var httpport = ( ( args . aliasport != null ) ? args . aliasport : args . port ) ;
2019-12-20 17:50:06 -05:00
var origin = 'https://' + ( domain . dns ? domain . dns : parent . certificates . CommonName ) ;
2019-05-16 17:55:07 -04:00
if ( httpport != 443 ) { origin += ':' + httpport ; }
var assertionExpectations = {
challenge : req . session . u2fchallenge ,
origin : origin ,
2019-12-20 17:50:06 -05:00
factor : 'either' ,
fmt : 'fido-u2f' ,
2019-05-16 17:55:07 -04:00
publicKey : webAuthnKey . publicKey ,
prevCounter : webAuthnKey . counter ,
2019-12-06 19:49:40 -05:00
userHandle : Buffer . from ( user . _id , 'binary' ) . toString ( 'base64' )
2019-05-16 17:55:07 -04:00
} ;
var webauthnResponse = null ;
2019-08-23 18:45:36 -04:00
try { webauthnResponse = obj . webauthn . verifyAuthenticatorAssertionResponse ( clientAssertionResponse . response , assertionExpectations ) ; } catch ( ex ) { parent . debug ( 'web' , 'checkUserOneTimePassword: exception ' + ex ) ; console . log ( ex ) ; }
2019-05-16 17:55:07 -04:00
if ( ( webauthnResponse != null ) && ( webauthnResponse . verified === true ) ) {
// Update the hardware key counter and accept the 2nd factor
webAuthnKey . counter = webauthnResponse . counter ;
obj . db . SetUser ( user ) ;
2020-03-13 23:39:21 -04:00
parent . debug ( 'web' , 'checkUserOneTimePassword: success (hardware).' ) ;
2019-05-16 17:55:07 -04:00
func ( true ) ;
} else {
2020-03-13 23:39:21 -04:00
parent . debug ( 'web' , 'checkUserOneTimePassword: fail (hardware).' ) ;
2019-05-16 17:55:07 -04:00
func ( false ) ;
2019-03-25 17:43:58 -04:00
}
2019-03-23 15:14:53 -04:00
return ;
}
2019-02-08 12:24:00 -05:00
}
2019-02-08 01:30:33 -05:00
}
}
// Check Google Authenticator
const otplib = require ( 'otplib' )
2019-02-10 19:04:36 -05:00
otplib . authenticator . options = { window : 2 } ; // Set +/- 1 minute window
2020-03-13 23:39:21 -04:00
if ( user . otpsecret && ( typeof ( token ) == 'string' ) && ( token . length == 6 ) && ( otplib . authenticator . check ( token , user . otpsecret ) == true ) ) {
parent . debug ( 'web' , 'checkUserOneTimePassword: success (authenticator).' ) ;
func ( true ) ;
return ;
} ;
2019-02-08 01:30:33 -05:00
// Check written down keys
2019-02-08 12:24:00 -05:00
if ( ( user . otpkeys != null ) && ( user . otpkeys . keys != null ) && ( typeof ( token ) == 'string' ) && ( token . length == 8 ) ) {
2019-02-08 01:30:33 -05:00
var tokenNumber = parseInt ( token ) ;
2020-03-13 23:39:21 -04:00
for ( var i = 0 ; i < user . otpkeys . keys . length ; i ++ ) {
if ( ( tokenNumber === user . otpkeys . keys [ i ] . p ) && ( user . otpkeys . keys [ i ] . u === true ) ) {
parent . debug ( 'web' , 'checkUserOneTimePassword: success (one-time).' ) ;
user . otpkeys . keys [ i ] . u = false ; func ( true ) ; return ;
}
}
2019-02-08 01:30:33 -05:00
}
2019-02-08 12:24:00 -05:00
// Check OTP hardware key
2019-02-08 17:46:07 -05:00
if ( ( domain . yubikey != null ) && ( domain . yubikey . id != null ) && ( domain . yubikey . secret != null ) && ( user . otphkeys != null ) && ( user . otphkeys . length > 0 ) && ( typeof ( token ) == 'string' ) && ( token . length == 44 ) ) {
2019-02-08 12:24:00 -05:00
var keyId = token . substring ( 0 , 12 ) ;
2019-02-10 23:13:03 -05:00
// Find a matching OTP key
2019-02-08 12:24:00 -05:00
var match = false ;
for ( var i = 0 ; i < user . otphkeys . length ; i ++ ) { if ( ( user . otphkeys [ i ] . type === 2 ) && ( user . otphkeys [ i ] . keyid === keyId ) ) { match = true ; } }
// If we have a match, check the OTP
if ( match === true ) {
var yubikeyotp = require ( 'yubikeyotp' ) ;
var request = { otp : token , id : domain . yubikey . id , key : domain . yubikey . secret , timestamp : true }
if ( domain . yubikey . proxy ) { request . requestParams = { proxy : domain . yubikey . proxy } ; }
2020-03-13 23:39:21 -04:00
yubikeyotp . verifyOTP ( request , function ( err , results ) {
if ( ( results != null ) && ( results . status == 'OK' ) ) {
parent . debug ( 'web' , 'checkUserOneTimePassword: success (Yubikey).' ) ;
func ( true ) ;
} else {
parent . debug ( 'web' , 'checkUserOneTimePassword: fail (Yubikey).' ) ;
func ( false ) ;
}
} ) ;
2019-02-08 12:24:00 -05:00
return ;
}
}
2019-08-23 18:45:36 -04:00
parent . debug ( 'web' , 'checkUserOneTimePassword: fail (2).' ) ;
2019-02-08 12:24:00 -05:00
func ( false ) ;
2019-02-08 01:30:33 -05:00
}
2019-02-08 12:24:00 -05:00
// Return a U2F hardware key challenge
2019-02-10 19:04:36 -05:00
function getHardwareKeyChallenge ( req , domain , user , func ) {
if ( req . session . u2fchallenge ) { delete req . session . u2fchallenge ; } ;
2019-02-08 01:30:33 -05:00
if ( user . otphkeys && ( user . otphkeys . length > 0 ) ) {
2019-03-23 01:33:53 -04:00
// Get all WebAuthn keys
2019-05-16 17:55:07 -04:00
var webAuthnKeys = [ ] ;
for ( var i = 0 ; i < user . otphkeys . length ; i ++ ) { if ( user . otphkeys [ i ] . type == 3 ) { webAuthnKeys . push ( user . otphkeys [ i ] ) ; } }
if ( webAuthnKeys . length > 0 ) {
// Generate a Webauthn challenge, this is really easy, no need to call any modules to do this.
var authnOptions = { type : 'webAuthn' , keyIds : [ ] , timeout : 60000 , challenge : obj . crypto . randomBytes ( 64 ) . toString ( 'base64' ) } ;
for ( var i = 0 ; i < webAuthnKeys . length ; i ++ ) { authnOptions . keyIds . push ( webAuthnKeys [ i ] . keyId ) ; }
req . session . u2fchallenge = authnOptions . challenge ;
2019-08-23 18:45:36 -04:00
parent . debug ( 'web' , 'getHardwareKeyChallenge: success' ) ;
2019-05-16 17:55:07 -04:00
func ( JSON . stringify ( authnOptions ) ) ;
2019-05-21 17:19:32 -04:00
return ;
2019-02-08 12:24:00 -05:00
}
2019-02-08 01:30:33 -05:00
}
2019-08-23 18:45:36 -04:00
parent . debug ( 'web' , 'getHardwareKeyChallenge: fail' ) ;
2019-05-21 17:19:32 -04:00
func ( '' ) ;
2019-02-08 01:30:33 -05:00
}
2019-08-26 15:20:24 -04:00
function handleLoginRequest ( req , res , direct ) {
2019-01-05 15:04:18 -05:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 15:06:34 -04:00
if ( domain == null ) { return ; }
2019-11-25 17:30:23 -05:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2019-01-15 21:21:03 -05:00
2019-11-18 20:31:42 -05:00
// Check if this is a banned ip address
if ( obj . checkAllowLogin ( req ) == false ) {
// Wait and redirect the user
setTimeout ( function ( ) {
req . session . messageid = 114 ; // IP address blocked, try again later.
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
} , 2000 + ( obj . crypto . randomBytes ( 2 ) . readUInt16BE ( 0 ) % 4095 ) ) ;
return ;
}
2019-01-15 21:21:03 -05:00
// Normally, use the body username/password. If this is a token, use the username/password in the session.
var xusername = req . body . username , xpassword = req . body . password ;
if ( ( xusername == null ) && ( xpassword == null ) && ( req . body . token != null ) ) { xusername = req . session . tokenusername ; xpassword = req . session . tokenpassword ; }
// Authenticate the user
obj . authenticate ( xusername , xpassword , domain , function ( err , userid , passhint ) {
2017-08-28 12:27:45 -04:00
if ( userid ) {
var user = obj . users [ userid ] ;
2017-12-13 17:52:57 -05:00
2019-02-08 01:30:33 -05:00
// Check if this user has 2-step login active
2019-11-28 23:57:34 -05:00
if ( ( req . session . loginmode != '6' ) && checkUserOneTimePasswordRequired ( domain , user , req ) ) {
2020-03-13 23:39:21 -04:00
if ( ( req . body . hwtoken == '**email**' ) && ( user . email != null ) && ( user . emailVerified == true ) && ( parent . mailserver != null ) && ( user . otpekey != null ) ) {
user . otpekey = { k : obj . common . zeroPad ( getRandomEightDigitInteger ( ) , 8 ) , d : Date . now ( ) } ;
obj . db . SetUser ( user ) ;
parent . debug ( 'web' , 'Sending 2FA email to: ' + user . email ) ;
2020-04-04 22:29:20 -04:00
parent . mailserver . sendAccountLoginMail ( domain , user . email , user . otpekey . k , obj . getLanguageCodes ( req ) ) ;
2020-03-13 23:39:21 -04:00
req . session . messageid = 2 ; // "Email sent" message
req . session . loginmode = '4' ;
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
return ;
}
2019-02-10 19:04:36 -05:00
checkUserOneTimePassword ( req , domain , user , req . body . token , req . body . hwtoken , function ( result ) {
2019-02-08 12:24:00 -05:00
if ( result == false ) {
2019-02-12 22:23:40 -05:00
var randomWaitTime = 0 ;
2019-10-04 15:18:56 -04:00
2019-02-05 22:07:12 -05:00
// 2-step auth is required, but the token is not present or not valid.
2019-02-12 22:23:40 -05:00
if ( ( req . body . token != null ) || ( req . body . hwtoken != null ) ) {
randomWaitTime = 2000 + ( obj . crypto . randomBytes ( 2 ) . readUInt16BE ( 0 ) % 4095 ) ; // This is a fail, wait a random time. 2 to 6 seconds.
2019-11-17 17:33:29 -05:00
req . session . messageid = 108 ; // Invalid token, try again.
2020-02-17 13:24:32 -05:00
if ( obj . parent . authlog ) { obj . parent . authLog ( 'https' , 'Failed 2FA for ' + xusername + ' from ' + cleanRemoteAddr ( req . ip ) + ' port ' + req . port ) ; }
2019-08-23 14:51:48 -04:00
parent . debug ( 'web' , 'handleLoginRequest: invalid 2FA token' ) ;
2019-11-17 19:20:53 -05:00
obj . parent . DispatchEvent ( [ '*' , 'server-users' , 'user/' + domain . id + '/' + user . name ] , obj , { action : 'authfail' , username : user . name , userid : 'user/' + domain . id + '/' + user . name , domain : domain . id , msg : 'User login attempt with incorrect 2nd factor from ' + cleanRemoteAddr ( req . ip ) } ) ;
2019-11-18 20:31:42 -05:00
obj . setbadLogin ( req ) ;
2019-08-23 14:51:48 -04:00
} else {
parent . debug ( 'web' , 'handleLoginRequest: 2FA token required' ) ;
2019-02-12 22:23:40 -05:00
}
// Wait and redirect the user
setTimeout ( function ( ) {
req . session . loginmode = '4' ;
2020-03-13 23:39:21 -04:00
req . session . tokenemail = ( ( user . email != null ) && ( user . emailVerified == true ) && ( parent . mailserver != null ) && ( user . otpekey != null ) ) ;
2019-02-12 22:23:40 -05:00
req . session . tokenusername = xusername ;
req . session . tokenpassword = xpassword ;
2019-08-26 15:20:24 -04:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-02-12 22:23:40 -05:00
} , randomWaitTime ) ;
2019-02-08 12:24:00 -05:00
} else {
2019-11-29 01:29:22 -05:00
// Check if we need to remember this device
if ( req . body . remembertoken === 'on' ) {
const twoFactorCookie = obj . parent . encodeCookie ( { userid : user . _id /*, ip: cleanRemoteAddr(req.ip)*/ } , obj . parent . loginCookieEncryptionKey ) ;
res . cookie ( 'twofactor' , twoFactorCookie , { maxAge : ( 30 * 24 * 60 * 60 * 1000 ) , httpOnly : true , sameSite : 'strict' , secure : true } ) ;
}
2020-04-21 04:50:27 -04:00
// Check if email address needs to be confirmed
var emailcheck = ( ( obj . parent . mailserver != null ) && ( obj . parent . certificates . CommonName != null ) && ( obj . parent . certificates . CommonName . indexOf ( '.' ) != - 1 ) && ( obj . args . lanonly != true ) && ( domain . auth != 'sspi' ) && ( domain . auth != 'ldap' ) )
if ( emailcheck && ( user . emailVerified !== true ) ) {
parent . debug ( 'web' , 'Redirecting using ' + user . name + ' to email check login page' ) ;
req . session . messageid = 3 ; // "Email verification required" message
req . session . loginmode = '7' ;
req . session . passhint = user . email ;
req . session . cuserid = userid ;
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
return ;
}
2019-11-29 01:29:22 -05:00
// Login successful
2020-02-17 13:24:32 -05:00
if ( obj . parent . authlog ) { obj . parent . authLog ( 'https' , 'Accepted password for ' + xusername + ' from ' + cleanRemoteAddr ( req . ip ) + ' port ' + req . connection . remotePort ) ; }
2019-11-29 01:29:22 -05:00
parent . debug ( 'web' , 'handleLoginRequest: successful 2FA login' ) ;
2019-08-26 15:20:24 -04:00
completeLoginRequest ( req , res , domain , user , userid , xusername , xpassword , direct ) ;
2018-08-30 15:05:23 -04:00
}
} ) ;
2019-02-08 12:24:00 -05:00
return ;
2018-08-30 15:05:23 -04:00
}
2017-12-13 17:52:57 -05:00
2020-04-21 04:50:27 -04:00
// Check if email address needs to be confirmed
var emailcheck = ( ( obj . parent . mailserver != null ) && ( obj . parent . certificates . CommonName != null ) && ( obj . parent . certificates . CommonName . indexOf ( '.' ) != - 1 ) && ( obj . args . lanonly != true ) && ( domain . auth != 'sspi' ) && ( domain . auth != 'ldap' ) )
if ( emailcheck && ( user . emailVerified !== true ) ) {
parent . debug ( 'web' , 'Redirecting using ' + user . name + ' to email check login page' ) ;
req . session . messageid = 3 ; // "Email verification required" message
req . session . loginmode = '7' ;
req . session . passhint = user . email ;
req . session . cuserid = userid ;
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
return ;
}
2019-11-29 01:29:22 -05:00
// Login successful
2020-02-17 13:24:32 -05:00
if ( obj . parent . authlog ) { obj . parent . authLog ( 'https' , 'Accepted password for ' + xusername + ' from ' + cleanRemoteAddr ( req . ip ) + ' port ' + req . connection . remotePort ) ; }
2019-11-29 01:29:22 -05:00
parent . debug ( 'web' , 'handleLoginRequest: successful login' ) ;
2019-08-26 15:20:24 -04:00
completeLoginRequest ( req , res , domain , user , userid , xusername , xpassword , direct ) ;
2017-08-28 12:27:45 -04:00
} else {
2020-02-17 13:24:32 -05:00
// Login failed, log the error
if ( obj . parent . authlog ) { obj . parent . authLog ( 'https' , 'Failed password for ' + xusername + ' from ' + cleanRemoteAddr ( req . ip ) + ' port ' + req . connection . remotePort ) ; }
// Wait a random delay
2019-02-12 22:23:40 -05:00
setTimeout ( function ( ) {
// If the account is locked, display that.
2019-11-18 17:17:27 -05:00
if ( typeof xusername == 'string' ) {
var xuserid = 'user/' + domain . id + '/' + xusername . toLowerCase ( ) ;
if ( err == 'locked' ) {
parent . debug ( 'web' , 'handleLoginRequest: login failed, locked account' ) ;
req . session . messageid = 110 ; // Account locked.
obj . parent . DispatchEvent ( [ '*' , 'server-users' , xuserid ] , obj , { action : 'authfail' , userid : xuserid , username : xusername , domain : domain . id , msg : 'User login attempt on locked account from ' + cleanRemoteAddr ( req . ip ) } ) ;
2019-11-18 20:31:42 -05:00
obj . setbadLogin ( req ) ;
2019-11-18 17:17:27 -05:00
} else {
parent . debug ( 'web' , 'handleLoginRequest: login failed, bad username and password' ) ;
req . session . messageid = 112 ; // Login failed, check username and password.
obj . parent . DispatchEvent ( [ '*' , 'server-users' , xuserid ] , obj , { action : 'authfail' , userid : xuserid , username : xusername , domain : domain . id , msg : 'Invalid user login attempt from ' + cleanRemoteAddr ( req . ip ) } ) ;
2019-11-18 20:31:42 -05:00
obj . setbadLogin ( req ) ;
2019-11-18 17:17:27 -05:00
}
2019-08-23 14:51:48 -04:00
}
2019-02-12 22:23:40 -05:00
// Clean up login mode and display password hint if present.
delete req . session . loginmode ;
if ( ( passhint != null ) && ( passhint . length > 0 ) ) {
req . session . passhint = passhint ;
} else {
delete req . session . passhint ;
}
2019-08-26 15:20:24 -04:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-02-12 22:23:40 -05:00
} , 2000 + ( obj . crypto . randomBytes ( 2 ) . readUInt16BE ( 0 ) % 4095 ) ) ; // Wait for 2 to ~6 seconds.
2017-08-28 12:27:45 -04:00
}
} ) ;
}
2018-05-24 00:43:44 -04:00
2019-08-26 15:20:24 -04:00
function completeLoginRequest ( req , res , domain , user , userid , xusername , xpassword , direct ) {
2019-02-27 21:48:50 -05:00
// Check if we need to change the password
if ( ( typeof user . passchange == 'number' ) && ( ( user . passchange == - 1 ) || ( ( typeof domain . passwordrequirements == 'object' ) && ( typeof domain . passwordrequirements . reset == 'number' ) && ( user . passchange + ( domain . passwordrequirements . reset * 86400 ) < Math . floor ( Date . now ( ) / 1000 ) ) ) ) ) {
// Request a password change
2019-08-23 14:51:48 -04:00
parent . debug ( 'web' , 'handleLoginRequest: login ok, password change requested' ) ;
2019-02-27 21:48:50 -05:00
req . session . loginmode = '6' ;
2019-11-17 17:33:29 -05:00
req . session . messageid = 113 ; // Password change requested.
2019-02-27 21:48:50 -05:00
req . session . resettokenusername = xusername ;
req . session . resettokenpassword = xpassword ;
2019-08-26 15:20:24 -04:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-02-27 21:48:50 -05:00
return ;
}
2019-02-08 12:24:00 -05:00
// Save login time
2019-11-17 19:20:53 -05:00
user . pastlogin = user . login ;
2019-02-08 12:24:00 -05:00
user . login = Math . floor ( Date . now ( ) / 1000 ) ;
obj . db . SetUser ( user ) ;
2019-05-04 15:55:46 -04:00
// Notify account login
var targets = [ '*' , 'server-users' ] ;
if ( user . groups ) { for ( var i in user . groups ) { targets . push ( 'server-users:' + i ) ; } }
2019-07-29 19:35:48 -04:00
obj . parent . DispatchEvent ( targets , obj , { etype : 'user' , userid : user . _id , username : user . name , account : obj . CloneSafeUser ( user ) , action : 'login' , msg : 'Account login' , domain : domain . id } ) ;
2019-02-08 12:24:00 -05:00
// Regenerate session when signing in to prevent fixation
//req.session.regenerate(function () {
// Store the user's primary key in the session store to be retrieved, or in this case the entire user object
delete req . session . loginmode ;
2020-03-13 23:39:21 -04:00
delete req . session . tokenemail ;
2019-02-08 12:24:00 -05:00
delete req . session . tokenusername ;
delete req . session . tokenpassword ;
2019-02-11 17:41:15 -05:00
delete req . session . tokenemail ;
2019-11-17 17:33:29 -05:00
delete req . session . messageid ;
2019-02-10 23:13:03 -05:00
delete req . session . passhint ;
2020-04-21 04:50:27 -04:00
delete req . session . cuserid ;
2019-02-08 12:24:00 -05:00
req . session . userid = userid ;
req . session . domainid = domain . id ;
req . session . currentNode = '' ;
2019-09-20 18:20:59 -04:00
req . session . ip = req . ip ;
2019-02-08 12:24:00 -05:00
if ( req . body . viewmode ) { req . session . viewmode = req . body . viewmode ; }
if ( req . body . host ) {
// TODO: This is a terrible search!!! FIX THIS.
/ *
obj . db . GetAllType ( 'node' , function ( err , docs ) {
for ( var i = 0 ; i < docs . length ; i ++ ) {
if ( docs [ i ] . name == req . body . host ) {
req . session . currentNode = docs [ i ] . _id ;
break ;
}
}
console . log ( "CurrentNode: " + req . session . currentNode ) ;
// This redirect happens after finding node is completed
2019-08-26 15:20:24 -04:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-02-08 12:24:00 -05:00
} ) ;
* /
2019-08-23 14:51:48 -04:00
parent . debug ( 'web' , 'handleLoginRequest: login ok (1)' ) ;
2019-08-26 15:20:24 -04:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; } // Temporary
2019-02-08 12:24:00 -05:00
} else {
2019-08-23 14:51:48 -04:00
parent . debug ( 'web' , 'handleLoginRequest: login ok (2)' ) ;
2019-08-26 15:20:24 -04:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-02-08 12:24:00 -05:00
}
//});
}
2019-08-26 15:20:24 -04:00
function handleCreateAccountRequest ( req , res , direct ) {
2019-01-05 15:04:18 -05:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 15:06:34 -04:00
if ( domain == null ) { return ; }
if ( ( domain . auth == 'sspi' ) || ( domain . auth == 'ldap' ) ) { parent . debug ( 'web' , 'handleCreateAccountRequest: failed checks.' ) ; res . sendStatus ( 404 ) ; return ; }
2019-11-25 17:30:23 -05:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2019-01-16 15:04:48 -05:00
2019-07-15 13:24:31 -04:00
// Always lowercase the email address
if ( req . body . email ) { req . body . email = req . body . email . toLowerCase ( ) ; }
2019-07-08 18:59:44 -04:00
// If the email is the username, set this here.
if ( domain . usernameisemail ) { req . body . username = req . body . email ; }
2019-09-27 13:15:45 -04:00
// Count the number of users in this domain
var domainUserCount = 0 ;
for ( var i in obj . users ) { if ( obj . users [ i ] . domain == domain . id ) { domainUserCount ++ ; } }
2019-04-23 16:42:54 -04:00
// Check if we are allowed to create new users using the login screen
2019-09-27 13:15:45 -04:00
if ( ( domain . newaccounts !== 1 ) && ( domain . newaccounts !== true ) && ( domainUserCount > 0 ) ) {
parent . debug ( 'web' , 'handleCreateAccountRequest: domainUserCount > 1.' ) ;
res . sendStatus ( 401 ) ;
return ;
2019-04-23 16:42:54 -04:00
}
2019-02-10 23:13:03 -05:00
2019-02-25 19:07:27 -05:00
// Check if this request is for an allows email domain
if ( ( domain . newaccountemaildomains != null ) && Array . isArray ( domain . newaccountemaildomains ) ) {
var i = - 1 ;
if ( typeof req . body . email == 'string' ) { i = req . body . email . indexOf ( '@' ) ; }
if ( i == - 1 ) {
2019-08-23 14:51:48 -04:00
parent . debug ( 'web' , 'handleCreateAccountRequest: unable to create account (1)' ) ;
2019-02-27 21:48:50 -05:00
req . session . loginmode = '2' ;
2019-11-17 17:33:29 -05:00
req . session . messageid = 100 ; // Unable to create account.
2019-08-26 15:20:24 -04:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-02-25 19:07:27 -05:00
return ;
}
var emailok = false , emaildomain = req . body . email . substring ( i + 1 ) . toLowerCase ( ) ;
for ( var i in domain . newaccountemaildomains ) { if ( emaildomain == domain . newaccountemaildomains [ i ] . toLowerCase ( ) ) { emailok = true ; } }
if ( emailok == false ) {
2019-08-23 14:51:48 -04:00
parent . debug ( 'web' , 'handleCreateAccountRequest: unable to create account (2)' ) ;
2019-02-27 21:48:50 -05:00
req . session . loginmode = '2' ;
2019-11-17 17:33:29 -05:00
req . session . messageid = 100 ; // Unable to create account.
2019-08-26 15:20:24 -04:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-02-25 19:07:27 -05:00
return ;
}
}
2019-02-10 23:13:03 -05:00
// Check if we exceed the maximum number of user accounts
2019-02-11 17:41:15 -05:00
obj . db . isMaxType ( domain . limits . maxuseraccounts , 'user' , domain . id , function ( maxExceed ) {
2019-02-10 23:13:03 -05:00
if ( maxExceed ) {
2019-08-23 14:51:48 -04:00
parent . debug ( 'web' , 'handleCreateAccountRequest: account limit reached' ) ;
2019-02-27 21:48:50 -05:00
req . session . loginmode = '2' ;
2019-11-17 17:33:29 -05:00
req . session . messageid = 101 ; // Account limit reached.
2019-08-26 15:20:24 -04:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-02-10 23:13:03 -05:00
} else {
if ( ! obj . common . validateUsername ( req . body . username , 1 , 64 ) || ! obj . common . validateEmail ( req . body . email , 1 , 256 ) || ! obj . common . validateString ( req . body . password1 , 1 , 256 ) || ! obj . common . validateString ( req . body . password2 , 1 , 256 ) || ( req . body . password1 != req . body . password2 ) || req . body . username == '~' || ! obj . common . checkPasswordRequirements ( req . body . password1 , domain . passwordrequirements ) ) {
2019-08-23 14:51:48 -04:00
parent . debug ( 'web' , 'handleCreateAccountRequest: unable to create account (3)' ) ;
2019-02-27 21:48:50 -05:00
req . session . loginmode = '2' ;
2019-11-17 17:33:29 -05:00
req . session . messageid = 100 ; // Unable to create account.
2019-08-26 15:20:24 -04:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2017-12-12 19:04:54 -05:00
} else {
2019-02-10 23:13:03 -05:00
// Check if this email was already verified
obj . db . GetUserWithVerifiedEmail ( domain . id , req . body . email , function ( err , docs ) {
if ( docs . length > 0 ) {
2019-08-23 14:51:48 -04:00
parent . debug ( 'web' , 'handleCreateAccountRequest: Existing account with this email address' ) ;
2019-02-27 21:48:50 -05:00
req . session . loginmode = '2' ;
2019-11-17 17:33:29 -05:00
req . session . messageid = 102 ; // Existing account with this email address.
2019-08-26 15:20:24 -04:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-02-10 23:13:03 -05:00
} else {
// Check if there is domain.newAccountToken, check if supplied token is valid
if ( ( domain . newaccountspass != null ) && ( domain . newaccountspass != '' ) && ( req . body . anewaccountpass != domain . newaccountspass ) ) {
2019-08-23 14:51:48 -04:00
parent . debug ( 'web' , 'handleCreateAccountRequest: Invalid account creation token' ) ;
2019-02-27 21:48:50 -05:00
req . session . loginmode = '2' ;
2019-11-17 17:33:29 -05:00
req . session . messageid = 103 ; // Invalid account creation token.
2019-08-26 15:20:24 -04:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-02-10 23:13:03 -05:00
return ;
}
// Check if user exists
if ( obj . users [ 'user/' + domain . id + '/' + req . body . username . toLowerCase ( ) ] ) {
2019-08-23 14:51:48 -04:00
parent . debug ( 'web' , 'handleCreateAccountRequest: Username already exists' ) ;
2019-02-27 21:48:50 -05:00
req . session . loginmode = '2' ;
2019-11-17 17:33:29 -05:00
req . session . messageid = 104 ; // Username already exists.
2019-02-10 23:13:03 -05:00
} else {
2019-02-28 19:17:22 -05:00
var user = { type : 'user' , _id : 'user/' + domain . id + '/' + req . body . username . toLowerCase ( ) , name : req . body . username , email : req . body . email , creation : Math . floor ( Date . now ( ) / 1000 ) , login : Math . floor ( Date . now ( ) / 1000 ) , domain : domain . id } ;
2019-05-12 22:14:24 -04:00
if ( domain . newaccountsrights ) { user . siteadmin = domain . newaccountsrights ; }
2019-02-28 19:17:22 -05:00
if ( ( domain . passwordrequirements != null ) && ( domain . passwordrequirements . hint === true ) && ( req . body . apasswordhint ) ) { var hint = req . body . apasswordhint ; if ( hint . length > 250 ) { hint = hint . substring ( 0 , 250 ) ; } user . passhint = hint ; }
2019-06-27 01:13:56 -04:00
if ( domainUserCount == 0 ) { user . siteadmin = 4294967295 ; /*if (domain.newaccounts === 2) { delete domain.newaccounts; }*/ } // If this is the first user, give the account site admin.
2019-02-10 23:13:03 -05:00
obj . users [ user . _id ] = user ;
req . session . userid = user . _id ;
req . session . domainid = domain . id ;
2019-09-20 18:20:59 -04:00
req . session . ip = req . ip ; // Bind this session to the IP address of the request
2019-02-10 23:13:03 -05:00
// Create a user, generate a salt and hash the password
2019-05-14 17:39:26 -04:00
require ( './pass' ) . hash ( req . body . password1 , function ( err , salt , hash , tag ) {
2019-02-10 23:13:03 -05:00
if ( err ) throw err ;
user . salt = salt ;
user . hash = hash ;
2019-02-12 22:23:40 -05:00
delete user . passtype ;
2019-02-10 23:13:03 -05:00
obj . db . SetUser ( user ) ;
// Send the verification email
2020-04-04 22:29:20 -04:00
if ( ( obj . parent . mailserver != null ) && ( domain . auth != 'sspi' ) && ( domain . auth != 'ldap' ) && ( obj . common . validateEmail ( user . email , 1 , 256 ) == true ) ) { obj . parent . mailserver . sendAccountCheckMail ( domain , user . name , user . email , obj . getLanguageCodes ( req ) ) ; }
2019-05-14 17:39:26 -04:00
} , 0 ) ;
2019-07-29 19:35:48 -04:00
var event = { etype : 'user' , userid : user . _id , username : user . name , account : obj . CloneSafeUser ( user ) , action : 'accountcreate' , msg : 'Account created, email is ' + req . body . email , domain : domain . id } ;
2019-05-29 17:36:14 -04:00
if ( obj . db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to create the user. Another event will come.
obj . parent . DispatchEvent ( [ '*' , 'server-users' ] , obj , event ) ;
2019-02-10 23:13:03 -05:00
}
2019-08-26 15:20:24 -04:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-02-10 23:13:03 -05:00
}
} ) ;
2017-12-12 19:04:54 -05:00
}
2019-02-10 23:13:03 -05:00
}
} ) ;
2017-12-12 19:04:54 -05:00
}
2019-02-27 21:48:50 -05:00
// Called to process an account password reset
2019-08-26 15:20:24 -04:00
function handleResetPasswordRequest ( req , res , direct ) {
2019-02-27 21:48:50 -05:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 15:06:34 -04:00
if ( domain == null ) { return ; }
2019-11-25 17:30:23 -05:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2019-02-27 21:48:50 -05:00
// Check everything is ok
2019-04-11 16:41:51 -04:00
if ( ( domain == null ) || ( domain . auth == 'sspi' ) || ( domain . auth == 'ldap' ) || ( typeof req . body . rpassword1 != 'string' ) || ( typeof req . body . rpassword2 != 'string' ) || ( req . body . rpassword1 != req . body . rpassword2 ) || ( typeof req . body . rpasswordhint != 'string' ) || ( req . session == null ) || ( typeof req . session . resettokenusername != 'string' ) || ( typeof req . session . resettokenpassword != 'string' ) ) {
2019-08-23 14:51:48 -04:00
parent . debug ( 'web' , 'handleResetPasswordRequest: checks failed' ) ;
2019-02-27 21:48:50 -05:00
delete req . session . loginmode ;
delete req . session . tokenusername ;
delete req . session . tokenpassword ;
delete req . session . resettokenusername ;
delete req . session . resettokenpassword ;
delete req . session . tokenemail ;
2019-11-17 17:33:29 -05:00
delete req . session . messageid ;
2019-02-27 21:48:50 -05:00
delete req . session . passhint ;
2020-04-21 04:50:27 -04:00
delete req . session . cuserid ;
2019-08-26 15:20:24 -04:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-02-27 21:48:50 -05:00
return ;
}
// Authenticate the user
obj . authenticate ( req . session . resettokenusername , req . session . resettokenpassword , domain , function ( err , userid , passhint ) {
if ( userid ) {
// Login
var user = obj . users [ userid ] ;
// If we have password requirements, check this here.
if ( ! obj . common . checkPasswordRequirements ( req . body . rpassword1 , domain . passwordrequirements ) ) {
2019-08-23 14:51:48 -04:00
parent . debug ( 'web' , 'handleResetPasswordRequest: password rejected, use a different one (1)' ) ;
2019-02-27 21:48:50 -05:00
req . session . loginmode = '6' ;
2019-11-17 17:33:29 -05:00
req . session . messageid = 105 ; // Password rejected, use a different one.
2019-08-26 15:20:24 -04:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-02-27 21:48:50 -05:00
return ;
}
// Check if the password is the same as the previous one
2019-05-14 17:39:26 -04:00
require ( './pass' ) . hash ( req . body . rpassword1 , user . salt , function ( err , hash , tag ) {
2019-02-27 21:48:50 -05:00
if ( user . hash == hash ) {
// This is the same password, request a password change again
2019-08-23 14:51:48 -04:00
parent . debug ( 'web' , 'handleResetPasswordRequest: password rejected, use a different one (2)' ) ;
2019-02-27 21:48:50 -05:00
req . session . loginmode = '6' ;
2019-11-17 17:33:29 -05:00
req . session . messageid = 105 ; // Password rejected, use a different one.
2019-08-26 15:20:24 -04:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-02-27 21:48:50 -05:00
} else {
// Update the password, use a different salt.
2019-05-14 17:39:26 -04:00
require ( './pass' ) . hash ( req . body . rpassword1 , function ( err , salt , hash , tag ) {
2019-02-27 21:48:50 -05:00
if ( err ) throw err ;
user . salt = salt ;
user . hash = hash ;
2019-02-28 19:17:22 -05:00
if ( ( domain . passwordrequirements != null ) && ( domain . passwordrequirements . hint === true ) ) { var hint = req . body . rpasswordhint ; if ( hint . length > 250 ) { hint = hint . substring ( 0 , 250 ) ; } user . passhint = hint ; } else { delete user . passhint ; }
2019-02-27 21:48:50 -05:00
user . passchange = Math . floor ( Date . now ( ) / 1000 ) ;
delete user . passtype ;
obj . db . SetUser ( user ) ;
2019-07-29 19:35:48 -04:00
var event = { etype : 'user' , userid : user . _id , username : user . name , account : obj . CloneSafeUser ( user ) , action : 'accountchange' , msg : 'User password reset' , domain : domain . id } ;
2019-05-28 20:25:23 -04:00
if ( obj . db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the user. Another event will come.
obj . parent . DispatchEvent ( [ '*' , 'server-users' , user . _id ] , obj , event ) ;
2019-02-27 21:48:50 -05:00
2019-11-29 01:29:22 -05:00
// Login successful
2019-08-23 14:51:48 -04:00
parent . debug ( 'web' , 'handleResetPasswordRequest: success' ) ;
2019-02-27 21:48:50 -05:00
req . session . userid = userid ;
req . session . domainid = domain . id ;
2019-09-20 18:20:59 -04:00
req . session . ip = req . ip ; // Bind this session to the IP address of the request
2019-08-26 15:20:24 -04:00
completeLoginRequest ( req , res , domain , obj . users [ userid ] , userid , req . session . tokenusername , req . session . tokenpassword , direct ) ;
2019-05-14 17:39:26 -04:00
} , 0 ) ;
2019-02-27 21:48:50 -05:00
}
2019-05-14 17:39:26 -04:00
} , 0 ) ;
2019-02-27 21:48:50 -05:00
} else {
// Failed, error out.
2019-08-23 14:51:48 -04:00
parent . debug ( 'web' , 'handleResetPasswordRequest: failed authenticate()' ) ;
2019-02-27 21:48:50 -05:00
delete req . session . loginmode ;
2020-03-13 23:39:21 -04:00
delete req . session . tokenemail ;
2019-02-27 21:48:50 -05:00
delete req . session . tokenusername ;
delete req . session . tokenpassword ;
delete req . session . resettokenusername ;
delete req . session . resettokenpassword ;
delete req . session . tokenemail ;
2019-11-17 17:33:29 -05:00
delete req . session . messageid ;
2019-02-27 21:48:50 -05:00
delete req . session . passhint ;
2020-04-21 04:50:27 -04:00
delete req . session . cuserid ;
2019-08-26 15:20:24 -04:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-02-27 21:48:50 -05:00
return ;
}
} ) ;
}
2017-12-12 19:04:54 -05:00
// Called to process an account reset request
2019-08-26 15:20:24 -04:00
function handleResetAccountRequest ( req , res , direct ) {
2019-01-05 15:04:18 -05:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 15:06:34 -04:00
if ( domain == null ) { return ; }
if ( ( domain . auth == 'sspi' ) || ( domain . auth == 'ldap' ) || ( obj . args . lanonly == true ) || ( obj . parent . certificates . CommonName == null ) || ( obj . parent . certificates . CommonName . indexOf ( '.' ) == - 1 ) ) { parent . debug ( 'web' , 'handleResetAccountRequest: check failed' ) ; res . sendStatus ( 404 ) ; return ; }
2019-11-25 17:30:23 -05:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2019-01-16 15:04:48 -05:00
2019-07-15 13:24:31 -04:00
// Always lowercase the email address
if ( req . body . email ) { req . body . email = req . body . email . toLowerCase ( ) ; }
2019-02-11 17:59:04 -05:00
// Get the email from the body or session.
2019-02-11 17:41:15 -05:00
var email = req . body . email ;
if ( ( email == null ) || ( email == '' ) ) { email = req . session . tokenemail ; }
2019-06-11 14:33:44 -04:00
// Check the email string format
2019-02-11 17:41:15 -05:00
if ( ! email || checkEmail ( email ) == false ) {
2019-08-23 14:51:48 -04:00
parent . debug ( 'web' , 'handleResetAccountRequest: Invalid email' ) ;
2019-02-27 21:48:50 -05:00
req . session . loginmode = '3' ;
2019-11-17 17:33:29 -05:00
req . session . messageid = 106 ; // Invalid email.
2019-08-26 15:20:24 -04:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2017-12-12 19:04:54 -05:00
} else {
2019-02-11 17:41:15 -05:00
obj . db . GetUserWithVerifiedEmail ( domain . id , email , function ( err , docs ) {
if ( ( err != null ) || ( docs . length == 0 ) ) {
2019-08-23 14:51:48 -04:00
parent . debug ( 'web' , 'handleResetAccountRequest: Account not found' ) ;
2019-02-27 21:48:50 -05:00
req . session . loginmode = '3' ;
2019-11-17 17:33:29 -05:00
req . session . messageid = 107 ; // Account not found.
2019-08-26 15:20:24 -04:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2017-12-12 19:04:54 -05:00
} else {
2019-06-11 14:33:44 -04:00
// If many accounts have the same validated e-mail, we are going to use the first one for display, but sent a reset email for all accounts.
var responseSent = false ;
for ( var i in docs ) {
var user = docs [ i ] ;
2019-11-28 23:57:34 -05:00
if ( checkUserOneTimePasswordRequired ( domain , user , req ) == true ) {
2019-06-11 14:33:44 -04:00
// Second factor setup, request it now.
checkUserOneTimePassword ( req , domain , user , req . body . token , req . body . hwtoken , function ( result ) {
if ( result == false ) {
if ( i == 0 ) {
// 2-step auth is required, but the token is not present or not valid.
2019-08-23 14:51:48 -04:00
parent . debug ( 'web' , 'handleResetAccountRequest: Invalid 2FA token, try again' ) ;
2019-11-17 17:33:29 -05:00
if ( ( req . body . token != null ) || ( req . body . hwtoken != null ) ) {
req . session . messageid = 108 ; // Invalid token, try again.
2019-11-17 19:20:53 -05:00
obj . parent . DispatchEvent ( [ '*' , 'server-users' , 'user/' + domain . id + '/' + user . name ] , obj , { action : 'authfail' , username : user . name , userid : 'user/' + domain . id + '/' + user . name , domain : domain . id , msg : 'User login attempt with incorrect 2nd factor from ' + cleanRemoteAddr ( req . ip ) } ) ;
2019-11-18 20:31:42 -05:00
obj . setbadLogin ( req ) ;
2019-11-17 17:33:29 -05:00
}
2019-06-11 14:33:44 -04:00
req . session . loginmode = '5' ;
req . session . tokenemail = email ;
2019-08-26 15:20:24 -04:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-06-11 14:33:44 -04:00
}
} else {
// Send email to perform recovery.
delete req . session . tokenemail ;
if ( obj . parent . mailserver != null ) {
2020-04-04 22:29:20 -04:00
obj . parent . mailserver . sendAccountResetMail ( domain , user . name , user . email , obj . getLanguageCodes ( req ) ) ;
2019-06-11 14:33:44 -04:00
if ( i == 0 ) {
2019-08-23 14:51:48 -04:00
parent . debug ( 'web' , 'handleResetAccountRequest: Hold on, reset mail sent.' ) ;
2019-06-11 14:33:44 -04:00
req . session . loginmode = '1' ;
2019-11-17 17:33:29 -05:00
req . session . messageid = 1 ; // Hold on, reset mail sent.
2019-08-26 15:20:24 -04:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-06-11 14:33:44 -04:00
}
} else {
if ( i == 0 ) {
2019-08-23 14:51:48 -04:00
parent . debug ( 'web' , 'handleResetAccountRequest: Unable to sent email.' ) ;
2019-06-11 14:33:44 -04:00
req . session . loginmode = '3' ;
2019-11-17 17:33:29 -05:00
req . session . messageid = 109 ; // Unable to sent email.
2019-08-26 15:20:24 -04:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-06-11 14:33:44 -04:00
}
}
}
} ) ;
} else {
// No second factor, send email to perform recovery.
if ( obj . parent . mailserver != null ) {
2020-04-04 22:29:20 -04:00
obj . parent . mailserver . sendAccountResetMail ( domain , user . name , user . email , obj . getLanguageCodes ( req ) ) ;
2019-06-11 14:33:44 -04:00
if ( i == 0 ) {
2019-08-23 14:51:48 -04:00
parent . debug ( 'web' , 'handleResetAccountRequest: Hold on, reset mail sent.' ) ;
2019-02-27 21:48:50 -05:00
req . session . loginmode = '1' ;
2019-11-17 17:33:29 -05:00
req . session . messageid = 1 ; // Hold on, reset mail sent.
2019-08-26 15:20:24 -04:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-06-11 14:33:44 -04:00
}
} else {
if ( i == 0 ) {
2019-08-23 14:51:48 -04:00
parent . debug ( 'web' , 'handleResetAccountRequest: Unable to sent email.' ) ;
2019-02-27 21:48:50 -05:00
req . session . loginmode = '3' ;
2019-11-17 17:33:29 -05:00
req . session . messageid = 109 ; // Unable to sent email.
2019-08-26 15:20:24 -04:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-02-11 17:41:15 -05:00
}
}
}
2017-12-12 19:04:54 -05:00
}
}
} ) ;
}
}
2020-04-21 04:50:27 -04:00
// Handle account email change and email verification request
function handleCheckAccountEmailRequest ( req , res , direct ) {
const domain = checkUserIpAddress ( req , res ) ;
if ( domain == null ) { return ; }
if ( ( obj . parent . mailserver == null ) || ( domain . auth == 'sspi' ) || ( domain . auth == 'ldap' ) || ( typeof req . session . cuserid != 'string' ) || ( obj . users [ req . session . cuserid ] == null ) || ( ! obj . common . validateEmail ( req . body . email , 1 , 256 ) ) ) { parent . debug ( 'web' , 'handleCheckAccountEmailRequest: failed checks.' ) ; res . sendStatus ( 404 ) ; return ; }
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
// Always lowercase the email address
if ( req . body . email ) { req . body . email = req . body . email . toLowerCase ( ) ; }
// Get the email from the body or session.
var email = req . body . email ;
if ( ( email == null ) || ( email == '' ) ) { email = req . session . tokenemail ; }
// Check if this request is for an allows email domain
if ( ( domain . newaccountemaildomains != null ) && Array . isArray ( domain . newaccountemaildomains ) ) {
var i = - 1 ;
if ( typeof req . body . email == 'string' ) { i = req . body . email . indexOf ( '@' ) ; }
if ( i == - 1 ) {
parent . debug ( 'web' , 'handleCreateAccountRequest: unable to create account (1)' ) ;
req . session . loginmode = '7' ;
req . session . messageid = 106 ; // Invalid email.
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
return ;
}
var emailok = false , emaildomain = req . body . email . substring ( i + 1 ) . toLowerCase ( ) ;
for ( var i in domain . newaccountemaildomains ) { if ( emaildomain == domain . newaccountemaildomains [ i ] . toLowerCase ( ) ) { emailok = true ; } }
if ( emailok == false ) {
parent . debug ( 'web' , 'handleCreateAccountRequest: unable to create account (2)' ) ;
req . session . loginmode = '7' ;
req . session . messageid = 106 ; // Invalid email.
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
return ;
}
}
// Check the email string format
if ( ! email || checkEmail ( email ) == false ) {
parent . debug ( 'web' , 'handleCheckAccountEmailRequest: Invalid email' ) ;
req . session . loginmode = '7' ;
req . session . messageid = 106 ; // Invalid email.
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
} else {
// Check is email already exists
obj . db . GetUserWithVerifiedEmail ( domain . id , email , function ( err , docs ) {
if ( ( err != null ) || ( docs . length > 0 ) ) {
// Email already exitst
req . session . messageid = 102 ; // Existing account with this email address.
} else {
// Update the user and notify of user email address change
var user = obj . users [ req . session . cuserid ] ;
if ( user . email != email ) {
user . email = email ;
db . SetUser ( user ) ;
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 : obj . CloneSafeUser ( user ) , action : 'accountchange' , msg : 'Account changed: ' + user . name , 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 . DispatchEvent ( targets , obj , event ) ;
}
// Send the verification email
obj . parent . mailserver . sendAccountCheckMail ( domain , user . name , user . email , obj . getLanguageCodes ( req ) ) ;
// Send the response
req . session . messageid = 2 ; // Email sent.
}
req . session . loginmode = '7' ;
delete req . session . cuserid ;
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
} ) ;
}
}
2017-12-12 19:04:54 -05:00
// Called to process a web based email verification request
function handleCheckMailRequest ( req , res ) {
2019-01-05 15:04:18 -05:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 15:06:34 -04:00
if ( domain == null ) { return ; }
if ( ( domain . auth == 'sspi' ) || ( domain . auth == 'ldap' ) ) { parent . debug ( 'web' , 'handleCheckMailRequest: failed checks.' ) ; res . sendStatus ( 404 ) ; return ; }
2019-11-25 17:30:23 -05:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2019-01-16 15:04:48 -05:00
2017-12-12 19:04:54 -05:00
if ( req . query . c != null ) {
2017-12-13 17:52:57 -05:00
var cookie = obj . parent . decodeCookie ( req . query . c , obj . parent . mailserver . mailCookieEncryptionKey , 30 ) ;
2017-12-12 19:04:54 -05:00
if ( ( cookie != null ) && ( cookie . u != null ) && ( cookie . e != null ) ) {
var idsplit = cookie . u . split ( '/' ) ;
if ( ( idsplit . length != 2 ) || ( idsplit [ 0 ] != domain . id ) ) {
2019-08-23 18:45:36 -04:00
parent . debug ( 'web' , 'handleCheckMailRequest: Invalid domain.' ) ;
2020-03-25 16:21:14 -04:00
render ( req , res , getRenderPage ( 'message' , req , domain ) , getRenderArgs ( { title3 : 'Account Verification' , message : 'ERROR: Invalid domain. <a href="' + domain . url + '">Go to login page</a>.' } , domain ) ) ;
2017-12-12 19:04:54 -05:00
} else {
2019-02-12 16:37:01 -05:00
obj . db . Get ( 'user/' + cookie . u . toLowerCase ( ) , function ( err , docs ) {
2017-12-12 19:04:54 -05:00
if ( docs . length == 0 ) {
2019-08-23 18:45:36 -04:00
parent . debug ( 'web' , 'handleCheckMailRequest: Invalid username.' ) ;
2020-03-25 16:21:14 -04:00
render ( req , res , getRenderPage ( 'message' , req , domain ) , getRenderArgs ( { title3 : 'Account Verification' , message : 'ERROR: Invalid username \"' + EscapeHtml ( idsplit [ 1 ] ) + '\". <a href="' + domain . url + '">Go to login page</a>.' } , domain ) ) ;
2017-12-12 19:04:54 -05:00
} else {
var user = docs [ 0 ] ;
if ( user . email != cookie . e ) {
2019-08-23 18:45:36 -04:00
parent . debug ( 'web' , 'handleCheckMailRequest: Invalid e-mail.' ) ;
2020-03-25 16:21:14 -04:00
render ( req , res , getRenderPage ( 'message' , req , domain ) , getRenderArgs ( { title3 : 'Account Verification' , message : 'ERROR: Invalid e-mail \"' + EscapeHtml ( user . email ) + '\" for user \"' + EscapeHtml ( user . name ) + '\". <a href="' + domain . url + '">Go to login page</a>.' } , domain ) ) ;
2017-12-12 19:04:54 -05:00
} else {
if ( cookie . a == 1 ) {
// Account email verification
if ( user . emailVerified == true ) {
2019-08-23 18:45:36 -04:00
parent . debug ( 'web' , 'handleCheckMailRequest: email already verified.' ) ;
2020-03-25 16:21:14 -04:00
render ( req , res , getRenderPage ( 'message' , req , domain ) , getRenderArgs ( { title3 : 'Account Verification' , message : 'E-mail \"' + EscapeHtml ( user . email ) + '\" for user \"' + EscapeHtml ( user . name ) + '\" already verified. <a href="' + domain . url + '">Go to login page</a>.' } , domain ) ) ;
2017-12-12 19:04:54 -05:00
} else {
obj . db . GetUserWithVerifiedEmail ( domain . id , user . email , function ( err , docs ) {
if ( docs . length > 0 ) {
2019-08-23 18:45:36 -04:00
parent . debug ( 'web' , 'handleCheckMailRequest: email already in use.' ) ;
2020-03-25 16:21:14 -04:00
render ( req , res , getRenderPage ( 'message' , req , domain ) , getRenderArgs ( { title3 : 'Account Verification' , message : 'E-mail \"' + EscapeHtml ( user . email ) + '\" already in use on a different account. Change the email address and try again. <a href="' + domain . url + '">Go to login page</a>.' } , domain ) ) ;
2017-12-12 19:04:54 -05:00
} else {
2019-08-23 18:45:36 -04:00
parent . debug ( 'web' , 'handleCheckMailRequest: email verification success.' ) ;
2017-12-12 19:04:54 -05:00
// Set the verified flag
obj . users [ user . _id ] . emailVerified = true ;
user . emailVerified = true ;
obj . db . SetUser ( user ) ;
// Event the change
2019-07-29 19:35:48 -04:00
var event = { etype : 'user' , userid : user . _id , username : user . name , account : obj . CloneSafeUser ( user ) , action : 'accountchange' , msg : 'Verified email of user ' + EscapeHtml ( user . name ) + ' (' + EscapeHtml ( user . email ) + ')' , domain : domain . id } ;
2019-05-28 20:25:23 -04:00
if ( obj . db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the user. Another event will come.
obj . parent . DispatchEvent ( [ '*' , 'server-users' , user . _id ] , obj , event ) ;
2017-12-12 19:04:54 -05:00
// Send the confirmation page
2020-03-25 16:21:14 -04:00
render ( req , res , getRenderPage ( 'message' , req , domain ) , getRenderArgs ( { title3 : 'Account Verification' , message : 'Verified email <b>' + EscapeHtml ( user . email ) + '</b> for user account <b>' + EscapeHtml ( user . name ) + '</b>. <a href="' + domain . url + '">Go to login page</a>.' } , domain ) ) ;
2017-12-13 17:52:57 -05:00
// Send a notification
2020-01-07 14:08:32 -05:00
obj . parent . DispatchEvent ( [ user . _id ] , obj , { action : 'notify' , value : 'Email verified:<br /><b>' + EscapeHtml ( user . email ) + '</b>.' , nolog : 1 , id : Math . random ( ) } ) ;
2020-04-20 20:19:25 -04:00
// Send to authlog
if ( obj . parent . authlog ) { obj . parent . authLog ( 'https' , 'Verified email address ' + user . email + ' for user ' + user . name ) ; }
2017-12-12 19:04:54 -05:00
}
} ) ;
}
} else if ( cookie . a == 2 ) {
// Account reset
if ( user . emailVerified != true ) {
2019-08-23 18:45:36 -04:00
parent . debug ( 'web' , 'handleCheckMailRequest: email not verified.' ) ;
2020-03-25 16:21:14 -04:00
render ( req , res , getRenderPage ( 'message' , req , domain ) , getRenderArgs ( { title3 : 'Account Verification' , message : 'E-mail \"' + EscapeHtml ( user . email ) + '\" for user \"' + EscapeHtml ( user . name ) + '\" not verified. <a href="' + domain . url + '">Go to login page</a>.' } , domain ) ) ;
2017-12-12 19:04:54 -05:00
} else {
// Set a temporary password
obj . crypto . randomBytes ( 16 , function ( err , buf ) {
var newpass = buf . toString ( 'base64' ) . split ( '=' ) . join ( '' ) . split ( '/' ) . join ( '' ) ;
2019-05-14 17:39:26 -04:00
require ( './pass' ) . hash ( newpass , function ( err , salt , hash , tag ) {
2018-08-30 15:05:23 -04:00
var userinfo = null ;
2017-12-12 19:04:54 -05:00
if ( err ) throw err ;
// Change the password
2018-08-30 15:05:23 -04:00
userinfo = obj . users [ user . _id ] ;
2017-12-12 19:04:54 -05:00
userinfo . salt = salt ;
userinfo . hash = hash ;
2019-02-12 22:23:40 -05:00
delete userinfo . passtype ;
2019-01-24 20:01:50 -05:00
userinfo . passchange = Math . floor ( Date . now ( ) / 1000 ) ;
2019-02-28 19:17:22 -05:00
delete userinfo . passhint ;
2019-02-11 17:41:15 -05:00
//delete userinfo.otpsecret; // Currently a email password reset will turn off 2-step login.
2017-12-12 19:04:54 -05:00
obj . db . SetUser ( userinfo ) ;
// Event the change
2019-07-29 19:35:48 -04:00
var event = { etype : 'user' , userid : user . _id , username : userinfo . name , account : obj . CloneSafeUser ( userinfo ) , action : 'accountchange' , msg : 'Password reset for user ' + EscapeHtml ( user . name ) , domain : domain . id } ;
2019-05-28 20:25:23 -04:00
if ( obj . db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the user. Another event will come.
obj . parent . DispatchEvent ( [ '*' , 'server-users' , user . _id ] , obj , event ) ;
2017-12-12 19:04:54 -05:00
// Send the new password
2020-03-25 16:21:14 -04:00
render ( req , res , getRenderPage ( 'message' , req , domain ) , getRenderArgs ( { title3 : 'Account Verification' , message : '<div>Password for account <b>' + EscapeHtml ( user . name ) + '</b> has been reset to:</div><div style=padding:14px;font-size:18px><b>' + EscapeHtml ( newpass ) + '</b></div>Login and go to the \"My Account\" tab to update your password. <a href="' + domain . url + '">Go to login page</a>.' } , domain ) ) ;
2019-08-23 18:45:36 -04:00
parent . debug ( 'web' , 'handleCheckMailRequest: send temporary password.' ) ;
2020-04-20 20:19:25 -04:00
// Send to authlog
if ( obj . parent . authlog ) { obj . parent . authLog ( 'https' , 'Performed account reset for user ' + user . name ) ; }
2019-05-14 17:39:26 -04:00
} , 0 ) ;
2017-12-12 19:04:54 -05:00
} ) ;
}
} else {
2020-03-25 16:21:14 -04:00
render ( req , res , getRenderPage ( 'message' , req , domain ) , getRenderArgs ( { title3 : 'Account Verification' , message : 'ERROR: Invalid account check. <a href="' + domain . url + '">Go to login page</a>.' } , domain ) ) ;
2017-12-12 19:04:54 -05:00
}
}
}
} ) ;
}
2017-08-28 12:27:45 -04:00
} else {
2020-03-25 16:21:14 -04:00
render ( req , res , getRenderPage ( 'message' , req , domain ) , getRenderArgs ( { title3 : 'Account Verification' , message : 'ERROR: Invalid account check, verification url is only valid for 30 minutes. <a href="' + domain . url + '">Go to login page</a>.' } , domain ) ) ;
2017-08-28 12:27:45 -04:00
}
}
}
2017-12-12 19:04:54 -05:00
2020-03-16 18:23:59 -04:00
// Called to process an agent invite GET/POST request
function handleInviteRequest ( req , res ) {
const domain = getDomain ( req ) ;
if ( domain == null ) { parent . debug ( 'web' , 'handleInviteRequest: failed checks.' ) ; res . sendStatus ( 404 ) ; return ; }
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2020-03-25 16:21:14 -04:00
if ( ( req . body . inviteCode == null ) || ( req . body . inviteCode == '' ) ) { render ( req , res , getRenderPage ( 'invite' , req , domain ) , getRenderArgs ( { messageid : 0 } , domain ) ) ; return ; } // No invitation code
2020-03-16 18:23:59 -04:00
2020-03-16 21:03:14 -04:00
// Each for a device group that has this invite code.
for ( var i in obj . meshes ) {
if ( ( obj . meshes [ i ] . invite != null ) && ( obj . meshes [ i ] . invite . codes . indexOf ( req . body . inviteCode ) >= 0 ) ) {
// Send invitation link, valid for 1 minute.
res . redirect ( domain . url + 'agentinvite?c=' + parent . encodeCookie ( { a : 4 , mid : i , f : obj . meshes [ i ] . invite . flags , expire : 1 } , parent . invitationLinkEncryptionKey ) + ( req . query . key ? ( '&key=' + req . query . key ) : '' ) ) ;
return ;
}
}
2020-03-16 18:23:59 -04:00
2020-03-25 16:21:14 -04:00
render ( req , res , getRenderPage ( 'invite' , req , domain ) , getRenderArgs ( { messageid : 100 } , domain ) ) ; // Bad invitation code
2020-03-16 18:23:59 -04:00
}
2019-06-03 16:15:38 -04:00
// Called to process an agent invite request
function handleAgentInviteRequest ( req , res ) {
2019-10-11 18:39:15 -04:00
const domain = getDomain ( req ) ;
2019-11-21 21:57:57 -05:00
if ( ( domain == null ) || ( ( req . query . m == null ) && ( req . query . c == null ) ) ) { parent . debug ( 'web' , 'handleAgentInviteRequest: failed checks.' ) ; res . sendStatus ( 404 ) ; return ; }
2019-11-25 17:30:23 -05:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2019-11-21 21:57:57 -05:00
2019-06-03 16:15:38 -04:00
if ( req . query . c != null ) {
// A cookie is specified in the query string, use that
2019-06-07 20:11:56 -04:00
var cookie = obj . parent . decodeCookie ( req . query . c , obj . parent . invitationLinkEncryptionKey ) ;
2019-06-03 16:15:38 -04:00
if ( cookie == null ) { res . sendStatus ( 404 ) ; return ; }
var mesh = obj . meshes [ cookie . mid ] ;
if ( mesh == null ) { res . sendStatus ( 404 ) ; return ; }
var installflags = cookie . f ;
if ( typeof installflags != 'number' ) { installflags = 0 ; }
2019-08-23 18:45:36 -04:00
parent . debug ( 'web' , 'handleAgentInviteRequest using cookie.' ) ;
2020-04-17 14:27:34 -04:00
render ( req , res , getRenderPage ( 'agentinvite' , req , domain ) , getRenderArgs ( { meshid : mesh . _id . split ( '/' ) [ 2 ] , serverport : ( ( args . aliasport != null ) ? args . aliasport : args . port ) , serverhttps : ( ( args . notls == true ) ? '0' : '1' ) , servernoproxy : ( ( domain . agentnoproxy === true ) ? '1' : '0' ) , meshname : encodeURIComponent ( mesh . name ) . split ( '\'' ) . join ( '%27' ) , installflags : installflags } , domain ) ) ;
2019-06-03 16:15:38 -04:00
} else if ( req . query . m != null ) {
// The MeshId is specified in the query string, use that
var mesh = obj . meshes [ 'mesh/' + domain . id + '/' + req . query . m . toLowerCase ( ) ] ;
if ( mesh == null ) { res . sendStatus ( 404 ) ; return ; }
var installflags = 0 ;
if ( req . query . f ) { installflags = parseInt ( req . query . f ) ; }
if ( typeof installflags != 'number' ) { installflags = 0 ; }
2019-08-23 18:45:36 -04:00
parent . debug ( 'web' , 'handleAgentInviteRequest using meshid.' ) ;
2020-04-17 14:27:34 -04:00
render ( req , res , getRenderPage ( 'agentinvite' , req , domain ) , getRenderArgs ( { meshid : mesh . _id . split ( '/' ) [ 2 ] , serverport : ( ( args . aliasport != null ) ? args . aliasport : args . port ) , serverhttps : ( ( args . notls == true ) ? '0' : '1' ) , servernoproxy : ( ( domain . agentnoproxy === true ) ? '1' : '0' ) , meshname : encodeURIComponent ( mesh . name ) . split ( '\'' ) . join ( '%27' ) , installflags : installflags } , domain ) ) ;
2019-06-03 16:15:38 -04:00
}
}
2019-08-26 15:20:24 -04:00
function handleDeleteAccountRequest ( req , res , direct ) {
parent . debug ( 'web' , 'handleDeleteAccountRequest()' ) ;
2019-01-05 15:04:18 -05:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 15:06:34 -04:00
if ( domain == null ) { return ; }
if ( ( domain . auth == 'sspi' ) || ( domain . auth == 'ldap' ) ) { parent . debug ( 'web' , 'handleDeleteAccountRequest: failed checks.' ) ; res . sendStatus ( 404 ) ; return ; }
2019-11-25 17:30:23 -05:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2019-01-16 15:04:48 -05:00
2019-08-26 15:20:24 -04:00
var user = null ;
if ( req . body . authcookie ) {
// If a authentication cookie is provided, decode it here
var loginCookie = obj . parent . decodeCookie ( req . body . authcookie , obj . parent . loginCookieEncryptionKey , 60 ) ; // 60 minute timeout
if ( ( loginCookie != null ) && ( domain . id == loginCookie . domainid ) ) { user = obj . users [ loginCookie . userid ] ; }
} else {
// Check if the user is logged and we have all required parameters
if ( ! req . session || ! req . session . userid || ! req . body . apassword1 || ( req . body . apassword1 != req . body . apassword2 ) || ( req . session . domainid != domain . id ) ) {
parent . debug ( 'web' , 'handleDeleteAccountRequest: required parameters not present.' ) ;
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
return ;
} else {
user = obj . users [ req . session . userid ] ;
}
}
2019-08-23 18:45:36 -04:00
if ( ! user ) { parent . debug ( 'web' , 'handleDeleteAccountRequest: user not found.' ) ; res . sendStatus ( 404 ) ; return ; }
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
// Check if the password is correct
obj . authenticate ( user . name , req . body . apassword1 , domain , function ( err , userid ) {
2020-03-27 03:21:44 -04:00
var deluser = obj . users [ userid ] ;
if ( ( deluser != null ) || ( userid == null ) ) {
// Remove all links to this user
if ( deluser . links != null ) {
for ( var i in deluser . links ) {
if ( i . startsWith ( 'mesh/' ) ) {
// Get the device group
mesh = parent . meshes [ i ] ;
if ( mesh ) {
// Remove user from the mesh
if ( mesh . links [ deluser . _id ] != null ) { delete mesh . links [ deluser . _id ] ; parent . db . Set ( mesh ) ; }
// Notify mesh change
change = 'Removed user ' + deluser . name + ' from group ' + mesh . name ;
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 , invite : mesh . invite } ;
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 ) ;
}
} else if ( i . startsWith ( 'node/' ) ) {
// Get the node and the rights for this node
parent . GetNodeWithRights ( domain , deluser , i , function ( node , rights , visible ) {
if ( ( node == null ) || ( node . links == null ) || ( node . links [ deluser . _id ] == null ) ) return ;
// Remove the link and save the node to the database
delete node . links [ deluser . _id ] ;
if ( Object . keys ( node . links ) . length == 0 ) { delete node . links ; }
2020-03-30 20:47:04 -04:00
db . Set ( obj . cleanDevice ( node ) ) ;
2020-03-27 03:21:44 -04:00
// Event the node change
var event = { etype : 'node' , userid : user . _id , username : user . name , action : 'changenode' , nodeid : node . _id , domain : domain . id , msg : ( command . rights == 0 ) ? ( 'Removed user device rights for ' + node . name ) : ( 'Changed user device rights for ' + node . name ) , node : parent . CloneSafeNode ( node ) }
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 ( [ '*' , node . meshid , node . _id ] , obj , event ) ;
} ) ;
2018-03-26 20:13:32 -04:00
}
}
}
2018-05-17 16:37:50 -04:00
// Remove notes for this user
2020-03-27 03:21:44 -04:00
obj . db . Remove ( 'nt' + deluser . _id ) ;
2018-05-17 16:37:50 -04:00
2018-03-26 20:13:32 -04:00
// Remove the user
2020-03-27 03:21:44 -04:00
obj . db . Remove ( deluser . _id ) ;
delete obj . users [ deluser . _id ] ;
2018-08-22 19:18:01 -04:00
req . session = null ;
2019-08-26 15:20:24 -04:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2020-03-27 03:21:44 -04:00
obj . parent . DispatchEvent ( [ '*' , 'server-users' ] , obj , { etype : 'user' , userid : deluser . _id , username : deluser . name , action : 'accountremove' , msg : 'Account removed' , domain : domain . id } ) ;
2019-08-23 18:45:36 -04:00
parent . debug ( 'web' , 'handleDeleteAccountRequest: removed user.' ) ;
2017-08-28 12:27:45 -04:00
} else {
2019-08-23 18:45:36 -04:00
parent . debug ( 'web' , 'handleDeleteAccountRequest: auth failed.' ) ;
2019-08-26 15:20:24 -04:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2017-08-28 12:27:45 -04:00
}
} ) ;
}
2018-05-24 00:43:44 -04:00
2019-02-12 22:23:40 -05:00
// Check a user's password
2019-10-04 15:18:56 -04:00
obj . checkUserPassword = function ( domain , user , password , func ) {
2019-02-12 22:23:40 -05:00
// Check the old password
if ( user . passtype != null ) {
// IIS default clear or weak password hashing (SHA-1)
require ( './pass' ) . iishash ( user . passtype , password , user . salt , function ( err , hash ) {
2019-08-23 18:45:36 -04:00
if ( err ) { parent . debug ( 'web' , 'checkUserPassword: SHA-1 fail.' ) ; return func ( false ) ; }
2019-02-12 22:23:40 -05:00
if ( hash == user . hash ) {
2019-08-23 18:45:36 -04:00
if ( ( user . siteadmin ) && ( user . siteadmin != 0xFFFFFFFF ) && ( user . siteadmin & 32 ) != 0 ) { parent . debug ( 'web' , 'checkUserPassword: SHA-1 locked.' ) ; return func ( false ) ; } // Account is locked
2019-10-04 15:18:56 -04:00
parent . debug ( 'web' , 'checkUserPassword: SHA-1 ok.' ) ;
2019-02-12 22:23:40 -05:00
return func ( true ) ; // Allow password change
}
func ( false ) ;
} ) ;
} else {
// Default strong password hashing (pbkdf2 SHA384)
2019-05-14 17:39:26 -04:00
require ( './pass' ) . hash ( password , user . salt , function ( err , hash , tag ) {
2019-08-23 18:45:36 -04:00
if ( err ) { parent . debug ( 'web' , 'checkUserPassword: pbkdf2 SHA384 fail.' ) ; return func ( false ) ; }
2019-02-12 22:23:40 -05:00
if ( hash == user . hash ) {
2019-08-23 18:45:36 -04:00
if ( ( user . siteadmin ) && ( user . siteadmin != 0xFFFFFFFF ) && ( user . siteadmin & 32 ) != 0 ) { parent . debug ( 'web' , 'checkUserPassword: pbkdf2 SHA384 locked.' ) ; return func ( false ) ; } // Account is locked
2019-10-04 15:18:56 -04:00
parent . debug ( 'web' , 'checkUserPassword: pbkdf2 SHA384 ok.' ) ;
2019-02-12 22:23:40 -05:00
return func ( true ) ; // Allow password change
}
func ( false ) ;
2019-05-14 17:39:26 -04:00
} , 0 ) ;
2019-02-12 22:23:40 -05:00
}
}
2017-08-28 12:27:45 -04:00
// Handle password changes
2019-08-26 15:20:24 -04:00
function handlePasswordChangeRequest ( req , res , direct ) {
2019-01-05 15:04:18 -05:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 15:06:34 -04:00
if ( domain == null ) { return ; }
if ( ( domain . auth == 'sspi' ) || ( domain . auth == 'ldap' ) ) { parent . debug ( 'web' , 'handlePasswordChangeRequest: failed checks (1).' ) ; res . sendStatus ( 404 ) ; return ; }
2019-11-25 17:30:23 -05:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2019-01-16 15:04:48 -05:00
2017-08-28 12:27:45 -04:00
// Check if the user is logged and we have all required parameters
2019-08-23 18:45:36 -04:00
if ( ! req . session || ! req . session . userid || ! req . body . apassword0 || ! req . body . apassword1 || ( req . body . apassword1 != req . body . apassword2 ) || ( req . session . domainid != domain . id ) ) {
parent . debug ( 'web' , 'handlePasswordChangeRequest: failed checks (2).' ) ;
2019-08-26 15:20:24 -04:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
return ;
2019-08-23 18:45:36 -04:00
}
2018-05-24 00:43:44 -04:00
2019-02-12 22:23:40 -05:00
// Get the current user
var user = obj . users [ req . session . userid ] ;
2019-08-23 18:45:36 -04:00
if ( ! user ) {
parent . debug ( 'web' , 'handlePasswordChangeRequest: user not found.' ) ;
2019-08-26 15:20:24 -04:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
return ;
2019-08-23 18:45:36 -04:00
}
2019-02-12 22:23:40 -05:00
// Check old password
obj . checkUserPassword ( domain , user , req . body . apassword0 , function ( result ) {
if ( result == true ) {
// Update the password
2019-05-14 17:39:26 -04:00
require ( './pass' ) . hash ( req . body . apassword1 , function ( err , salt , hash , tag ) {
2019-08-23 18:45:36 -04:00
if ( err ) { parent . debug ( 'web' , 'handlePasswordChangeRequest: hash error.' ) ; throw err ; }
2019-02-12 22:23:40 -05:00
user . salt = salt ;
user . hash = hash ;
2019-02-28 19:17:22 -05:00
if ( ( domain . passwordrequirements != null ) && ( domain . passwordrequirements . hint === true ) && ( req . body . apasswordhint ) ) { var hint = req . body . apasswordhint ; if ( hint . length > 250 ) hint = hint . substring ( 0 , 250 ) ; user . passhint = hint ; } else { delete user . passhint ; }
2019-02-12 22:23:40 -05:00
user . passchange = Math . floor ( Date . now ( ) / 1000 ) ;
delete user . passtype ;
obj . db . SetUser ( user ) ;
req . session . viewmode = 2 ;
2019-08-26 15:20:24 -04:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-07-29 19:35:48 -04:00
obj . parent . DispatchEvent ( [ '*' , 'server-users' ] , obj , { etype : 'user' , userid : user . _id , username : user . name , action : 'passchange' , msg : 'Account password changed: ' + user . name , domain : domain . id } ) ;
2019-05-14 17:39:26 -04:00
} , 0 ) ;
2019-02-12 22:23:40 -05:00
}
2017-08-28 12:27:45 -04:00
} ) ;
}
// Indicates that any request to "/" should render "default" or "login" depending on login state
2019-08-26 15:20:24 -04:00
function handleRootRequest ( req , res , direct ) {
2019-01-05 15:04:18 -05:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 15:06:34 -04:00
if ( domain == null ) { return ; }
2019-11-25 17:30:23 -05:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2019-08-23 14:51:48 -04:00
if ( ! obj . args ) { parent . debug ( 'web' , 'handleRootRequest: no obj.args.' ) ; res . sendStatus ( 500 ) ; return ; }
2018-01-02 19:52:49 -05:00
if ( ( domain . sspi != null ) && ( ( req . query . login == null ) || ( obj . parent . loginCookieEncryptionKey == null ) ) ) {
// Login using SSPI
2019-08-23 18:45:36 -04:00
domain . sspi . authenticate ( req , res , function ( err ) {
if ( ( err != null ) || ( req . connection . user == null ) ) {
2020-02-17 13:24:32 -05:00
if ( obj . parent . authlog ) { obj . parent . authLog ( 'https' , 'Failed SSPI-auth for ' + req . connection . user + ' from ' + cleanRemoteAddr ( req . ip ) + ' port ' + req . connection . remotePort ) ; }
2019-08-23 18:45:36 -04:00
parent . debug ( 'web' , 'handleRootRequest: SSPI auth required.' ) ;
res . end ( 'Authentication Required...' ) ;
} else {
2020-02-17 13:24:32 -05:00
if ( obj . parent . authlog ) { obj . parent . authLog ( 'https' , 'Accepted SSPI-auth for ' + req . connection . user + ' from ' + cleanRemoteAddr ( req . ip ) + ' port ' + req . connection . remotePort ) ; }
2019-08-23 18:45:36 -04:00
parent . debug ( 'web' , 'handleRootRequest: SSPI auth ok.' ) ;
2019-08-26 15:20:24 -04:00
handleRootRequestEx ( req , res , domain , direct ) ;
2019-08-23 18:45:36 -04:00
}
} ) ;
2018-12-20 15:12:24 -05:00
} else if ( req . query . user && req . query . pass ) {
2020-02-17 13:24:32 -05:00
// User credentials are being passed in the URL. WARNING: Putting credentials in a URL is bad security... but people are requesting this option.
2019-04-11 16:41:51 -04:00
obj . authenticate ( req . query . user , req . query . pass , domain , function ( err , userid ) {
2020-02-17 13:24:32 -05:00
if ( obj . parent . authlog ) { obj . parent . authLog ( 'https' , 'Accepted password for ' + req . connection . user + ' from ' + cleanRemoteAddr ( req . ip ) + ' port ' + req . connection . remotePort ) ; }
2019-08-23 18:45:36 -04:00
parent . debug ( 'web' , 'handleRootRequest: user/pass in URL auth ok.' ) ;
2019-04-11 16:41:51 -04:00
req . session . userid = userid ;
req . session . domainid = domain . id ;
req . session . currentNode = '' ;
2019-09-20 18:20:59 -04:00
req . session . ip = req . ip ; // Bind this session to the IP address of the request
2019-08-26 15:20:24 -04:00
handleRootRequestEx ( req , res , domain , direct ) ;
2019-04-11 16:41:51 -04:00
} ) ;
2018-01-02 19:52:49 -05:00
} else {
// Login using a different system
2019-08-26 15:20:24 -04:00
handleRootRequestEx ( req , res , domain , direct ) ;
2018-01-02 19:52:49 -05:00
}
}
2019-08-26 15:20:24 -04:00
function handleRootRequestEx ( req , res , domain , direct ) {
2018-08-30 15:05:23 -04:00
var nologout = false , user = null , features = 0 ;
2017-08-28 12:27:45 -04:00
res . set ( { 'Cache-Control' : 'no-cache, no-store, must-revalidate' , 'Pragma' : 'no-cache' , 'Expires' : '0' } ) ;
2018-01-02 19:52:49 -05:00
2017-08-28 12:27:45 -04:00
// Check if we have an incomplete domain name in the path
2019-08-23 18:45:36 -04:00
if ( ( domain . id != '' ) && ( domain . dns == null ) && ( req . url . split ( '/' ) . length == 2 ) ) {
parent . debug ( 'web' , 'handleRootRequestEx: incomplete domain name in the path.' ) ;
2019-08-26 15:20:24 -04:00
res . redirect ( domain . url + getQueryPortion ( req ) ) ; // BAD***
2019-08-23 18:45:36 -04:00
return ;
}
2018-01-02 19:52:49 -05:00
2017-08-28 12:27:45 -04:00
if ( obj . args . nousers == true ) {
// If in single user mode, setup things here.
if ( req . session && req . session . loginmode ) { delete req . session . loginmode ; }
req . session . userid = 'user/' + domain . id + '/~' ;
req . session . domainid = domain . id ;
req . session . currentNode = '' ;
2019-09-20 18:20:59 -04:00
req . session . ip = req . ip ; // Bind this session to the IP address of the request
2017-09-15 14:45:06 -04:00
if ( obj . users [ req . session . userid ] == null ) {
2017-08-28 12:27:45 -04:00
// Create the dummy user ~ with impossible password
2019-08-23 18:45:36 -04:00
parent . debug ( 'web' , 'handleRootRequestEx: created dummy user in nouser mode.' ) ;
2019-06-27 01:13:56 -04:00
obj . users [ req . session . userid ] = { type : 'user' , _id : req . session . userid , name : '~' , email : '~' , domain : domain . id , siteadmin : 4294967295 } ;
2017-08-28 12:27:45 -04:00
obj . db . SetUser ( obj . users [ req . session . userid ] ) ;
}
2018-09-11 17:28:47 -04:00
} else if ( obj . args . user && obj . users [ 'user/' + domain . id + '/' + obj . args . user . toLowerCase ( ) ] ) {
2017-08-28 12:27:45 -04:00
// If a default user is active, setup the session here.
2019-08-23 18:45:36 -04:00
parent . debug ( 'web' , 'handleRootRequestEx: auth using default user.' ) ;
2017-08-28 12:27:45 -04:00
if ( req . session && req . session . loginmode ) { delete req . session . loginmode ; }
req . session . userid = 'user/' + domain . id + '/' + obj . args . user . toLowerCase ( ) ;
req . session . domainid = domain . id ;
req . session . currentNode = '' ;
2019-09-20 18:20:59 -04:00
req . session . ip = req . ip ; // Bind this session to the IP address of the request
2017-12-13 17:52:57 -05:00
} else if ( req . query . login && ( obj . parent . loginCookieEncryptionKey != null ) ) {
var loginCookie = obj . parent . decodeCookie ( req . query . login , obj . parent . loginCookieEncryptionKey , 60 ) ; // 60 minute timeout
2020-01-26 13:05:27 -05:00
//if ((loginCookie != null) && (obj.args.cookieipcheck !== false) && (loginCookie.ip != null) && (loginCookie.ip != cleanRemoteAddr(req.ip))) { loginCookie = null; } // If the cookie if binded to an IP address, check here.
2017-12-13 17:52:57 -05:00
if ( ( loginCookie != null ) && ( loginCookie . a == 3 ) && ( loginCookie . u != null ) && ( loginCookie . u . split ( '/' ) [ 1 ] == domain . id ) ) {
// If a login cookie was provided, setup the session here.
2019-08-23 18:45:36 -04:00
parent . debug ( 'web' , 'handleRootRequestEx: cookie auth ok.' ) ;
2017-12-13 17:52:57 -05:00
if ( req . session && req . session . loginmode ) { delete req . session . loginmode ; }
req . session . userid = loginCookie . u ;
req . session . domainid = domain . id ;
req . session . currentNode = '' ;
2019-09-20 18:20:59 -04:00
req . session . ip = req . ip ; // Bind this session to the IP address of the request
2019-08-23 18:45:36 -04:00
} else {
parent . debug ( 'web' , 'handleRootRequestEx: cookie auth failed.' ) ;
2017-12-13 17:52:57 -05:00
}
2018-01-02 19:52:49 -05:00
} else if ( domain . sspi != null ) {
// SSPI login (Windows only)
//console.log(req.connection.user, req.connection.userSid);
if ( ( req . connection . user == null ) || ( req . connection . userSid == null ) ) {
2019-08-23 18:45:36 -04:00
parent . debug ( 'web' , 'handleRootRequestEx: SSPI no user auth.' ) ;
2018-01-02 19:52:49 -05:00
res . sendStatus ( 404 ) ; return ;
} else {
nologout = true ;
2019-01-21 17:05:50 -05:00
req . session . userid = 'user/' + domain . id + '/' + req . connection . user . toLowerCase ( ) ;
2018-01-02 19:52:49 -05:00
req . session . usersid = req . connection . userSid ;
req . session . usersGroups = req . connection . userGroups ;
req . session . domainid = domain . id ;
req . session . currentNode = '' ;
2019-09-20 18:20:59 -04:00
req . session . ip = req . ip ; // Bind this session to the IP address of the request
2018-01-02 19:52:49 -05:00
// Check if this user exists, create it if not.
2018-08-30 15:05:23 -04:00
user = obj . users [ req . session . userid ] ;
2018-01-02 19:52:49 -05:00
if ( ( user == null ) || ( user . sid != req . session . usersid ) ) {
// Create the domain user
2019-01-24 20:01:50 -05:00
var usercount = 0 , user2 = { type : 'user' , _id : req . session . userid , name : req . connection . user , domain : domain . id , sid : req . session . usersid , creation : Math . floor ( Date . now ( ) / 1000 ) , login : Math . floor ( Date . now ( ) / 1000 ) } ;
2019-05-12 22:14:24 -04:00
if ( domain . newaccountsrights ) { user2 . siteadmin = domain . newaccountsrights ; }
2018-01-02 19:52:49 -05:00
for ( var i in obj . users ) { if ( obj . users [ i ] . domain == domain . id ) { usercount ++ ; } }
2019-06-27 01:13:56 -04:00
if ( usercount == 0 ) { user2 . siteadmin = 4294967295 ; } // If this is the first user, give the account site admin.
2018-08-30 15:05:23 -04:00
obj . users [ req . session . userid ] = user2 ;
obj . db . SetUser ( user2 ) ;
2019-07-29 19:35:48 -04:00
var event = { etype : 'user' , userid : req . session . userid , username : req . connection . user , account : obj . CloneSafeUser ( user2 ) , action : 'accountcreate' , msg : 'Domain account created, user ' + req . connection . user , domain : domain . id } ;
2019-05-29 17:36:14 -04:00
if ( obj . db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to create the user. Another event will come.
obj . parent . DispatchEvent ( [ '*' , 'server-users' ] , obj , event ) ;
2019-08-23 18:45:36 -04:00
parent . debug ( 'web' , 'handleRootRequestEx: SSPI new domain user.' ) ;
2018-01-02 19:52:49 -05:00
}
}
2017-08-28 12:27:45 -04:00
}
2018-01-02 19:52:49 -05:00
2018-12-20 17:14:37 -05:00
// Figure out the minimal password requirement
var passRequirements = null ;
if ( domain . passwordrequirements != null ) {
2019-12-12 14:44:03 -05:00
if ( domain . passrequirementstr == null ) {
var passRequirements = { } ;
if ( typeof domain . passwordrequirements . min == 'number' ) { passRequirements . min = domain . passwordrequirements . min ; }
if ( typeof domain . passwordrequirements . max == 'number' ) { passRequirements . max = domain . passwordrequirements . max ; }
if ( typeof domain . passwordrequirements . upper == 'number' ) { passRequirements . upper = domain . passwordrequirements . upper ; }
if ( typeof domain . passwordrequirements . lower == 'number' ) { passRequirements . lower = domain . passwordrequirements . lower ; }
if ( typeof domain . passwordrequirements . numeric == 'number' ) { passRequirements . numeric = domain . passwordrequirements . numeric ; }
if ( typeof domain . passwordrequirements . nonalpha == 'number' ) { passRequirements . nonalpha = domain . passwordrequirements . nonalpha ; }
domain . passwordrequirementsstr = encodeURIComponent ( JSON . stringify ( passRequirements ) ) ;
}
2018-12-20 17:14:37 -05:00
passRequirements = domain . passwordrequirementsstr ;
}
2018-11-06 22:41:21 -05:00
// If a user exists and is logged in, serve the default app, otherwise server the login app.
if ( req . session && req . session . userid && obj . users [ req . session . userid ] ) {
var user = obj . users [ req . session . userid ] ;
2020-03-25 16:21:14 -04:00
if ( req . session . domainid != domain . id ) { // Check if the session is for the correct domain
2019-08-23 18:45:36 -04:00
parent . debug ( 'web' , 'handleRootRequestEx: incorrect domain.' ) ;
req . session = null ;
2019-08-26 15:20:24 -04:00
res . redirect ( domain . url + getQueryPortion ( req ) ) ; // BAD***
2019-08-23 18:45:36 -04:00
return ;
}
2019-02-10 23:13:03 -05:00
// Check if this is a locked account
if ( ( user . siteadmin != null ) && ( ( user . siteadmin & 32 ) != 0 ) && ( user . siteadmin != 0xFFFFFFFF ) ) {
// Locked account
2019-08-23 18:45:36 -04:00
parent . debug ( 'web' , 'handleRootRequestEx: locked account.' ) ;
2019-02-10 23:13:03 -05:00
delete req . session . userid ;
delete req . session . domainid ;
delete req . session . currentNode ;
delete req . session . passhint ;
2020-04-21 04:50:27 -04:00
delete req . session . cuserid ;
2019-11-17 17:33:29 -05:00
req . session . messageid = 110 ; // Account locked.
2019-08-26 15:20:24 -04:00
res . redirect ( domain . url + getQueryPortion ( req ) ) ; // BAD***
2019-02-10 23:13:03 -05:00
return ;
}
2017-08-28 12:27:45 -04:00
var viewmode = 1 ;
if ( req . session . viewmode ) {
viewmode = req . session . viewmode ;
delete req . session . viewmode ;
2017-12-13 17:52:57 -05:00
} else if ( req . query . viewmode ) {
viewmode = req . query . viewmode ;
2017-08-28 12:27:45 -04:00
}
var currentNode = '' ;
if ( req . session . currentNode ) {
currentNode = req . session . currentNode ;
delete req . session . currentNode ;
2017-12-13 17:52:57 -05:00
} else if ( req . query . node ) {
currentNode = 'node/' + domain . id + '/' + req . query . node ;
2017-08-28 12:27:45 -04:00
}
2019-12-01 18:21:33 -05:00
var logoutcontrols = { } ;
if ( obj . args . nousers != true ) { logoutcontrols . name = user . name ; }
2018-01-23 17:15:59 -05:00
// Give the web page a list of supported server features
2018-08-30 15:05:23 -04:00
features = 0 ;
2019-02-28 19:17:22 -05:00
if ( obj . args . wanonly == true ) { features += 0x00000001 ; } // WAN-only mode
if ( obj . args . lanonly == true ) { features += 0x00000002 ; } // LAN-only mode
if ( obj . args . nousers == true ) { features += 0x00000004 ; } // Single user mode
if ( domain . userQuota == - 1 ) { features += 0x00000008 ; } // No server files mode
if ( obj . args . mpstlsoffload ) { features += 0x00000010 ; } // No mutual-auth CIRA
2019-10-22 20:51:37 -04:00
if ( ( parent . config . settings . allowframing == true ) || ( typeof parent . config . settings . allowframing == 'string' ) ) { features += 0x00000020 ; } // Allow site within iframe
2019-03-05 02:48:45 -05:00
if ( ( obj . parent . mailserver != null ) && ( obj . parent . certificates . CommonName != null ) && ( obj . parent . certificates . CommonName . indexOf ( '.' ) != - 1 ) && ( obj . args . lanonly != true ) ) { features += 0x00000040 ; } // Email invites
2019-02-28 19:17:22 -05:00
if ( obj . args . webrtc == true ) { features += 0x00000080 ; } // Enable WebRTC (Default false for now)
if ( obj . args . clickonce !== false ) { features += 0x00000100 ; } // Enable ClickOnce (Default true)
2020-01-12 16:50:06 -05:00
if ( obj . args . allowhighqualitydesktop !== false ) { features += 0x00000200 ; } // Enable AllowHighQualityDesktop (Default true)
2019-02-28 19:17:22 -05:00
if ( obj . args . lanonly == true || obj . args . mpsport == 0 ) { features += 0x00000400 ; } // No CIRA
if ( ( obj . parent . serverSelfWriteAllowed == true ) && ( user != null ) && ( user . siteadmin == 0xFFFFFFFF ) ) { features += 0x00000800 ; } // Server can self-write (Allows self-update)
2019-03-26 17:11:51 -04:00
if ( ( parent . config . settings . no2factorauth !== true ) && ( domain . auth != 'sspi' ) && ( obj . parent . certificates . CommonName . indexOf ( '.' ) != - 1 ) && ( obj . args . nousers !== true ) ) { features += 0x00001000 ; } // 2-step login supported
2019-02-28 19:17:22 -05:00
if ( domain . agentnoproxy === true ) { features += 0x00002000 ; } // Indicates that agents should be installed without using a HTTP proxy
2019-03-26 17:11:51 -04:00
if ( ( parent . config . settings . no2factorauth !== true ) && domain . yubikey && domain . yubikey . id && domain . yubikey . secret ) { features += 0x00004000 ; } // Indicates Yubikey support
2019-02-28 19:17:22 -05:00
if ( domain . geolocation == true ) { features += 0x00008000 ; } // Enable geo-location features
if ( ( domain . passwordrequirements != null ) && ( domain . passwordrequirements . hint === true ) ) { features += 0x00010000 ; } // Enable password hints
2019-05-16 17:55:07 -04:00
if ( parent . config . settings . no2factorauth !== true ) { features += 0x00020000 ; } // Enable WebAuthn/FIDO2 support
2020-03-11 19:53:09 -04:00
if ( ( obj . args . nousers != true ) && ( domain . passwordrequirements != null ) && ( domain . passwordrequirements . force2factor === true ) ) {
// Check if we can skip 2nd factor auth because of the source IP address
var skip2factor = false ;
if ( ( req != null ) && ( req . ip != null ) && ( domain . passwordrequirements != null ) && ( domain . passwordrequirements . skip2factor != null ) ) {
for ( var i in domain . passwordrequirements . skip2factor ) {
if ( require ( 'ipcheck' ) . match ( req . ip , domain . passwordrequirements . skip2factor [ i ] ) === true ) { skip2factor = true ; }
}
}
if ( skip2factor == false ) { features += 0x00040000 ; } // Force 2-factor auth
}
2019-05-08 18:15:34 -04:00
if ( ( domain . auth == 'sspi' ) || ( domain . auth == 'ldap' ) ) { features += 0x00080000 ; } // LDAP or SSPI in use, warn that users must login first before adding a user to a group.
2019-06-12 22:40:27 -04:00
if ( domain . amtacmactivation ) { features += 0x00100000 ; } // Intel AMT ACM activation/upgrade is possible
2019-07-08 18:59:44 -04:00
if ( domain . usernameisemail ) { features += 0x00200000 ; } // Username is email address
2019-10-05 17:24:40 -04:00
if ( parent . mqttbroker != null ) { features += 0x00400000 ; } // This server supports MQTT channels
2020-03-14 18:03:50 -04:00
if ( ( ( typeof domain . passwordrequirements != 'object' ) || ( domain . passwordrequirements . email2factor != false ) ) && ( parent . mailserver != null ) ) { features += 0x00800000 ; } // using email for 2FA is allowed
2020-03-16 21:03:14 -04:00
if ( domain . agentinvitecodes == true ) { features += 0x01000000 ; } // Support for agent invite codes
2019-01-28 18:47:54 -05:00
// Create a authentication cookie
2019-09-12 19:24:35 -04:00
const authCookie = obj . parent . encodeCookie ( { userid : user . _id , domainid : domain . id , ip : cleanRemoteAddr ( req . ip ) } , obj . parent . loginCookieEncryptionKey ) ;
2019-10-15 18:50:11 -04:00
const authRelayCookie = obj . parent . encodeCookie ( { ruserid : user . _id , domainid : domain . id } , obj . parent . loginCookieEncryptionKey ) ;
2019-01-28 18:47:54 -05:00
2018-01-23 17:15:59 -05:00
// Send the master web application
2019-11-21 21:57:57 -05:00
var extras = ( req . query . key != null ) ? ( '&key=' + req . query . key ) : '' ;
2019-12-01 18:21:33 -05:00
if ( ( ! obj . args . user ) && ( obj . args . nousers != true ) && ( nologout == false ) ) { logoutcontrols . logoutUrl = ( domain . url + 'logout?' + Math . random ( ) + extras ) ; } // If a default user is in use or no user mode, don't display the logout button
2018-03-08 20:58:22 -05:00
var httpsPort = ( ( obj . args . aliasport == null ) ? obj . args . port : obj . args . aliasport ) ; // Use HTTPS alias port is specified
2018-08-23 19:55:06 -04:00
2019-09-16 13:49:35 -04:00
// Clean up the U2F challenge if needed
if ( req . session . u2fchallenge ) { delete req . session . u2fchallenge ; } ;
2019-12-02 17:00:28 -05:00
// If geolocation is disabled, comment out all geolocation code to speed up the web application
var StartGeoLocation = '' , EndGeoLocation = '' , StartGeoLocationJS = '' , EndGeoLocationJS = '' ;
if ( domain . geolocation !== true ) { StartGeoLocation = '<!--' ; EndGeoLocation = '-->' ; StartGeoLocationJS = '/*' ; EndGeoLocationJS = '*/' ; }
2019-04-29 18:31:11 -04:00
// Fetch the web state
2019-08-23 18:45:36 -04:00
parent . debug ( 'web' , 'handleRootRequestEx: success.' ) ;
2019-04-29 18:31:11 -04:00
obj . db . Get ( 'ws' + user . _id , function ( err , states ) {
2019-10-25 17:41:14 -04:00
var webstate = ( states . length == 1 ) ? obj . filterUserWebState ( states [ 0 ] . state ) : '' ;
2020-02-27 21:05:18 -05:00
if ( ( webstate == '' ) && ( typeof domain . defaultuserwebstate == 'object' ) ) { webstate = JSON . stringify ( domain . defaultuserwebstate ) ; } // User has no web state, use defaults.
if ( typeof domain . forceduserwebstate == 'object' ) { // Forces initial user web state is present, use it.
var webstate2 = { } ;
try { if ( webstate != '' ) { webstate2 = JSON . parse ( webstate ) ; } } catch ( ex ) { }
for ( var i in domain . forceduserwebstate ) { webstate2 [ i ] = domain . forceduserwebstate [ i ] ; }
webstate = JSON . stringify ( webstate2 ) ;
}
2020-03-25 16:21:14 -04:00
render ( req , res , getRenderPage ( 'default' , req , domain ) , getRenderArgs ( { authCookie : authCookie , authRelayCookie : authRelayCookie , viewmode : viewmode , currentNode : currentNode , logoutControls : JSON . stringify ( logoutcontrols ) , domain : domain . id , debuglevel : parent . debugLevel , serverDnsName : obj . getWebServerName ( domain ) , serverRedirPort : args . redirport , serverPublicPort : httpsPort , noServerBackup : ( args . noserverbackup == 1 ? 1 : 0 ) , features : features , sessiontime : args . sessiontime , mpspass : args . mpspass , passRequirements : passRequirements , webcerthash : Buffer . from ( obj . webCertificateFullHashs [ domain . id ] , 'binary' ) . toString ( 'base64' ) . replace ( /\+/g , '@' ) . replace ( /\//g , '$' ) , footer : ( domain . footer == null ) ? '' : domain . footer , webstate : encodeURIComponent ( webstate ) , pluginHandler : ( parent . pluginHandler == null ) ? 'null' : parent . pluginHandler . prepExports ( ) , StartGeoLocation : StartGeoLocation , EndGeoLocation : EndGeoLocation , StartGeoLocationJS : StartGeoLocationJS , EndGeoLocationJS : EndGeoLocationJS } , domain ) ) ;
2019-04-29 18:31:11 -04:00
} ) ;
2017-08-28 12:27:45 -04:00
} else {
// Send back the login application
2019-02-08 01:30:33 -05:00
// If this is a 2 factor auth request, look for a hardware key challenge.
2019-02-11 17:41:15 -05:00
// Normal login 2 factor request
2019-08-26 15:20:24 -04:00
if ( req . session && ( req . session . loginmode == '4' ) && ( req . session . tokenusername ) ) {
2019-03-25 17:43:58 -04:00
var user = obj . users [ 'user/' + domain . id + '/' + req . session . tokenusername . toLowerCase ( ) ] ;
2019-02-08 01:30:33 -05:00
if ( user != null ) {
2019-08-23 18:45:36 -04:00
parent . debug ( 'web' , 'handleRootRequestEx: sending 2FA challenge.' ) ;
2019-03-25 17:43:58 -04:00
getHardwareKeyChallenge ( req , domain , user , function ( hwchallenge ) { handleRootRequestLogin ( req , res , domain , hwchallenge , passRequirements ) ; } ) ;
2019-02-10 19:04:36 -05:00
return ;
2019-02-08 01:30:33 -05:00
}
}
2019-02-11 17:41:15 -05:00
// Password recovery 2 factor request
2019-08-26 15:20:24 -04:00
if ( req . session && ( req . session . loginmode == '5' ) && ( req . session . tokenemail ) ) {
2019-02-11 17:41:15 -05:00
obj . db . GetUserWithVerifiedEmail ( domain . id , req . session . tokenemail , function ( err , docs ) {
if ( ( err != null ) || ( docs . length == 0 ) ) {
2019-08-23 18:45:36 -04:00
parent . debug ( 'web' , 'handleRootRequestEx: password recover 2FA fail.' ) ;
2019-02-11 17:41:15 -05:00
req . session = null ;
2019-08-26 15:20:24 -04:00
res . redirect ( domain . url + getQueryPortion ( req ) ) ; // BAD***
2019-02-11 17:41:15 -05:00
} else {
var user = obj . users [ docs [ 0 ] . _id ] ;
if ( user != null ) {
2019-08-23 18:45:36 -04:00
parent . debug ( 'web' , 'handleRootRequestEx: password recover 2FA challenge.' ) ;
2019-03-25 17:43:58 -04:00
getHardwareKeyChallenge ( req , domain , user , function ( hwchallenge ) { handleRootRequestLogin ( req , res , domain , hwchallenge , passRequirements ) ; } ) ;
2019-02-11 17:41:15 -05:00
} else {
2019-08-23 18:45:36 -04:00
parent . debug ( 'web' , 'handleRootRequestEx: password recover 2FA no user.' ) ;
2019-02-11 17:41:15 -05:00
req . session = null ;
2019-08-26 15:20:24 -04:00
res . redirect ( domain . url + getQueryPortion ( req ) ) ; // BAD***
2019-02-11 17:41:15 -05:00
}
}
} ) ;
return ;
}
2019-02-10 19:04:36 -05:00
handleRootRequestLogin ( req , res , domain , '' , passRequirements ) ;
}
}
2019-02-08 01:30:33 -05:00
2019-02-10 19:04:36 -05:00
function handleRootRequestLogin ( req , res , domain , hardwareKeyChallenge , passRequirements ) {
2019-08-23 18:45:36 -04:00
parent . debug ( 'web' , 'handleRootRequestLogin()' ) ;
2019-02-10 19:04:36 -05:00
var features = 0 ;
2019-10-22 20:51:37 -04:00
if ( ( parent . config != null ) && ( parent . config . settings != null ) && ( ( parent . config . settings . allowframing == true ) || ( typeof parent . config . settings . allowframing == 'string' ) ) ) { features += 32 ; } // Allow site within iframe
2019-07-08 18:59:44 -04:00
if ( domain . usernameisemail ) { features += 0x00200000 ; } // Username is email address
2019-02-10 19:04:36 -05:00
var httpsPort = ( ( obj . args . aliasport == null ) ? obj . args . port : obj . args . aliasport ) ; // Use HTTPS alias port is specified
2019-08-26 15:20:24 -04:00
var loginmode = '' ;
if ( req . session ) { loginmode = req . session . loginmode ; delete req . session . loginmode ; } // Clear this state, if the user hits refresh, we want to go back to the login page.
2019-02-10 19:04:36 -05:00
2019-02-10 23:13:03 -05:00
// Format an error message if needed
2019-11-17 17:33:29 -05:00
var passhint = null , msgid = 0 ;
2019-02-10 23:13:03 -05:00
if ( req . session != null ) {
2019-11-17 17:33:29 -05:00
msgid = req . session . messageid ;
2020-04-21 04:50:27 -04:00
if ( ( loginmode == '7' ) || ( ( domain . passwordrequirements != null ) && ( domain . passwordrequirements . hint === true ) ) ) { passhint = EscapeHtml ( req . session . passhint ) ; }
2019-11-17 17:33:29 -05:00
delete req . session . messageid ;
2019-02-10 23:13:03 -05:00
delete req . session . passhint ;
}
2019-06-07 18:55:24 -04:00
var emailcheck = ( ( obj . parent . mailserver != null ) && ( obj . parent . certificates . CommonName != null ) && ( obj . parent . certificates . CommonName . indexOf ( '.' ) != - 1 ) && ( obj . args . lanonly != true ) && ( domain . auth != 'sspi' ) && ( domain . auth != 'ldap' ) )
2019-02-20 21:09:57 -05:00
2019-04-23 16:42:54 -04:00
// Check if we are allowed to create new users using the login screen
var newAccountsAllowed = true ;
if ( ( domain . newaccounts !== 1 ) && ( domain . newaccounts !== true ) ) { for ( var i in obj . users ) { if ( obj . users [ i ] . domain == domain . id ) { newAccountsAllowed = false ; break ; } } }
2019-08-26 15:20:24 -04:00
// Encrypt the hardware key challenge state if needed
var hwstate = null ;
if ( hardwareKeyChallenge ) { hwstate = obj . parent . encodeCookie ( { u : req . session . tokenusername , p : req . session . tokenpassword , c : req . session . u2fchallenge } , obj . parent . loginCookieEncryptionKey ) }
2020-03-13 23:39:21 -04:00
// Check if we can use OTP tokens with email
var otpemail = ( parent . mailserver != null ) && ( req . session . tokenemail ) ;
2020-03-14 18:03:50 -04:00
if ( ( typeof domain . passwordrequirements == 'object' ) && ( domain . passwordrequirements . email2factor == false ) ) { otpemail = false ; }
2020-03-13 23:39:21 -04:00
2019-08-25 14:08:32 -04:00
// Render the login page
2020-03-25 16:21:14 -04:00
render ( req , res , getRenderPage ( 'login' , req , domain ) , getRenderArgs ( { loginmode : loginmode , rootCertLink : getRootCertLink ( ) , newAccount : newAccountsAllowed , newAccountPass : ( ( ( domain . newaccountspass == null ) || ( domain . newaccountspass == '' ) ) ? 0 : 1 ) , serverDnsName : obj . getWebServerName ( domain ) , serverPublicPort : httpsPort , emailcheck : emailcheck , features : features , sessiontime : args . sessiontime , passRequirements : passRequirements , footer : ( domain . footer == null ) ? '' : domain . footer , hkey : encodeURIComponent ( hardwareKeyChallenge ) , messageid : msgid , passhint : passhint , welcometext : domain . welcometext ? encodeURIComponent ( domain . welcometext ) . split ( '\'' ) . join ( '\\\'' ) : null , hwstate : hwstate , otpemail : otpemail } , domain ) ) ;
2019-08-26 15:20:24 -04:00
}
// Handle a post request on the root
function handleRootPostRequest ( req , res ) {
2019-11-21 21:57:57 -05:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 15:06:34 -04:00
if ( domain == null ) { return ; }
2019-12-21 19:07:25 -05:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . end ( "Not Found" ) ; return ; } // Check 3FA URL key
2019-08-26 15:20:24 -04:00
parent . debug ( 'web' , 'handleRootPostRequest, action: ' + req . body . action ) ;
2020-01-31 17:44:11 -05:00
2019-08-26 15:20:24 -04:00
switch ( req . body . action ) {
case 'login' : { handleLoginRequest ( req , res , true ) ; break ; }
case 'tokenlogin' : {
if ( req . body . hwstate ) {
var cookie = obj . parent . decodeCookie ( req . body . hwstate , obj . parent . loginCookieEncryptionKey , 10 ) ;
if ( cookie != null ) { req . session . tokenusername = cookie . u ; req . session . tokenpassword = cookie . p ; req . session . u2fchallenge = cookie . c ; }
}
handleLoginRequest ( req , res , true ) ; break ;
}
case 'changepassword' : { handlePasswordChangeRequest ( req , res , true ) ; break ; }
case 'deleteaccount' : { handleDeleteAccountRequest ( req , res , true ) ; break ; }
case 'createaccount' : { handleCreateAccountRequest ( req , res , true ) ; break ; }
case 'resetpassword' : { handleResetPasswordRequest ( req , res , true ) ; break ; }
case 'resetaccount' : { handleResetAccountRequest ( req , res , true ) ; break ; }
2020-04-21 04:50:27 -04:00
case 'checkemail' : { handleCheckAccountEmailRequest ( req , res , true ) ; break ; }
2019-08-26 15:20:24 -04:00
default : { handleLoginRequest ( req , res , true ) ; break ; }
}
2017-08-28 12:27:45 -04:00
}
2018-05-24 00:43:44 -04:00
2019-02-14 17:44:38 -05:00
// Return true if it looks like we are using a real TLS certificate.
2020-01-31 17:44:11 -05:00
obj . isTrustedCert = function ( domain ) {
2019-02-14 17:44:38 -05:00
if ( obj . args . notls == true ) return false ; // We are not using TLS, so not trusted cert.
2019-08-22 18:31:39 -04:00
if ( ( domain != null ) && ( typeof domain . trustedcert == 'boolean' ) ) return domain . trustedcert ; // If the status of the cert specified, use that.
if ( typeof obj . args . trustedcert == 'boolean' ) return obj . args . trustedcert ; // If the status of the cert specified, use that.
2019-02-14 17:44:38 -05:00
if ( obj . args . tlsoffload != null ) return true ; // We are using TLS offload, a real cert is likely used.
2019-08-22 18:31:39 -04:00
if ( obj . parent . config . letsencrypt != null ) return ( obj . parent . config . letsencrypt . production === true ) ; // We are using Let's Encrypt, real cert in use if production is set to true.
2019-02-14 17:44:38 -05:00
if ( obj . certificates . WebIssuer . indexOf ( 'MeshCentralRoot-' ) == 0 ) return false ; // Our cert is issued by self-signed cert.
2019-03-05 02:48:45 -05:00
if ( obj . certificates . CommonName . indexOf ( '.' ) == - 1 ) return false ; // Our cert is named with a fake name
2019-02-14 17:44:38 -05:00
return true ; // This is a guess
}
2017-08-28 12:27:45 -04:00
// Get the link to the root certificate if needed
function getRootCertLink ( ) {
2018-08-29 20:40:30 -04:00
// Check if the HTTPS certificate is issued from MeshCentralRoot, if so, add download link to root certificate.
2019-03-05 02:48:45 -05:00
if ( ( obj . args . notls == null ) && ( obj . args . tlsoffload == null ) && ( obj . parent . config . letsencrypt == null ) && ( obj . tlsSniCredentials == null ) && ( obj . certificates . WebIssuer . indexOf ( 'MeshCentralRoot-' ) == 0 ) && ( obj . certificates . CommonName . indexOf ( '.' ) != - 1 ) ) { return '<a href=/MeshServerRootCert.cer title="Download the root certificate for this server">Root Certificate</a>' ; }
2017-08-28 12:27:45 -04:00
return '' ;
}
2020-01-23 18:15:56 -05:00
// Serve the xterm page
function handleXTermRequest ( req , res ) {
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 15:06:34 -04:00
if ( domain == null ) { return ; }
2020-01-23 18:15:56 -05:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
parent . debug ( 'web' , 'handleXTermRequest: sending xterm' ) ;
res . set ( { 'Cache-Control' : 'no-cache, no-store, must-revalidate' , 'Pragma' : 'no-cache' , 'Expires' : '0' } ) ;
if ( req . session && req . session . userid ) {
if ( req . session . domainid != domain . id ) { res . redirect ( domain . url + getQueryPortion ( req ) ) ; return ; } // Check if the session is for the correct domain
var user = obj . users [ req . session . userid ] ;
if ( ( user == null ) || ( req . query . nodeid == null ) ) { res . redirect ( domain . url + getQueryPortion ( req ) ) ; return ; } // Check if the user exists
// Check permissions
obj . GetNodeWithRights ( domain , user , req . query . nodeid , function ( node , rights , visible ) {
if ( ( node == null ) || ( ( rights & 8 ) == 0 ) || ( ( rights != 0xFFFFFFFF ) && ( ( rights & 512 ) != 0 ) ) ) { res . redirect ( domain . url + getQueryPortion ( req ) ) ; return ; }
var logoutcontrols = { name : user . name } ;
var extras = ( req . query . key != null ) ? ( '&key=' + req . query . key ) : '' ;
if ( ( domain . ldap == null ) && ( domain . sspi == null ) && ( obj . args . user == null ) && ( obj . args . nousers != true ) ) { logoutcontrols . logoutUrl = ( domain . url + 'logout?' + Math . random ( ) + extras ) ; } // If a default user is in use or no user mode, don't display the logout button
// Create a authentication cookie
const authCookie = obj . parent . encodeCookie ( { userid : user . _id , domainid : domain . id , ip : cleanRemoteAddr ( req . ip ) } , obj . parent . loginCookieEncryptionKey ) ;
const authRelayCookie = obj . parent . encodeCookie ( { ruserid : user . _id , domainid : domain . id } , obj . parent . loginCookieEncryptionKey ) ;
var httpsPort = ( ( obj . args . aliasport == null ) ? obj . args . port : obj . args . aliasport ) ; // Use HTTPS alias port is specified
2020-03-25 16:21:14 -04:00
render ( req , res , getRenderPage ( 'xterm' , req , domain ) , getRenderArgs ( { serverDnsName : obj . getWebServerName ( domain ) , serverRedirPort : args . redirport , serverPublicPort : httpsPort , authCookie : authCookie , authRelayCookie : authRelayCookie , logoutControls : JSON . stringify ( logoutcontrols ) , name : EscapeHtml ( node . name ) } , domain ) ) ;
2020-01-23 18:15:56 -05:00
} ) ;
} else {
res . redirect ( domain . url + getQueryPortion ( req ) ) ;
return ;
}
}
2018-12-20 15:12:24 -05:00
// Render the terms of service.
2017-08-28 12:27:45 -04:00
function handleTermsRequest ( req , res ) {
2019-01-05 15:04:18 -05:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 15:06:34 -04:00
if ( domain == null ) { return ; }
2019-11-25 17:30:23 -05:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2018-11-28 19:33:07 -05:00
2019-02-02 17:54:36 -05:00
// See if term.txt was loaded from the database
if ( ( parent . configurationFiles != null ) && ( parent . configurationFiles [ 'terms.txt' ] != null ) ) {
// Send the terms from the database
res . set ( { 'Cache-Control' : 'no-cache, no-store, must-revalidate' , 'Pragma' : 'no-cache' , 'Expires' : '0' } ) ;
if ( req . session && req . session . userid ) {
2020-03-25 16:21:14 -04:00
if ( req . session . domainid != domain . id ) { req . session = null ; res . redirect ( domain . url + getQueryPortion ( req ) ) ; return ; } // Check if the session is for the correct domain
2019-02-02 17:54:36 -05:00
var user = obj . users [ req . session . userid ] ;
2019-12-01 18:21:33 -05:00
var logoutcontrols = { name : user . name } ;
2019-11-21 21:57:57 -05:00
var extras = ( req . query . key != null ) ? ( '&key=' + req . query . key ) : '' ;
2019-12-01 18:21:33 -05:00
if ( ( domain . ldap == null ) && ( domain . sspi == null ) && ( obj . args . user == null ) && ( obj . args . nousers != true ) ) { logoutcontrols . logoutUrl = ( domain . url + 'logout?' + Math . random ( ) + extras ) ; } // If a default user is in use or no user mode, don't display the logout button
2020-03-25 16:21:14 -04:00
render ( req , res , getRenderPage ( 'terms' , req , domain ) , getRenderArgs ( { terms : encodeURIComponent ( parent . configurationFiles [ 'terms.txt' ] . toString ( ) ) . split ( '\'' ) . join ( '\\\'' ) , logoutControls : JSON . stringify ( logoutcontrols ) } , domain ) ) ;
2019-02-02 17:54:36 -05:00
} else {
2020-03-25 16:21:14 -04:00
render ( req , res , getRenderPage ( 'terms' , req , domain ) , getRenderArgs ( { terms : encodeURIComponent ( parent . configurationFiles [ 'terms.txt' ] . toString ( ) ) . split ( '\'' ) . join ( '\\\'' ) , logoutControls : '{}' } , domain ) ) ;
2019-02-02 17:54:36 -05:00
}
} else {
// See if there is a terms.txt file in meshcentral-data
var p = obj . path . join ( obj . parent . datapath , 'terms.txt' ) ;
if ( obj . fs . existsSync ( p ) ) {
obj . fs . readFile ( p , 'utf8' , function ( err , data ) {
2019-08-23 18:45:36 -04:00
if ( err != null ) { parent . debug ( 'web' , 'handleTermsRequest: no terms.txt' ) ; res . sendStatus ( 404 ) ; return ; }
2018-11-28 19:33:07 -05:00
2019-02-02 17:54:36 -05:00
// Send the terms from terms.txt
res . set ( { 'Cache-Control' : 'no-cache, no-store, must-revalidate' , 'Pragma' : 'no-cache' , 'Expires' : '0' } ) ;
if ( req . session && req . session . userid ) {
2020-03-25 16:21:14 -04:00
if ( req . session . domainid != domain . id ) { req . session = null ; res . redirect ( domain . url + getQueryPortion ( req ) ) ; return ; } // Check if the session is for the correct domain
2019-02-02 17:54:36 -05:00
var user = obj . users [ req . session . userid ] ;
2019-12-01 18:21:33 -05:00
var logoutcontrols = { name : user . name } ;
2019-11-21 21:57:57 -05:00
var extras = ( req . query . key != null ) ? ( '&key=' + req . query . key ) : '' ;
2019-12-01 18:21:33 -05:00
if ( ( domain . ldap == null ) && ( domain . sspi == null ) && ( obj . args . user == null ) && ( obj . args . nousers != true ) ) { logoutcontrols . logoutUrl = ( domain . url + 'logout?' + Math . random ( ) + extras ) ; } // If a default user is in use or no user mode, don't display the logout button
2020-03-25 16:21:14 -04:00
render ( req , res , getRenderPage ( 'terms' , req , domain ) , getRenderArgs ( { terms : encodeURIComponent ( data ) . split ( '\'' ) . join ( '\\\'' ) , logoutControls : JSON . stringify ( logoutcontrols ) } , domain ) ) ;
2019-02-02 17:54:36 -05:00
} else {
2020-03-25 16:21:14 -04:00
render ( req , res , getRenderPage ( 'terms' , req , domain ) , getRenderArgs ( { terms : encodeURIComponent ( data ) . split ( '\'' ) . join ( '\\\'' ) , logoutControls : '{}' } , domain ) ) ;
2019-02-02 17:54:36 -05:00
}
} ) ;
} else {
// Send the default terms
2019-08-23 18:45:36 -04:00
parent . debug ( 'web' , 'handleTermsRequest: sending default terms' ) ;
2018-11-28 19:33:07 -05:00
res . set ( { 'Cache-Control' : 'no-cache, no-store, must-revalidate' , 'Pragma' : 'no-cache' , 'Expires' : '0' } ) ;
if ( req . session && req . session . userid ) {
2020-03-25 16:21:14 -04:00
if ( req . session . domainid != domain . id ) { req . session = null ; res . redirect ( domain . url + getQueryPortion ( req ) ) ; return ; } // Check if the session is for the correct domain
2018-11-28 19:33:07 -05:00
var user = obj . users [ req . session . userid ] ;
2019-12-01 18:21:33 -05:00
var logoutcontrols = { name : user . name } ;
2019-11-21 21:57:57 -05:00
var extras = ( req . query . key != null ) ? ( '&key=' + req . query . key ) : '' ;
2019-12-01 18:21:33 -05:00
if ( ( domain . ldap == null ) && ( domain . sspi == null ) && ( obj . args . user == null ) && ( obj . args . nousers != true ) ) { logoutcontrols . logoutUrl = ( domain . url + 'logout?' + Math . random ( ) + extras ) ; } // If a default user is in use or no user mode, don't display the logout button
2020-03-25 16:21:14 -04:00
render ( req , res , getRenderPage ( 'terms' , req , domain ) , getRenderArgs ( { logoutControls : JSON . stringify ( logoutcontrols ) } , domain ) ) ;
2018-11-28 19:33:07 -05:00
} else {
2020-03-25 16:21:14 -04:00
render ( req , res , getRenderPage ( 'terms' , req , domain ) , getRenderArgs ( { logoutControls : '{}' } , domain ) ) ;
2018-11-28 19:33:07 -05:00
}
}
2017-08-28 12:27:45 -04:00
}
}
2018-12-20 15:12:24 -05:00
// Render the messenger application.
function handleMessengerRequest ( req , res ) {
2019-10-10 14:43:21 -04:00
const domain = getDomain ( req ) ;
2019-08-23 18:45:36 -04:00
if ( domain == null ) { parent . debug ( 'web' , 'handleMessengerRequest: no domain' ) ; res . sendStatus ( 404 ) ; return ; }
parent . debug ( 'web' , 'handleMessengerRequest()' ) ;
2019-05-30 12:40:59 -04:00
2018-12-20 15:12:24 -05:00
var webRtcConfig = null ;
if ( obj . parent . config . settings && obj . parent . config . settings . webrtconfig && ( typeof obj . parent . config . settings . webrtconfig == 'object' ) ) { webRtcConfig = encodeURIComponent ( JSON . stringify ( obj . parent . config . settings . webrtconfig ) ) ; }
res . set ( { 'Cache-Control' : 'no-cache, no-store, must-revalidate' , 'Pragma' : 'no-cache' , 'Expires' : '0' } ) ;
2020-03-25 16:21:14 -04:00
render ( req , res , getRenderPage ( 'messenger' , req , domain ) , getRenderArgs ( { webrtconfig : webRtcConfig } , domain ) ) ;
2018-12-20 15:12:24 -05:00
}
2017-08-28 12:27:45 -04:00
// Returns the server root certificate encoded in base64
function getRootCertBase64 ( ) {
var rootcert = obj . certificates . root . cert ;
2019-12-27 01:53:01 -05:00
var i = rootcert . indexOf ( '-----BEGIN CERTIFICATE-----\r\n' ) ;
2017-08-28 12:27:45 -04:00
if ( i >= 0 ) { rootcert = rootcert . substring ( i + 29 ) ; }
2019-12-27 01:53:01 -05:00
i = rootcert . indexOf ( '-----END CERTIFICATE-----' ) ;
2017-08-28 12:27:45 -04:00
if ( i >= 0 ) { rootcert = rootcert . substring ( i , 0 ) ; }
2019-01-02 21:03:34 -05:00
return Buffer . from ( rootcert , 'base64' ) . toString ( 'base64' ) ;
2017-08-28 12:27:45 -04:00
}
// Returns the mesh server root certificate
function handleRootCertRequest ( req , res ) {
2019-11-21 21:57:57 -05:00
const domain = getDomain ( req ) ;
if ( domain == null ) { parent . debug ( 'web' , 'handleRootCertRequest: no domain' ) ; res . sendStatus ( 404 ) ; return ; }
2019-11-25 17:30:23 -05:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2019-08-23 18:45:36 -04:00
if ( ( obj . userAllowedIp != null ) && ( checkIpAddressEx ( req , res , obj . userAllowedIp , false ) === false ) ) { parent . debug ( 'web' , 'handleRootCertRequest: invalid ip' ) ; return ; } // Check server-wide IP filter only.
parent . debug ( 'web' , 'handleRootCertRequest()' ) ;
2019-06-24 21:56:14 -04:00
try {
res . set ( { 'Cache-Control' : 'no-cache, no-store, must-revalidate' , 'Pragma' : 'no-cache' , 'Expires' : '0' , 'Content-Type' : 'application/octet-stream' , 'Content-Disposition' : 'attachment; filename="' + certificates . RootName + '.cer"' } ) ;
} catch ( ex ) {
res . set ( { 'Cache-Control' : 'no-cache, no-store, must-revalidate' , 'Pragma' : 'no-cache' , 'Expires' : '0' , 'Content-Type' : 'application/octet-stream' , 'Content-Disposition' : 'attachment; filename="rootcert.cer"' } ) ;
}
2019-01-02 21:03:34 -05:00
res . send ( Buffer . from ( getRootCertBase64 ( ) , 'base64' ) ) ;
2017-08-28 12:27:45 -04:00
}
2019-02-07 23:06:01 -05:00
// Return the CIRA configuration script
2019-10-04 15:18:56 -04:00
obj . getCiraCleanupScript = function ( func ) {
2019-02-07 23:06:01 -05:00
obj . fs . readFile ( obj . parent . path . join ( obj . parent . webPublicPath , 'scripts/cira_cleanup.mescript' ) , 'utf8' , function ( err , data ) {
if ( err != null ) { func ( null ) ; return ; }
func ( Buffer . from ( data ) ) ;
} ) ;
}
// Return the CIRA configuration script
2019-09-20 13:41:31 -04:00
obj . getCiraConfigurationScript = function ( meshid , func ) {
var meshidx = meshid . split ( '/' ) [ 2 ] . replace ( /\@/g , 'X' ) . replace ( /\$/g , 'X' ) . substring ( 0 , 16 ) ;
2019-02-07 23:06:01 -05:00
var serverNameSplit = obj . certificates . AmtMpsName . split ( '.' ) ;
// Figure out the MPS port, use the alias if set
var mpsport = ( ( obj . args . mpsaliasport != null ) ? obj . args . mpsaliasport : obj . args . mpsport ) ;
if ( ( serverNameSplit . length == 4 ) && ( parseInt ( serverNameSplit [ 0 ] ) == serverNameSplit [ 0 ] ) && ( parseInt ( serverNameSplit [ 1 ] ) == serverNameSplit [ 1 ] ) && ( parseInt ( serverNameSplit [ 2 ] ) == serverNameSplit [ 2 ] ) && ( parseInt ( serverNameSplit [ 3 ] ) == serverNameSplit [ 3 ] ) ) {
// Server name is an IPv4 address
obj . fs . readFile ( obj . parent . path . join ( obj . parent . webPublicPath , 'scripts/cira_setup_script_ip.mescript' ) , 'utf8' , function ( err , data ) {
if ( err != null ) { func ( null ) ; return ; }
2020-02-10 15:22:01 -05:00
// Randomize the environement detection
var randomDnsName ;
do { randomDnsName = getRandomLowerCase ( 14 ) ; } while ( randomDnsName == 'aabbccddeeffgg' ) ;
while ( data . indexOf ( 'aabbccddeeffgg' ) >= 0 ) { data = data . replace ( 'aabbccddeeffgg' , randomDnsName ) ; }
2019-02-07 23:06:01 -05:00
var scriptFile = JSON . parse ( data ) ;
// Change a few things in the script
scriptFile . scriptBlocks [ 2 ] . vars . CertBin . value = getRootCertBase64 ( ) ; // Set the root certificate
scriptFile . scriptBlocks [ 3 ] . vars . IP . value = obj . certificates . AmtMpsName ; // Set the server IPv4 address name
scriptFile . scriptBlocks [ 3 ] . vars . ServerName . value = obj . certificates . AmtMpsName ; // Set the server certificate name
scriptFile . scriptBlocks [ 3 ] . vars . Port . value = mpsport ; // Set the server MPS port
2019-09-20 13:41:31 -04:00
scriptFile . scriptBlocks [ 3 ] . vars . username . value = meshidx ; // Set the username
2019-02-07 23:06:01 -05:00
scriptFile . scriptBlocks [ 3 ] . vars . password . value = obj . args . mpspass ? obj . args . mpspass : 'A@xew9rt' ; // Set the password
scriptFile . scriptBlocks [ 4 ] . vars . AccessInfo1 . value = obj . certificates . AmtMpsName + ':' + mpsport ; // Set the primary server name:port to set periodic timer
//scriptFile.scriptBlocks[4].vars.AccessInfo2.value = obj.certificates.AmtMpsName + ':' + mpsport; // Set the secondary server name:port to set periodic timer
if ( obj . args . ciralocalfqdn != null ) { scriptFile . scriptBlocks [ 6 ] . vars . DetectionStrings . value = obj . args . ciralocalfqdn ; } // Set the environment detection local FQDN's
// Compile the script
var scriptEngine = require ( './amtscript.js' ) . CreateAmtScriptEngine ( ) ;
var runscript = scriptEngine . script _blocksToScript ( scriptFile . blocks , scriptFile . scriptBlocks ) ;
scriptFile . mescript = Buffer . from ( scriptEngine . script _compile ( runscript ) , 'binary' ) . toString ( 'base64' ) ;
scriptFile . scriptText = runscript ;
// Send the script
2020-02-10 15:22:01 -05:00
func ( Buffer . from ( JSON . stringify ( scriptFile , null , ' ' ) ) ) ;
2019-02-07 23:06:01 -05:00
} ) ;
} else {
// Server name is a hostname
obj . fs . readFile ( obj . parent . path . join ( obj . parent . webPublicPath , 'scripts/cira_setup_script_dns.mescript' ) , 'utf8' , function ( err , data ) {
if ( err != null ) { res . sendStatus ( 404 ) ; return ; }
2020-02-10 15:22:01 -05:00
// Randomize the environement detection
var randomDnsName ;
do { randomDnsName = getRandomLowerCase ( 14 ) ; } while ( randomDnsName == 'aabbccddeeffgg' ) ;
while ( data . indexOf ( 'aabbccddeeffgg' ) >= 0 ) { data = data . replace ( 'aabbccddeeffgg' , randomDnsName ) ; }
2019-02-07 23:06:01 -05:00
var scriptFile = JSON . parse ( data ) ;
// Change a few things in the script
scriptFile . scriptBlocks [ 2 ] . vars . CertBin . value = getRootCertBase64 ( ) ; // Set the root certificate
scriptFile . scriptBlocks [ 3 ] . vars . FQDN . value = obj . certificates . AmtMpsName ; // Set the server DNS name
scriptFile . scriptBlocks [ 3 ] . vars . Port . value = mpsport ; // Set the server MPS port
2019-09-20 13:41:31 -04:00
scriptFile . scriptBlocks [ 3 ] . vars . username . value = meshidx ; // Set the username
2019-02-07 23:06:01 -05:00
scriptFile . scriptBlocks [ 3 ] . vars . password . value = obj . args . mpspass ? obj . args . mpspass : 'A@xew9rt' ; // Set the password
scriptFile . scriptBlocks [ 4 ] . vars . AccessInfo1 . value = obj . certificates . AmtMpsName + ':' + mpsport ; // Set the primary server name:port to set periodic timer
//scriptFile.scriptBlocks[4].vars.AccessInfo2.value = obj.certificates.AmtMpsName + ':' + mpsport; // Set the secondary server name:port to set periodic timer
if ( obj . args . ciralocalfqdn != null ) { scriptFile . scriptBlocks [ 6 ] . vars . DetectionStrings . value = obj . args . ciralocalfqdn ; } // Set the environment detection local FQDN's
// Compile the script
var scriptEngine = require ( './amtscript.js' ) . CreateAmtScriptEngine ( ) ;
var runscript = scriptEngine . script _blocksToScript ( scriptFile . blocks , scriptFile . scriptBlocks ) ;
scriptFile . mescript = Buffer . from ( scriptEngine . script _compile ( runscript ) , 'binary' ) . toString ( 'base64' ) ;
scriptFile . scriptText = runscript ;
// Send the script
2020-02-10 15:22:01 -05:00
func ( Buffer . from ( JSON . stringify ( scriptFile , null , ' ' ) ) ) ;
2019-02-07 23:06:01 -05:00
} ) ;
}
}
2017-08-28 12:27:45 -04:00
// Returns an mescript for Intel AMT configuration
function handleMeScriptRequest ( req , res ) {
2019-11-21 21:57:57 -05:00
const domain = getDomain ( req ) ;
if ( domain == null ) { parent . debug ( 'web' , 'handleMeScriptRequest: no domain' ) ; res . sendStatus ( 404 ) ; return ; }
2019-11-25 17:30:23 -05:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2019-11-21 21:57:57 -05:00
2019-02-07 18:00:10 -05:00
if ( ( obj . userAllowedIp != null ) && ( checkIpAddressEx ( req , res , obj . userAllowedIp , false ) === false ) ) { return ; } // Check server-wide IP filter only.
2017-08-28 12:27:45 -04:00
if ( req . query . type == 1 ) {
2019-02-10 19:04:36 -05:00
obj . getCiraConfigurationScript ( req . query . meshid , function ( script ) {
2019-02-07 23:06:01 -05:00
if ( script == null ) { res . sendStatus ( 404 ) ; } else {
2019-09-20 13:41:31 -04:00
try {
var cirafilename = obj . meshes [ req . query . meshid ] . name . split ( '\\' ) . join ( '' ) . split ( '/' ) . join ( '' ) . split ( ':' ) . join ( '' ) . split ( '*' ) . join ( '' ) . split ( '?' ) . join ( '' ) . split ( '"' ) . join ( '' ) . split ( '<' ) . join ( '' ) . split ( '>' ) . join ( '' ) . split ( '|' ) . join ( '' ) . split ( ' ' ) . join ( '' ) . split ( '\'' ) . join ( '' ) ;
res . set ( { 'Cache-Control' : 'no-cache, no-store, must-revalidate' , 'Pragma' : 'no-cache' , 'Expires' : '0' , 'Content-Type' : 'application/octet-stream' , 'Content-Disposition' : 'attachment; filename="cira_setup_' + cirafilename + '.mescript"' } ) ;
} catch ( ex ) {
res . set ( { 'Cache-Control' : 'no-cache, no-store, must-revalidate' , 'Pragma' : 'no-cache' , 'Expires' : '0' , 'Content-Type' : 'application/octet-stream' , 'Content-Disposition' : 'attachment; filename="cira_setup.mescript"' } ) ;
}
2019-02-07 23:06:01 -05:00
res . send ( script ) ;
}
} ) ;
} else if ( req . query . type == 2 ) {
2019-02-10 19:04:36 -05:00
obj . getCiraCleanupScript ( function ( script ) {
2019-02-07 23:06:01 -05:00
if ( script == null ) { res . sendStatus ( 404 ) ; } else {
2019-04-30 18:40:19 -04:00
res . set ( { 'Cache-Control' : 'no-cache, no-store, must-revalidate' , 'Pragma' : 'no-cache' , 'Expires' : '0' , 'Content-Type' : 'application/octet-stream' , 'Content-Disposition' : 'attachment; filename="cira_cleanup.mescript"' } ) ;
2019-02-07 23:06:01 -05:00
res . send ( script ) ;
}
2017-08-28 12:27:45 -04:00
} ) ;
}
}
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
// Handle user public file downloads
function handleDownloadUserFiles ( req , res ) {
2019-01-05 15:04:18 -05:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 15:06:34 -04:00
if ( domain == null ) { return ; }
2019-11-25 17:30:23 -05:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2019-11-21 21:57:57 -05:00
2019-01-04 20:59:13 -05:00
if ( obj . common . validateString ( req . path , 1 , 4096 ) == false ) { res . sendStatus ( 404 ) ; return ; }
2017-10-02 17:12:29 -04:00
var domainname = 'domain' , spliturl = decodeURIComponent ( req . path ) . split ( '/' ) , filename = '' ;
2017-08-28 12:27:45 -04:00
if ( ( spliturl . length < 3 ) || ( obj . common . IsFilenameValid ( spliturl [ 2 ] ) == false ) || ( domain . userQuota == - 1 ) ) { res . sendStatus ( 404 ) ; return ; }
if ( domain . id != '' ) { domainname = 'domain-' + domain . id ; }
2019-12-27 01:53:01 -05:00
var path = obj . path . join ( obj . filespath , domainname + '/user-' + spliturl [ 2 ] + '/Public' ) ;
2017-08-28 12:27:45 -04:00
for ( var i = 3 ; i < spliturl . length ; i ++ ) { if ( obj . common . IsFilenameValid ( spliturl [ i ] ) == true ) { path += '/' + spliturl [ i ] ; filename = spliturl [ i ] ; } else { res . sendStatus ( 404 ) ; return ; } }
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
var stat = null ;
2018-08-30 15:05:23 -04:00
try { stat = obj . fs . statSync ( path ) ; } catch ( e ) { }
2017-08-28 12:27:45 -04:00
if ( ( stat != null ) && ( ( stat . mode & 0x004000 ) == 0 ) ) {
if ( req . query . download == 1 ) {
2019-06-24 21:56:14 -04:00
try {
res . set ( { 'Cache-Control' : 'no-cache, no-store, must-revalidate' , 'Pragma' : 'no-cache' , 'Expires' : '0' , 'Content-Type' : 'application/octet-stream' , 'Content-Disposition' : 'attachment; filename=\"' + filename + '\"' } ) ;
} catch ( ex ) {
res . set ( { 'Cache-Control' : 'no-cache, no-store, must-revalidate' , 'Pragma' : 'no-cache' , 'Expires' : '0' , 'Content-Type' : 'application/octet-stream' , 'Content-Disposition' : 'attachment; filename=\"file.bin\"' } ) ;
}
2017-08-28 12:27:45 -04:00
try { res . sendFile ( obj . path . resolve ( _ _dirname , path ) ) ; } catch ( e ) { res . sendStatus ( 404 ) ; }
} else {
2020-03-25 16:21:14 -04:00
render ( req , res , getRenderPage ( 'download' , req , domain ) , getRenderArgs ( { rootCertLink : getRootCertLink ( ) , message : "<a href='" + req . path + "?download=1'>" + filename + "</a>, " + stat . size + " byte" + ( ( stat . size < 2 ) ? '' : 's' ) + "." } , domain ) ) ;
2017-08-28 12:27:45 -04:00
}
} else {
2020-03-25 16:21:14 -04:00
render ( req , res , getRenderPage ( 'download' , req , domain ) , getRenderArgs ( { rootCertLink : getRootCertLink ( ) , message : "Invalid file link, please check the URL again." } , domain ) ) ;
2017-08-28 12:27:45 -04:00
}
}
2018-12-21 17:39:26 -05:00
// Handle logo request
function handleLogoRequest ( req , res ) {
2019-01-05 15:04:18 -05:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 15:06:34 -04:00
if ( domain == null ) { return ; }
2018-12-21 17:39:26 -05:00
2019-12-20 17:50:06 -05:00
//res.set({ 'Cache-Control': 'max-age=86400' }); // 1 day
2020-03-26 15:06:34 -04:00
if ( domain . titlepicture ) {
2019-02-02 17:54:36 -05:00
if ( ( parent . configurationFiles != null ) && ( parent . configurationFiles [ domain . titlepicture ] != null ) ) {
// Use the logo in the database
res . set ( { 'Content-Type' : 'image/jpeg' } ) ;
res . send ( parent . configurationFiles [ domain . titlepicture ] ) ;
2019-09-15 15:47:59 -04:00
return ;
2019-02-02 17:54:36 -05:00
} else {
// Use the logo on file
2019-09-15 15:47:59 -04:00
try { res . sendFile ( obj . path . join ( obj . parent . datapath , domain . titlepicture ) ) ; return ; } catch ( ex ) { }
2018-12-21 17:39:26 -05:00
}
2019-09-15 15:47:59 -04:00
}
2020-03-26 15:06:34 -04:00
if ( ( domain . webpublicpath != null ) && ( obj . fs . existsSync ( obj . path . join ( domain . webpublicpath , 'images/logoback.png' ) ) ) ) {
2020-03-25 16:21:14 -04:00
// Use the domain logo picture
try { res . sendFile ( obj . path . join ( domain . webpublicpath , 'images/logoback.png' ) ) ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
} else if ( parent . webPublicOverridePath && obj . fs . existsSync ( obj . path . join ( obj . parent . webPublicOverridePath , 'images/logoback.png' ) ) ) {
2019-09-15 15:47:59 -04:00
// Use the override logo picture
try { res . sendFile ( obj . path . join ( obj . parent . webPublicOverridePath , 'images/logoback.png' ) ) ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
2018-12-21 17:39:26 -05:00
} else {
2019-09-15 15:47:59 -04:00
// Use the default logo picture
try { res . sendFile ( obj . path . join ( obj . parent . webPublicPath , 'images/logoback.png' ) ) ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
2018-12-21 17:39:26 -05:00
}
}
2019-12-05 18:33:22 -05:00
// Handle translation request
function handleTranslationsRequest ( req , res ) {
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 15:06:34 -04:00
if ( domain == null ) { return ; }
2019-12-05 18:33:22 -05:00
//if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
if ( ( obj . userAllowedIp != null ) && ( checkIpAddressEx ( req , res , obj . userAllowedIp , false ) === false ) ) { return ; } // Check server-wide IP filter only.
var user = null ;
if ( obj . args . user != null ) {
// A default user is active
user = obj . users [ 'user/' + domain . id + '/' + obj . args . user ] ;
if ( ! user ) { parent . debug ( 'web' , 'handleTranslationsRequest: user not found.' ) ; res . sendStatus ( 401 ) ; return ; }
} else {
// Check if the user is logged and we have all required parameters
if ( ! req . session || ! req . session . userid ) { parent . debug ( 'web' , 'handleTranslationsRequest: failed checks (2).' ) ; res . sendStatus ( 401 ) ; return ; }
// Get the current user
user = obj . users [ req . session . userid ] ;
if ( ! user ) { parent . debug ( 'web' , 'handleTranslationsRequest: user not found.' ) ; res . sendStatus ( 401 ) ; return ; }
if ( user . siteadmin != 0xFFFFFFFF ) { parent . debug ( 'web' , 'handleTranslationsRequest: user not site administrator.' ) ; res . sendStatus ( 401 ) ; return ; }
}
var data = '' ;
req . setEncoding ( 'utf8' ) ;
req . on ( 'data' , function ( chunk ) { data += chunk ; } ) ;
req . on ( 'end' , function ( ) {
try { data = JSON . parse ( data ) ; } catch ( ex ) { data = null ; }
if ( data == null ) { res . sendStatus ( 404 ) ; return ; }
if ( data . action == 'getTranslations' ) {
if ( obj . fs . existsSync ( obj . path . join ( obj . parent . datapath , 'translate.json' ) ) ) {
// Return the translation file (JSON)
try { res . sendFile ( obj . path . join ( obj . parent . datapath , 'translate.json' ) ) ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
} else if ( obj . fs . existsSync ( obj . path . join ( _ _dirname , 'translate' , 'translate.json' ) ) ) {
// Return the default translation file (JSON)
try { res . sendFile ( obj . path . join ( _ _dirname , 'translate' , 'translate.json' ) ) ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
} else { res . sendStatus ( 404 ) ; }
} else if ( data . action == 'setTranslations' ) {
2020-01-07 18:10:12 -05:00
obj . fs . writeFile ( obj . path . join ( obj . parent . datapath , 'translate.json' ) , obj . common . translationsToJson ( { strings : data . strings } ) , function ( err ) { if ( err == null ) { res . send ( JSON . stringify ( { response : 'ok' } ) ) ; } else { res . send ( JSON . stringify ( { response : err } ) ) ; } } ) ;
2019-12-06 19:49:40 -05:00
} else if ( data . action == 'translateServer' ) {
if ( obj . pendingTranslation === true ) { res . send ( JSON . stringify ( { response : 'Server is already performing a translation.' } ) ) ; return ; }
const nodeVersion = Number ( process . version . match ( /^v(\d+\.\d+)/ ) [ 1 ] ) ;
if ( nodeVersion < 8 ) { res . send ( JSON . stringify ( { response : 'Server requires NodeJS 8.x or better.' } ) ) ; return ; }
var translateFile = obj . path . join ( obj . parent . datapath , 'translate.json' ) ;
if ( obj . fs . existsSync ( translateFile ) == false ) { translateFile = obj . path . join ( _ _dirname , 'translate' , 'translate.json' ) ; }
if ( obj . fs . existsSync ( translateFile ) == false ) { res . send ( JSON . stringify ( { response : 'Unable to find translate.js file on the server.' } ) ) ; return ; }
res . send ( JSON . stringify ( { response : 'ok' } ) ) ;
console . log ( 'Started server translation...' ) ;
obj . pendingTranslation = true ;
require ( 'child_process' ) . exec ( 'node translate.js translateall \"' + translateFile + '\"' , { maxBuffer : 512000 , timeout : 120000 , cwd : obj . path . join ( _ _dirname , 'translate' ) } , function ( error , stdout , stderr ) {
delete obj . pendingTranslation ;
//console.log('error', error);
//console.log('stdout', stdout);
//console.log('stderr', stderr);
//console.log('Server restart...'); // Perform a server restart
//process.exit(0);
console . log ( 'Server translation completed.' ) ;
} ) ;
2019-12-05 18:33:22 -05:00
} else {
// Unknown request
res . sendStatus ( 404 ) ;
}
} ) ;
}
2019-02-13 16:22:52 -05:00
// Handle welcome image request
function handleWelcomeImageRequest ( req , res ) {
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 15:06:34 -04:00
if ( domain == null ) { return ; }
2019-02-13 16:22:52 -05:00
2019-12-20 17:50:06 -05:00
//res.set({ 'Cache-Control': 'max-age=86400' }); // 1 day
2020-03-26 15:06:34 -04:00
if ( domain . welcomepicture ) {
2019-02-13 16:22:52 -05:00
if ( ( parent . configurationFiles != null ) && ( parent . configurationFiles [ domain . welcomepicture ] != null ) ) {
// Use the welcome image in the database
res . set ( { 'Content-Type' : 'image/jpeg' } ) ;
res . send ( parent . configurationFiles [ domain . welcomepicture ] ) ;
2019-09-15 15:47:59 -04:00
return ;
2019-02-13 16:22:52 -05:00
}
2019-09-26 13:16:22 -04:00
// Use the configured logo picture
try { res . sendFile ( obj . path . join ( obj . parent . datapath , domain . welcomepicture ) ) ; return ; } catch ( ex ) { }
2019-09-15 15:47:59 -04:00
}
2020-03-26 15:06:34 -04:00
if ( domain . webpublicpath != null ) {
2020-03-25 16:21:14 -04:00
obj . fs . exists ( obj . path . join ( domain . webpublicpath , 'images/mainwelcome.jpg' ) , function ( exists ) {
if ( exists ) {
// Use the domain logo picture
try { res . sendFile ( obj . path . join ( domain . webpublicpath , 'images/mainwelcome.jpg' ) ) ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
} else {
// Use the default logo picture
try { res . sendFile ( obj . path . join ( obj . parent . webPublicPath , 'images/mainwelcome.jpg' ) ) ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
}
} ) ;
} else if ( parent . webPublicOverridePath ) {
2019-12-20 17:02:49 -05:00
obj . fs . exists ( obj . path . join ( obj . parent . webPublicOverridePath , 'images/mainwelcome.jpg' ) , function ( exists ) {
if ( exists ) {
// Use the override logo picture
try { res . sendFile ( obj . path . join ( obj . parent . webPublicOverridePath , 'images/mainwelcome.jpg' ) ) ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
} else {
// Use the default logo picture
try { res . sendFile ( obj . path . join ( obj . parent . webPublicPath , 'images/mainwelcome.jpg' ) ) ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
}
} ) ;
2019-02-13 16:22:52 -05:00
} else {
2019-09-15 15:47:59 -04:00
// Use the default logo picture
try { res . sendFile ( obj . path . join ( obj . parent . webPublicPath , 'images/mainwelcome.jpg' ) ) ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
2019-02-13 16:22:52 -05:00
}
}
2020-02-04 19:22:00 -05:00
// Server the player page
function handlePlayerRequest ( req , res ) {
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 15:06:34 -04:00
if ( domain == null ) { return ; }
2020-02-04 19:22:00 -05:00
parent . debug ( 'web' , 'handlePlayerRequest: sending player' ) ;
res . set ( { 'Cache-Control' : 'no-cache, no-store, must-revalidate' , 'Pragma' : 'no-cache' , 'Expires' : '0' } ) ;
2020-03-25 16:21:14 -04:00
render ( req , res , getRenderPage ( 'player' , req , domain ) , getRenderArgs ( { } , domain ) ) ;
2020-02-04 19:22:00 -05:00
}
2019-06-07 13:20:54 -04:00
// Handle domain redirection
2019-10-04 15:18:56 -04:00
obj . handleDomainRedirect = function ( req , res ) {
2019-06-07 13:20:54 -04:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 15:06:34 -04:00
if ( domain == null ) { return ; }
if ( domain . redirects == null ) { res . sendStatus ( 404 ) ; return ; }
2019-12-27 01:53:01 -05:00
var urlArgs = '' , urlName = null , splitUrl = req . originalUrl . split ( '?' ) ;
2019-06-07 13:20:54 -04:00
if ( splitUrl . length > 1 ) { urlArgs = '?' + splitUrl [ 1 ] ; }
if ( ( splitUrl . length > 0 ) && ( splitUrl [ 0 ] . length > 1 ) ) { urlName = splitUrl [ 0 ] . substring ( 1 ) . toLowerCase ( ) ; }
2019-06-17 18:37:13 -04:00
if ( ( urlName == null ) || ( domain . redirects [ urlName ] == null ) || ( urlName [ 0 ] == '_' ) ) { res . sendStatus ( 404 ) ; return ; }
2019-06-17 20:17:23 -04:00
if ( domain . redirects [ urlName ] == '~showversion' ) {
// Show the current version
res . end ( 'MeshCentral v' + obj . parent . currentVer ) ;
} else {
// Perform redirection
res . redirect ( domain . redirects [ urlName ] + urlArgs + getQueryPortion ( req ) ) ;
}
2019-06-07 13:20:54 -04:00
}
2017-11-30 17:40:46 -05:00
// Take a "user/domain/userid/path/file" format and return the actual server disk file path if access is allowed
2018-05-24 00:43:44 -04:00
obj . getServerFilePath = function ( user , domain , path ) {
2017-11-30 17:40:46 -05:00
var splitpath = path . split ( '/' ) , serverpath = obj . path . join ( obj . filespath , 'domain' ) , filename = '' ;
if ( ( splitpath . length < 3 ) || ( splitpath [ 0 ] != 'user' && splitpath [ 0 ] != 'mesh' ) || ( splitpath [ 1 ] != domain . id ) ) return null ; // Basic validation
var objid = splitpath [ 0 ] + '/' + splitpath [ 1 ] + '/' + splitpath [ 2 ] ;
if ( splitpath [ 0 ] == 'user' && ( objid != user . _id ) ) return null ; // User validation, only self allowed
2019-12-27 18:18:43 -05:00
if ( splitpath [ 0 ] == 'mesh' ) { if ( ( obj . GetMeshRights ( user , objid ) & 32 ) == 0 ) { return null ; } } // Check mesh server file rights
2017-11-30 17:40:46 -05:00
if ( splitpath [ 1 ] != '' ) { serverpath += '-' + splitpath [ 1 ] ; } // Add the domain if needed
serverpath += ( '/' + splitpath [ 0 ] + '-' + splitpath [ 2 ] ) ;
for ( var i = 3 ; i < splitpath . length ; i ++ ) { if ( obj . common . IsFilenameValid ( splitpath [ i ] ) == true ) { serverpath += '/' + splitpath [ i ] ; filename = splitpath [ i ] ; } else { return null ; } } // Check that each folder is correct
2018-08-30 15:05:23 -04:00
return { fullpath : obj . path . resolve ( obj . filespath , serverpath ) , path : serverpath , name : filename , quota : obj . getQuota ( objid , domain ) } ;
} ;
2017-11-30 17:40:46 -05:00
// Return the maximum number of bytes allowed in the user account "My Files".
2018-05-24 00:43:44 -04:00
obj . getQuota = function ( objid , domain ) {
2017-11-30 17:40:46 -05:00
if ( objid == null ) return 0 ;
if ( objid . startsWith ( 'user/' ) ) {
var user = obj . users [ objid ] ;
if ( user == null ) return 0 ;
2018-08-31 18:23:42 -04:00
if ( user . siteadmin == 0xFFFFFFFF ) return null ; // Administrators have no user limit
2017-11-30 17:40:46 -05:00
if ( ( user . quota != null ) && ( typeof user . quota == 'number' ) ) { return user . quota ; }
2018-08-31 18:23:42 -04:00
if ( ( domain != null ) && ( domain . userquota != null ) && ( typeof domain . userquota == 'number' ) ) { return domain . userquota ; }
return null ; // By default, the user will have no limit
2017-11-30 17:40:46 -05:00
} else if ( objid . startsWith ( 'mesh/' ) ) {
var mesh = obj . meshes [ objid ] ;
if ( mesh == null ) return 0 ;
if ( ( mesh . quota != null ) && ( typeof mesh . quota == 'number' ) ) { return mesh . quota ; }
2018-08-31 18:23:42 -04:00
if ( ( domain != null ) && ( domain . meshquota != null ) && ( typeof domain . meshquota == 'number' ) ) { return domain . meshquota ; }
return null ; // By default, the mesh will have no limit
2017-11-30 17:40:46 -05:00
}
return 0 ;
2018-08-30 15:05:23 -04:00
} ;
2017-11-30 17:40:46 -05:00
2017-08-28 12:27:45 -04:00
// Download a file from the server
function handleDownloadFile ( req , res ) {
2019-01-05 15:04:18 -05:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 15:06:34 -04:00
if ( domain == null ) { return ; }
2017-09-15 14:45:06 -04:00
if ( ( req . query . link == null ) || ( req . session == null ) || ( req . session . userid == null ) || ( domain == null ) || ( domain . userQuota == - 1 ) ) { res . sendStatus ( 404 ) ; return ; }
2019-01-05 15:04:18 -05:00
const user = obj . users [ req . session . userid ] ;
2017-09-15 14:45:06 -04:00
if ( user == null ) { res . sendStatus ( 404 ) ; return ; }
2019-01-05 15:04:18 -05:00
const file = obj . getServerFilePath ( user , domain , req . query . link ) ;
2017-08-28 12:27:45 -04:00
if ( file == null ) { res . sendStatus ( 404 ) ; return ; }
2019-06-24 21:56:14 -04:00
try {
res . set ( { 'Cache-Control' : 'no-cache, no-store, must-revalidate' , 'Pragma' : 'no-cache' , 'Expires' : '0' , 'Content-Type' : 'application/octet-stream' , 'Content-Disposition' : 'attachment; filename=\"' + file . name + '\"' } ) ;
} catch ( ex ) {
res . set ( { 'Cache-Control' : 'no-cache, no-store, must-revalidate' , 'Pragma' : 'no-cache' , 'Expires' : '0' , 'Content-Type' : 'application/octet-stream' , 'Content-Disposition' : 'attachment; filename=\"file.bin\"' } ) ;
}
2017-08-28 12:27:45 -04:00
try { res . sendFile ( file . fullpath ) ; } catch ( e ) { res . sendStatus ( 404 ) ; }
}
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
// Upload a MeshCore.js file to the server
function handleUploadMeshCoreFile ( req , res ) {
2019-01-05 15:04:18 -05:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 15:06:34 -04:00
if ( domain == null ) { return ; }
2019-09-30 17:50:28 -04:00
if ( domain . id !== '' ) { res . sendStatus ( 401 ) ; return ; }
var authUserid = null ;
if ( ( req . session != null ) && ( typeof req . session . userid == 'string' ) ) { authUserid = req . session . userid ; }
2018-05-24 00:43:44 -04:00
2019-01-05 15:04:18 -05:00
const multiparty = require ( 'multiparty' ) ;
const form = new multiparty . Form ( ) ;
2017-08-28 12:27:45 -04:00
form . parse ( req , function ( err , fields , files ) {
2019-09-30 17:50:28 -04:00
// If an authentication cookie is embedded in the form, use that.
if ( ( fields != null ) && ( fields . auth != null ) && ( fields . auth . length == 1 ) && ( typeof fields . auth [ 0 ] == 'string' ) ) {
var loginCookie = obj . parent . decodeCookie ( fields . auth [ 0 ] , obj . parent . loginCookieEncryptionKey , 60 ) ; // 60 minute timeout
2020-01-26 13:05:27 -05:00
if ( ( loginCookie != null ) && ( obj . args . cookieipcheck !== false ) && ( loginCookie . ip != null ) && ( loginCookie . ip != cleanRemoteAddr ( req . ip ) ) ) { loginCookie = null ; } // Check cookie IP binding.
2019-09-30 17:50:28 -04:00
if ( ( loginCookie != null ) && ( domain . id == loginCookie . domainid ) ) { authUserid = loginCookie . userid ; } // Use cookie authentication
}
if ( authUserid == null ) { res . sendStatus ( 401 ) ; return ; }
// Get the user
const user = obj . users [ authUserid ] ;
if ( user . siteadmin != 0xFFFFFFFF ) { res . sendStatus ( 401 ) ; return ; } // Check if we have mesh core upload rights (Full admin only)
2017-09-15 14:45:06 -04:00
if ( ( fields == null ) || ( fields . attrib == null ) || ( fields . attrib . length != 1 ) ) { res . sendStatus ( 404 ) ; return ; }
2017-08-28 12:27:45 -04:00
for ( var i in files . files ) {
var file = files . files [ i ] ;
2019-01-05 15:04:18 -05:00
obj . fs . readFile ( file . path , 'utf8' , function ( err , data ) {
if ( err != null ) return ;
2017-08-28 12:27:45 -04:00
data = obj . common . IntToStr ( 0 ) + data ; // Add the 4 bytes encoding type & flags (Set to 0 for raw)
2019-01-21 17:05:50 -05:00
obj . sendMeshAgentCore ( user , domain , fields . attrib [ 0 ] , 'custom' , data ) ; // Upload the core
2017-08-28 12:27:45 -04:00
try { obj . fs . unlinkSync ( file . path ) ; } catch ( e ) { }
} ) ;
}
res . send ( '' ) ;
} ) ;
}
// Upload a file to the server
function handleUploadFile ( req , res ) {
2019-01-05 15:04:18 -05:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 15:06:34 -04:00
if ( domain == null ) { return ; }
2019-09-30 17:08:51 -04:00
if ( domain . userQuota == - 1 ) { res . sendStatus ( 401 ) ; return ; }
var authUserid = null ;
if ( ( req . session != null ) && ( typeof req . session . userid == 'string' ) ) { authUserid = req . session . userid ; }
2019-01-05 15:04:18 -05:00
const multiparty = require ( 'multiparty' ) ;
const form = new multiparty . Form ( ) ;
2017-08-28 12:27:45 -04:00
form . parse ( req , function ( err , fields , files ) {
2019-09-30 17:08:51 -04:00
// If an authentication cookie is embedded in the form, use that.
if ( ( fields != null ) && ( fields . auth != null ) && ( fields . auth . length == 1 ) && ( typeof fields . auth [ 0 ] == 'string' ) ) {
var loginCookie = obj . parent . decodeCookie ( fields . auth [ 0 ] , obj . parent . loginCookieEncryptionKey , 60 ) ; // 60 minute timeout
2020-01-26 13:05:27 -05:00
if ( ( loginCookie != null ) && ( obj . args . cookieipcheck !== false ) && ( loginCookie . ip != null ) && ( loginCookie . ip != cleanRemoteAddr ( req . ip ) ) ) { loginCookie = null ; } // Check cookie IP binding.
2019-09-30 17:08:51 -04:00
if ( ( loginCookie != null ) && ( domain . id == loginCookie . domainid ) ) { authUserid = loginCookie . userid ; } // Use cookie authentication
}
if ( authUserid == null ) { res . sendStatus ( 401 ) ; return ; }
// Get the user
const user = obj . users [ authUserid ] ;
if ( ( user == null ) || ( user . siteadmin & 8 ) == 0 ) { res . sendStatus ( 401 ) ; return ; } // Check if we have file rights
if ( ( fields == null ) || ( fields . link == null ) || ( fields . link . length != 1 ) ) { /*console.log('UploadFile, Invalid Fields:', fields, files);*/ console . log ( 'err4' ) ; res . sendStatus ( 404 ) ; return ; }
2017-11-30 17:40:46 -05:00
var xfile = obj . getServerFilePath ( user , domain , decodeURIComponent ( fields . link [ 0 ] ) ) ;
2017-08-28 12:27:45 -04:00
if ( xfile == null ) { res . sendStatus ( 404 ) ; return ; }
// Get total bytes in the path
var totalsize = readTotalFileSize ( xfile . fullpath ) ;
2018-08-31 18:23:42 -04:00
if ( ( xfile . quota == null ) || ( totalsize < xfile . quota ) ) { // Check if the quota is not already broken
2019-02-19 18:38:27 -05:00
if ( fields . name != null ) {
2019-02-19 01:20:25 -05:00
2019-02-19 18:38:27 -05:00
// See if we need to create the folder
var domainx = 'domain' ;
if ( domain . id . length > 0 ) { domainx = 'domain-' + usersplit [ 1 ] ; }
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 ( xfile . fullpath ) ; } catch ( e ) { }
2019-02-19 01:20:25 -05:00
2017-08-28 12:27:45 -04:00
// Upload method where all the file data is within the fields.
var names = fields . name [ 0 ] . split ( '*' ) , sizes = fields . size [ 0 ] . split ( '*' ) , types = fields . type [ 0 ] . split ( '*' ) , datas = fields . data [ 0 ] . split ( '*' ) ;
if ( ( names . length == sizes . length ) && ( types . length == datas . length ) && ( names . length == types . length ) ) {
for ( var i = 0 ; i < names . length ; i ++ ) {
if ( obj . common . IsFilenameValid ( names [ i ] ) == false ) { res . sendStatus ( 404 ) ; return ; }
2019-01-02 21:03:34 -05:00
var filedata = Buffer . from ( datas [ i ] . split ( ',' ) [ 1 ] , 'base64' ) ;
2018-08-31 18:23:42 -04:00
if ( ( xfile . quota == null ) || ( ( totalsize + filedata . length ) < xfile . quota ) ) { // Check if quota would not be broken if we add this file
2017-11-30 17:40:46 -05:00
// Create the user folder if needed
( function ( fullpath , filename , filedata ) {
obj . fs . mkdir ( xfile . fullpath , function ( ) {
// Write the file
obj . fs . writeFile ( obj . path . join ( xfile . fullpath , filename ) , filedata , function ( ) {
2018-08-30 15:05:23 -04:00
obj . parent . DispatchEvent ( [ user . _id ] , obj , 'updatefiles' ) ; // Fire an event causing this user to update this files
2017-11-30 17:40:46 -05:00
} ) ;
} ) ;
} ) ( xfile . fullpath , names [ i ] , filedata ) ;
2017-08-28 12:27:45 -04:00
}
}
}
} else {
// More typical upload method, the file data is in a multipart mime post.
for ( var i in files . files ) {
2017-11-30 17:40:46 -05:00
var file = files . files [ i ] , fpath = obj . path . join ( xfile . fullpath , file . originalFilename ) ;
2018-08-31 18:23:42 -04:00
if ( obj . common . IsFilenameValid ( file . originalFilename ) && ( ( xfile . quota == null ) || ( ( totalsize + file . size ) < xfile . quota ) ) ) { // Check if quota would not be broken if we add this file
2019-02-19 18:38:27 -05:00
// See if we need to create the folder
var domainx = 'domain' ;
2019-08-22 19:39:50 -04:00
if ( domain . id . length > 0 ) { domainx = 'domain-' + domain . id ; }
2019-02-19 18:38:27 -05:00
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 ( xfile . fullpath ) ; } catch ( e ) { }
2020-01-31 17:44:11 -05:00
// Rename the file
obj . fs . rename ( file . path , fpath , function ( err ) {
2020-02-02 15:37:27 -05:00
if ( err && ( err . code === 'EXDEV' ) ) {
2020-01-31 17:44:11 -05:00
// On some Linux, the rename will fail with a "EXDEV" error, do a copy+unlink instead.
obj . common . copyFile ( file . path , fpath , function ( err ) {
obj . fs . unlink ( file . path , function ( err ) {
obj . parent . DispatchEvent ( [ user . _id ] , obj , 'updatefiles' ) ; // Fire an event causing this user to update this files
} ) ;
} ) ;
} else {
obj . parent . DispatchEvent ( [ user . _id ] , obj , 'updatefiles' ) ; // Fire an event causing this user to update this files
}
2017-11-30 17:40:46 -05:00
} ) ;
2017-08-28 12:27:45 -04:00
} else {
2019-01-06 23:32:12 -05:00
try { obj . fs . unlink ( file . path , function ( err ) { } ) ; } catch ( e ) { }
2017-08-28 12:27:45 -04:00
}
}
}
}
res . send ( '' ) ;
} ) ;
}
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
// Subscribe to all events we are allowed to receive
obj . subscribe = function ( userid , target ) {
2019-01-05 15:04:18 -05:00
const user = obj . users [ userid ] ;
const subscriptions = [ userid , 'server-global' ] ;
2017-09-15 14:45:06 -04:00
if ( user . siteadmin != null ) {
2017-08-28 12:27:45 -04:00
if ( user . siteadmin == 0xFFFFFFFF ) subscriptions . push ( '*' ) ;
2019-04-16 20:32:18 -04:00
if ( ( user . siteadmin & 2 ) != 0 ) {
if ( ( user . groups == null ) || ( user . groups . length == 0 ) ) {
// Subscribe to all user changes
subscriptions . push ( 'server-users' ) ;
} else {
// Subscribe to user changes for some groups
for ( var i in user . groups ) { subscriptions . push ( 'server-users:' + i ) ; }
}
}
2017-08-28 12:27:45 -04:00
}
2018-07-13 22:18:43 -04:00
if ( user . links != null ) { for ( var i in user . links ) { subscriptions . push ( i ) ; } }
2017-08-28 12:27:45 -04:00
obj . parent . RemoveAllEventDispatch ( target ) ;
obj . parent . AddEventDispatch ( subscriptions , target ) ;
return subscriptions ;
2018-08-30 15:05:23 -04:00
} ;
2017-08-28 12:27:45 -04:00
// Handle a web socket relay request
2018-10-15 20:21:37 -04:00
function handleRelayWebSocket ( ws , req , domain , user , cookie ) {
if ( ! ( req . query . host ) ) { console . log ( 'ERR: No host target specified' ) ; try { ws . close ( ) ; } catch ( e ) { } return ; } // Disconnect websocket
2019-08-22 18:31:39 -04:00
parent . debug ( 'web' , 'Websocket relay connected from ' + user . name + ' for ' + req . query . host + '.' ) ;
2019-10-04 15:18:56 -04:00
2018-10-13 19:04:04 -04:00
try { ws . _socket . setKeepAlive ( true , 240000 ) ; } catch ( ex ) { } // Set TCP keep alive
2017-09-15 14:45:06 -04:00
2017-08-28 12:27:45 -04:00
// Fetch information about the target
obj . db . Get ( req . query . host , function ( err , docs ) {
2018-10-15 20:21:37 -04:00
if ( docs . length == 0 ) { console . log ( 'ERR: Node not found' ) ; try { ws . close ( ) ; } catch ( e ) { } return ; } // Disconnect websocket
2017-10-02 17:12:29 -04:00
var node = docs [ 0 ] ;
2018-10-15 20:21:37 -04:00
if ( ! node . intelamt ) { console . log ( 'ERR: Not AMT node' ) ; try { ws . close ( ) ; } catch ( e ) { } return ; } // Disconnect websocket
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
// Check if this user has permission to manage this computer
2020-04-01 19:41:35 -04:00
if ( ( obj . GetNodeRights ( user , node . meshid , node . _id ) & MESHRIGHT _REMOTECONTROL ) == 0 ) { console . log ( 'ERR: Access denied (3)' ) ; try { ws . close ( ) ; } catch ( e ) { } return ; }
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
// Check what connectivity is available for this node
2017-09-13 14:25:57 -04:00
var state = parent . GetConnectivityState ( req . query . host ) ;
2017-08-28 12:27:45 -04:00
var conn = 0 ;
2019-08-22 18:31:39 -04:00
if ( ! state || state . connectivity == 0 ) { parent . debug ( 'web' , 'ERR: No routing possible (1)' ) ; try { ws . close ( ) ; } catch ( e ) { } return ; } else { conn = state . connectivity ; }
2017-09-15 14:45:06 -04:00
// Check what server needs to handle this connection
2019-08-15 14:47:40 -04:00
if ( ( obj . parent . multiServer != null ) && ( ( cookie == null ) || ( cookie . ps != 1 ) ) ) { // If a cookie is provided and is from a peer server, don't allow the connection to jump again to a different server
2017-09-15 14:45:06 -04:00
var server = obj . parent . GetRoutingServerId ( req . query . host , 2 ) ; // Check for Intel CIRA connection
if ( server != null ) {
if ( server . serverid != obj . parent . serverId ) {
// Do local Intel CIRA routing using a different server
2019-08-22 18:31:39 -04:00
parent . debug ( 'web' , 'Route Intel AMT CIRA connection to peer server: ' + server . serverid ) ;
2017-09-15 14:45:06 -04:00
obj . parent . multiServer . createPeerRelay ( ws , req , server . serverid , user ) ;
return ;
}
} else {
server = obj . parent . GetRoutingServerId ( req . query . host , 4 ) ; // Check for local Intel AMT connection
if ( ( server != null ) && ( server . serverid != obj . parent . serverId ) ) {
// Do local Intel AMT routing using a different server
2019-08-22 18:31:39 -04:00
parent . debug ( 'web' , 'Route Intel AMT direct connection to peer server: ' + server . serverid ) ;
2017-09-15 14:45:06 -04:00
obj . parent . multiServer . createPeerRelay ( ws , req , server . serverid , user ) ;
return ;
}
}
2017-08-28 12:27:45 -04:00
}
2017-09-15 14:45:06 -04:00
2019-08-07 17:25:23 -04:00
// Setup session recording if needed
2019-08-07 19:07:12 -04:00
if ( domain . sessionrecording == true || ( ( typeof domain . sessionrecording == 'object' ) && ( ( domain . sessionrecording . protocols == null ) || ( domain . sessionrecording . protocols . indexOf ( ( req . query . p == 2 ) ? 101 : 100 ) >= 0 ) ) ) ) { // TODO 100
2019-08-11 01:34:21 -04:00
var now = new Date ( Date . now ( ) ) ;
var recFilename = 'relaysession' + ( ( domain . id == '' ) ? '' : '-' ) + domain . id + '-' + now . getUTCFullYear ( ) + '-' + obj . common . zeroPad ( now . getUTCMonth ( ) , 2 ) + '-' + obj . common . zeroPad ( now . getUTCDate ( ) , 2 ) + '-' + obj . common . zeroPad ( now . getUTCHours ( ) , 2 ) + '-' + obj . common . zeroPad ( now . getUTCMinutes ( ) , 2 ) + '-' + obj . common . zeroPad ( now . getUTCSeconds ( ) , 2 ) + '-' + getRandomPassword ( ) + '.mcrec'
2019-08-07 17:25:23 -04:00
var recFullFilename = null ;
if ( domain . sessionrecording . filepath ) {
try { obj . fs . mkdirSync ( domain . sessionrecording . filepath ) ; } catch ( e ) { }
recFullFilename = obj . path . join ( domain . sessionrecording . filepath , recFilename ) ;
} else {
try { obj . fs . mkdirSync ( parent . recordpath ) ; } catch ( e ) { }
recFullFilename = obj . path . join ( parent . recordpath , recFilename ) ;
}
var fd = obj . fs . openSync ( recFullFilename , 'w' ) ;
if ( fd != null ) {
// Write the recording file header
2019-08-31 22:40:50 -04:00
var firstBlock = JSON . stringify ( { magic : 'MeshCentralRelaySession' , ver : 1 , userid : user . _id , username : user . name , ipaddr : cleanRemoteAddr ( req . ip ) , nodeid : node . _id , intelamt : true , protocol : ( req . query . p == 2 ) ? 101 : 100 , time : new Date ( ) . toLocaleString ( ) } )
2019-08-07 17:25:23 -04:00
recordingEntry ( fd , 1 , 0 , firstBlock , function ( ) { } ) ;
ws . logfile = { fd : fd , lock : false } ;
2019-08-13 14:49:05 -04:00
if ( req . query . p == 2 ) { ws . send ( Buffer . from ( String . fromCharCode ( 0xF0 ) , 'binary' ) ) ; } // Intel AMT Redirection: Indicate the session is being recorded
2019-08-07 17:25:23 -04:00
}
}
2017-08-28 12:27:45 -04:00
// If Intel AMT CIRA connection is available, use it
2017-09-15 14:45:06 -04:00
if ( ( ( conn & 2 ) != 0 ) && ( parent . mpsserver . ciraConnections [ req . query . host ] != null ) ) {
2019-08-22 18:31:39 -04:00
parent . debug ( 'web' , 'Opening relay CIRA channel connection to ' + req . query . host + '.' ) ;
2018-10-16 13:52:05 -04:00
2017-08-28 12:27:45 -04:00
var ciraconn = parent . mpsserver . ciraConnections [ req . query . host ] ;
2017-09-15 14:45:06 -04:00
2017-08-28 12:27:45 -04:00
// Compute target port, look at the CIRA port mappings, if non-TLS is allowed, use that, if not use TLS
var port = 16993 ;
//if (node.intelamt.tls == 0) port = 16992; // DEBUG: Allow TLS flag to set TLS mode within CIRA
if ( ciraconn . tag . boundPorts . indexOf ( 16992 ) >= 0 ) port = 16992 ; // RELEASE: Always use non-TLS mode if available within CIRA
if ( req . query . p == 2 ) port += 2 ;
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
// Setup a new CIRA channel
if ( ( port == 16993 ) || ( port == 16995 ) ) {
2018-10-16 13:52:05 -04:00
// Perform TLS - ( TODO: THIS IS BROKEN on Intel AMT v7 but works on v10, Not sure why. Well, could be broken TLS 1.0 in firmware )
2017-08-28 12:27:45 -04:00
var ser = new SerialTunnel ( ) ;
var chnl = parent . mpsserver . SetupCiraChannel ( ciraconn , port ) ;
2019-08-07 19:07:12 -04:00
// Let's chain up the TLSSocket <-> SerialTunnel <-> CIRA APF (chnl)
2017-08-28 12:27:45 -04:00
// Anything that needs to be forwarded by SerialTunnel will be encapsulated by chnl write
ser . forwardwrite = function ( msg ) {
// TLS ---> CIRA
2017-10-15 02:22:19 -04:00
chnl . write ( msg . toString ( 'binary' ) ) ;
2017-08-28 12:27:45 -04:00
} ;
// When APF tunnel return something, update SerialTunnel buffer
chnl . onData = function ( ciraconn , data ) {
// CIRA ---> TLS
2019-08-22 18:31:39 -04:00
parent . debug ( 'webrelay' , 'Relay TLS CIRA data' , data . length ) ;
2019-12-16 20:20:39 -05:00
if ( data . length > 0 ) { try { ser . updateBuffer ( Buffer . from ( data , 'binary' ) ) ; } catch ( ex ) { console . log ( ex ) ; } }
2018-08-30 15:05:23 -04:00
} ;
2017-08-28 12:27:45 -04:00
2018-10-13 19:04:04 -04:00
// Handle CIRA tunnel state change
2017-08-28 12:27:45 -04:00
chnl . onStateChange = function ( ciraconn , state ) {
2019-08-22 18:31:39 -04:00
parent . debug ( 'webrelay' , 'Relay TLS CIRA state change' , state ) ;
2017-08-28 12:27:45 -04:00
if ( state == 0 ) { try { ws . close ( ) ; } catch ( e ) { } }
2018-08-30 15:05:23 -04:00
} ;
2017-08-28 12:27:45 -04:00
// TLSSocket to encapsulate TLS communication, which then tunneled via SerialTunnel an then wrapped through CIRA APF
2019-01-05 15:04:18 -05:00
const TLSSocket = require ( 'tls' ) . TLSSocket ;
2020-03-30 22:29:46 -04:00
const tlsoptions = { ciphers : 'RSA+AES:!aNULL:!MD5:!DSS' , secureOptions : constants . SSL _OP _NO _SSLv2 | constants . SSL _OP _NO _SSLv3 | constants . SSL _OP _NO _COMPRESSION | constants . SSL _OP _CIPHER _SERVER _PREFERENCE , rejectUnauthorized : false } ;
if ( req . query . tls1only == 1 ) { tlsoptions . secureProtocol = 'TLSv1_method' ; }
2019-01-05 15:04:18 -05:00
const tlsock = new TLSSocket ( ser , tlsoptions ) ;
2019-08-22 18:31:39 -04:00
tlsock . on ( 'error' , function ( err ) { parent . debug ( 'webrelay' , "CIRA TLS Connection Error " , err ) ; } ) ;
tlsock . on ( 'secureConnect' , function ( ) { parent . debug ( 'webrelay' , "CIRA Secure TLS Connection" ) ; ws . _socket . resume ( ) ; } ) ;
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
// Decrypted tunnel from TLS communcation to be forwarded to websocket
tlsock . on ( 'data' , function ( data ) {
// AMT/TLS ---> WS
try {
2017-10-15 02:22:19 -04:00
data = data . toString ( 'binary' ) ;
if ( ws . interceptor ) { data = ws . interceptor . processAmtData ( data ) ; } // Run data thru interceptor
2018-10-13 19:04:04 -04:00
//ws.send(Buffer.from(data, 'binary'));
2017-10-15 02:22:19 -04:00
ws . send ( data ) ;
2017-08-28 12:27:45 -04:00
} catch ( e ) { }
} ) ;
// If TLS is on, forward it through TLSSocket
ws . forwardclient = tlsock ;
ws . forwardclient . xtls = 1 ;
} else {
// Without TLS
ws . forwardclient = parent . mpsserver . SetupCiraChannel ( ciraconn , port ) ;
ws . forwardclient . xtls = 0 ;
2018-12-01 00:23:10 -05:00
ws . _socket . resume ( ) ;
2017-08-28 12:27:45 -04:00
}
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
// When data is received from the web socket, forward the data into the associated CIRA cahnnel.
// If the CIRA connection is pending, the CIRA channel has built-in buffering, so we are ok sending anyway.
ws . on ( 'message' , function ( msg ) {
// WS ---> AMT/TLS
2017-10-15 02:22:19 -04:00
msg = msg . toString ( 'binary' ) ;
if ( ws . interceptor ) { msg = ws . interceptor . processBrowserData ( msg ) ; } // Run data thru interceptor
2019-04-25 18:13:32 -04:00
//console.log('WS --> AMT', Buffer.from(msg, 'binary').toString('hex'));
2019-08-07 19:07:12 -04:00
// Log to recording file
if ( ws . logfile == null ) {
// Forward data to the associated TCP connection.
if ( ws . forwardclient . xtls == 1 ) { ws . forwardclient . write ( Buffer . from ( msg , 'binary' ) ) ; } else { ws . forwardclient . write ( msg ) ; }
} else {
// Log to recording file
var msg2 = Buffer . from ( msg , 'binary' ) ;
recordingEntry ( ws . logfile . fd , 2 , 2 , msg2 , function ( ) { try { if ( ws . forwardclient . xtls == 1 ) { ws . forwardclient . write ( msg2 ) ; } else { ws . forwardclient . write ( msg ) ; } } catch ( ex ) { } } ) ;
}
2017-08-28 12:27:45 -04:00
} ) ;
2019-01-02 21:34:50 -05:00
// If error, close the associated TCP connection.
ws . on ( 'error' , function ( err ) {
2019-08-31 22:40:50 -04:00
console . log ( 'CIRA server websocket error from ' + cleanRemoteAddr ( req . ip ) + ', ' + err . toString ( ) . split ( '\r' ) [ 0 ] + '.' ) ;
2019-08-22 18:31:39 -04:00
parent . debug ( 'webrelay' , 'Websocket relay closed on error.' ) ;
2019-01-02 21:34:50 -05:00
if ( ws . forwardclient && ws . forwardclient . close ) { ws . forwardclient . close ( ) ; } // TODO: If TLS is used, we need to close the socket that is wrapped by TLS
2019-08-07 19:07:12 -04:00
// Close the recording file
2019-08-13 14:49:05 -04:00
if ( ws . logfile != null ) {
recordingEntry ( ws . logfile . fd , 3 , 0 , 'MeshCentralMCREC' , function ( fd , ws ) {
obj . fs . close ( fd ) ;
ws . logfile = null ;
} , ws ) ;
}
2019-01-02 21:34:50 -05:00
} ) ;
2017-08-28 12:27:45 -04:00
// If the web socket is closed, close the associated TCP connection.
ws . on ( 'close' , function ( req ) {
2019-08-22 18:31:39 -04:00
parent . debug ( 'webrelay' , 'Websocket relay closed.' ) ;
2017-08-28 12:27:45 -04:00
if ( ws . forwardclient && ws . forwardclient . close ) { ws . forwardclient . close ( ) ; } // TODO: If TLS is used, we need to close the socket that is wrapped by TLS
2019-08-07 19:07:12 -04:00
// Close the recording file
2019-08-13 14:49:05 -04:00
if ( ws . logfile != null ) {
recordingEntry ( ws . logfile . fd , 3 , 0 , 'MeshCentralMCREC' , function ( fd , ws ) {
obj . fs . close ( fd ) ;
ws . logfile = null ;
} , ws ) ;
}
2017-08-28 12:27:45 -04:00
} ) ;
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
ws . forwardclient . onStateChange = function ( ciraconn , state ) {
2019-08-22 18:31:39 -04:00
parent . debug ( 'webrelay' , 'Relay CIRA state change' , state ) ;
2017-08-28 12:27:45 -04:00
if ( state == 0 ) { try { ws . close ( ) ; } catch ( e ) { } }
2018-08-30 15:05:23 -04:00
} ;
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
ws . forwardclient . onData = function ( ciraconn , data ) {
2019-08-22 18:31:39 -04:00
parent . debug ( 'webrelaydata' , 'Relay CIRA data' , data . length ) ;
2017-08-28 12:27:45 -04:00
if ( ws . interceptor ) { data = ws . interceptor . processAmtData ( data ) ; } // Run data thru interceptor
2019-04-25 18:13:32 -04:00
//console.log('AMT --> WS', Buffer.from(data, 'binary').toString('hex'));
2019-08-07 19:07:12 -04:00
if ( data . length > 0 ) {
if ( ws . logfile == null ) {
try { ws . send ( Buffer . from ( data , 'binary' ) ) ; } catch ( e ) { } // TODO: Add TLS support
} else {
// Log to recording file
data = Buffer . from ( data , 'binary' ) ;
2019-12-16 20:20:39 -05:00
recordingEntry ( ws . logfile . fd , 2 , 0 , data , function ( ) { try { ws . send ( data ) ; } catch ( ex ) { console . log ( ex ) ; } } ) ; // TODO: Add TLS support
2019-08-07 19:07:12 -04:00
}
2019-10-04 15:18:56 -04:00
}
2018-08-30 15:05:23 -04:00
} ;
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
ws . forwardclient . onSendOk = function ( ciraconn ) {
// TODO: Flow control? (Dont' really need it with AMT, but would be nice)
//console.log('onSendOk');
2018-08-30 15:05:23 -04:00
} ;
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
// Fetch Intel AMT credentials & Setup interceptor
if ( req . query . p == 1 ) {
2019-08-22 18:31:39 -04:00
parent . debug ( 'webrelaydata' , 'INTERCEPTOR1' , { host : node . host , port : port , user : node . intelamt . user , pass : node . intelamt . pass } ) ;
2017-08-28 12:27:45 -04:00
ws . interceptor = obj . interceptor . CreateHttpInterceptor ( { host : node . host , port : port , user : node . intelamt . user , pass : node . intelamt . pass } ) ;
2018-02-05 14:56:29 -05:00
ws . interceptor . blockAmtStorage = true ;
2017-08-28 12:27:45 -04:00
}
else if ( req . query . p == 2 ) {
2019-08-22 18:31:39 -04:00
parent . debug ( 'webrelaydata' , 'INTERCEPTOR2' , { user : node . intelamt . user , pass : node . intelamt . pass } ) ;
2017-08-28 12:27:45 -04:00
ws . interceptor = obj . interceptor . CreateRedirInterceptor ( { user : node . intelamt . user , pass : node . intelamt . pass } ) ;
2018-02-05 14:56:29 -05:00
ws . interceptor . blockAmtStorage = true ;
2017-08-28 12:27:45 -04:00
}
return ;
}
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
// If Intel AMT direct connection is possible, option a direct socket
if ( ( conn & 4 ) != 0 ) { // We got a new web socket connection, initiate a TCP connection to the target Intel AMT host/port.
2019-08-22 18:31:39 -04:00
parent . debug ( 'webrelay' , 'Opening relay TCP socket connection to ' + req . query . host + '.' ) ;
2017-09-15 14:45:06 -04:00
// When data is received from the web socket, forward the data into the associated TCP connection.
ws . on ( 'message' , function ( msg ) {
2018-08-06 21:57:30 -04:00
if ( obj . parent . debugLevel >= 1 ) { // DEBUG
2019-08-22 18:31:39 -04:00
parent . debug ( 'webrelaydata' , 'TCP relay data to ' + node . host + ', ' + msg . length + ' bytes' ) ;
//if (obj.parent.debugLevel >= 4) { parent.debug('webrelaydatahex', ' ' + msg.toString('hex')); }
2018-08-06 21:57:30 -04:00
}
2017-10-15 02:22:19 -04:00
msg = msg . toString ( 'binary' ) ;
if ( ws . interceptor ) { msg = ws . interceptor . processBrowserData ( msg ) ; } // Run data thru interceptor
2019-08-07 19:07:12 -04:00
// Log to recording file
if ( ws . logfile == null ) {
// Forward data to the associated TCP connection.
try { ws . forwardclient . write ( Buffer . from ( msg , 'binary' ) ) ; } catch ( ex ) { }
} else {
// Log to recording file
msg = Buffer . from ( msg , 'binary' ) ;
recordingEntry ( ws . logfile . fd , 2 , 2 , msg , function ( ) { try { ws . forwardclient . write ( msg ) ; } catch ( ex ) { } } ) ;
}
2017-09-15 14:45:06 -04:00
} ) ;
2019-01-02 21:34:50 -05:00
// If error, close the associated TCP connection.
ws . on ( 'error' , function ( err ) {
2019-08-31 22:40:50 -04:00
console . log ( 'Error with relay web socket connection from ' + cleanRemoteAddr ( req . ip ) + ', ' + err . toString ( ) . split ( '\r' ) [ 0 ] + '.' ) ;
parent . debug ( 'webrelay' , 'Error with relay web socket connection from ' + cleanRemoteAddr ( req . ip ) + '.' ) ;
2019-01-02 21:34:50 -05:00
if ( ws . forwardclient ) { try { ws . forwardclient . destroy ( ) ; } catch ( e ) { } }
2019-08-07 19:07:12 -04:00
// Close the recording file
2019-08-13 14:49:05 -04:00
if ( ws . logfile != null ) {
recordingEntry ( ws . logfile . fd , 3 , 0 , 'MeshCentralMCREC' , function ( fd ) {
obj . fs . close ( fd ) ;
ws . logfile = null ;
} ) ;
}
2019-01-02 21:34:50 -05:00
} ) ;
2017-09-15 14:45:06 -04:00
// If the web socket is closed, close the associated TCP connection.
2018-12-07 19:36:27 -05:00
ws . on ( 'close' , function ( ) {
2019-08-22 18:31:39 -04:00
parent . debug ( 'webrelay' , 'Closing relay web socket connection to ' + req . query . host + '.' ) ;
2017-09-15 14:45:06 -04:00
if ( ws . forwardclient ) { try { ws . forwardclient . destroy ( ) ; } catch ( e ) { } }
2019-08-07 19:07:12 -04:00
// Close the recording file
2019-08-13 14:49:05 -04:00
if ( ws . logfile != null ) {
recordingEntry ( ws . logfile . fd , 3 , 0 , 'MeshCentralMCREC' , function ( fd ) {
obj . fs . close ( fd ) ;
ws . logfile = null ;
} ) ;
}
2017-09-15 14:45:06 -04:00
} ) ;
2017-08-28 12:27:45 -04:00
// Compute target port
var port = 16992 ;
if ( node . intelamt . tls > 0 ) port = 16993 ; // This is a direct connection, use TLS when possible
if ( req . query . p == 2 ) port += 2 ;
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
if ( node . intelamt . tls == 0 ) {
// If this is TCP (without TLS) set a normal TCP socket
ws . forwardclient = new obj . net . Socket ( ) ;
ws . forwardclient . setEncoding ( 'binary' ) ;
ws . forwardclient . xstate = 0 ;
ws . forwardclient . forwardwsocket = ws ;
2018-12-01 00:23:10 -05:00
ws . _socket . resume ( ) ;
2017-08-28 12:27:45 -04:00
} else {
// If TLS is going to be used, setup a TLS socket
2020-03-30 22:29:46 -04:00
var tlsoptions = { ciphers : 'RSA+AES:!aNULL:!MD5:!DSS' , secureOptions : constants . SSL _OP _NO _SSLv2 | constants . SSL _OP _NO _SSLv3 | constants . SSL _OP _NO _COMPRESSION | constants . SSL _OP _CIPHER _SERVER _PREFERENCE , rejectUnauthorized : false } ;
if ( req . query . tls1only == 1 ) { tlsoptions . secureProtocol = 'TLSv1_method' ; }
2017-09-29 17:26:51 -04:00
ws . forwardclient = obj . tls . connect ( port , node . host , tlsoptions , function ( ) {
2017-08-28 12:27:45 -04:00
// The TLS connection method is the same as TCP, but located a bit differently.
2019-08-22 18:31:39 -04:00
parent . debug ( 'webrelay' , 'TLS connected to ' + node . host + ':' + port + '.' ) ;
2017-08-28 12:27:45 -04:00
ws . forwardclient . xstate = 1 ;
2018-12-01 00:23:10 -05:00
ws . _socket . resume ( ) ;
2017-08-28 12:27:45 -04:00
} ) ;
ws . forwardclient . setEncoding ( 'binary' ) ;
ws . forwardclient . xstate = 0 ;
ws . forwardclient . forwardwsocket = ws ;
}
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
// When we receive data on the TCP connection, forward it back into the web socket connection.
ws . forwardclient . on ( 'data' , function ( data ) {
2018-08-06 21:57:30 -04:00
if ( obj . parent . debugLevel >= 1 ) { // DEBUG
2019-08-22 18:31:39 -04:00
parent . debug ( 'webrelaydata' , 'TCP relay data from ' + node . host + ', ' + data . length + ' bytes.' ) ;
//if (obj.parent.debugLevel >= 4) { Debug(4, ' ' + Buffer.from(data, 'binary').toString('hex')); }
2018-08-06 21:57:30 -04:00
}
2017-08-28 12:27:45 -04:00
if ( ws . interceptor ) { data = ws . interceptor . processAmtData ( data ) ; } // Run data thru interceptor
2019-08-07 19:07:12 -04:00
if ( ws . logfile == null ) {
// No logging
try { ws . send ( Buffer . from ( data , 'binary' ) ) ; } catch ( e ) { }
} else {
// Log to recording file
data = Buffer . from ( data , 'binary' ) ;
recordingEntry ( ws . logfile . fd , 2 , 0 , data , function ( ) { try { ws . send ( data ) ; } catch ( e ) { } } ) ;
}
2017-08-28 12:27:45 -04:00
} ) ;
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
// If the TCP connection closes, disconnect the associated web socket.
ws . forwardclient . on ( 'close' , function ( ) {
2020-03-30 22:29:46 -04:00
parent . debug ( 'webrelay' , 'TCP relay disconnected from ' + node . host + ':' + port + '.' ) ;
2017-08-28 12:27:45 -04:00
try { ws . close ( ) ; } catch ( e ) { }
} ) ;
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
// If the TCP connection causes an error, disconnect the associated web socket.
ws . forwardclient . on ( 'error' , function ( err ) {
2020-03-30 22:29:46 -04:00
parent . debug ( 'webrelay' , 'TCP relay error from ' + node . host + ':' + port + ': ' + err ) ;
2017-08-28 12:27:45 -04:00
try { ws . close ( ) ; } catch ( e ) { }
} ) ;
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
// Fetch Intel AMT credentials & Setup interceptor
if ( req . query . p == 1 ) { ws . interceptor = obj . interceptor . CreateHttpInterceptor ( { host : node . host , port : port , user : node . intelamt . user , pass : node . intelamt . pass } ) ; }
else if ( req . query . p == 2 ) { ws . interceptor = obj . interceptor . CreateRedirInterceptor ( { user : node . intelamt . user , pass : node . intelamt . pass } ) ; }
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
if ( node . intelamt . tls == 0 ) {
2017-09-15 14:45:06 -04:00
// A TCP connection to Intel AMT just connected, start forwarding.
2017-08-28 12:27:45 -04:00
ws . forwardclient . connect ( port , node . host , function ( ) {
2019-08-22 18:31:39 -04:00
parent . debug ( 'webrelay' , 'TCP relay connected to ' + node . host + ':' + port + '.' ) ;
2017-08-28 12:27:45 -04:00
ws . forwardclient . xstate = 1 ;
2018-12-01 00:23:10 -05:00
ws . _socket . resume ( ) ;
2017-08-28 12:27:45 -04:00
} ) ;
}
return ;
}
} ) ;
}
2019-06-21 20:18:48 -04:00
// Handle a Intel AMT activation request
function handleAmtActivateWebSocket ( ws , req ) {
const domain = checkUserIpAddress ( ws , req ) ;
2020-03-26 15:06:34 -04:00
if ( domain == null ) { return ; }
2019-06-21 20:18:48 -04:00
if ( req . query . id == null ) { ws . send ( JSON . stringify ( { errorText : 'Missing group identifier' } ) ) ; ws . close ( ) ; return ; }
// Fetch the mesh object
ws . meshid = 'mesh/' + domain . id + '/' + req . query . id ;
const mesh = obj . meshes [ ws . meshid ] ;
2020-02-18 15:02:42 -05:00
if ( mesh == null ) { ws . send ( JSON . stringify ( { errorText : 'Invalid device group: ' + ws . meshid } ) ) ; delete ws . meshid ; ws . close ( ) ; return ; }
if ( mesh . mtype != 1 ) { ws . send ( JSON . stringify ( { errorText : 'Invalid device group type:' + ws . meshid } ) ) ; delete ws . meshid ; ws . close ( ) ; return ; }
2019-10-04 15:18:56 -04:00
2019-06-21 20:18:48 -04:00
// Fetch the remote IP:Port for logging
2019-08-31 22:40:50 -04:00
ws . remoteaddr = cleanRemoteAddr ( req . ip ) ;
2019-06-24 21:56:14 -04:00
ws . remoteaddrport = ws . remoteaddr + ':' + ws . _socket . remotePort ;
2019-06-21 20:18:48 -04:00
// When data is received from the web socket, echo it back
ws . on ( 'message' , function ( data ) {
// Parse the incoming command
var cmd = null ;
try { cmd = JSON . parse ( data ) ; } catch ( ex ) { } ;
if ( cmd == null ) return ;
// Process the command
switch ( cmd . action ) {
2019-06-27 19:04:52 -04:00
case 'ccmactivate' :
2019-06-21 20:18:48 -04:00
case 'acmactivate' : {
// Check the command
if ( cmd . version != 1 ) { ws . send ( JSON . stringify ( { errorText : 'Unsupported version' } ) ) ; ws . close ( ) ; return ; }
2019-06-27 19:04:52 -04:00
if ( obj . common . validateString ( cmd . realm , 16 , 256 ) == false ) { ws . send ( JSON . stringify ( { errorText : 'Invalid realm argument' } ) ) ; ws . close ( ) ; return ; }
if ( obj . common . validateString ( cmd . uuid , 36 , 36 ) == false ) { ws . send ( JSON . stringify ( { errorText : 'Invalid UUID argument' } ) ) ; ws . close ( ) ; return ; }
2019-10-01 17:54:38 -04:00
if ( typeof cmd . hashes !== 'object' ) { ws . send ( JSON . stringify ( { errorText : 'Invalid hashes' } ) ) ; ws . close ( ) ; return ; }
if ( typeof cmd . fqdn !== 'string' ) { ws . send ( JSON . stringify ( { errorText : 'Invalid FQDN' } ) ) ; ws . close ( ) ; return ; }
2019-06-24 21:56:14 -04:00
if ( ( obj . common . validateString ( cmd . ver , 5 , 16 ) == false ) || ( cmd . ver . split ( '.' ) . length != 3 ) ) { ws . send ( JSON . stringify ( { errorText : 'Invalid Intel AMT version' } ) ) ; ws . close ( ) ; return ; }
2019-06-27 19:04:52 -04:00
if ( obj . common . validateArray ( cmd . modes , 1 , 2 ) == false ) { ws . send ( JSON . stringify ( { errorText : 'Invalid activation modes' } ) ) ; ws . close ( ) ; return ; }
2019-06-28 21:31:18 -04:00
if ( obj . common . validateInt ( cmd . currentMode , 0 , 2 ) == false ) { ws . send ( JSON . stringify ( { errorText : 'Invalid current mode' } ) ) ; ws . close ( ) ; return ; }
2019-10-01 17:54:38 -04:00
if ( typeof cmd . sku !== 'number' ) { ws . send ( JSON . stringify ( { errorText : 'Invalid SKU number' } ) ) ; ws . close ( ) ; return ; }
2019-06-21 20:18:48 -04:00
// Get the current Intel AMT policy
2019-06-27 19:04:52 -04:00
var mesh = obj . meshes [ ws . meshid ] , activationMode = 4 ; // activationMode: 2 = CCM, 4 = ACM
if ( ( mesh == null ) || ( mesh . amt == null ) || ( mesh . amt . password == null ) || ( ( mesh . amt . type != 2 ) && ( mesh . amt . type != 3 ) ) ) { ws . send ( JSON . stringify ( { errorText : 'Unable to activate' } ) ) ; ws . close ( ) ; return ; }
if ( ( mesh . amt . type != 3 ) || ( domain . amtacmactivation == null ) || ( domain . amtacmactivation . acmmatch == null ) ) { activationMode = 2 ; }
if ( activationMode == 4 ) {
// Check if we have a FQDN/Hash match
var matchingHash = null , matchingCN = null ;
for ( var i in domain . amtacmactivation . acmmatch ) {
// Check for a matching FQDN
if ( ( domain . amtacmactivation . acmmatch [ i ] . cn == '*' ) || ( domain . amtacmactivation . acmmatch [ i ] . cn . toLowerCase ( ) == cmd . fqdn ) ) {
// Check for a matching certificate
if ( cmd . hashes . indexOf ( domain . amtacmactivation . acmmatch [ i ] . sha256 ) >= 0 ) {
matchingCN = domain . amtacmactivation . acmmatch [ i ] . cn ;
matchingHash = domain . amtacmactivation . acmmatch [ i ] . sha256 ;
continue ;
} else if ( cmd . hashes . indexOf ( domain . amtacmactivation . acmmatch [ i ] . sha1 ) >= 0 ) {
matchingCN = domain . amtacmactivation . acmmatch [ i ] . cn ;
matchingHash = domain . amtacmactivation . acmmatch [ i ] . sha1 ;
continue ;
}
2019-06-21 20:18:48 -04:00
}
}
2019-06-27 19:04:52 -04:00
// If no cert match or wildcard match which is not yet supported, do CCM activation.
2019-10-01 17:54:38 -04:00
if ( ( matchingHash == null ) || ( matchingCN == '*' ) ) { ws . send ( JSON . stringify ( { messageText : 'No matching ACM activation certificates, activating in CCM instead...' } ) ) ; activationMode = 2 ; } else { cmd . hash = matchingHash ; }
2019-06-21 20:18:48 -04:00
}
2019-06-27 19:04:52 -04:00
// Check if we are going to activate in an allowed mode. cmd.modes: 1 = CCM, 2 = ACM
2019-10-01 17:54:38 -04:00
if ( ( activationMode == 4 ) && ( cmd . modes . indexOf ( 2 ) == - 1 ) ) { ws . send ( JSON . stringify ( { messageText : 'ACM not allowed on this machine, activating in CCM instead...' } ) ) ; activationMode = 2 ; } // We want to do ACM, but mode is not allowed. Change to CCM.
2019-06-27 19:04:52 -04:00
// If we want to do CCM, but mode is not allowed. Error out.
2019-10-04 15:18:56 -04:00
if ( ( activationMode == 2 ) && ( cmd . modes . indexOf ( 1 ) == - 1 ) ) { ws . send ( JSON . stringify ( { errorText : 'CCM is not an allowed activation mode' } ) ) ; ws . close ( ) ; return ; }
2019-06-21 20:18:48 -04:00
// Get the Intel AMT admin password, randomize if needed.
var amtpassword = ( ( mesh . amt . password == '' ) ? getRandomAmtPassword ( ) : mesh . amt . password ) ;
if ( checkAmtPassword ( amtpassword ) == false ) { ws . send ( JSON . stringify ( { errorText : 'Invalid Intel AMT password' } ) ) ; ws . close ( ) ; return ; } // Invalid Intel AMT password, this should never happen.
2019-11-29 01:29:22 -05:00
// Save some state, if activation is successful, we need this to add the device
2019-10-01 17:54:38 -04:00
ws . xxstate = { uuid : cmd . uuid , realm : cmd . realm , tag : cmd . tag , name : cmd . name , hostname : cmd . hostname , pass : amtpassword , flags : activationMode , ver : cmd . ver , sku : cmd . sku } ; // Flags: 2 = CCM, 4 = ACM
2019-06-27 19:04:52 -04:00
if ( activationMode == 4 ) {
// ACM: Agent is asking the server to sign an Intel AMT ACM activation request
var signResponse = parent . certificateOperations . signAcmRequest ( domain , cmd , 'admin' , amtpassword , ws . remoteaddrport , null , ws . meshid , null , null ) ;
ws . send ( JSON . stringify ( signResponse ) ) ;
} else {
// CCM: Log the activation request, logging is a required step for activation.
if ( parent . certificateOperations . logAmtActivation ( domain , { time : new Date ( ) , action : 'ccmactivate' , domain : domain . id , amtUuid : cmd . uuid , amtRealm : cmd . realm , user : 'admin' , password : amtpassword , ipport : ws . remoteaddrport , meshid : ws . meshid , tag : cmd . tag , name : cmd . name } ) == false ) return { errorText : 'Unable to log operation' } ;
2019-06-24 21:56:14 -04:00
2019-06-27 19:04:52 -04:00
// Compute the HTTP digest hash and send the response for CCM activation
ws . send ( JSON . stringify ( { action : 'ccmactivate' , password : obj . crypto . createHash ( 'md5' ) . update ( 'admin:' + cmd . realm + ':' + amtpassword ) . digest ( 'hex' ) } ) ) ;
}
2019-06-24 21:56:14 -04:00
break ;
}
case 'ccmactivate-failed' :
case 'acmactivate-failed' : {
// Log the activation response
parent . certificateOperations . logAmtActivation ( domain , { time : new Date ( ) , action : cmd . action , domain : domain . id , amtUuid : cmd . uuid , ipport : ws . remoteaddrport , meshid : ws . meshid } ) ;
break ;
}
2019-06-28 21:31:18 -04:00
case 'amtdiscover' :
2019-06-24 21:56:14 -04:00
case 'ccmactivate-success' :
case 'acmactivate-success' : {
2019-06-28 21:31:18 -04:00
// If this is a discovery command, set the state.
if ( cmd . action == 'amtdiscover' ) {
if ( cmd . version != 1 ) { ws . send ( JSON . stringify ( { errorText : 'Unsupported version' } ) ) ; ws . close ( ) ; return ; }
if ( obj . common . validateString ( cmd . realm , 16 , 256 ) == false ) { ws . send ( JSON . stringify ( { errorText : 'Invalid realm argument' } ) ) ; ws . close ( ) ; return ; }
if ( obj . common . validateString ( cmd . uuid , 36 , 36 ) == false ) { ws . send ( JSON . stringify ( { errorText : 'Invalid UUID argument' } ) ) ; ws . close ( ) ; return ; }
if ( typeof cmd . hashes != 'object' ) { ws . send ( JSON . stringify ( { errorText : 'Invalid hashes' } ) ) ; ws . close ( ) ; return ; }
if ( typeof cmd . fqdn != 'string' ) { ws . send ( JSON . stringify ( { errorText : 'Invalid FQDN' } ) ) ; ws . close ( ) ; return ; }
if ( ( obj . common . validateString ( cmd . ver , 5 , 16 ) == false ) || ( cmd . ver . split ( '.' ) . length != 3 ) ) { ws . send ( JSON . stringify ( { errorText : 'Invalid Intel AMT version' } ) ) ; ws . close ( ) ; return ; }
if ( obj . common . validateArray ( cmd . modes , 1 , 2 ) == false ) { ws . send ( JSON . stringify ( { errorText : 'Invalid activation modes' } ) ) ; ws . close ( ) ; return ; }
if ( obj . common . validateInt ( cmd . currentMode , 0 , 2 ) == false ) { ws . send ( JSON . stringify ( { errorText : 'Invalid current mode' } ) ) ; ws . close ( ) ; return ; }
var activationMode = 0 ; if ( cmd . currentMode == 1 ) { activationMode = 2 ; } else if ( cmd . currentMode == 2 ) { activationMode = 4 ; }
2019-10-01 17:54:38 -04:00
ws . xxstate = { uuid : cmd . uuid , realm : cmd . realm , tag : cmd . tag , name : cmd . name , hostname : cmd . hostname , flags : activationMode , ver : cmd . ver , sku : cmd . sku } ; // Flags: 2 = CCM, 4 = ACM
2019-06-28 21:31:18 -04:00
} else {
// If this is an activation success, check that state was set already.
if ( ws . xxstate == null ) { ws . send ( JSON . stringify ( { errorText : 'Invalid command' } ) ) ; ws . close ( ) ; return ; }
}
2019-06-24 21:56:14 -04:00
// Log the activation response
parent . certificateOperations . logAmtActivation ( domain , { time : new Date ( ) , action : cmd . action , domain : domain . id , amtUuid : cmd . uuid , ipport : ws . remoteaddrport , meshid : ws . meshid } ) ;
// Get the current Intel AMT policy
var mesh = obj . meshes [ ws . meshid ] ;
if ( mesh == null ) { ws . send ( JSON . stringify ( { errorText : 'Unknown device group' } ) ) ; ws . close ( ) ; return ; }
// Fix the computer name if needed
2019-10-01 17:54:38 -04:00
if ( ( ws . xxstate . name == null ) || ( ws . xxstate . name . length == 0 ) ) { ws . xxstate . name = ws . xxstate . hostname ; }
2019-06-24 21:56:14 -04:00
if ( ( ws . xxstate . name == null ) || ( ws . xxstate . name . length == 0 ) ) { ws . xxstate . name = ws . xxstate . uuid ; }
db . getAmtUuidNode ( ws . meshid , ws . xxstate . uuid , function ( err , nodes ) {
if ( ( nodes == null ) || ( nodes . length == 0 ) ) {
// Create a new nodeid
parent . crypto . randomBytes ( 48 , function ( err , buf ) {
// Create the new node
var xxnodeid = 'node/' + domain . id + '/' + buf . toString ( 'base64' ) . replace ( /\+/g , '@' ) . replace ( /\//g , '$' ) ;
2019-10-01 17:54:38 -04:00
var device = { type : 'node' , _id : xxnodeid , meshid : ws . meshid , name : ws . xxstate . name , rname : ws . xxstate . name , host : ws . remoteaddr , domain : domain . id , intelamt : { state : 2 , flags : ws . xxstate . flags , tls : 0 , uuid : ws . xxstate . uuid , realm : ws . xxstate . realm , tag : ws . xxstate . tag , ver : ws . xxstate . ver , sku : ws . xxstate . sku } } ;
2019-06-28 21:31:18 -04:00
if ( ws . xxstate . pass != null ) { device . intelamt . user = 'admin' ; device . intelamt . pass = ws . xxstate . pass ; }
if ( device . intelamt . flags != 0 ) { device . intelamt . state = 2 ; } else { device . intelamt . state = 0 ; }
2019-06-24 21:56:14 -04:00
db . Set ( device ) ;
// Event the new node
2019-10-11 14:16:36 -04:00
var device2 = Object . assign ( { } , device ) ; // Shallow clone
device2 . intelamt = Object . assign ( { } , device2 . intelamt ) ; // Shallow clone
2019-06-24 21:56:14 -04:00
delete device2 . intelamt . pass ; // Remove the Intel AMT password before eventing this.
parent . DispatchEvent ( [ '*' , ws . meshid ] , obj , { etype : 'node' , action : 'addnode' , node : device2 , msg : 'Added device ' + ws . xxstate . name + ' to mesh ' + mesh . name , domain : domain . id } ) ;
} ) ;
} else {
// Change an existing device
var device = nodes [ 0 ] ;
if ( device . host != ws . remoteaddr ) { device . host = ws . remoteaddr ; }
2019-06-28 21:31:18 -04:00
if ( ( ws . xxstate . name != null ) && ( device . rname != ws . xxstate . name ) ) { device . rname = ws . xxstate . name ; }
if ( device . intelamt . flags != 0 ) {
if ( device . intelamt . state != 2 ) { device . intelamt . state = 2 ; }
}
2019-06-24 21:56:14 -04:00
if ( device . intelamt . flags != ws . xxstate . flags ) { device . intelamt . state = ws . xxstate . flags ; }
2019-06-28 21:31:18 -04:00
if ( ws . xxstate . pass != null ) {
if ( device . intelamt . user != 'admin' ) { device . intelamt . user = 'admin' ; }
if ( device . intelamt . pass != ws . xxstate . pass ) { device . intelamt . pass = ws . xxstate . pass ; }
}
2019-06-24 21:56:14 -04:00
if ( device . intelamt . realm != ws . xxstate . realm ) { device . intelamt . realm = ws . xxstate . realm ; }
if ( ws . xxstate . realm == null ) { delete device . intelamt . tag ; }
else if ( device . intelamt . tag != ws . xxstate . tag ) { device . intelamt . tag = ws . xxstate . tag ; }
if ( device . intelamt . ver != ws . xxstate . ver ) { device . intelamt . ver = ws . xxstate . ver ; }
2019-10-01 17:54:38 -04:00
if ( device . intelamt . sku != ws . xxstate . sku ) { device . intelamt . sku = ws . xxstate . sku ; }
2019-06-24 21:56:14 -04:00
db . Set ( device ) ;
// Event the new node
2019-10-11 14:16:36 -04:00
var device2 = Object . assign ( { } , device ) ; // Shallow clone
device2 . intelamt = Object . assign ( { } , device2 . intelamt ) ; // Shallow clone
2019-06-24 21:56:14 -04:00
delete device2 . intelamt . pass ; // Remove the Intel AMT password before eventing this.
if ( obj . db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the node. Another event will come.
parent . DispatchEvent ( [ '*' , ws . meshid ] , obj , { etype : 'node' , action : 'changenode' , nodeid : device2 . _id , node : device2 , msg : 'Changed device ' + device . name + ' in mesh ' + mesh . name , domain : domain . id } ) ;
}
} ) ;
2019-06-28 21:31:18 -04:00
if ( cmd . action == 'amtdiscover' ) { ws . send ( JSON . stringify ( { action : 'amtdiscover' } ) ) ; }
2019-06-21 20:18:48 -04:00
break ;
}
default : {
// This is not a known command
ws . send ( JSON . stringify ( { errorText : 'Invalid command' } ) ) ; ws . close ( ) ; return ;
}
}
} ) ;
// If close or error, do nothing.
ws . on ( 'error' , function ( err ) { } ) ;
ws . on ( 'close' , function ( req ) { } ) ;
}
2017-08-28 12:27:45 -04:00
// Handle the web socket echo request, just echo back the data sent
function handleEchoWebSocket ( ws , req ) {
2019-01-05 15:04:18 -05:00
const domain = checkUserIpAddress ( ws , req ) ;
2020-03-26 15:06:34 -04:00
if ( domain == null ) { return ; }
2017-10-25 12:58:14 -04:00
ws . _socket . setKeepAlive ( true , 240000 ) ; // Set TCP keep alive
2017-09-27 15:43:20 -04:00
2017-08-28 12:27:45 -04:00
// When data is received from the web socket, echo it back
ws . on ( 'message' , function ( data ) {
2019-01-05 15:04:18 -05:00
if ( data . toString ( 'utf8' ) == 'close' ) {
2017-08-28 12:27:45 -04:00
try { ws . close ( ) ; } catch ( e ) { console . log ( e ) ; }
} else {
try { ws . send ( data ) ; } catch ( e ) { console . log ( e ) ; }
}
} ) ;
2019-01-02 21:34:50 -05:00
// If error, do nothing.
2019-08-31 22:40:50 -04:00
ws . on ( 'error' , function ( err ) { console . log ( 'Echo server error from ' + cleanRemoteAddr ( req . ip ) + ', ' + err . toString ( ) . split ( '\r' ) [ 0 ] + '.' ) ; } ) ;
2017-08-28 12:27:45 -04:00
// If closed, do nothing
ws . on ( 'close' , function ( req ) { } ) ;
}
2018-05-24 00:43:44 -04:00
2017-11-30 17:40:46 -05:00
// Get the total size of all files in a folder and all sub-folders. (TODO: try to make all async version)
2017-08-28 12:27:45 -04:00
function readTotalFileSize ( path ) {
2017-11-30 17:40:46 -05:00
var r = 0 , dir ;
try { dir = obj . fs . readdirSync ( path ) ; } catch ( e ) { return 0 ; }
2017-08-28 12:27:45 -04:00
for ( var i in dir ) {
2018-08-30 15:05:23 -04:00
var stat = obj . fs . statSync ( path + '/' + dir [ i ] ) ;
2017-08-28 12:27:45 -04:00
if ( ( stat . mode & 0x004000 ) == 0 ) { r += stat . size ; } else { r += readTotalFileSize ( path + '/' + dir [ i ] ) ; }
}
return r ;
}
2018-05-24 00:43:44 -04:00
2017-11-30 17:40:46 -05:00
// Delete a folder and all sub items. (TODO: try to make all async version)
2017-08-28 12:27:45 -04:00
function deleteFolderRec ( path ) {
if ( obj . fs . existsSync ( path ) == false ) return ;
2019-10-30 17:30:34 -04:00
try {
obj . fs . readdirSync ( path ) . forEach ( function ( file , index ) {
var pathx = path + '/' + file ;
if ( obj . fs . lstatSync ( pathx ) . isDirectory ( ) ) { deleteFolderRec ( pathx ) ; } else { obj . fs . unlinkSync ( pathx ) ; }
} ) ;
obj . fs . rmdirSync ( path ) ;
} catch ( ex ) { }
2018-08-30 15:05:23 -04:00
}
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
// Handle Intel AMT events
// To subscribe, add "http://server:port/amtevents.ashx" to Intel AMT subscriptions.
obj . handleAmtEventRequest = function ( req , res ) {
2019-01-05 15:04:18 -05:00
const domain = getDomain ( req ) ;
2017-08-28 12:27:45 -04:00
try {
2018-08-30 15:05:23 -04:00
if ( req . headers . authorization ) {
var authstr = req . headers . authorization ;
2019-12-27 01:53:01 -05:00
if ( authstr . substring ( 0 , 7 ) == 'Digest ' ) {
2017-08-28 12:27:45 -04:00
var auth = obj . common . parseNameValueList ( obj . common . quoteSplit ( authstr . substring ( 7 ) ) ) ;
2017-10-15 02:22:19 -04:00
if ( ( req . url === auth . uri ) && ( obj . httpAuthRealm === auth . realm ) && ( auth . opaque === obj . crypto . createHmac ( 'SHA384' , obj . httpAuthRandom ) . update ( auth . nonce ) . digest ( 'hex' ) ) ) {
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
// Read the data, we need to get the arg field
var eventData = '' ;
req . on ( 'data' , function ( chunk ) { eventData += chunk ; } ) ;
req . on ( 'end' , function ( ) {
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
// Completed event read, let get the argument that must contain the nodeid
var i = eventData . indexOf ( '<m:arg xmlns:m="http://x.com">' ) ;
if ( i > 0 ) {
2017-10-15 20:36:06 -04:00
var nodeid = eventData . substring ( i + 30 , i + 30 + 64 ) ;
2017-08-28 12:27:45 -04:00
if ( nodeid . length == 64 ) {
var nodekey = 'node/' + domain . id + '/' + nodeid ;
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
// See if this node exists in the database
obj . db . Get ( nodekey , function ( err , nodes ) {
if ( nodes . length == 1 ) {
// Yes, the node exists, compute Intel AMT digest password
var node = nodes [ 0 ] ;
2019-12-27 01:53:01 -05:00
var amtpass = obj . crypto . createHash ( 'sha384' ) . update ( auth . username . toLowerCase ( ) + ':' + nodeid + ":" + obj . parent . dbconfig . amtWsEventSecret ) . digest ( 'base64' ) . substring ( 0 , 12 ) . split ( '/' ) . join ( 'x' ) . split ( '\\' ) . join ( 'x' ) ;
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
// Check the MD5 hash
2019-12-27 01:53:01 -05:00
if ( auth . response === obj . common . ComputeDigesthash ( auth . username , amtpass , auth . realm , 'POST' , auth . uri , auth . qop , auth . nonce , auth . nc , auth . cnonce ) ) {
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
// This is an authenticated Intel AMT event, update the host address
2018-11-01 18:01:21 -04:00
var amthost = req . ip ;
2017-08-28 12:27:45 -04:00
if ( amthost . substring ( 0 , 7 ) === '::ffff:' ) { amthost = amthost . substring ( 7 ) ; }
if ( node . host != amthost ) {
// Get the mesh for this device
var mesh = obj . meshes [ node . meshid ] ;
if ( mesh ) {
// Update the database
var oldname = node . host ;
node . host = amthost ;
2020-03-30 20:47:04 -04:00
obj . db . Set ( obj . cleanDevice ( node ) ) ;
2017-08-28 12:27:45 -04:00
// Event the node change
2018-09-24 20:47:03 -04:00
var event = { etype : 'node' , action : 'changenode' , nodeid : node . _id , domain : domain . id , msg : 'Intel(R) AMT host change ' + node . name + ' from group ' + mesh . name + ': ' + oldname + ' to ' + amthost } ;
2019-10-11 14:16:36 -04:00
// Remove the Intel AMT password before eventing this.
event . node = node ;
if ( event . node . intelamt && event . node . intelamt . pass ) {
event . node = Object . assign ( { } , event . node ) ; // Shallow clone
event . node . intelamt = Object . assign ( { } , event . node . intelamt ) ; // Shallow clone
delete event . node . intelamt . pass ;
}
2019-05-28 20:25:23 -04:00
if ( obj . db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the node. Another event will come.
2017-08-28 12:27:45 -04:00
obj . parent . DispatchEvent ( [ '*' , node . meshid ] , obj , event ) ;
}
}
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
parent . amtEventHandler . handleAmtEvent ( eventData , nodeid , amthost ) ;
//res.send('OK');
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
return ;
}
}
} ) ;
}
}
} ) ;
}
}
}
} catch ( e ) { console . log ( e ) ; }
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
// Send authentication response
2017-10-15 02:22:19 -04:00
obj . crypto . randomBytes ( 48 , function ( err , buf ) {
var nonce = buf . toString ( 'hex' ) , opaque = obj . crypto . createHmac ( 'SHA384' , obj . httpAuthRandom ) . update ( nonce ) . digest ( 'hex' ) ;
2017-08-28 12:27:45 -04:00
res . set ( { 'WWW-Authenticate' : 'Digest realm="' + obj . httpAuthRealm + '", qop="auth,auth-int", nonce="' + nonce + '", opaque="' + opaque + '"' } ) ;
res . sendStatus ( 401 ) ;
} ) ;
2018-08-30 15:05:23 -04:00
} ;
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
// Handle a server backup request
function handleBackupRequest ( req , res ) {
2019-01-05 15:04:18 -05:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 15:06:34 -04:00
if ( domain == null ) { return ; }
2019-11-25 17:30:23 -05:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2019-05-24 18:11:00 -04:00
if ( ( ! req . session ) || ( req . session == null ) || ( ! req . session . userid ) || ( obj . parent . args . noserverbackup == 1 ) ) { res . sendStatus ( 401 ) ; return ; }
2017-08-28 12:27:45 -04:00
var user = obj . users [ req . session . userid ] ;
2019-05-24 18:11:00 -04:00
if ( ( user == null ) || ( ( user . siteadmin & 1 ) == 0 ) ) { res . sendStatus ( 401 ) ; return ; } // Check if we have server backup rights
2017-08-28 12:27:45 -04:00
// Require modules
2019-01-02 21:34:50 -05:00
const archive = require ( 'archiver' ) ( 'zip' , { level : 9 } ) ; // Sets the compression method to maximum.
2018-05-24 00:43:44 -04:00
2019-12-09 14:30:08 -05:00
// Good practice to catch this error explicitly
2017-08-28 12:27:45 -04:00
archive . on ( 'error' , function ( err ) { throw err ; } ) ;
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
// Set the archive name
2019-12-22 15:44:16 -05:00
res . attachment ( ( domain . title ? domain . title : 'MeshCentral' ) + '-Backup-' + new Date ( ) . toLocaleDateString ( ) . replace ( '/' , '-' ) . replace ( '/' , '-' ) + '.zip' ) ;
2017-08-28 12:27:45 -04:00
// Pipe archive data to the file
archive . pipe ( res ) ;
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
// Append all of the files for this backup
var backupList = [ 'config.json' , 'meshcentral.db' , 'agentserver-cert-private.key' , 'agentserver-cert-public.crt' , 'mpsserver-cert-private.key' , 'mpsserver-cert-public.crt' , 'data/root-cert-private.key' , 'root-cert-public.crt' , 'webserver-cert-private.key' , 'webserver-cert-public.crt' ] ;
for ( var i in backupList ) {
var filename = backupList [ i ] ;
var filepath = obj . path . join ( obj . parent . datapath , filename ) ;
2019-01-05 15:04:18 -05:00
if ( obj . fs . existsSync ( filepath ) ) { archive . file ( filepath , { name : filename } ) ; }
2017-08-28 12:27:45 -04:00
}
// Finalize the archive (ie we are done appending files but streams have to finish yet)
archive . finalize ( ) ;
}
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
// Handle a server restore request
function handleRestoreRequest ( req , res ) {
2019-01-05 15:04:18 -05:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 15:06:34 -04:00
if ( domain == null ) { return ; }
2019-11-25 17:30:23 -05:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2019-09-30 17:50:28 -04:00
if ( obj . parent . args . noserverbackup == 1 ) { res . sendStatus ( 401 ) ; return ; }
var authUserid = null ;
if ( ( req . session != null ) && ( typeof req . session . userid == 'string' ) ) { authUserid = req . session . userid ; }
2019-01-05 15:04:18 -05:00
const multiparty = require ( 'multiparty' ) ;
const form = new multiparty . Form ( ) ;
2017-08-28 12:27:45 -04:00
form . parse ( req , function ( err , fields , files ) {
2019-09-30 17:50:28 -04:00
// If an authentication cookie is embedded in the form, use that.
if ( ( fields != null ) && ( fields . auth != null ) && ( fields . auth . length == 1 ) && ( typeof fields . auth [ 0 ] == 'string' ) ) {
var loginCookie = obj . parent . decodeCookie ( fields . auth [ 0 ] , obj . parent . loginCookieEncryptionKey , 60 ) ; // 60 minute timeout
2020-01-26 13:05:27 -05:00
if ( ( loginCookie != null ) && ( obj . args . cookieipcheck !== false ) && ( loginCookie . ip != null ) && ( loginCookie . ip != cleanRemoteAddr ( req . ip ) ) ) { loginCookie = null ; } // Check cookie IP binding.
2019-09-30 17:50:28 -04:00
if ( ( loginCookie != null ) && ( domain . id == loginCookie . domainid ) ) { authUserid = loginCookie . userid ; } // Use cookie authentication
}
if ( authUserid == null ) { res . sendStatus ( 401 ) ; return ; }
// Get the user
const user = obj . users [ req . session . userid ] ;
if ( ( user == null ) || ( ( user . siteadmin & 4 ) == 0 ) ) { res . sendStatus ( 401 ) ; return ; } // Check if we have server restore rights
2017-08-28 12:27:45 -04:00
res . send ( 'Server must be restarted, <a href="' + domain . url + '">click here to login</a>.' ) ;
parent . Stop ( files . datafile [ 0 ] . path ) ;
} ) ;
}
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
// Handle a request to download a mesh agent
obj . handleMeshAgentRequest = function ( req , res ) {
2020-03-31 13:37:52 -04:00
var domain = getDomain ( req , res ) ;
2020-03-26 15:06:34 -04:00
if ( domain == null ) { parent . debug ( 'web' , 'handleRootRequest: invalid domain.' ) ; try { res . sendStatus ( 404 ) ; } catch ( ex ) { } return ; }
2018-01-30 21:23:57 -05:00
// If required, check if this user has rights to do this
2019-07-10 17:27:38 -04:00
if ( ( obj . parent . config . settings != null ) && ( ( obj . parent . config . settings . lockagentdownload == true ) || ( domain . lockagentdownload == true ) ) && ( req . session . userid == null ) ) { res . sendStatus ( 401 ) ; return ; }
2018-01-30 21:23:57 -05:00
2017-09-15 14:45:06 -04:00
if ( req . query . id != null ) {
2017-08-28 12:27:45 -04:00
// Send a specific mesh agent back
var argentInfo = obj . parent . meshAgentBinaries [ req . query . id ] ;
2017-09-15 14:45:06 -04:00
if ( argentInfo == null ) { res . sendStatus ( 404 ) ; return ; }
2018-01-19 21:04:54 -05:00
if ( ( req . query . meshid == null ) || ( argentInfo . platform != 'win32' ) ) {
2019-04-30 18:40:19 -04:00
res . set ( { 'Cache-Control' : 'no-cache, no-store, must-revalidate' , 'Pragma' : 'no-cache' , 'Expires' : '0' , 'Content-Type' : 'application/octet-stream' , 'Content-Disposition' : 'attachment; filename="' + argentInfo . rname + '"' } ) ;
2019-10-23 16:12:30 -04:00
if ( argentInfo . data == null ) { res . sendFile ( argentInfo . path ) ; } else { res . end ( argentInfo . data ) ; }
2018-01-19 21:04:54 -05:00
} else {
// We are going to embed the .msh file into the Windows executable (signed or not).
2018-08-21 18:08:15 -04:00
// First, fetch the mesh object to build the .msh file
var mesh = obj . meshes [ 'mesh/' + domain . id + '/' + req . query . meshid ] ;
if ( mesh == null ) { res . sendStatus ( 401 ) ; return ; }
// If required, check if this user has rights to do this
2019-07-10 17:27:38 -04:00
if ( ( obj . parent . config . settings != null ) && ( ( obj . parent . config . settings . lockagentdownload == true ) || ( domain . lockagentdownload == true ) ) ) {
2019-12-27 18:18:43 -05:00
if ( ( domain . id != mesh . domain ) || ( ( obj . GetMeshRights ( req . session . userid , mesh ) & 1 ) == 0 ) ) { res . sendStatus ( 401 ) ; return ; }
2018-08-21 18:08:15 -04:00
}
2018-01-19 21:04:54 -05:00
2019-01-02 21:03:34 -05:00
var meshidhex = Buffer . from ( req . query . meshid . replace ( /\@/g , '+' ) . replace ( /\$/g , '/' ) , 'base64' ) . toString ( 'hex' ) . toUpperCase ( ) ;
var serveridhex = Buffer . from ( obj . agentCertificateHashBase64 . replace ( /\@/g , '+' ) . replace ( /\$/g , '/' ) , 'base64' ) . toString ( 'hex' ) . toUpperCase ( ) ;
2018-08-21 18:08:15 -04:00
var httpsPort = ( ( obj . args . aliasport == null ) ? obj . args . port : obj . args . aliasport ) ; // Use HTTPS alias port is specified
2018-01-19 21:04:54 -05:00
2019-06-21 15:10:06 -04:00
// Prepare a mesh agent file name using the device group name.
var meshfilename = mesh . name
meshfilename = meshfilename . split ( '\\' ) . join ( '' ) . split ( '/' ) . join ( '' ) . split ( ':' ) . join ( '' ) . split ( '*' ) . join ( '' ) . split ( '?' ) . join ( '' ) . split ( '"' ) . join ( '' ) . split ( '<' ) . join ( '' ) . split ( '>' ) . join ( '' ) . split ( '|' ) . join ( '' ) . split ( ' ' ) . join ( '' ) . split ( '\'' ) . join ( '' ) ;
if ( argentInfo . rname . endsWith ( '.exe' ) ) { meshfilename = argentInfo . rname . substring ( 0 , argentInfo . rname . length - 4 ) + '-' + meshfilename + '.exe' ; } else { meshfilename = argentInfo . rname + '-' + meshfilename ; }
2018-08-21 18:08:15 -04:00
// Build the agent connection URL. If we are using a sub-domain or one with a DNS, we need to craft the URL correctly.
var xdomain = ( domain . dns == null ) ? domain . id : '' ;
2019-12-27 01:53:01 -05:00
if ( xdomain != '' ) xdomain += '/' ;
var meshsettings = 'MeshName=' + mesh . name + '\r\nMeshType=' + mesh . mtype + '\r\nMeshID=0x' + meshidhex + '\r\nServerID=' + serveridhex + '\r\n' ;
if ( obj . args . lanonly != true ) { meshsettings += 'MeshServer=ws' + ( obj . args . notls ? '' : 's' ) + '://' + obj . getWebServerName ( domain ) + ':' + httpsPort + '/' + xdomain + 'agent.ashx\r\n' ; } else { meshsettings += 'MeshServer=local\r\n' ; }
if ( req . query . tag != null ) { meshsettings += 'Tag=' + req . query . tag + '\r\n' ; }
if ( ( req . query . installflags != null ) && ( req . query . installflags != 0 ) ) { meshsettings += 'InstallFlags=' + req . query . installflags + '\r\n' ; }
if ( ( domain . agentnoproxy === true ) || ( obj . args . lanonly == true ) ) { meshsettings += 'ignoreProxyFile=1\r\n' ; }
if ( obj . args . agentconfig ) { for ( var i in obj . args . agentconfig ) { meshsettings += obj . args . agentconfig [ i ] + '\r\n' ; } }
if ( domain . agentconfig ) { for ( var i in domain . agentconfig ) { meshsettings += domain . agentconfig [ i ] + '\r\n' ; } }
2018-11-28 16:51:12 -05:00
2019-06-24 21:56:14 -04:00
try {
res . set ( { 'Cache-Control' : 'no-cache, no-store, must-revalidate' , 'Pragma' : 'no-cache' , 'Expires' : '0' , 'Content-Type' : 'application/octet-stream' , 'Content-Disposition' : 'attachment; filename="' + meshfilename + '"' } ) ;
} catch ( ex ) {
res . set ( { 'Cache-Control' : 'no-cache, no-store, must-revalidate' , 'Pragma' : 'no-cache' , 'Expires' : '0' , 'Content-Type' : 'application/octet-stream' , 'Content-Disposition' : 'attachment; filename="' + argentInfo . rname + '"' } ) ;
}
2018-08-21 18:08:15 -04:00
obj . parent . exeHandler . streamExeWithMeshPolicy ( { platform : 'win32' , sourceFileName : obj . parent . meshAgentBinaries [ req . query . id ] . path , destinationStream : res , msh : meshsettings , peinfo : obj . parent . meshAgentBinaries [ req . query . id ] . pe } ) ;
2018-01-19 21:04:54 -05:00
}
2017-09-15 14:45:06 -04:00
} else if ( req . query . script != null ) {
2020-03-13 18:43:24 -04:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2017-08-28 12:27:45 -04:00
// Send a specific mesh install script back
var scriptInfo = obj . parent . meshAgentInstallScripts [ req . query . script ] ;
2017-09-15 14:45:06 -04:00
if ( scriptInfo == null ) { res . sendStatus ( 404 ) ; return ; }
2019-04-30 18:40:19 -04:00
res . set ( { 'Cache-Control' : 'no-cache, no-store, must-revalidate' , 'Pragma' : 'no-cache' , 'Expires' : '0' , 'Content-Type' : 'text/plain' , 'Content-Disposition' : 'attachment; filename="' + scriptInfo . rname + '"' } ) ;
2019-07-11 14:43:45 -04:00
var data = scriptInfo . data ;
var cmdoptions = { wgetoptionshttp : '' , wgetoptionshttps : '' , curloptionshttp : '-L ' , curloptionshttps : '-L ' }
2019-10-05 17:24:40 -04:00
if ( obj . isTrustedCert ( domain ) != true ) {
2019-07-11 14:43:45 -04:00
cmdoptions . wgetoptionshttps += '--no-check-certificate ' ;
cmdoptions . curloptionshttps += '-k ' ;
}
if ( domain . agentnoproxy === true ) {
cmdoptions . wgetoptionshttp += '--no-proxy ' ;
cmdoptions . wgetoptionshttps += '--no-proxy ' ;
cmdoptions . curloptionshttp += '--noproxy \'*\' ' ;
cmdoptions . curloptionshttps += '--noproxy \'*\' ' ;
}
for ( var i in cmdoptions ) { data = data . split ( '{{{' + i + '}}}' ) . join ( cmdoptions [ i ] ) ; }
res . send ( data ) ;
2017-10-31 19:19:58 -04:00
} else if ( req . query . meshcmd != null ) {
2020-03-13 18:43:24 -04:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2017-10-31 19:19:58 -04:00
// Send meshcmd for a specific platform back
2018-03-08 20:58:22 -05:00
var agentid = parseInt ( req . query . meshcmd ) ;
2018-03-09 19:39:14 -05:00
// If the agentid is 3 or 4, check if we have a signed MeshCmd.exe
if ( ( agentid == 3 ) ) { // Signed Windows MeshCmd.exe x86
var stats = null , meshCmdPath = obj . path . join ( _ _dirname , 'agents' , 'MeshCmd-signed.exe' ) ;
2018-08-30 15:05:23 -04:00
try { stats = obj . fs . statSync ( meshCmdPath ) ; } catch ( e ) { }
2019-04-30 18:40:19 -04:00
if ( ( stats != null ) ) { res . set ( { 'Cache-Control' : 'no-cache, no-store, must-revalidate' , 'Pragma' : 'no-cache' , 'Expires' : '0' , 'Content-Type' : 'application/octet-stream' , 'Content-Disposition' : 'attachment; filename="meshcmd' + ( ( req . query . meshcmd <= 3 ) ? '.exe' : '' ) + '"' } ) ; res . sendFile ( meshCmdPath ) ; return ; }
2018-03-09 19:39:14 -05:00
} else if ( ( agentid == 4 ) ) { // Signed Windows MeshCmd64.exe x64
var stats = null , meshCmd64Path = obj . path . join ( _ _dirname , 'agents' , 'MeshCmd64-signed.exe' ) ;
2018-08-30 15:05:23 -04:00
try { stats = obj . fs . statSync ( meshCmd64Path ) ; } catch ( e ) { }
2019-04-30 18:40:19 -04:00
if ( ( stats != null ) ) { res . set ( { 'Cache-Control' : 'no-cache, no-store, must-revalidate' , 'Pragma' : 'no-cache' , 'Expires' : '0' , 'Content-Type' : 'application/octet-stream' , 'Content-Disposition' : 'attachment; filename="meshcmd' + ( ( req . query . meshcmd <= 4 ) ? '.exe' : '' ) + '"' } ) ; res . sendFile ( meshCmd64Path ) ; return ; }
2018-03-09 19:39:14 -05:00
}
// No signed agents, we are going to merge a new MeshCmd.
2018-03-08 20:58:22 -05:00
if ( ( agentid < 10000 ) && ( obj . parent . meshAgentBinaries [ agentid + 10000 ] != null ) ) { agentid += 10000 ; } // Avoid merging javascript to a signed mesh agent.
var argentInfo = obj . parent . meshAgentBinaries [ agentid ] ;
2017-12-12 19:04:54 -05:00
if ( ( argentInfo == null ) || ( obj . parent . defaultMeshCmd == null ) ) { res . sendStatus ( 404 ) ; return ; }
2019-04-30 18:40:19 -04:00
res . set ( { 'Cache-Control' : 'no-cache, no-store, must-revalidate' , 'Pragma' : 'no-cache' , 'Expires' : '0' , 'Content-Type' : 'application/octet-stream' , 'Content-Disposition' : 'attachment; filename="meshcmd' + ( ( req . query . meshcmd <= 4 ) ? '.exe' : '' ) + '"' } ) ;
2018-01-19 21:04:54 -05:00
res . statusCode = 200 ;
2018-03-08 20:58:22 -05:00
if ( argentInfo . signedMeshCmdPath != null ) {
// If we hav a pre-signed MeshCmd, send that.
res . sendFile ( argentInfo . signedMeshCmdPath ) ;
} else {
// Merge JavaScript to a unsigned agent and send that.
2019-01-02 21:03:34 -05:00
obj . parent . exeHandler . streamExeWithJavaScript ( { platform : argentInfo . platform , sourceFileName : argentInfo . path , destinationStream : res , js : Buffer . from ( obj . parent . defaultMeshCmd , 'utf8' ) , peinfo : argentInfo . pe } ) ;
2018-03-08 20:58:22 -05:00
}
2017-10-31 19:19:58 -04:00
} else if ( req . query . meshaction != null ) {
2020-03-13 18:43:24 -04:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2017-10-31 19:19:58 -04:00
var user = obj . users [ req . session . userid ] ;
2017-12-15 16:43:04 -05:00
if ( ( req . query . meshaction == 'route' ) && ( req . query . nodeid != null ) ) {
obj . db . Get ( req . query . nodeid , function ( err , nodes ) {
if ( nodes . length != 1 ) { res . sendStatus ( 401 ) ; return ; }
var node = nodes [ 0 ] ;
2019-04-10 14:54:55 -04:00
2017-12-15 16:43:04 -05:00
// Create the meshaction.txt file for meshcmd.exe
var meshaction = {
action : req . query . meshaction ,
localPort : 1234 ,
remoteName : node . name ,
remoteNodeId : node . _id ,
2020-04-03 19:06:38 -04:00
remoteTarget : null ,
2017-12-15 16:43:04 -05:00
remotePort : 3389 ,
2020-04-06 23:25:52 -04:00
username : '' ,
2017-12-15 16:43:04 -05:00
password : '' ,
serverId : obj . agentCertificateHashHex . toUpperCase ( ) , // SHA384 of server HTTPS public key
2019-04-10 14:54:55 -04:00
serverHttpsHash : Buffer . from ( obj . webCertificateHashs [ domain . id ] , 'binary' ) . toString ( 'hex' ) . toUpperCase ( ) , // SHA384 of server HTTPS certificate
2017-12-15 16:43:04 -05:00
debugLevel : 0
2018-08-30 15:05:23 -04:00
} ;
2017-12-15 16:43:04 -05:00
if ( user != null ) { meshaction . username = user . name ; }
2018-03-08 20:58:22 -05:00
var httpsPort = ( ( obj . args . aliasport == null ) ? obj . args . port : obj . args . aliasport ) ; // Use HTTPS alias port is specified
2018-12-07 19:36:27 -05:00
if ( obj . args . lanonly != true ) { meshaction . serverUrl = ( ( obj . args . notls == true ) ? 'ws://' : 'wss://' ) + obj . getWebServerName ( domain ) + ':' + httpsPort + '/' + ( ( domain . id == '' ) ? '' : ( '/' + domain . id ) ) + 'meshrelay.ashx' ; }
2019-04-30 18:40:19 -04:00
res . set ( { 'Cache-Control' : 'no-cache, no-store, must-revalidate' , 'Pragma' : 'no-cache' , 'Expires' : '0' , 'Content-Type' : 'text/plain' , 'Content-Disposition' : 'attachment; filename="meshaction.txt"' } ) ;
2017-12-15 16:43:04 -05:00
res . send ( JSON . stringify ( meshaction , null , ' ' ) ) ;
} ) ;
2020-03-17 21:15:54 -04:00
} else if ( req . query . meshaction == 'generic' ) {
2017-10-31 19:19:58 -04:00
var meshaction = {
2020-04-03 19:06:38 -04:00
username : user . name ,
2017-10-31 19:19:58 -04:00
password : '' ,
serverId : obj . agentCertificateHashHex . toUpperCase ( ) , // SHA384 of server HTTPS public key
2019-04-10 14:54:55 -04:00
serverHttpsHash : Buffer . from ( obj . webCertificateHashs [ domain . id ] , 'binary' ) . toString ( 'hex' ) . toUpperCase ( ) , // SHA384 of server HTTPS certificate
2017-10-31 19:19:58 -04:00
debugLevel : 0
2018-08-30 15:05:23 -04:00
} ;
2017-10-31 19:19:58 -04:00
if ( user != null ) { meshaction . username = user . name ; }
2018-03-08 20:58:22 -05:00
var httpsPort = ( ( obj . args . aliasport == null ) ? obj . args . port : obj . args . aliasport ) ; // Use HTTPS alias port is specified
2018-12-07 19:36:27 -05:00
if ( obj . args . lanonly != true ) { meshaction . serverUrl = ( ( obj . args . notls == true ) ? 'ws://' : 'wss://' ) + obj . getWebServerName ( domain ) + ':' + httpsPort + '/' + ( ( domain . id == '' ) ? '' : ( '/' + domain . id ) ) + 'meshrelay.ashx' ; }
2019-04-30 18:40:19 -04:00
res . set ( { 'Cache-Control' : 'no-cache, no-store, must-revalidate' , 'Pragma' : 'no-cache' , 'Expires' : '0' , 'Content-Type' : 'text/plain' , 'Content-Disposition' : 'attachment; filename="meshaction.txt"' } ) ;
2017-10-31 19:19:58 -04:00
res . send ( JSON . stringify ( meshaction , null , ' ' ) ) ;
2019-04-23 17:15:48 -04:00
} else if ( req . query . meshaction == 'winrouter' ) {
var p = obj . path . join ( _ _dirname , 'agents' , 'MeshCentralRouter.exe' ) ;
if ( obj . fs . existsSync ( p ) ) {
2019-06-21 15:10:06 -04:00
res . set ( { 'Cache-Control' : 'no-cache, no-store, must-revalidate' , 'Pragma' : 'no-cache' , 'Expires' : '0' , 'Content-Type' : 'application/octet-stream' , 'Content-Disposition' : 'attachment; filename="MeshCentralRouter.exe"' } ) ;
2019-04-23 17:15:48 -04:00
try { res . sendFile ( p ) ; } catch ( e ) { res . sendStatus ( 404 ) ; }
} else { res . sendStatus ( 404 ) ; }
2017-12-15 16:43:04 -05:00
} else {
res . sendStatus ( 401 ) ;
}
2017-08-28 12:27:45 -04:00
} else {
2020-03-26 15:06:34 -04:00
domain = checkUserIpAddress ( req , res ) ; // Recheck the domain to apply user IP filtering.
if ( domain == null ) return ;
2020-03-13 18:43:24 -04:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2017-08-28 12:27:45 -04:00
// Send a list of available mesh agents
var response = '<html><head><title>Mesh Agents</title><style>table,th,td { border:1px solid black;border-collapse:collapse;padding:3px; }</style></head><body><table>' ;
2017-12-12 19:04:54 -05:00
response += '<tr style="background-color:lightgray"><th>ID</th><th>Description</th><th>Link</th><th>Size</th><th>SHA384</th><th>MeshCmd</th></tr>' ;
2017-08-28 12:27:45 -04:00
for ( var agentid in obj . parent . meshAgentBinaries ) {
var agentinfo = obj . parent . meshAgentBinaries [ agentid ] ;
2020-03-13 18:43:24 -04:00
var originalUrl = req . originalUrl . split ( '?' ) [ 0 ] ;
2017-08-28 12:27:45 -04:00
response += '<tr><td>' + agentinfo . id + '</td><td>' + agentinfo . desc + '</td>' ;
2020-03-13 18:43:24 -04:00
response += '<td><a download href="' + originalUrl + '?id=' + agentinfo . id + ( req . query . key ? ( '&key=' + req . query . key ) : '' ) + '">' + agentinfo . rname + '</a></td>' ;
2020-01-27 17:03:36 -05:00
response += '<td>' + agentinfo . size + '</td><td>' + agentinfo . hashhex + '</td>' ;
2020-03-13 18:43:24 -04:00
response += '<td><a download href="' + originalUrl + '?meshcmd=' + agentinfo . id + ( req . query . key ? ( '&key=' + req . query . key ) : '' ) + '">' + agentinfo . rname . replace ( 'agent' , 'cmd' ) + '</a></td></tr>' ;
2017-08-28 12:27:45 -04:00
}
response += '</table></body></html>' ;
res . send ( response ) ;
}
2018-08-30 15:05:23 -04:00
} ;
2018-01-02 19:52:49 -05:00
// Get the web server hostname. This may change if using a domain with a DNS name.
2019-10-04 15:18:56 -04:00
obj . getWebServerName = function ( domain ) {
2018-01-02 19:52:49 -05:00
if ( domain . dns != null ) return domain . dns ;
return obj . certificates . CommonName ;
}
2018-09-07 20:09:02 -04:00
// Create a OSX mesh agent installer
obj . handleMeshOsxAgentRequest = function ( req , res ) {
2020-03-26 15:06:34 -04:00
const domain = getDomain ( req , res ) ;
if ( domain == null ) { parent . debug ( 'web' , 'handleRootRequest: invalid domain.' ) ; try { res . sendStatus ( 404 ) ; } catch ( ex ) { } return ; }
if ( req . query . id == null ) { res . sendStatus ( 404 ) ; return ; }
2018-09-07 20:09:02 -04:00
// If required, check if this user has rights to do this
2019-07-10 17:27:38 -04:00
if ( ( obj . parent . config . settings != null ) && ( ( obj . parent . config . settings . lockagentdownload == true ) || ( domain . lockagentdownload == true ) ) && ( req . session . userid == null ) ) { res . sendStatus ( 401 ) ; return ; }
2018-09-07 20:09:02 -04:00
// Send a specific mesh agent back
var argentInfo = obj . parent . meshAgentBinaries [ req . query . id ] ;
2018-09-21 19:34:35 -04:00
if ( ( argentInfo == null ) || ( req . query . meshid == null ) ) { res . sendStatus ( 404 ) ; return ; }
2018-09-07 20:09:02 -04:00
// We are going to embed the .msh file into the Windows executable (signed or not).
// First, fetch the mesh object to build the .msh file
var mesh = obj . meshes [ 'mesh/' + domain . id + '/' + req . query . meshid ] ;
if ( mesh == null ) { res . sendStatus ( 401 ) ; return ; }
// If required, check if this user has rights to do this
2019-07-10 17:27:38 -04:00
if ( ( obj . parent . config . settings != null ) && ( ( obj . parent . config . settings . lockagentdownload == true ) || ( domain . lockagentdownload == true ) ) ) {
2019-12-27 18:18:43 -05:00
if ( ( domain . id != mesh . domain ) || ( ( obj . GetMeshRights ( req . session . userid , mesh ) & 1 ) == 0 ) ) { res . sendStatus ( 401 ) ; return ; }
2018-09-07 20:09:02 -04:00
}
2019-01-02 21:03:34 -05:00
var meshidhex = Buffer . from ( req . query . meshid . replace ( /\@/g , '+' ) . replace ( /\$/g , '/' ) , 'base64' ) . toString ( 'hex' ) . toUpperCase ( ) ;
var serveridhex = Buffer . from ( obj . agentCertificateHashBase64 . replace ( /\@/g , '+' ) . replace ( /\$/g , '/' ) , 'base64' ) . toString ( 'hex' ) . toUpperCase ( ) ;
2018-09-07 20:09:02 -04:00
// Build the agent connection URL. If we are using a sub-domain or one with a DNS, we need to craft the URL correctly.
var xdomain = ( domain . dns == null ) ? domain . id : '' ;
2019-12-27 01:53:01 -05:00
if ( xdomain != '' ) xdomain += '/' ;
var meshsettings = 'MeshName=' + mesh . name + '\r\nMeshType=' + mesh . mtype + '\r\nMeshID=0x' + meshidhex + '\r\nServerID=' + serveridhex + '\r\n' ;
2019-03-01 20:13:11 -05:00
var httpsPort = ( ( obj . args . aliasport == null ) ? obj . args . port : obj . args . aliasport ) ; // Use HTTPS alias port is specified
2019-12-27 01:53:01 -05:00
if ( obj . args . lanonly != true ) { meshsettings += 'MeshServer=ws' + ( obj . args . notls ? '' : 's' ) + '://' + obj . getWebServerName ( domain ) + ':' + httpsPort + '/' + xdomain + 'agent.ashx\r\n' ; } else { meshsettings += 'MeshServer=local\r\n' ; }
if ( req . query . tag != null ) { meshsettings += 'Tag=' + req . query . tag + '\r\n' ; }
if ( ( req . query . installflags != null ) && ( req . query . installflags != 0 ) ) { meshsettings += 'InstallFlags=' + req . query . installflags + '\r\n' ; }
if ( ( domain . agentnoproxy === true ) || ( obj . args . lanonly == true ) ) { meshsettings += 'ignoreProxyFile=1\r\n' ; }
if ( obj . args . agentconfig ) { for ( var i in obj . args . agentconfig ) { meshsettings += obj . args . agentconfig [ i ] + '\r\n' ; } }
if ( domain . agentconfig ) { for ( var i in domain . agentconfig ) { meshsettings += domain . agentconfig [ i ] + '\r\n' ; } }
2018-09-07 20:09:02 -04:00
// Setup the response output
var archive = require ( 'archiver' ) ( 'zip' , { level : 5 } ) ; // Sets the compression method.
archive . on ( 'error' , function ( err ) { throw err ; } ) ;
2019-04-30 19:07:14 -04:00
try {
// Set the agent download including the mesh name.
res . set ( { 'Cache-Control' : 'no-cache, no-store, must-revalidate' , 'Pragma' : 'no-cache' , 'Expires' : '0' , 'Content-Type' : 'application/zip' , 'Content-Disposition' : 'attachment; filename="MeshAgent-' + mesh . name + '.zip"' } ) ;
} catch ( ex ) {
// If the mesh name contains invalid characters, just use a generic name.
res . set ( { 'Cache-Control' : 'no-cache, no-store, must-revalidate' , 'Pragma' : 'no-cache' , 'Expires' : '0' , 'Content-Type' : 'application/zip' , 'Content-Disposition' : 'attachment; filename="MeshAgent.zip"' } ) ;
}
2018-09-07 20:09:02 -04:00
archive . pipe ( res ) ;
// Opens the "MeshAgentOSXPackager.zip"
2019-12-27 01:53:01 -05:00
var yauzl = require ( 'yauzl' ) ;
2018-09-07 20:09:02 -04:00
yauzl . open ( obj . path . join ( _ _dirname , 'agents' , 'MeshAgentOSXPackager.zip' ) , { lazyEntries : true } , function ( err , zipfile ) {
if ( err ) { res . sendStatus ( 500 ) ; return ; }
zipfile . readEntry ( ) ;
2019-12-27 01:53:01 -05:00
zipfile . on ( 'entry' , function ( entry ) {
2018-09-07 20:09:02 -04:00
if ( /\/$/ . test ( entry . fileName ) ) {
// Skip all folder entries
zipfile . readEntry ( ) ;
} else {
2018-11-14 18:44:28 -05:00
if ( entry . fileName == 'MeshAgent.mpkg/Contents/distribution.dist' ) {
2018-09-07 20:09:02 -04:00
// This is a special file entry, we need to fix it.
zipfile . openReadStream ( entry , function ( err , readStream ) {
2019-12-27 01:53:01 -05:00
readStream . on ( 'data' , function ( data ) { if ( readStream . xxdata ) { readStream . xxdata += data ; } else { readStream . xxdata = data ; } } ) ;
readStream . on ( 'end' , function ( ) {
2018-11-14 18:44:28 -05:00
var meshname = mesh . name . split ( ']' ) . join ( '' ) . split ( '[' ) . join ( '' ) ; // We can't have ']]' in the string since it will terminate the CDATA.
var welcomemsg = 'Welcome to the MeshCentral agent for MacOS\n\nThis installer will install the mesh agent for "' + meshname + '" and allow the administrator to remotely monitor and control this computer over the internet. For more information, go to https://www.meshcommander.com/meshcentral2.\n\nThis software is provided under Apache 2.0 license.\n' ;
2018-09-07 20:09:02 -04:00
var installsize = Math . floor ( ( argentInfo . size + meshsettings . length ) / 1024 ) ;
archive . append ( readStream . xxdata . toString ( ) . split ( '###WELCOMEMSG###' ) . join ( welcomemsg ) . split ( '###INSTALLSIZE###' ) . join ( installsize ) , { name : entry . fileName } ) ;
zipfile . readEntry ( ) ;
} ) ;
} ) ;
} else {
// Normal file entry
zipfile . openReadStream ( entry , function ( err , readStream ) {
if ( err ) { throw err ; }
2018-11-14 18:44:28 -05:00
var options = { name : entry . fileName } ;
if ( entry . fileName . endsWith ( 'postflight' ) || entry . fileName . endsWith ( 'Uninstall.command' ) ) { options . mode = 493 ; }
archive . append ( readStream , options ) ;
2018-09-07 20:09:02 -04:00
readStream . on ( 'end' , function ( ) { zipfile . readEntry ( ) ; } ) ;
} ) ;
}
}
} ) ;
2019-12-27 01:53:01 -05:00
zipfile . on ( 'end' , function ( ) {
archive . file ( argentInfo . path , { name : 'MeshAgent.mpkg/Contents/Packages/internal.pkg/Contents/meshagent_osx64.bin' } ) ;
archive . append ( meshsettings , { name : 'MeshAgent.mpkg/Contents/Packages/internal.pkg/Contents/meshagent_osx64.msh' } ) ;
2018-09-07 20:09:02 -04:00
archive . finalize ( ) ;
} ) ;
} ) ;
}
2017-08-28 12:27:45 -04:00
// Handle a request to download a mesh settings
obj . handleMeshSettingsRequest = function ( req , res ) {
2020-04-07 18:17:23 -04:00
const domain = getDomain ( req ) ;
2020-03-26 15:06:34 -04:00
if ( domain == null ) { return ; }
2017-08-28 12:27:45 -04:00
//if ((domain.id !== '') || (!req.session) || (req.session == null) || (!req.session.userid)) { res.sendStatus(401); return; }
2018-01-30 21:23:57 -05:00
// If required, check if this user has rights to do this
2019-07-10 17:27:38 -04:00
if ( ( obj . parent . config . settings != null ) && ( ( obj . parent . config . settings . lockagentdownload == true ) || ( domain . lockagentdownload == true ) ) && ( req . session . userid == null ) ) { res . sendStatus ( 401 ) ; return ; }
2018-01-30 21:23:57 -05:00
2018-08-21 18:08:15 -04:00
// Fetch the mesh object
var mesh = obj . meshes [ 'mesh/' + domain . id + '/' + req . query . id ] ;
if ( mesh == null ) { res . sendStatus ( 401 ) ; return ; }
2018-01-30 21:23:57 -05:00
2018-08-21 18:08:15 -04:00
// If needed, check if this user has rights to do this
2019-07-10 17:27:38 -04:00
if ( ( obj . parent . config . settings != null ) && ( ( obj . parent . config . settings . lockagentdownload == true ) || ( domain . lockagentdownload == true ) ) ) {
2019-12-27 18:18:43 -05:00
if ( ( domain . id != mesh . domain ) || ( ( obj . GetMeshRights ( req . session . userid , mesh ) & 1 ) == 0 ) ) { res . sendStatus ( 401 ) ; return ; }
2018-08-21 18:08:15 -04:00
}
2017-10-15 20:36:06 -04:00
2019-01-02 21:03:34 -05:00
var meshidhex = Buffer . from ( req . query . id . replace ( /\@/g , '+' ) . replace ( /\$/g , '/' ) , 'base64' ) . toString ( 'hex' ) . toUpperCase ( ) ;
var serveridhex = Buffer . from ( obj . agentCertificateHashBase64 . replace ( /\@/g , '+' ) . replace ( /\$/g , '/' ) , 'base64' ) . toString ( 'hex' ) . toUpperCase ( ) ;
2017-10-15 20:36:06 -04:00
2018-08-21 18:08:15 -04:00
// Build the agent connection URL. If we are using a sub-domain or one with a DNS, we need to craft the URL correctly.
var xdomain = ( domain . dns == null ) ? domain . id : '' ;
2019-12-27 01:53:01 -05:00
if ( xdomain != '' ) xdomain += '/' ;
var meshsettings = 'MeshName=' + mesh . name + '\r\nMeshType=' + mesh . mtype + '\r\nMeshID=0x' + meshidhex + '\r\nServerID=' + serveridhex + '\r\n' ;
2018-08-21 18:08:15 -04:00
var httpsPort = ( ( obj . args . aliasport == null ) ? obj . args . port : obj . args . aliasport ) ; // Use HTTPS alias port is specified
2019-12-27 01:53:01 -05:00
if ( obj . args . lanonly != true ) { meshsettings += 'MeshServer=ws' + ( obj . args . notls ? '' : 's' ) + '://' + obj . getWebServerName ( domain ) + ':' + httpsPort + '/' + xdomain + 'agent.ashx\r\n' ; } else { meshsettings += 'MeshServer=local\r\n' ; }
if ( req . query . tag != null ) { meshsettings += 'Tag=' + req . query . tag + '\r\n' ; }
if ( ( req . query . installflags != null ) && ( req . query . installflags != 0 ) ) { meshsettings += 'InstallFlags=' + req . query . installflags + '\r\n' ; }
if ( ( domain . agentnoproxy === true ) || ( obj . args . lanonly == true ) ) { meshsettings += 'ignoreProxyFile=1\r\n' ; }
if ( obj . args . agentconfig ) { for ( var i in obj . args . agentconfig ) { meshsettings += obj . args . agentconfig [ i ] + '\r\n' ; } }
if ( domain . agentconfig ) { for ( var i in domain . agentconfig ) { meshsettings += domain . agentconfig [ i ] + '\r\n' ; } }
2017-08-28 12:27:45 -04:00
2019-04-30 18:40:19 -04:00
res . set ( { 'Cache-Control' : 'no-cache, no-store, must-revalidate' , 'Pragma' : 'no-cache' , 'Expires' : '0' , 'Content-Type' : 'application/octet-stream' , 'Content-Disposition' : 'attachment; filename="meshagent.msh"' } ) ;
2018-08-21 18:08:15 -04:00
res . send ( meshsettings ) ;
2018-08-30 15:05:23 -04:00
} ;
2017-08-28 12:27:45 -04:00
2019-03-20 15:40:02 -04:00
// Handle a request for power events
obj . handleDevicePowerEvents = function ( req , res ) {
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 15:06:34 -04:00
if ( domain == null ) { return ; }
2020-03-30 01:40:13 -04:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2019-03-20 15:40:02 -04:00
if ( ( domain . id !== '' ) || ( ! req . session ) || ( req . session == null ) || ( ! req . session . userid ) || ( req . query . id == null ) || ( typeof req . query . id != 'string' ) ) { res . sendStatus ( 401 ) ; return ; }
var x = req . query . id . split ( '/' ) ;
var user = obj . users [ req . session . userid ] ;
if ( ( x . length != 3 ) || ( x [ 0 ] != 'node' ) || ( x [ 1 ] != domain . id ) || ( user == null ) || ( user . links == null ) ) { res . sendStatus ( 401 ) ; return ; }
obj . db . Get ( req . query . id , function ( err , docs ) {
if ( docs . length != 1 ) {
res . sendStatus ( 401 ) ;
} else {
var node = docs [ 0 ] ;
// Check if we have right to this node
2020-03-30 01:40:13 -04:00
if ( obj . GetNodeRights ( user , node . meshid , node . _id ) == 0 ) { res . sendStatus ( 401 ) ; return ; }
2019-03-20 15:40:02 -04:00
// Get the list of power events and send them
2019-04-30 18:40:19 -04:00
res . set ( { 'Cache-Control' : 'no-cache, no-store, must-revalidate' , 'Pragma' : 'no-cache' , 'Expires' : '0' , 'Content-Type' : 'text/csv' , 'Content-Disposition' : 'attachment; filename="powerevents.csv"' } ) ;
2019-03-20 15:40:02 -04:00
obj . db . getPowerTimeline ( node . _id , function ( err , docs ) {
2019-10-04 15:18:56 -04:00
var xevents = [ 'Time, State, Previous State' ] , prevState = 0 ;
2019-03-20 15:40:02 -04:00
for ( var i in docs ) {
if ( docs [ i ] . power != prevState ) {
prevState = docs [ i ] . power ;
if ( docs [ i ] . oldPower != null ) {
xevents . push ( docs [ i ] . time . toString ( ) + ',' + docs [ i ] . power + ',' + docs [ i ] . oldPower ) ;
} else {
xevents . push ( docs [ i ] . time . toString ( ) + ',' + docs [ i ] . power ) ;
}
}
}
res . send ( xevents . join ( '\r\n' ) ) ;
} ) ;
}
} ) ;
}
2019-11-25 17:12:43 -05:00
if ( parent . pluginHandler != null ) {
// Handle a plugin admin request
obj . handlePluginAdminReq = function ( req , res ) {
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 15:06:34 -04:00
if ( domain == null ) { return ; }
2019-11-25 17:12:43 -05:00
if ( ( ! req . session ) || ( req . session == null ) || ( ! req . session . userid ) ) { res . sendStatus ( 401 ) ; return ; }
var user = obj . users [ req . session . userid ] ;
if ( user == null ) { res . sendStatus ( 401 ) ; return ; }
parent . pluginHandler . handleAdminReq ( req , res , user , obj ) ;
}
obj . handlePluginAdminPostReq = function ( req , res ) {
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 15:06:34 -04:00
if ( domain == null ) { return ; }
2019-11-25 17:12:43 -05:00
if ( ( ! req . session ) || ( req . session == null ) || ( ! req . session . userid ) ) { res . sendStatus ( 401 ) ; return ; }
var user = obj . users [ req . session . userid ] ;
if ( user == null ) { res . sendStatus ( 401 ) ; return ; }
parent . pluginHandler . handleAdminPostReq ( req , res , user , obj ) ;
}
obj . handlePluginJS = function ( req , res ) {
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 15:06:34 -04:00
if ( domain == null ) { return ; }
2019-11-25 17:12:43 -05:00
if ( ( ! req . session ) || ( req . session == null ) || ( ! req . session . userid ) ) { res . sendStatus ( 401 ) ; return ; }
var user = obj . users [ req . session . userid ] ;
if ( user == null ) { res . sendStatus ( 401 ) ; return ; }
parent . pluginHandler . refreshJS ( req , res ) ;
}
2019-11-22 14:25:13 -05:00
}
2019-03-20 15:40:02 -04:00
2019-01-22 18:40:08 -05:00
// Starts the HTTPS server, this should be called after the user/mesh tables are loaded
function serverStart ( ) {
// Start the server, only after users and meshes are loaded from the database.
if ( obj . args . notls || obj . args . tlsoffload ) {
// Setup the HTTP server without TLS
obj . expressWs = require ( 'express-ws' ) ( obj . app ) ;
2017-08-28 12:27:45 -04:00
} else {
2019-01-22 18:40:08 -05:00
// Setup the HTTP server with TLS, use only TLS 1.2 and higher with perfect forward secrecy (PFS).
2020-01-22 14:34:17 -05:00
//const tlsOptions = { cert: obj.certificates.web.cert, key: obj.certificates.web.key, ca: obj.certificates.web.ca, rejectUnauthorized: true, ciphers: "HIGH:!aNULL:!eNULL:!EXPORT:!RSA:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA", secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_COMPRESSION | constants.SSL_OP_CIPHER_SERVER_PREFERENCE | constants.SSL_OP_NO_TLSv1 | constants.SSL_OP_NO_TLSv1_1 }; // This does not work with TLS 1.3
const tlsOptions = { cert : obj . certificates . web . cert , key : obj . certificates . web . key , ca : obj . certificates . web . ca , rejectUnauthorized : true , ciphers : "HIGH:TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:TLS_AES_128_CCM_8_SHA256:TLS_AES_128_CCM_SHA256:TLS_CHACHA20_POLY1305_SHA256" , secureOptions : constants . SSL _OP _NO _SSLv2 | constants . SSL _OP _NO _SSLv3 | constants . SSL _OP _NO _COMPRESSION | constants . SSL _OP _CIPHER _SERVER _PREFERENCE | constants . SSL _OP _NO _TLSv1 | constants . SSL _OP _NO _TLSv1 _1 } ;
2019-01-22 18:40:08 -05:00
if ( obj . tlsSniCredentials != null ) { tlsOptions . SNICallback = TlsSniCallback ; } // We have multiple web server certificate used depending on the domain name
obj . tlsServer = require ( 'https' ) . createServer ( tlsOptions , obj . app ) ;
obj . tlsServer . on ( 'secureConnection' , function ( ) { /*console.log('tlsServer secureConnection');*/ } ) ;
2020-01-25 15:14:14 -05:00
obj . tlsServer . on ( 'error' , function ( err ) { console . log ( 'tlsServer error' , err ) ; } ) ;
//obj.tlsServer.on('tlsClientError', function (err) { console.log('tlsClientError', err); });
2019-01-22 18:40:08 -05:00
obj . tlsServer . on ( 'newSession' , function ( id , data , cb ) { if ( tlsSessionStoreCount > 1000 ) { tlsSessionStoreCount = 0 ; tlsSessionStore = { } ; } tlsSessionStore [ id . toString ( 'hex' ) ] = data ; tlsSessionStoreCount ++ ; cb ( ) ; } ) ;
obj . tlsServer . on ( 'resumeSession' , function ( id , cb ) { cb ( null , tlsSessionStore [ id . toString ( 'hex' ) ] || null ) ; } ) ;
obj . expressWs = require ( 'express-ws' ) ( obj . app , obj . tlsServer ) ;
2017-08-28 12:27:45 -04:00
}
2019-01-22 18:40:08 -05:00
// Setup middleware
2019-05-14 20:14:43 -04:00
obj . app . engine ( 'handlebars' , obj . exphbs ( { defaultLayout : null } ) ) ; // defaultLayout: 'main'
2019-01-22 18:40:08 -05:00
obj . app . set ( 'view engine' , 'handlebars' ) ;
2019-10-07 14:38:01 -04:00
if ( obj . args . trustedproxy ) { obj . app . set ( 'trust proxy' , obj . args . trustedproxy ) ; } // Reverse proxy should add the "X-Forwarded-*" headers
else if ( obj . args . tlsoffload ) { obj . app . set ( 'trust proxy' , obj . args . tlsoffload ) ; } // Reverse proxy should add the "X-Forwarded-*" headers
2019-01-22 18:40:08 -05:00
obj . app . use ( obj . bodyParser . urlencoded ( { extended : false } ) ) ;
var sessionOptions = {
name : 'xid' , // Recommended security practice to not use the default cookie name
httpOnly : true ,
keys : [ obj . args . sessionkey ] , // If multiple instances of this server are behind a load-balancer, this secret must be the same for all instances
2019-08-26 17:43:15 -04:00
secure : ( ( obj . args . notls != true ) && ( obj . args . tlsoffload == null ) ) // Use this cookie only over TLS (Check this: https://expressjs.com/en/guide/behind-proxies.html)
2019-01-22 18:40:08 -05:00
}
2019-09-12 19:24:35 -04:00
if ( obj . args . sessionsamesite != null ) { sessionOptions . sameSite = obj . args . sessionsamesite ; } else { sessionOptions . sameSite = 'strict' ; }
2019-01-22 18:40:08 -05:00
if ( obj . args . sessiontime != null ) { sessionOptions . maxAge = ( obj . args . sessiontime * 60 * 1000 ) ; }
obj . app . use ( obj . session ( sessionOptions ) ) ;
// Add HTTP security headers to all responses
obj . app . use ( function ( req , res , next ) {
2020-01-26 13:05:27 -05:00
parent . debug ( 'webrequest' , '(' + cleanRemoteAddr ( req . ip ) + ') ' + req . url ) ;
2019-12-20 17:50:06 -05:00
res . removeHeader ( 'X-Powered-By' ) ;
2019-01-22 18:40:08 -05:00
var domain = req . xdomain = getDomain ( req ) ;
2019-02-15 17:50:23 -05:00
// If this domain has configured headers, use them.
// Example headers: { 'Strict-Transport-Security': 'max-age=360000;includeSubDomains' };
// { 'Referrer-Policy': 'no-referrer', 'x-frame-options': 'SAMEORIGIN', 'X-XSS-Protection': '1; mode=block', 'X-Content-Type-Options': 'nosniff', 'Content-Security-Policy': "default-src http: ws: data: 'self';script-src http: 'unsafe-inline';style-src http: 'unsafe-inline'" };
2019-09-12 18:12:40 -04:00
if ( ( domain != null ) && ( domain . httpheaders != null ) && ( typeof domain . httpheaders == 'object' ) ) {
res . set ( domain . httpheaders ) ;
2019-09-23 14:45:10 -04:00
} else {
2019-09-12 18:12:40 -04:00
// Use default security headers
2019-10-11 14:16:36 -04:00
var geourl = ( domain . geolocation ? ' *.openstreetmap.org' : '' ) ;
2020-01-31 17:44:11 -05:00
var selfurl = ( ( args . notls !== true ) ? ( ' wss://' + req . headers . host ) : ( ' ws://' + req . headers . host ) ) ;
2019-10-22 20:51:37 -04:00
var headers = {
'Referrer-Policy' : 'no-referrer' ,
'X-XSS-Protection' : '1; mode=block' ,
'X-Content-Type-Options' : 'nosniff' ,
2020-01-12 16:50:06 -05:00
'Content-Security-Policy' : "default-src 'none'; script-src 'self' 'unsafe-inline'; connect-src 'self'" + geourl + selfurl + "; img-src 'self'" + geourl + " data:; style-src 'self' 'unsafe-inline'; frame-src 'self'; media-src 'self'; form-action 'self'"
2019-10-22 20:51:37 -04:00
} ;
if ( ( parent . config . settings . allowframing !== true ) && ( typeof parent . config . settings . allowframing !== 'string' ) ) { headers [ 'X-Frame-Options' ] = 'sameorigin' ; }
res . set ( headers ) ;
2019-09-12 18:12:40 -04:00
}
2019-02-15 17:50:23 -05:00
2019-09-12 19:24:35 -04:00
// Check the session if bound to the external IP address
2019-09-23 14:45:10 -04:00
if ( ( req . session . ip != null ) && ( req . ip != null ) && ( req . session . ip != req . ip ) ) { req . session = { } ; }
2019-09-12 19:24:35 -04:00
2019-01-22 18:40:08 -05:00
// Detect if this is a file sharing domain, if so, just share files.
if ( ( domain != null ) && ( domain . share != null ) ) {
var rpath ;
if ( domain . dns == null ) { rpath = req . url . split ( '/' ) ; rpath . splice ( 1 , 1 ) ; rpath = rpath . join ( '/' ) ; } else { rpath = req . url ; }
if ( ( res . headers != null ) && ( res . headers . upgrade ) ) {
// If this is a websocket, stop here.
res . sendStatus ( 404 ) ;
} else {
// Check if the file exists, if so, serve it.
obj . fs . exists ( obj . path . join ( domain . share , rpath ) , function ( exists ) { if ( exists == true ) { res . sendfile ( rpath , { root : domain . share } ) ; } else { res . sendStatus ( 404 ) ; } } ) ;
}
2018-09-26 17:58:55 -04:00
} else {
2019-02-15 17:50:23 -05:00
//if (parent.config.settings.accesscontrolalloworigin != null) { headers['Access-Control-Allow-Origin'] = parent.config.settings.accesscontrolalloworigin; }
2019-01-22 18:40:08 -05:00
return next ( ) ;
2018-09-26 17:58:55 -04:00
}
} ) ;
2019-01-22 18:40:08 -05:00
// Setup all HTTP handlers
if ( parent . multiServer != null ) { obj . app . ws ( '/meshserver.ashx' , function ( ws , req ) { parent . multiServer . CreatePeerInServer ( parent . multiServer , ws , req ) ; } ) ; }
for ( var i in parent . config . domains ) {
if ( parent . config . domains [ i ] . dns != null ) { continue ; } // This is a subdomain with a DNS name, no added HTTP bindings needed.
var url = parent . config . domains [ i ] . url ;
obj . app . get ( url , handleRootRequest ) ;
2019-08-26 15:20:24 -04:00
obj . app . post ( url , handleRootPostRequest ) ;
2019-05-24 18:11:00 -04:00
obj . app . get ( url + 'backup.zip' , handleBackupRequest ) ;
obj . app . post ( url + 'restoreserver.ashx' , handleRestoreRequest ) ;
2019-01-22 18:40:08 -05:00
obj . app . get ( url + 'terms' , handleTermsRequest ) ;
2020-01-23 18:15:56 -05:00
obj . app . get ( url + 'xterm' , handleXTermRequest ) ;
2019-01-22 18:40:08 -05:00
obj . app . post ( url + 'login' , handleLoginRequest ) ;
obj . app . post ( url + 'tokenlogin' , handleLoginRequest ) ;
obj . app . get ( url + 'logout' , handleLogoutRequest ) ;
obj . app . get ( url + 'MeshServerRootCert.cer' , handleRootCertRequest ) ;
obj . app . get ( url + 'mescript.ashx' , handleMeScriptRequest ) ;
obj . app . post ( url + 'changepassword' , handlePasswordChangeRequest ) ;
obj . app . post ( url + 'deleteaccount' , handleDeleteAccountRequest ) ;
obj . app . post ( url + 'createaccount' , handleCreateAccountRequest ) ;
2019-02-27 21:48:50 -05:00
obj . app . post ( url + 'resetpassword' , handleResetPasswordRequest ) ;
2019-01-22 18:40:08 -05:00
obj . app . post ( url + 'resetaccount' , handleResetAccountRequest ) ;
obj . app . get ( url + 'checkmail' , handleCheckMailRequest ) ;
2019-06-03 16:15:38 -04:00
obj . app . get ( url + 'agentinvite' , handleAgentInviteRequest ) ;
2019-01-22 18:40:08 -05:00
obj . app . post ( url + 'amtevents.ashx' , obj . handleAmtEventRequest ) ;
obj . app . get ( url + 'meshagents' , obj . handleMeshAgentRequest ) ;
obj . app . get ( url + 'messenger' , handleMessengerRequest ) ;
obj . app . get ( url + 'meshosxagent' , obj . handleMeshOsxAgentRequest ) ;
obj . app . get ( url + 'meshsettings' , obj . handleMeshSettingsRequest ) ;
2019-03-20 15:40:02 -04:00
obj . app . get ( url + 'devicepowerevents.ashx' , obj . handleDevicePowerEvents ) ;
2019-01-22 18:40:08 -05:00
obj . app . get ( url + 'downloadfile.ashx' , handleDownloadFile ) ;
obj . app . post ( url + 'uploadfile.ashx' , handleUploadFile ) ;
obj . app . post ( url + 'uploadmeshcorefile.ashx' , handleUploadMeshCoreFile ) ;
obj . app . get ( url + 'userfiles/*' , handleDownloadUserFiles ) ;
obj . app . ws ( url + 'echo.ashx' , handleEchoWebSocket ) ;
2019-10-04 15:18:56 -04:00
obj . app . ws ( url + 'apf.ashx' , function ( ws , req ) { obj . parent . apfserver . onConnection ( ws ) ; } )
2019-01-22 18:40:08 -05:00
obj . app . ws ( url + 'meshrelay.ashx' , function ( ws , req ) { PerformWSSessionAuth ( ws , req , true , function ( ws1 , req1 , domain , user , cookie ) { obj . meshRelayHandler . CreateMeshRelay ( obj , ws1 , req1 , domain , user , cookie ) ; } ) ; } ) ;
obj . app . get ( url + 'webrelay.ashx' , function ( req , res ) { res . send ( 'Websocket connection expected' ) ; } ) ;
2019-05-14 14:30:08 -04:00
obj . app . get ( url + 'health.ashx' , function ( req , res ) { res . send ( 'ok' ) ; } ) ; // TODO: Perform more server checking.
2019-01-22 18:40:08 -05:00
obj . app . ws ( url + 'webrelay.ashx' , function ( ws , req ) { PerformWSSessionAuth ( ws , req , false , handleRelayWebSocket ) ; } ) ;
2019-04-23 22:17:33 -04:00
obj . app . ws ( url + 'webider.ashx' , function ( ws , req ) { PerformWSSessionAuth ( ws , req , false , function ( ws1 , req1 , domain , user , cookie ) { obj . meshIderHandler . CreateAmtIderSession ( obj , obj . db , ws1 , req1 , obj . args , domain , user ) ; } ) ; } ) ;
2019-01-22 18:40:08 -05:00
obj . app . ws ( url + 'control.ashx' , function ( ws , req ) { PerformWSSessionAuth ( ws , req , false , function ( ws1 , req1 , domain , user , cookie ) { obj . meshUserHandler . CreateMeshUser ( obj , obj . db , ws1 , req1 , obj . args , domain , user ) ; } ) ; } ) ;
obj . app . get ( url + 'logo.png' , handleLogoRequest ) ;
2019-12-05 18:33:22 -05:00
obj . app . post ( url + 'translations' , handleTranslationsRequest ) ;
2019-02-13 16:22:52 -05:00
obj . app . get ( url + 'welcome.jpg' , handleWelcomeImageRequest ) ;
2020-02-04 19:22:00 -05:00
obj . app . get ( url + 'player.htm' , handlePlayerRequest ) ;
obj . app . get ( url + 'player' , handlePlayerRequest ) ;
2019-06-27 19:04:52 -04:00
obj . app . ws ( url + 'amtactivate' , handleAmtActivateWebSocket ) ;
2020-03-16 21:03:14 -04:00
if ( parent . config . domains [ i ] . agentinvitecodes == true ) {
obj . app . get ( url + 'invite' , handleInviteRequest ) ;
obj . app . post ( url + 'invite' , handleInviteRequest ) ;
}
2019-11-02 06:27:39 -04:00
if ( parent . pluginHandler != null ) {
obj . app . get ( url + 'pluginadmin.ashx' , obj . handlePluginAdminReq ) ;
obj . app . post ( url + 'pluginadmin.ashx' , obj . handlePluginAdminPostReq ) ;
2019-11-22 14:25:13 -05:00
obj . app . get ( url + 'pluginHandler.js' , obj . handlePluginJS ) ;
2019-11-02 06:27:39 -04:00
}
2019-01-22 18:40:08 -05:00
2019-06-07 13:20:54 -04:00
// Server redirects
2019-08-12 17:58:06 -04:00
if ( parent . config . domains [ i ] . redirects ) { for ( var j in parent . config . domains [ i ] . redirects ) { if ( j [ 0 ] != '_' ) { obj . app . get ( url + j , obj . handleDomainRedirect ) ; } } }
2019-06-07 13:20:54 -04:00
2019-01-22 18:40:08 -05:00
// Server picture
obj . app . get ( url + 'serverpic.ashx' , function ( req , res ) {
2019-02-02 17:54:36 -05:00
// Check if we have "server.jpg" in the data folder, if so, use that.
2019-05-11 17:02:42 -04:00
if ( ( parent . configurationFiles != null ) && ( parent . configurationFiles [ 'server.png' ] != null ) ) {
res . set ( { 'Content-Type' : 'image/png' } ) ;
res . send ( parent . configurationFiles [ 'server.png' ] ) ;
2019-01-22 18:40:08 -05:00
} else {
2019-02-02 17:54:36 -05:00
// Check if we have "server.jpg" in the data folder, if so, use that.
2019-05-11 17:02:42 -04:00
var p = obj . path . join ( obj . parent . datapath , 'server.png' ) ;
2019-02-02 17:54:36 -05:00
if ( obj . fs . existsSync ( p ) ) {
// Use the data folder server picture
2019-09-15 15:47:59 -04:00
try { res . sendFile ( p ) ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
2019-02-02 17:54:36 -05:00
} else {
2020-03-25 16:21:14 -04:00
var domain = getDomain ( req ) ;
if ( ( domain != null ) && ( domain . webpublicpath != null ) && ( obj . fs . existsSync ( obj . path . join ( domain . webpublicpath , 'images/server-256.png' ) ) ) ) {
// Use the domain server picture
try { res . sendFile ( obj . path . join ( domain . webpublicpath , 'images/server-256.png' ) ) ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
} else if ( parent . webPublicOverridePath && obj . fs . existsSync ( obj . path . join ( obj . parent . webPublicOverridePath , 'images/server-256.png' ) ) ) {
2019-09-15 15:47:59 -04:00
// Use the override server picture
try { res . sendFile ( obj . path . join ( obj . parent . webPublicOverridePath , 'images/server-256.png' ) ) ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
} else {
// Use the default server picture
try { res . sendFile ( obj . path . join ( obj . parent . webPublicPath , 'images/server-256.png' ) ) ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
}
2019-02-02 17:54:36 -05:00
}
2019-01-22 18:40:08 -05:00
}
} ) ;
2017-08-28 12:27:45 -04:00
2019-01-22 18:40:08 -05:00
// Receive mesh agent connections
obj . app . ws ( url + 'agent.ashx' , function ( ws , req ) {
2019-01-30 16:43:42 -05:00
var domain = checkAgentIpAddress ( ws , req ) ;
2020-03-26 15:06:34 -04:00
if ( domain == null ) { parent . debug ( 'web' , 'Got agent connection with bad domain or blocked IP address ' + cleanRemoteAddr ( req . ip ) + ', holding.' ) ; return ; }
2019-08-31 22:40:50 -04:00
//console.log('Agent connect: ' + cleanRemoteAddr(req.ip));
2019-01-30 16:43:42 -05:00
try { obj . meshAgentHandler . CreateMeshAgent ( obj , obj . db , ws , req , obj . args , domain ) ; } catch ( e ) { console . log ( e ) ; }
2019-01-22 18:40:08 -05:00
} ) ;
2019-10-04 15:18:56 -04:00
// Setup MQTT broker over websocket
if ( obj . parent . mqttbroker != null ) {
obj . app . ws ( url + 'mqtt.ashx' , function ( ws , req ) {
2019-10-04 20:24:30 -04:00
var domain = checkAgentIpAddress ( ws , req ) ;
2020-03-26 15:06:34 -04:00
if ( domain == null ) { parent . debug ( 'web' , 'Got agent connection with bad domain or blocked IP address ' + cleanRemoteAddr ( req . ip ) + ', holding.' ) ; return ; }
2019-10-04 20:24:30 -04:00
var serialtunnel = SerialTunnel ( ) ;
serialtunnel . xtransport = 'ws' ;
serialtunnel . xdomain = domain ;
serialtunnel . xip = req . ip ;
ws . on ( 'message' , function ( b ) { serialtunnel . updateBuffer ( Buffer . from ( b , 'binary' ) ) } ) ;
2019-12-20 17:50:06 -05:00
serialtunnel . forwardwrite = function ( b ) { ws . send ( b , 'binary' ) }
ws . on ( 'close' , function ( ) { serialtunnel . emit ( 'end' ) ; } ) ;
2019-10-04 20:24:30 -04:00
obj . parent . mqttbroker . handle ( serialtunnel ) ; // Pass socket wrapper to MQTT broker
2019-10-04 15:18:56 -04:00
} ) ;
}
2019-10-02 20:57:18 -04:00
2019-03-14 15:28:58 -04:00
// Memory Tracking
if ( typeof obj . args . memorytracking == 'number' ) {
obj . app . get ( url + 'memorytracking.csv' , function ( req , res ) {
try { res . sendFile ( obj . parent . getConfigFilePath ( 'memorytracking.csv' ) ) ; } catch ( e ) { res . sendStatus ( 404 ) ; }
} ) ;
}
2019-01-22 18:40:08 -05:00
// Creates a login token using the user/pass that is passed in as URL arguments.
// For example: https://localhost/createLoginToken.ashx?user=admin&pass=admin&a=3
// It's not advised to use this to create login tokens since the URL is often logged and you got credentials in the URL.
2019-02-18 17:32:55 -05:00
// Since it's bad, it's only offered when an untrusted certificate is used as a way to help developers get started.
2019-10-05 17:24:40 -04:00
if ( obj . isTrustedCert ( ) == false ) {
2019-02-18 17:32:55 -05:00
obj . app . get ( url + 'createLoginToken.ashx' , function ( req , res ) {
// A web socket session can be authenticated in many ways (Default user, session, user/pass and cookie). Check authentication here.
if ( ( req . query . user != null ) && ( req . query . pass != null ) ) {
// A user/pass is provided in URL arguments
obj . authenticate ( req . query . user , req . query . pass , getDomain ( req ) , function ( err , userid ) {
if ( ( err == null ) && ( obj . users [ userid ] ) ) {
// User is authenticated, create a token
var x = { a : 3 } ; for ( var i in req . query ) { if ( ( i != 'user' ) && ( i != 'pass' ) ) { x [ i ] = obj . common . toNumber ( req . query [ i ] ) ; } } x . u = userid ;
res . send ( obj . parent . encodeCookie ( x , obj . parent . loginCookieEncryptionKey ) ) ;
} else {
res . sendStatus ( 404 ) ;
}
} ) ;
} else {
res . sendStatus ( 404 ) ;
}
} ) ;
}
2018-12-29 00:55:23 -05:00
2019-11-02 04:50:49 -04:00
//obj.app.get(url + 'stop', function (req, res) { res.send('Stopping Server, <a href="' + url + '">click here to login</a>.'); setTimeout(function () { parent.Stop(); }, 500); });
2017-08-28 12:27:45 -04:00
2019-08-25 14:08:32 -04:00
// Indicates to ExpressJS that the override public folder should be used to serve static files.
2020-03-25 16:21:14 -04:00
if ( parent . config . domains [ i ] . webpublicpath != null ) {
// Use domain public path
obj . app . use ( url , obj . express . static ( parent . config . domains [ i ] . webpublicpath ) ) ;
} else if ( obj . parent . webPublicOverridePath != null ) {
// Use override path
obj . app . use ( url , obj . express . static ( obj . parent . webPublicOverridePath ) ) ;
}
2019-08-25 14:08:32 -04:00
// Indicates to ExpressJS that the default public folder should be used to serve static files.
2019-12-20 17:50:06 -05:00
obj . app . use ( url , obj . express . static ( obj . parent . webPublicPath ) ) ;
2019-01-22 22:34:55 -05:00
// Start regular disconnection list flush every 2 minutes.
obj . wsagentsDisconnectionsTimer = setInterval ( function ( ) { obj . wsagentsDisconnections = { } ; } , 120000 ) ;
2019-01-22 18:40:08 -05:00
}
2019-08-22 19:39:50 -04:00
// Handle 404 error
if ( obj . args . nice404 !== false ) {
obj . app . use ( function ( req , res , next ) {
parent . debug ( 'web' , '404 Error ' + req . url ) ;
var domain = getDomain ( req ) ;
2020-02-27 15:55:14 -05:00
if ( ( domain == null ) || ( domain . auth == 'sspi' ) ) { res . sendStatus ( 404 ) ; return ; }
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2020-03-25 16:21:14 -04:00
res . status ( 404 ) . render ( getRenderPage ( 'error404' , req , domain ) , getRenderArgs ( { } , domain ) ) ;
2019-08-22 19:39:50 -04:00
} ) ;
}
2019-10-04 15:18:56 -04:00
2019-01-22 18:40:08 -05:00
// Start server on a free port
CheckListenPort ( obj . args . port , StartWebServer ) ;
2017-08-28 12:27:45 -04:00
}
2018-10-13 19:04:04 -04:00
// Authenticates a session and forwards
2018-10-15 20:51:03 -04:00
function PerformWSSessionAuth ( ws , req , noAuthOk , func ) {
2019-11-18 20:31:42 -05:00
// Check if this is a banned ip address
if ( obj . checkAllowLogin ( req ) == false ) { try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'banned' , msg : 'banned-1' } ) ) ; ws . close ( ) ; } catch ( e ) { } return ; }
2018-10-13 19:04:04 -04:00
try {
2018-10-16 13:52:05 -04:00
// Hold this websocket until we are ready.
2018-12-01 00:23:10 -05:00
ws . _socket . pause ( ) ;
2018-10-16 13:52:05 -04:00
2018-10-15 20:21:37 -04:00
// Check IP filtering and domain
2019-01-04 17:35:01 -05:00
var domain = null ;
2020-03-26 15:06:34 -04:00
if ( noAuthOk == true ) {
domain = getDomain ( req ) ;
2020-03-27 02:01:49 -04:00
if ( domain == null ) { try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'noauth-1' } ) ) ; ws . close ( ) ; return ; } catch ( e ) { } return ; }
2020-03-26 15:06:34 -04:00
} else {
// If authentication is required, enforce IP address filtering.
domain = checkUserIpAddress ( ws , req ) ;
if ( domain == null ) { return ; }
}
2018-10-15 20:21:37 -04:00
2020-04-21 04:50:27 -04:00
var emailcheck = ( ( obj . parent . mailserver != null ) && ( obj . parent . certificates . CommonName != null ) && ( obj . parent . certificates . CommonName . indexOf ( '.' ) != - 1 ) && ( obj . args . lanonly != true ) && ( domain . auth != 'sspi' ) && ( domain . auth != 'ldap' ) )
2018-10-15 20:21:37 -04:00
// A web socket session can be authenticated in many ways (Default user, session, user/pass and cookie). Check authentication here.
if ( ( req . query . user != null ) && ( req . query . pass != null ) ) {
// A user/pass is provided in URL arguments
obj . authenticate ( req . query . user , req . query . pass , domain , function ( err , userid ) {
2019-03-21 14:45:08 -04:00
var user = obj . users [ userid ] ;
if ( ( err == null ) && ( user ) ) {
// Check if a 2nd factor is needed
2019-11-28 23:57:34 -05:00
if ( checkUserOneTimePasswordRequired ( domain , user , req ) == true ) {
2020-03-22 01:52:23 -04:00
// Figure out if email 2FA is allowed
var email2fa = ( ( ( typeof domain . passwordrequirements != 'object' ) || ( domain . passwordrequirements . email2factor != false ) ) && ( parent . mailserver != null ) && ( user . otpekey != null ) ) ;
if ( ( typeof req . query . token != 'string' ) || ( req . query . token == '**email**' ) ) {
if ( ( req . query . token == '**email**' ) && ( email2fa == true ) ) {
// Cause a token to be sent to the user's registered email
user . otpekey = { k : obj . common . zeroPad ( getRandomEightDigitInteger ( ) , 8 ) , d : Date . now ( ) } ;
obj . db . SetUser ( user ) ;
parent . debug ( 'web' , 'Sending 2FA email to: ' + user . email ) ;
2020-04-04 22:29:20 -04:00
parent . mailserver . sendAccountLoginMail ( domain , user . email , user . otpekey . k , obj . getLanguageCodes ( req ) ) ;
2020-03-22 01:52:23 -04:00
// Ask for a login token & confirm email was sent
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'tokenrequired' , email2fa : email2fa , email2fasent : true } ) ) ; ws . close ( ) ; } catch ( e ) { }
} else {
// Ask for a login token
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'tokenrequired' , email2fa : email2fa } ) ) ; ws . close ( ) ; } catch ( e ) { }
}
2019-03-21 14:45:08 -04:00
} else {
checkUserOneTimePassword ( req , domain , user , req . query . token , null , function ( result ) {
if ( result == false ) {
2020-03-22 01:52:23 -04:00
// Failed, ask for a login token again
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'tokenrequired' , email2fa : email2fa } ) ) ; ws . close ( ) ; } catch ( e ) { }
2019-03-21 14:45:08 -04:00
} else {
// We are authenticated with 2nd factor.
2020-04-21 04:50:27 -04:00
// Check email verification
if ( emailcheck && ( user . email != null ) && ( user . emailVerified !== true ) ) {
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'emailvalidation' , msg : 'emailvalidationrequired' , email2fa : email2fa , email2fasent : true } ) ) ; ws . close ( ) ; } catch ( e ) { }
} else {
func ( ws , req , domain , user ) ;
}
2019-03-21 14:45:08 -04:00
}
} ) ;
}
} else {
2020-04-21 04:50:27 -04:00
// Check email verification
if ( emailcheck && ( user . email != null ) && ( user . emailVerified !== true ) ) {
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'emailvalidation' , msg : 'emailvalidationrequired' , email2fa : email2fa , email2fasent : true } ) ) ; ws . close ( ) ; } catch ( e ) { }
} else {
// We are authenticated
func ( ws , req , domain , user ) ;
}
2019-03-21 14:45:08 -04:00
}
2018-10-13 19:04:04 -04:00
} else {
2018-10-16 13:52:05 -04:00
// Failed to authenticate, see if a default user is active
if ( obj . args . user && obj . users [ 'user/' + domain . id + '/' + obj . args . user . toLowerCase ( ) ] ) {
// A default user is active
func ( ws , req , domain , obj . users [ 'user/' + domain . id + '/' + obj . args . user . toLowerCase ( ) ] ) ;
} else {
// If not authenticated, close the websocket connection
2019-08-22 18:31:39 -04:00
parent . debug ( 'web' , 'ERR: Websocket bad user/pass auth' ) ;
2019-11-17 19:20:53 -05:00
//obj.parent.DispatchEvent(['*', 'server-users', 'user/' + domain.id + '/' + obj.args.user.toLowerCase()], obj, { action: 'authfail', userid: 'user/' + domain.id + '/' + obj.args.user.toLowerCase(), username: obj.args.user, domain: domain.id, msg: 'Invalid user login attempt from ' + cleanRemoteAddr(req.ip) });
2019-11-18 20:31:42 -05:00
//obj.setbadLogin(req);
2019-01-28 18:47:54 -05:00
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'noauth-2' } ) ) ; ws . close ( ) ; } catch ( e ) { }
2018-10-16 13:52:05 -04:00
}
2018-10-13 19:04:04 -04:00
}
2018-10-15 20:21:37 -04:00
} ) ;
return ;
2019-01-28 18:47:54 -05:00
} else if ( ( req . query . auth != null ) && ( req . query . auth != '' ) ) {
2018-10-15 20:21:37 -04:00
// This is a encrypted cookie authentication
2019-01-28 18:47:54 -05:00
var cookie = obj . parent . decodeCookie ( req . query . auth , obj . parent . loginCookieEncryptionKey , 240 ) ; // Cookie with 4 hour timeout
2019-08-14 19:51:45 -04:00
if ( ( cookie == null ) && ( obj . parent . multiServer != null ) ) { cookie = obj . parent . decodeCookie ( req . query . auth , obj . parent . serverKey , 240 ) ; } // Try the server key
2020-01-26 13:05:27 -05:00
if ( ( obj . args . cookieipcheck !== false ) && ( cookie != null ) && ( cookie . ip != null ) && ( cookie . ip != cleanRemoteAddr ( req . ip ) && ( cookie . ip != req . ip ) ) ) { // If the cookie if binded to an IP address, check here.
parent . debug ( 'web' , 'ERR: Invalid cookie IP address, got \"' + cookie . ip + '\", expected \"' + cleanRemoteAddr ( req . ip ) + '\".' ) ;
cookie = null ;
}
2019-01-28 18:47:54 -05:00
if ( ( cookie != null ) && ( obj . users [ cookie . userid ] ) && ( cookie . domainid == domain . id ) ) {
2018-10-15 20:21:37 -04:00
// Valid cookie, we are authenticated
func ( ws , req , domain , obj . users [ cookie . userid ] , cookie ) ;
2018-10-13 19:04:04 -04:00
} else {
2019-01-28 18:47:54 -05:00
// This is a bad cookie, keep going anyway, maybe we have a active session that will save us.
2020-01-26 13:05:27 -05:00
if ( ( cookie != null ) && ( cookie . domainid != domain . id ) ) { parent . debug ( 'web' , 'ERR: Invalid domain, got \"' + cookie . domainid + '\", expected \"' + domain . id + '\".' ) ; }
parent . debug ( 'web' , 'ERR: Websocket bad cookie auth (Cookie:' + ( cookie != null ) + '): ' + req . query . auth ) ;
2019-03-21 14:45:08 -04:00
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'noauth-2' } ) ) ; ws . close ( ) ; } catch ( e ) { }
2018-10-13 19:04:04 -04:00
}
2019-03-21 14:45:08 -04:00
return ;
} else if ( req . headers [ 'x-meshauth' ] != null ) {
// This is authentication using a custom HTTP header
var s = req . headers [ 'x-meshauth' ] . split ( ',' ) ;
for ( var i in s ) { s [ i ] = Buffer . from ( s [ i ] , 'base64' ) . toString ( ) ; }
if ( ( s . length < 2 ) || ( s . length > 3 ) ) { try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'noauth-2' } ) ) ; ws . close ( ) ; } catch ( e ) { } return ; }
obj . authenticate ( s [ 0 ] , s [ 1 ] , domain , function ( err , userid ) {
var user = obj . users [ userid ] ;
if ( ( err == null ) && ( user ) ) {
// Check if a 2nd factor is needed
2019-11-28 23:57:34 -05:00
if ( checkUserOneTimePasswordRequired ( domain , user , req ) == true ) {
2020-03-22 01:52:23 -04:00
// Figure out if email 2FA is allowed
var email2fa = ( ( ( typeof domain . passwordrequirements != 'object' ) || ( domain . passwordrequirements . email2factor != false ) ) && ( parent . mailserver != null ) && ( user . otpekey != null ) ) ;
2019-03-21 14:45:08 -04:00
if ( s . length != 3 ) {
2020-03-22 01:52:23 -04:00
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'tokenrequired' , email2fa : email2fa } ) ) ; ws . close ( ) ; } catch ( e ) { }
2019-03-21 14:45:08 -04:00
} else {
checkUserOneTimePassword ( req , domain , user , s [ 2 ] , null , function ( result ) {
if ( result == false ) {
2020-03-22 01:52:23 -04:00
if ( ( s [ 2 ] == '**email**' ) && ( email2fa == true ) ) {
// Cause a token to be sent to the user's registered email
user . otpekey = { k : obj . common . zeroPad ( getRandomEightDigitInteger ( ) , 8 ) , d : Date . now ( ) } ;
obj . db . SetUser ( user ) ;
parent . debug ( 'web' , 'Sending 2FA email to: ' + user . email ) ;
2020-04-04 22:29:20 -04:00
parent . mailserver . sendAccountLoginMail ( domain , user . email , user . otpekey . k , obj . getLanguageCodes ( req ) ) ;
2020-03-22 01:52:23 -04:00
// Ask for a login token & confirm email was sent
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'tokenrequired' , email2fa : email2fa , email2fasent : true } ) ) ; ws . close ( ) ; } catch ( e ) { }
} else {
// Ask for a login token
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'tokenrequired' , email2fa : email2fa } ) ) ; ws . close ( ) ; } catch ( e ) { }
}
2019-03-21 14:45:08 -04:00
} else {
// We are authenticated with 2nd factor.
2020-04-21 04:50:27 -04:00
// Check email verification
if ( emailcheck && ( user . email != null ) && ( user . emailVerified !== true ) ) {
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'emailvalidation' , msg : 'emailvalidationrequired' , email2fa : email2fa , email2fasent : true } ) ) ; ws . close ( ) ; } catch ( e ) { }
} else {
func ( ws , req , domain , user ) ;
}
2019-03-21 14:45:08 -04:00
}
} ) ;
}
} else {
// We are authenticated
2020-04-21 04:50:27 -04:00
// Check email verification
if ( emailcheck && ( user . email != null ) && ( user . emailVerified !== true ) ) {
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'emailvalidation' , msg : 'emailvalidationrequired' , email2fa : email2fa , email2fasent : true } ) ) ; ws . close ( ) ; } catch ( e ) { }
} else {
func ( ws , req , domain , user ) ;
} }
2019-03-21 14:45:08 -04:00
} else {
// Failed to authenticate, see if a default user is active
if ( obj . args . user && obj . users [ 'user/' + domain . id + '/' + obj . args . user . toLowerCase ( ) ] ) {
// A default user is active
func ( ws , req , domain , obj . users [ 'user/' + domain . id + '/' + obj . args . user . toLowerCase ( ) ] ) ;
} else {
// If not authenticated, close the websocket connection
2019-08-22 18:31:39 -04:00
parent . debug ( 'web' , 'ERR: Websocket bad user/pass auth' ) ;
2019-03-21 14:45:08 -04:00
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'noauth-2' } ) ) ; ws . close ( ) ; } catch ( e ) { }
}
}
} ) ;
return ;
2019-01-28 18:47:54 -05:00
}
2019-03-21 14:45:08 -04:00
//console.log(req.headers['x-meshauth']);
2019-01-28 18:47:54 -05:00
if ( obj . args . user && obj . users [ 'user/' + domain . id + '/' + obj . args . user . toLowerCase ( ) ] ) {
2018-10-15 20:21:37 -04:00
// A default user is active
func ( ws , req , domain , obj . users [ 'user/' + domain . id + '/' + obj . args . user . toLowerCase ( ) ] ) ;
return ;
2018-10-16 13:52:05 -04:00
} else if ( req . session && ( req . session . userid != null ) && ( req . session . domainid == domain . id ) && ( obj . users [ req . session . userid ] ) ) {
2018-10-15 20:21:37 -04:00
// This user is logged in using the ExpressJS session
2018-10-16 13:52:05 -04:00
func ( ws , req , domain , obj . users [ req . session . userid ] ) ;
2018-10-15 20:21:37 -04:00
return ;
2018-10-13 19:04:04 -04:00
}
2018-10-15 20:51:03 -04:00
if ( noAuthOk != true ) {
// If not authenticated, close the websocket connection
2019-08-22 18:31:39 -04:00
parent . debug ( 'web' , 'ERR: Websocket no auth' ) ;
2019-01-28 18:47:54 -05:00
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'noauth-4' } ) ) ; ws . close ( ) ; } catch ( e ) { }
2018-10-15 20:51:03 -04:00
} else {
// Continue this session without user authentication,
// this is expected if the agent is connecting for a tunnel.
func ( ws , req , domain , null ) ;
}
2018-10-13 19:04:04 -04:00
} catch ( e ) { console . log ( e ) ; }
}
2017-08-28 12:27:45 -04:00
// Find a free port starting with the specified one and going up.
function CheckListenPort ( port , func ) {
var s = obj . net . createServer ( function ( socket ) { } ) ;
obj . tcpServer = s . listen ( port , function ( ) { s . close ( function ( ) { if ( func ) { func ( port ) ; } } ) ; } ) . on ( 'error' , function ( err ) {
2019-01-02 21:34:50 -05:00
if ( args . exactports ) { console . error ( 'ERROR: MeshCentral HTTPS server port ' + port + ' not available.' ) ; process . exit ( ) ; }
2017-08-28 12:27:45 -04:00
else { if ( port < 65535 ) { CheckListenPort ( port + 1 , func ) ; } else { if ( func ) { func ( 0 ) ; } } }
} ) ;
}
// Start the ExpressJS web server
function StartWebServer ( port ) {
2019-12-10 21:17:25 -05:00
if ( ( port < 1 ) || ( port > 65535 ) ) return ;
2017-08-28 12:27:45 -04:00
obj . args . port = port ;
if ( obj . tlsServer != null ) {
if ( obj . args . lanonly == true ) {
2019-01-02 21:34:50 -05:00
obj . tcpServer = obj . tlsServer . listen ( port , function ( ) { console . log ( 'MeshCentral HTTPS server running on port ' + port + ( ( args . aliasport != null ) ? ( ', alias port ' + args . aliasport ) : '' ) + '.' ) ; } ) ;
2017-08-28 12:27:45 -04:00
} else {
2019-01-02 21:34:50 -05:00
obj . tcpServer = obj . tlsServer . listen ( port , function ( ) { console . log ( 'MeshCentral HTTPS server running on ' + certificates . CommonName + ':' + port + ( ( args . aliasport != null ) ? ( ', alias port ' + args . aliasport ) : '' ) + '.' ) ; } ) ;
2018-03-14 15:10:13 -04:00
obj . parent . updateServerState ( 'servername' , certificates . CommonName ) ;
2017-08-28 12:27:45 -04:00
}
2020-02-17 13:24:32 -05:00
if ( obj . parent . authlog ) { obj . parent . authLog ( 'https' , 'Server listening on 0.0.0.0 port ' + port + '.' ) ; }
2018-03-14 15:10:13 -04:00
obj . parent . updateServerState ( 'https-port' , port ) ;
if ( args . aliasport != null ) { obj . parent . updateServerState ( 'https-aliasport' , args . aliasport ) ; }
2017-08-28 12:27:45 -04:00
} else {
2019-01-02 21:34:50 -05:00
obj . tcpServer = obj . app . listen ( port , function ( ) { console . log ( 'MeshCentral HTTP server running on port ' + port + ( ( args . aliasport != null ) ? ( ', alias port ' + args . aliasport ) : '' ) + '.' ) ; } ) ;
2018-03-14 15:10:13 -04:00
obj . parent . updateServerState ( 'http-port' , port ) ;
if ( args . aliasport != null ) { obj . parent . updateServerState ( 'http-aliasport' , args . aliasport ) ; }
2017-08-28 12:27:45 -04:00
}
2019-12-10 21:17:25 -05:00
// Check if there is a permissions problem with the ports.
if ( require ( 'os' ) . platform ( ) != 'win32' ) {
var expectedPort = obj . parent . config . settings . port ? obj . parent . config . settings . port : 443 ;
if ( ( expectedPort != port ) && ( port >= 1024 ) && ( port < 1034 ) ) {
console . log ( '' ) ;
console . log ( 'WARNING: MeshCentral is running without permissions to use ports below 1025.' ) ;
console . log ( ' Use setcap to grant access to lower ports, or read installation guide.' ) ;
console . log ( '' ) ;
console . log ( ' sudo setcap \'cap_net_bind_service= +ep\' `which node` \r\n' ) ;
obj . parent . addServerWarning ( 'Server running without permissions to use ports below 1025.' , false ) ;
}
}
2017-08-28 12:27:45 -04:00
}
// Force mesh agent disconnection
2018-05-24 00:43:44 -04:00
obj . forceMeshAgentDisconnect = function ( user , domain , nodeid , disconnectMode ) {
2017-09-15 14:45:06 -04:00
if ( nodeid == null ) return ;
2017-08-28 12:27:45 -04:00
var splitnode = nodeid . split ( '/' ) ;
if ( ( splitnode . length != 3 ) || ( splitnode [ 1 ] != domain . id ) ) return ; // Check that nodeid is valid and part of our domain
var agent = obj . wsagents [ nodeid ] ;
2017-09-15 14:45:06 -04:00
if ( agent == null ) return ;
2017-08-28 12:27:45 -04:00
// Check we have agent rights
2019-12-27 18:18:43 -05:00
if ( ( ( obj . GetMeshRights ( user , agent . dbMeshKey ) & MESHRIGHT _AGENTCONSOLE ) != 0 ) || ( user . siteadmin == 0xFFFFFFFF ) ) { agent . close ( disconnectMode ) ; }
2018-08-30 15:05:23 -04:00
} ;
2017-08-28 12:27:45 -04:00
// Send the core module to the mesh agent
2019-01-21 17:05:50 -05:00
obj . sendMeshAgentCore = function ( user , domain , nodeid , coretype , coredata ) {
2017-09-15 14:45:06 -04:00
if ( nodeid == null ) return ;
2019-01-21 17:05:50 -05:00
const splitnode = nodeid . split ( '/' ) ;
2017-08-28 12:27:45 -04:00
if ( ( splitnode . length != 3 ) || ( splitnode [ 1 ] != domain . id ) ) return ; // Check that nodeid is valid and part of our domain
2019-01-21 17:05:50 -05:00
// TODO: This command only works if the agent is connected on the same server. Will not work with multi server peering.
const agent = obj . wsagents [ nodeid ] ;
2017-09-15 14:45:06 -04:00
if ( agent == null ) return ;
2017-08-28 12:27:45 -04:00
// Check we have agent rights
2019-12-27 18:18:43 -05:00
if ( ( ( obj . GetMeshRights ( user , agent . dbMeshKey ) & MESHRIGHT _AGENTCONSOLE ) != 0 ) || ( user . siteadmin == 0xFFFFFFFF ) ) {
2019-01-21 17:05:50 -05:00
if ( coretype == 'clear' ) {
2017-08-28 12:27:45 -04:00
// Clear the mesh agent core
2019-01-21 17:05:50 -05:00
agent . agentCoreCheck = 1000 ; // Tell the agent object we are using a custom core.
2017-08-28 12:27:45 -04:00
agent . send ( obj . common . ShortToStr ( 10 ) + obj . common . ShortToStr ( 0 ) ) ;
2019-01-21 17:05:50 -05:00
} else if ( coretype == 'default' ) {
// Reset to default code
2017-08-28 12:27:45 -04:00
agent . agentCoreCheck = 0 ; // Tell the agent object we are using a default code
agent . send ( obj . common . ShortToStr ( 11 ) + obj . common . ShortToStr ( 0 ) ) ; // Command 11, ask for mesh core hash.
2019-01-21 17:05:50 -05:00
} else if ( coretype == 'recovery' ) {
// Reset to recovery core
agent . agentCoreCheck = 1001 ; // Tell the agent object we are using the recovery core.
agent . send ( obj . common . ShortToStr ( 11 ) + obj . common . ShortToStr ( 0 ) ) ; // Command 11, ask for mesh core hash.
} else if ( coretype == 'custom' ) {
agent . agentCoreCheck = 1000 ; // Tell the agent object we are using a custom core.
2019-01-22 18:40:08 -05:00
const hash = obj . crypto . createHash ( 'sha384' ) . update ( Buffer . from ( coredata , 'binary' ) ) . digest ( ) . toString ( 'binary' ) ; // Perform a SHA384 hash on the core module
agent . send ( obj . common . ShortToStr ( 10 ) + obj . common . ShortToStr ( 0 ) + hash + coredata ) ; // Send the code module to the agent
2017-08-28 12:27:45 -04:00
}
}
2018-08-30 15:05:23 -04:00
} ;
2018-05-24 00:43:44 -04:00
2017-08-28 12:27:45 -04:00
// Get the server path of a user or mesh object
function getServerRootFilePath ( obj ) {
2017-09-15 14:45:06 -04:00
if ( ( typeof obj != 'object' ) || ( obj . domain == null ) || ( obj . _id == null ) ) return null ;
2017-08-28 12:27:45 -04:00
var domainname = 'domain' , splitname = obj . _id . split ( '/' ) ;
if ( splitname . length != 3 ) return null ;
if ( obj . domain !== '' ) domainname = 'domain-' + obj . domain ;
return obj . path . join ( obj . filespath , domainname + "/" + splitname [ 0 ] + "-" + splitname [ 2 ] ) ;
}
2018-05-24 00:43:44 -04:00
2017-12-12 19:04:54 -05:00
// Return true is the input string looks like an email address
function checkEmail ( str ) {
var x = str . split ( '@' ) ;
var ok = ( ( x . length == 2 ) && ( x [ 0 ] . length > 0 ) && ( x [ 1 ] . split ( '.' ) . length > 1 ) && ( x [ 1 ] . length > 2 ) ) ;
if ( ok == true ) { var y = x [ 1 ] . split ( '.' ) ; for ( var i in y ) { if ( y [ i ] . length == 0 ) { ok = false ; } } }
return ok ;
}
2018-05-24 00:43:44 -04:00
/ *
obj . wssessions = { } ; // UserId --> Array Of Sessions
obj . wssessions2 = { } ; // "UserId + SessionRnd" --> Session (Note that the SessionId is the UserId + / + SessionRnd)
obj . wsPeerSessions = { } ; // ServerId --> Array Of "UserId + SessionRnd"
obj . wsPeerSessions2 = { } ; // "UserId + SessionRnd" --> ServerId
obj . wsPeerSessions3 = { } ; // ServerId --> UserId --> [ SessionId ]
* /
2017-09-20 17:44:22 -04:00
// Count sessions and event any changes
obj . recountSessions = function ( changedSessionId ) {
2018-08-30 15:05:23 -04:00
var userid , oldcount , newcount , x , serverid ;
2017-09-20 17:44:22 -04:00
if ( changedSessionId == null ) {
// Recount all sessions
// Calculate the session count for all userid's
var newSessionsCount = { } ;
2018-08-30 15:05:23 -04:00
for ( userid in obj . wssessions ) { newSessionsCount [ userid ] = obj . wssessions [ userid ] . length ; }
for ( serverid in obj . wsPeerSessions3 ) {
for ( userid in obj . wsPeerSessions3 [ serverid ] ) {
x = obj . wsPeerSessions3 [ serverid ] [ userid ] . length ;
if ( newSessionsCount [ userid ] == null ) { newSessionsCount [ userid ] = x ; } else { newSessionsCount [ userid ] += x ; }
2017-09-20 17:44:22 -04:00
}
}
// See what session counts have changed, event any changes
2018-08-30 15:05:23 -04:00
for ( userid in newSessionsCount ) {
newcount = newSessionsCount [ userid ] ;
oldcount = obj . sessionsCount [ userid ] ;
2017-09-20 17:44:22 -04:00
if ( oldcount == null ) { oldcount = 0 ; } else { delete obj . sessionsCount [ userid ] ; }
if ( newcount != oldcount ) {
2018-08-30 15:05:23 -04:00
x = userid . split ( '/' ) ;
2019-08-12 17:58:06 -04:00
var u = obj . users [ userid ] ;
2019-05-04 15:55:46 -04:00
if ( u ) {
var targets = [ '*' , 'server-users' ] ;
if ( u . groups ) { for ( var i in u . groups ) { targets . push ( 'server-users:' + i ) ; } }
2019-07-29 19:35:48 -04:00
obj . parent . DispatchEvent ( targets , obj , { action : 'wssessioncount' , userid : userid , username : x [ 2 ] , count : newcount , domain : x [ 1 ] , nolog : 1 , nopeers : 1 } ) ;
2019-05-04 15:55:46 -04:00
}
2017-09-20 17:44:22 -04:00
}
}
// If there are any counts left in the old counts, event to zero
2018-08-30 15:05:23 -04:00
for ( userid in obj . sessionsCount ) {
oldcount = obj . sessionsCount [ userid ] ;
2017-09-20 17:44:22 -04:00
if ( ( oldcount != null ) && ( oldcount != 0 ) ) {
2018-08-30 15:05:23 -04:00
x = userid . split ( '/' ) ;
2019-08-12 17:58:06 -04:00
var u = obj . users [ userid ] ;
2019-05-04 15:55:46 -04:00
if ( u ) {
var targets = [ '*' , 'server-users' ] ;
if ( u . groups ) { for ( var i in u . groups ) { targets . push ( 'server-users:' + i ) ; } }
2019-07-29 19:35:48 -04:00
obj . parent . DispatchEvent ( [ '*' ] , obj , { action : 'wssessioncount' , userid : userid , username : x [ 2 ] , count : 0 , domain : x [ 1 ] , nolog : 1 , nopeers : 1 } )
2019-05-04 15:55:46 -04:00
}
2017-09-20 17:44:22 -04:00
}
}
// Set the new session counts
obj . sessionsCount = newSessionsCount ;
} else {
// Figure out the userid
2018-08-30 15:05:23 -04:00
userid = changedSessionId . split ( '/' ) . slice ( 0 , 3 ) . join ( '/' ) ;
2017-09-20 17:44:22 -04:00
// Recount only changedSessionId
2018-08-30 15:05:23 -04:00
newcount = 0 ;
2017-09-20 17:44:22 -04:00
if ( obj . wssessions [ userid ] != null ) { newcount = obj . wssessions [ userid ] . length ; }
2018-08-30 15:05:23 -04:00
for ( serverid in obj . wsPeerSessions3 ) { if ( obj . wsPeerSessions3 [ serverid ] [ userid ] != null ) { newcount += obj . wsPeerSessions3 [ serverid ] [ userid ] . length ; } }
oldcount = obj . sessionsCount [ userid ] ;
2017-09-20 17:44:22 -04:00
if ( oldcount == null ) { oldcount = 0 ; }
// If the count changed, update and event
if ( newcount != oldcount ) {
2018-08-30 15:05:23 -04:00
x = userid . split ( '/' ) ;
2019-08-12 17:58:06 -04:00
var u = obj . users [ userid ] ;
2019-05-04 15:55:46 -04:00
if ( u ) {
var targets = [ '*' , 'server-users' ] ;
if ( u . groups ) { for ( var i in u . groups ) { targets . push ( 'server-users:' + i ) ; } }
2019-07-29 19:35:48 -04:00
obj . parent . DispatchEvent ( targets , obj , { action : 'wssessioncount' , userid : userid , username : x [ 2 ] , count : newcount , domain : x [ 1 ] , nolog : 1 , nopeers : 1 } ) ;
2019-05-04 15:55:46 -04:00
obj . sessionsCount [ userid ] = newcount ;
}
2017-09-20 17:44:22 -04:00
}
}
2018-08-30 15:05:23 -04:00
} ;
2017-09-20 17:44:22 -04:00
2019-12-27 18:18:43 -05:00
//
// Access Control Functions
//
// Return the node and rights for a given nodeid
obj . GetNodeWithRights = function ( domain , user , nodeid , func ) {
// Perform user pre-validation
if ( ( user == null ) || ( nodeid == null ) ) { func ( null , 0 , false ) ; return ; } // Invalid user
if ( typeof user == 'string' ) { user = obj . users [ user ] ; }
if ( ( user == null ) || ( user . links == null ) ) { func ( null , 0 , false ) ; return ; } // No rights
// Perform node pre-validation
if ( obj . common . validateString ( nodeid , 0 , 128 ) == false ) { func ( null , 0 , false ) ; return ; } // Invalid nodeid
const snode = nodeid . split ( '/' ) ;
if ( ( snode . length != 3 ) || ( snode [ 0 ] != 'node' ) ) { func ( null , 0 , false ) ; return ; } // Invalid nodeid
if ( ( domain != null ) && ( snode [ 1 ] != domain . id ) ) { func ( null , 0 , false ) ; return ; } // Invalid domain
// Check that we have permissions for this node.
db . Get ( nodeid , function ( err , nodes ) {
if ( ( nodes == null ) || ( nodes . length != 1 ) ) { func ( null , 0 , false ) ; return ; } // No such nodeid
2020-01-04 15:44:12 -05:00
2020-03-17 20:17:04 -04:00
// Check device link
var rights = 0 , visible = false , r = user . links [ nodeid ] ;
2020-01-04 15:44:12 -05:00
if ( r != null ) {
2020-03-17 20:17:04 -04:00
if ( r . rights == 0xFFFFFFFF ) { func ( nodes [ 0 ] , 0xFFFFFFFF , true ) ; return ; } // User has full rights thru a device link, stop here.
rights |= r . rights ;
visible = true ;
}
// Check device group link
r = user . links [ nodes [ 0 ] . meshid ] ;
if ( r != null ) {
if ( r . rights == 0xFFFFFFFF ) { func ( nodes [ 0 ] , 0xFFFFFFFF , true ) ; return ; } // User has full rights thru a device group link, stop here.
rights |= r . rights ;
2020-01-04 15:44:12 -05:00
visible = true ;
}
// Check user group links
for ( var i in user . links ) {
if ( i . startsWith ( 'ugrp/' ) ) {
const g = obj . userGroups [ i ] ;
if ( g && ( g . links != null ) ) {
r = g . links [ nodes [ 0 ] . meshid ] ;
if ( r != null ) {
2020-03-17 20:17:04 -04:00
if ( r . rights == 0xFFFFFFFF ) { func ( nodes [ 0 ] , 0xFFFFFFFF , true ) ; return ; } // User has full rights thru a user group link, stop here.
2020-01-04 15:44:12 -05:00
rights |= r . rights ; // TODO: Deal with reverse rights
visible = true ;
}
2020-04-13 13:50:57 -04:00
r = g . links [ nodeid ] ;
if ( r != null ) {
if ( r . rights == 0xFFFFFFFF ) { func ( nodes [ 0 ] , 0xFFFFFFFF , true ) ; return ; } // User has full rights thru a user group direct link, stop here.
rights |= r . rights ; // TODO: Deal with reverse rights
visible = true ;
}
2020-01-04 15:44:12 -05:00
}
}
}
// Return the rights we found
func ( nodes [ 0 ] , rights , visible ) ;
2019-12-27 18:18:43 -05:00
} ) ;
}
2019-12-27 01:53:01 -05:00
// Returns a list of all meshes that this user has some rights too
obj . GetAllMeshWithRights = function ( user , rights ) {
if ( typeof user == 'string' ) { user = obj . users [ user ] ; }
if ( ( user == null ) || ( user . links == null ) ) { return [ ] ; }
2020-02-17 16:01:13 -05:00
2019-12-27 01:53:01 -05:00
var r = [ ] ;
2020-02-17 16:01:13 -05:00
if ( ( user . siteadmin == 0xFFFFFFFF ) && ( parent . config . settings . managealldevicegroups . indexOf ( user . _id ) >= 0 ) ) {
// This is a super user that can see all device groups for a given domain
var meshStartStr = 'mesh/' + user . domain + '/' ;
for ( var i in obj . meshes ) { if ( ( obj . meshes [ i ] . _id . startsWith ( meshStartStr ) ) && ( obj . meshes [ i ] . deleted == null ) ) { r . push ( obj . meshes [ i ] ) ; } }
return r ;
}
2020-01-04 15:44:12 -05:00
for ( var i in user . links ) {
if ( i . startsWith ( 'mesh/' ) ) {
// Grant access to a device group thru a direct link
const m = obj . meshes [ i ] ;
if ( ( m ) && ( m . deleted == null ) && ( ( rights == null ) || ( ( m . rights & rights ) != 0 ) ) ) {
if ( r . indexOf ( m ) == - 1 ) { r . push ( m ) ; }
}
} else if ( i . startsWith ( 'ugrp/' ) ) {
// Grant access to a device group thru a user group
const g = obj . userGroups [ i ] ;
if ( g && ( g . links != null ) && ( ( rights == null ) || ( ( g . rights & rights ) != 0 ) ) ) {
for ( var j in g . links ) {
if ( j . startsWith ( 'mesh/' ) ) {
const m = obj . meshes [ j ] ;
if ( ( m ) && ( m . deleted == null ) ) {
if ( r . indexOf ( m ) == - 1 ) { r . push ( m ) ; }
}
}
}
}
}
}
2019-12-27 01:53:01 -05:00
return r ;
}
// Returns a list of all mesh id's that this user has some rights too
obj . GetAllMeshIdWithRights = function ( user , rights ) {
if ( typeof user == 'string' ) { user = obj . users [ user ] ; }
if ( ( user == null ) || ( user . links == null ) ) { return [ ] ; }
var r = [ ] ;
2020-02-17 16:01:13 -05:00
if ( ( user . siteadmin == 0xFFFFFFFF ) && ( parent . config . settings . managealldevicegroups . indexOf ( user . _id ) >= 0 ) ) {
// This is a super user that can see all device groups for a given domain
var meshStartStr = 'mesh/' + user . domain + '/' ;
for ( var i in obj . meshes ) { if ( ( obj . meshes [ i ] . _id . startsWith ( meshStartStr ) ) && ( obj . meshes [ i ] . deleted == null ) ) { r . push ( obj . meshes [ i ] . _id ) ; } }
return r ;
}
2020-01-04 15:44:12 -05:00
for ( var i in user . links ) {
if ( i . startsWith ( 'mesh/' ) ) {
// Grant access to a device group thru a direct link
const m = obj . meshes [ i ] ;
if ( ( m ) && ( m . deleted == null ) && ( ( rights == null ) || ( ( m . rights & rights ) != 0 ) ) ) {
if ( r . indexOf ( m . _id ) == - 1 ) { r . push ( m . _id ) ; }
}
} else if ( i . startsWith ( 'ugrp/' ) ) {
// Grant access to a device group thru a user group
const g = obj . userGroups [ i ] ;
if ( g && ( g . links != null ) && ( ( rights == null ) || ( ( g . rights & rights ) != 0 ) ) ) {
for ( var j in g . links ) {
if ( j . startsWith ( 'mesh/' ) ) {
const m = obj . meshes [ j ] ;
if ( ( m ) && ( m . deleted == null ) ) {
if ( r . indexOf ( m . _id ) == - 1 ) { r . push ( m . _id ) ; }
}
}
}
}
}
}
2019-12-27 01:53:01 -05:00
return r ;
}
2020-03-17 20:17:04 -04:00
// Get the rights of a user on a given device group
2019-12-27 01:53:01 -05:00
obj . GetMeshRights = function ( user , mesh ) {
if ( ( user == null ) || ( mesh == null ) ) { return 0 ; }
if ( typeof user == 'string' ) { user = obj . users [ user ] ; }
if ( ( user == null ) || ( user . links == null ) ) { return 0 ; }
2020-01-04 15:44:12 -05:00
var r , meshid ;
if ( typeof mesh == 'string' ) {
meshid = mesh ;
} else if ( ( typeof mesh == 'object' ) && ( typeof mesh . _id == 'string' ) ) {
meshid = mesh . _id ;
} else return 0 ;
2020-02-17 16:01:13 -05:00
// Check if this is a super user that can see all device groups for a given domain
if ( ( user . siteadmin == 0xFFFFFFFF ) && ( parent . config . settings . managealldevicegroups . indexOf ( user . _id ) >= 0 ) && ( meshid . startsWith ( 'mesh/' + user . domain + '/' ) ) ) { return 0xFFFFFFFF ; }
2020-01-04 15:44:12 -05:00
// Check direct user to device group permissions
2020-01-04 16:19:32 -05:00
var rights = 0 ;
2020-01-04 15:44:12 -05:00
r = user . links [ meshid ] ;
2020-01-04 16:19:32 -05:00
if ( r != null ) {
var rights = r . rights ;
if ( rights == 0xFFFFFFFF ) { return rights ; } // If the user has full access thru direct link, stop here.
}
2020-01-04 15:44:12 -05:00
// Check if we are part of any user groups that would give this user more access.
for ( var i in user . links ) {
if ( i . startsWith ( 'ugrp' ) ) {
2020-01-04 16:19:32 -05:00
const g = obj . userGroups [ i ] ;
2020-01-04 15:44:12 -05:00
if ( g ) {
r = g . links [ meshid ] ;
if ( r != null ) {
if ( r . rights == 0xFFFFFFFF ) {
return r . rights ; // If the user hash full access thru a user group link, stop here.
} else {
rights |= r . rights ; // Add to existing rights (TODO: Deal with reverse rights)
}
}
}
}
}
return rights ;
2019-12-27 01:53:01 -05:00
}
// Returns true if the user can view the given device group
obj . IsMeshViewable = function ( user , mesh ) {
if ( ( user == null ) || ( mesh == null ) ) { return false ; }
if ( typeof user == 'string' ) { user = obj . users [ user ] ; }
if ( ( user == null ) || ( user . links == null ) ) { return false ; }
2020-01-04 15:44:12 -05:00
var meshid ;
if ( typeof mesh == 'string' ) {
meshid = mesh ;
} else if ( ( typeof mesh == 'object' ) && ( typeof mesh . _id == 'string' ) ) {
meshid = mesh . _id ;
} else return false ;
2020-02-17 16:01:13 -05:00
// Check if this is a super user that can see all device groups for a given domain
if ( ( user . siteadmin == 0xFFFFFFFF ) && ( parent . config . settings . managealldevicegroups . indexOf ( user . _id ) >= 0 ) && ( meshid . startsWith ( 'mesh/' + user . domain + '/' ) ) ) { return true ; }
2020-01-04 15:44:12 -05:00
// Check direct user to device group permissions
if ( user . links [ meshid ] != null ) { return true ; } // If the user has a direct link, stop here.
// Check if we are part of any user groups that would give this user visibility to this device group.
for ( var i in user . links ) {
if ( i . startsWith ( 'ugrp' ) ) {
2020-01-04 16:19:32 -05:00
const g = obj . userGroups [ i ] ;
2020-01-04 15:44:12 -05:00
if ( g && ( g . links [ meshid ] != null ) ) { return true ; } // If the user has a user group link, stop here.
}
}
return false ;
2019-12-27 01:53:01 -05:00
}
2020-03-30 01:40:13 -04:00
// Return the user rights for a given node
obj . GetNodeRights = function ( user , mesh , nodeid ) {
if ( ( user == null ) || ( mesh == null ) || ( nodeid == null ) ) { return 0 ; }
if ( typeof user == 'string' ) { user = obj . users [ user ] ; }
var r = obj . GetMeshRights ( user , mesh ) ;
if ( r == 0xFFFFFFFF ) return r ;
// Check direct device rights using device data
if ( ( user . links != null ) && ( user . links [ nodeid ] != null ) ) { r |= user . links [ nodeid ] . rights ; } // TODO: Deal with reverse permissions
2020-04-13 13:50:57 -04:00
if ( r == 0xFFFFFFFF ) return r ;
// Check direct device rights thru a user group
for ( var i in user . links ) {
if ( i . startsWith ( 'ugrp' ) ) {
const g = obj . userGroups [ i ] ;
if ( g && ( g . links [ nodeid ] != null ) ) { r |= g . links [ nodeid ] . rights ; }
}
}
2020-03-30 01:40:13 -04:00
return r ;
}
2020-01-04 16:46:55 -05:00
// Returns a list of displatch targets for a given mesh
// We have to target the meshid and all user groups for this mesh, plus any added targets
obj . CreateMeshDispatchTargets = function ( mesh , addedTargets ) {
var targets = ( addedTargets != null ) ? addedTargets : [ ] ;
if ( targets . indexOf ( '*' ) == - 1 ) { targets . push ( '*' ) ; }
if ( typeof mesh == 'string' ) { mesh = obj . meshes [ mesh ] ; }
2020-04-13 14:24:56 -04:00
if ( mesh != null ) { targets . push ( mesh . _id ) ; for ( var i in mesh . links ) { if ( i . startsWith ( 'ugrp/' ) ) { targets . push ( i ) ; } } }
return targets ;
}
// Returns a list of displatch targets for a given mesh
// We have to target the meshid and all user groups for this mesh, plus any added targets
obj . CreateNodeDispatchTargets = function ( mesh , nodeid , addedTargets ) {
var targets = ( addedTargets != null ) ? addedTargets : [ ] ;
targets . push ( nodeid ) ;
if ( targets . indexOf ( '*' ) == - 1 ) { targets . push ( '*' ) ; }
if ( typeof mesh == 'string' ) { mesh = obj . meshes [ mesh ] ; }
if ( mesh != null ) { targets . push ( mesh . _id ) ; for ( var i in mesh . links ) { if ( i . startsWith ( 'ugrp/' ) ) { targets . push ( i ) ; } } }
for ( var i in obj . userGroups ) { const g = obj . userGroups [ i ] ; if ( ( g != null ) && ( g . links != null ) && ( g . links [ nodeid ] != null ) ) { targets . push ( i ) ; } }
2020-01-04 16:46:55 -05:00
return targets ;
}
2019-02-05 22:07:12 -05:00
// Clone a safe version of a user object, remove everything that is secret.
2019-10-02 20:19:33 -04:00
obj . CloneSafeUser = function ( user ) {
2019-02-05 22:07:12 -05:00
if ( typeof user != 'object' ) { return user ; }
2019-10-11 14:16:36 -04:00
var user2 = Object . assign ( { } , user ) ; // Shallow clone
2019-02-05 22:07:12 -05:00
delete user2 . hash ;
delete user2 . passhint ;
delete user2 . salt ;
delete user2 . type ;
delete user2 . domain ;
delete user2 . subscriptions ;
delete user2 . passtype ;
2020-03-14 18:03:50 -04:00
if ( ( typeof user2 . otpekey == 'object' ) && ( user2 . otpekey != null ) ) { user2 . otpekey = 1 ; } // Indicates that email 2FA is enabled.
2019-10-02 20:19:33 -04:00
if ( ( typeof user2 . otpsecret == 'string' ) && ( user2 . otpsecret != null ) ) { user2 . otpsecret = 1 ; } // Indicates a time secret is present.
if ( ( typeof user2 . otpkeys == 'object' ) && ( user2 . otpkeys != null ) ) { user2 . otpkeys = 0 ; if ( user . otpkeys != null ) { for ( var i = 0 ; i < user . otpkeys . keys . length ; i ++ ) { if ( user . otpkeys . keys [ i ] . u == true ) { user2 . otpkeys = 1 ; } } } } // Indicates the number of one time backup codes that are active.
if ( ( typeof user2 . otphkeys == 'object' ) && ( user2 . otphkeys != null ) ) { user2 . otphkeys = user2 . otphkeys . length ; } // Indicates the number of hardware keys setup
2019-02-05 22:07:12 -05:00
return user2 ;
}
2019-10-11 14:16:36 -04:00
// Clone a safe version of a node object, remove everything that is secret.
obj . CloneSafeNode = function ( node ) {
if ( typeof node != 'object' ) { return node ; }
var r = node ;
if ( r . intelamt && r . intelamt . pass ) {
r = Object . assign ( { } , r ) ; // Shallow clone
r . intelamt = Object . assign ( { } , r . intelamt ) ; // Shallow clone
delete r . intelamt . pass ; // Remove the Intel AMT password from the node
}
return r ;
}
// Clone a safe version of a mesh object, remove everything that is secret.
obj . CloneSafeMesh = function ( mesh ) {
if ( typeof mesh != 'object' ) { return mesh ; }
var r = mesh ;
if ( r . amt && r . amt . password ) {
r = Object . assign ( { } , r ) ; // Shallow clone
r . amt = Object . assign ( { } , r . amt ) ; // Shallow clone
delete r . amt . password ; // Remove the Intel AMT password from the policy
}
return r ;
}
2019-10-25 17:41:14 -04:00
// Filter the user web site and only output state that we need to keep
2020-02-27 19:34:59 -05:00
const acceptableUserWebStateStrings = [ 'webPageStackMenu' , 'notifications' , 'deviceView' , 'nightMode' , 'webPageFullScreen' , 'search' , 'showRealNames' , 'sort' , 'deskAspectRatio' , 'viewsize' , 'DeskControl' , 'uiMode' , 'footerBar' ] ;
2019-10-25 17:41:14 -04:00
const acceptableUserWebStateDesktopStrings = [ 'encoding' , 'showfocus' , 'showmouse' , 'showcad' , 'limitFrameRate' , 'noMouseRotate' , 'quality' , 'scaling' ]
obj . filterUserWebState = function ( state ) {
if ( typeof state == 'string' ) { try { state = JSON . parse ( state ) ; } catch ( ex ) { return null ; } }
var out = { } ;
for ( var i in acceptableUserWebStateStrings ) {
var n = acceptableUserWebStateStrings [ i ] ;
if ( ( state [ n ] != null ) && ( ( typeof state [ n ] == 'number' ) || ( typeof state [ n ] == 'boolean' ) || ( ( typeof state [ n ] == 'string' ) && ( state [ n ] . length < 32 ) ) ) ) { out [ n ] = state [ n ] ; }
}
if ( typeof state . desktopsettings == 'string' ) { try { state . desktopsettings = JSON . parse ( state . desktopsettings ) ; } catch ( ex ) { delete state . desktopsettings ; } }
if ( state . desktopsettings != null ) {
out . desktopsettings = { } ;
for ( var i in acceptableUserWebStateDesktopStrings ) {
var n = acceptableUserWebStateDesktopStrings [ i ] ;
if ( ( state . desktopsettings [ n ] != null ) && ( ( typeof state . desktopsettings [ n ] == 'number' ) || ( typeof state . desktopsettings [ n ] == 'boolean' ) || ( ( typeof state . desktopsettings [ n ] == 'string' ) && ( state . desktopsettings [ n ] . length < 32 ) ) ) ) { out . desktopsettings [ n ] = state . desktopsettings [ n ] ; }
}
out . desktopsettings = JSON . stringify ( out . desktopsettings ) ;
}
return JSON . stringify ( out ) ;
}
2019-08-25 14:08:32 -04:00
// Return the correct render page given mobile, minify and override path.
2020-03-25 16:21:14 -04:00
function getRenderPage ( pagename , req , domain ) {
2019-08-25 14:08:32 -04:00
var mobile = isMobileBrowser ( req ) , minify = obj . args . minify && ! req . query . nominify , p ;
if ( mobile ) {
2020-03-25 16:21:14 -04:00
if ( ( domain != null ) && ( domain . webviewspath != null ) ) { // If the domain has a web views path, use that first
if ( minify ) {
p = obj . path . join ( domain . webviewspath , pagename + '-mobile-min' ) ;
if ( obj . fs . existsSync ( p + '.handlebars' ) ) { return p ; } // Mobile + Minify + Override document
}
p = obj . path . join ( domain . webviewspath , pagename + '-mobile' ) ;
if ( obj . fs . existsSync ( p + '.handlebars' ) ) { return p ; } // Mobile + Override document
}
2019-08-25 14:08:32 -04:00
if ( obj . parent . webViewsOverridePath != null ) {
if ( minify ) {
p = obj . path . join ( obj . parent . webViewsOverridePath , pagename + '-mobile-min' ) ;
if ( obj . fs . existsSync ( p + '.handlebars' ) ) { return p ; } // Mobile + Minify + Override document
}
2019-09-20 19:24:37 -04:00
p = obj . path . join ( obj . parent . webViewsOverridePath , pagename + '-mobile' ) ;
if ( obj . fs . existsSync ( p + '.handlebars' ) ) { return p ; } // Mobile + Override document
2019-08-25 14:08:32 -04:00
}
if ( minify ) {
p = obj . path . join ( obj . parent . webViewsPath , pagename + '-mobile-min' ) ;
if ( obj . fs . existsSync ( p + '.handlebars' ) ) { return p ; } // Mobile + Minify document
}
2019-09-20 19:24:37 -04:00
p = obj . path . join ( obj . parent . webViewsPath , pagename + '-mobile' ) ;
if ( obj . fs . existsSync ( p + '.handlebars' ) ) { return p ; } // Mobile document
2019-08-25 14:08:32 -04:00
}
2020-03-25 16:21:14 -04:00
if ( ( domain != null ) && ( domain . webviewspath != null ) ) { // If the domain has a web views path, use that first
if ( minify ) {
p = obj . path . join ( domain . webviewspath , pagename + '-min' ) ;
if ( obj . fs . existsSync ( p + '.handlebars' ) ) { return p ; } // Minify + Override document
}
p = obj . path . join ( domain . webviewspath , pagename ) ;
if ( obj . fs . existsSync ( p + '.handlebars' ) ) { return p ; } // Override document
}
2019-08-25 14:08:32 -04:00
if ( obj . parent . webViewsOverridePath != null ) {
if ( minify ) {
p = obj . path . join ( obj . parent . webViewsOverridePath , pagename + '-min' ) ;
if ( obj . fs . existsSync ( p + '.handlebars' ) ) { return p ; } // Minify + Override document
}
2019-09-20 19:24:37 -04:00
p = obj . path . join ( obj . parent . webViewsOverridePath , pagename ) ;
if ( obj . fs . existsSync ( p + '.handlebars' ) ) { return p ; } // Override document
2019-08-25 14:08:32 -04:00
}
if ( minify ) {
p = obj . path . join ( obj . parent . webViewsPath , pagename + '-min' ) ;
if ( obj . fs . existsSync ( p + '.handlebars' ) ) { return p ; } // Minify document
}
2019-09-20 19:24:37 -04:00
p = obj . path . join ( obj . parent . webViewsPath , pagename ) ;
if ( obj . fs . existsSync ( p + '.handlebars' ) ) { return p ; } // Default document
return null ;
2019-08-25 14:08:32 -04:00
}
2019-12-16 14:21:07 -05:00
// Return the correct render page arguments.
2019-12-18 15:00:08 -05:00
function getRenderArgs ( xargs , domain ) {
xargs . titlehtml = domain . titlehtml ;
xargs . title = ( domain . title != null ) ? domain . title : 'MeshCentral' ;
if ( ( domain . titlepicture == null ) && ( domain . titlehtml == null ) ) {
if ( domain . title == null ) {
xargs . title1 = 'MeshCentral' ;
2020-03-30 01:40:13 -04:00
xargs . title2 = '' ;
2019-12-18 15:00:08 -05:00
} else {
xargs . title1 = domain . title ;
xargs . title2 = domain . title2 ? domain . title2 : '' ;
}
} else {
xargs . title1 = domain . title1 ? domain . title1 : '' ;
2020-01-29 17:38:41 -05:00
xargs . title2 = ( domain . title1 && domain . title2 ) ? domain . title2 : '' ;
2019-12-18 15:00:08 -05:00
}
xargs . extitle = encodeURIComponent ( xargs . title ) ;
xargs . domainurl = domain . url ;
2019-12-20 17:02:49 -05:00
if ( typeof domain . hide == 'number' ) { xargs . hide = domain . hide ; }
2019-12-18 15:00:08 -05:00
return xargs ;
2019-12-16 14:21:07 -05:00
}
2019-10-09 18:56:27 -04:00
// Route a command from a agent. domainid, nodeid and meshid are the values of the source agent.
obj . routeAgentCommand = function ( command , domainid , nodeid , meshid ) {
// Route a message.
// If this command has a sessionid, that is the target.
if ( command . sessionid != null ) {
if ( typeof command . sessionid != 'string' ) return ;
var splitsessionid = command . sessionid . split ( '/' ) ;
// Check that we are in the same domain and the user has rights over this node.
2019-12-27 18:18:43 -05:00
if ( ( splitsessionid . length == 4 ) && ( splitsessionid [ 0 ] == 'user' ) && ( splitsessionid [ 1 ] == domainid ) ) {
2019-10-09 18:56:27 -04:00
// Check if this user has rights to get this message
2020-03-30 01:40:13 -04:00
if ( obj . GetNodeRights ( splitsessionid [ 0 ] + '/' + splitsessionid [ 1 ] + '/' + splitsessionid [ 2 ] , meshid , nodeid ) == 0 ) return ; // TODO: Check if this is ok
2019-10-09 18:56:27 -04:00
// See if the session is connected. If so, go ahead and send this message to the target node
var ws = obj . wssessions2 [ command . sessionid ] ;
if ( ws != null ) {
2020-03-30 01:40:13 -04:00
command . nodeid = nodeid ; // Set the nodeid, required for responses.
delete command . sessionid ; // Remove the sessionid, since we are sending to that sessionid, so it's implyed.
2019-10-09 18:56:27 -04:00
try { ws . send ( JSON . stringify ( command ) ) ; } catch ( ex ) { }
} else if ( parent . multiServer != null ) {
// See if we can send this to a peer server
var serverid = obj . wsPeerSessions2 [ command . sessionid ] ;
if ( serverid != null ) {
command . fromNodeid = nodeid ;
parent . multiServer . DispatchMessageSingleServer ( command , serverid ) ;
}
}
}
} else if ( command . userid != null ) { // If this command has a userid, that is the target.
if ( typeof command . userid != 'string' ) return ;
var splituserid = command . userid . split ( '/' ) ;
// Check that we are in the same domain and the user has rights over this node.
if ( ( splituserid [ 0 ] == 'user' ) && ( splituserid [ 1 ] == domainid ) ) {
// Check if this user has rights to get this message
2020-03-30 01:40:13 -04:00
if ( obj . GetNodeRights ( command . userid , meshid , nodeid ) == 0 ) return ; // TODO: Check if this is ok
2019-10-09 18:56:27 -04:00
// See if the session is connected
var sessions = obj . wssessions [ command . userid ] ;
// Go ahead and send this message to the target node
if ( sessions != null ) {
command . nodeid = nodeid ; // Set the nodeid, required for responses.
2020-03-30 01:40:13 -04:00
delete command . userid ; // Remove the userid, since we are sending to that userid, so it's implyed.
2019-10-09 18:56:27 -04:00
for ( i in sessions ) { sessions [ i ] . send ( JSON . stringify ( command ) ) ; }
}
if ( parent . multiServer != null ) {
// TODO: Add multi-server support
}
}
2020-01-07 16:56:26 -05:00
} else { // Route this command to all users with MESHRIGHT_AGENTCONSOLE rights to this device group
2019-10-09 18:56:27 -04:00
command . nodeid = nodeid ;
var cmdstr = JSON . stringify ( command ) ;
2019-12-27 18:18:43 -05:00
2020-01-07 16:56:26 -05:00
// Find all connected user sessions with access to this device
2019-12-27 18:18:43 -05:00
for ( var userid in obj . wssessions ) {
var xsessions = obj . wssessions [ userid ] ;
2020-03-30 01:40:13 -04:00
if ( obj . GetNodeRights ( userid , meshid , nodeid ) != 0 ) {
2020-01-07 16:56:26 -05:00
// Send the message to all sessions for this user on this server
for ( i in xsessions ) { try { xsessions [ i ] . send ( cmdstr ) ; } catch ( e ) { } }
}
2019-10-09 18:56:27 -04:00
}
// Send the message to all users of other servers
if ( parent . multiServer != null ) {
delete command . nodeid ;
command . fromNodeid = nodeid ;
command . meshid = meshid ;
parent . multiServer . DispatchMessage ( command ) ;
}
}
}
2020-04-04 22:29:20 -04:00
// Returns a list of acceptable languages in order
obj . getLanguageCodes = function ( req ) {
// If a user set a localization, use that
if ( ( req . query . lang == null ) && ( req . session != null ) && ( req . session . userid ) ) {
var user = obj . users [ req . session . userid ] ;
if ( ( user != null ) && ( user . lang != null ) ) { req . query . lang = user . lang ; }
} ;
2019-10-21 20:21:49 -04:00
2020-04-04 22:29:20 -04:00
// Get a list of acceptable languages in order
var acceptLanguages = [ ] ;
if ( req . query . lang != null ) {
acceptLanguages . push ( req . query . lang . toLowerCase ( ) ) ;
} else {
if ( req . headers [ 'accept-language' ] != null ) {
var acceptLanguageSplit = req . headers [ 'accept-language' ] . split ( ';' ) ;
for ( var i in acceptLanguageSplit ) {
var acceptLanguageSplitEx = acceptLanguageSplit [ i ] . split ( ',' ) ;
for ( var j in acceptLanguageSplitEx ) { if ( acceptLanguageSplitEx [ j ] . startsWith ( 'q=' ) == false ) { acceptLanguages . push ( acceptLanguageSplitEx [ j ] . toLowerCase ( ) ) ; } }
2019-10-21 18:59:20 -04:00
}
}
2020-04-04 22:29:20 -04:00
}
return acceptLanguages ;
}
// Render a page using the proper language
function render ( req , res , filename , args ) {
if ( obj . renderPages != null ) {
// Get the list of acceptable languages in order
var acceptLanguages = obj . getLanguageCodes ( req ) ;
2019-10-21 18:59:20 -04:00
// Take a look at the options we have for this file
var fileOptions = obj . renderPages [ obj . path . basename ( filename ) ] ;
2019-10-21 20:21:49 -04:00
if ( fileOptions != null ) {
for ( var i in acceptLanguages ) {
if ( ( acceptLanguages [ i ] == 'en' ) || ( acceptLanguages [ i ] . startsWith ( 'en-' ) ) ) { break ; } // English requested, break out.
2019-12-18 17:57:29 -05:00
if ( fileOptions [ acceptLanguages [ i ] ] != null ) {
// Found a match. If the file no longer exists, default to English.
2019-12-20 17:02:49 -05:00
obj . fs . exists ( fileOptions [ acceptLanguages [ i ] ] + '.handlebars' , function ( exists ) {
2019-12-29 21:10:58 -05:00
if ( exists ) { args . lang = acceptLanguages [ i ] ; res . render ( fileOptions [ acceptLanguages [ i ] ] , args ) ; } else { res . render ( filename , args ) ; }
2019-12-20 17:02:49 -05:00
} ) ;
2019-12-18 17:57:29 -05:00
return ;
2020-01-31 17:44:11 -05:00
}
2019-10-21 20:21:49 -04:00
}
2019-10-21 18:59:20 -04:00
}
}
// No matches found, render the default english page.
res . render ( filename , args ) ;
}
// Get the list of pages with different languages that can be rendered
function getRenderList ( ) {
2020-01-27 20:52:20 -05:00
// Fetch default rendeing pages
2019-10-22 20:19:17 -04:00
var translateFolder = null ;
if ( obj . fs . existsSync ( 'views/translations' ) ) { translateFolder = 'views/translations' ; }
if ( obj . fs . existsSync ( obj . path . join ( _ _dirname , 'views' , 'translations' ) ) ) { translateFolder = obj . path . join ( _ _dirname , 'views' , 'translations' ) ; }
if ( translateFolder != null ) {
2019-10-21 18:59:20 -04:00
obj . renderPages = { } ;
2019-10-22 14:59:19 -04:00
obj . renderLanguages = [ 'en' ] ;
2019-10-22 20:19:17 -04:00
var files = obj . fs . readdirSync ( translateFolder ) ;
2019-10-21 18:59:20 -04:00
for ( var i in files ) {
var name = files [ i ] ;
if ( name . endsWith ( '.handlebars' ) ) {
name = name . substring ( 0 , name . length - 11 ) ;
var xname = name . split ( '_' ) ;
if ( xname . length == 2 ) {
if ( obj . renderPages [ xname [ 0 ] ] == null ) { obj . renderPages [ xname [ 0 ] ] = { } ; }
2019-10-22 20:19:17 -04:00
obj . renderPages [ xname [ 0 ] ] [ xname [ 1 ] ] = obj . path . join ( translateFolder , name ) ;
2019-10-22 14:59:19 -04:00
if ( obj . renderLanguages . indexOf ( xname [ 1 ] ) == - 1 ) { obj . renderLanguages . push ( xname [ 1 ] ) ; }
2019-10-21 18:59:20 -04:00
}
}
}
2020-01-27 20:52:20 -05:00
// See if there are any custom rending pages that will override the default ones
2020-01-28 12:41:35 -05:00
if ( ( obj . parent . webViewsOverridePath != null ) && ( obj . fs . existsSync ( obj . path . join ( obj . parent . webViewsOverridePath , 'translations' ) ) ) ) {
translateFolder = obj . path . join ( obj . parent . webViewsOverridePath , 'translations' ) ;
2020-01-27 20:52:20 -05:00
var files = obj . fs . readdirSync ( translateFolder ) ;
for ( var i in files ) {
var name = files [ i ] ;
if ( name . endsWith ( '.handlebars' ) ) {
name = name . substring ( 0 , name . length - 11 ) ;
var xname = name . split ( '_' ) ;
if ( xname . length == 2 ) {
if ( obj . renderPages [ xname [ 0 ] ] == null ) { obj . renderPages [ xname [ 0 ] ] = { } ; }
obj . renderPages [ xname [ 0 ] ] [ xname [ 1 ] ] = obj . path . join ( translateFolder , name ) ;
if ( obj . renderLanguages . indexOf ( xname [ 1 ] ) == - 1 ) { obj . renderLanguages . push ( xname [ 1 ] ) ; }
}
}
}
}
2019-10-21 18:59:20 -04:00
}
}
2020-04-04 22:29:20 -04:00
// Get the list of pages with different languages that can be rendered
function getEmailLanguageList ( ) {
// Fetch default rendeing pages
var translateFolder = null ;
if ( obj . fs . existsSync ( 'emails/translations' ) ) { translateFolder = 'emails/translations' ; }
if ( obj . fs . existsSync ( obj . path . join ( _ _dirname , 'emails' , 'translations' ) ) ) { translateFolder = obj . path . join ( _ _dirname , 'emails' , 'translations' ) ; }
if ( translateFolder != null ) {
obj . emailLanguages = [ 'en' ] ;
var files = obj . fs . readdirSync ( translateFolder ) ;
for ( var i in files ) {
var name = files [ i ] ;
if ( name . endsWith ( '.html' ) ) {
name = name . substring ( 0 , name . length - 5 ) ;
var xname = name . split ( '_' ) ;
if ( xname . length == 2 ) {
if ( obj . emailLanguages . indexOf ( xname [ 1 ] ) == - 1 ) { obj . emailLanguages . push ( xname [ 1 ] ) ; }
}
}
}
// See if there are any custom rending pages that will override the default ones
if ( ( obj . parent . webEmailsOverridePath != null ) && ( obj . fs . existsSync ( obj . path . join ( obj . parent . webEmailsOverridePath , 'translations' ) ) ) ) {
translateFolder = obj . path . join ( obj . parent . webEmailsOverridePath , 'translations' ) ;
var files = obj . fs . readdirSync ( translateFolder ) ;
for ( var i in files ) {
var name = files [ i ] ;
if ( name . endsWith ( '.html' ) ) {
name = name . substring ( 0 , name . length - 5 ) ;
var xname = name . split ( '_' ) ;
if ( xname . length == 2 ) {
if ( obj . emailLanguages . indexOf ( xname [ 1 ] ) == - 1 ) { obj . emailLanguages . push ( xname [ 1 ] ) ; }
}
}
}
}
}
}
2018-05-21 19:36:51 -04:00
// Return true if a mobile browser is detected.
2018-05-24 00:43:44 -04:00
// This code comes from "http://detectmobilebrowsers.com/" and was modified, This is free and unencumbered software released into the public domain. For more information, please refer to the http://unlicense.org/
2018-05-29 19:57:08 -04:00
function isMobileBrowser ( req ) {
//var ua = req.headers['user-agent'].toLowerCase();
//return (/(android|bb\d+|meego).+mobile|mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(ua) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(ua.substr(0, 4)));
2018-08-29 20:40:30 -04:00
if ( typeof req . headers [ 'user-agent' ] != 'string' ) return false ;
2018-05-29 19:57:08 -04:00
return ( req . headers [ 'user-agent' ] . toLowerCase ( ) . indexOf ( 'mobile' ) >= 0 ) ;
2018-05-21 19:36:51 -04:00
}
2019-06-12 13:23:26 -04:00
// Return the query string portion of the URL, the ? and anything after.
function getQueryPortion ( req ) { var s = req . url . indexOf ( '?' ) ; if ( s == - 1 ) { if ( req . body && req . body . urlargs ) { return req . body . urlargs ; } return '' ; } return req . url . substring ( s ) ; }
2019-06-21 20:18:48 -04:00
// Generate a random Intel AMT password
function checkAmtPassword ( p ) { return ( p . length > 7 ) && ( /\d/ . test ( p ) ) && ( /[a-z]/ . test ( p ) ) && ( /[A-Z]/ . test ( p ) ) && ( /\W/ . test ( p ) ) ; }
function getRandomAmtPassword ( ) { var p ; do { p = Buffer . from ( obj . crypto . randomBytes ( 9 ) , 'binary' ) . toString ( 'base64' ) . split ( '/' ) . join ( '@' ) ; } while ( checkAmtPassword ( p ) == false ) ; return p ; }
2019-08-07 19:07:12 -04:00
function getRandomPassword ( ) { return Buffer . from ( obj . crypto . randomBytes ( 9 ) , 'binary' ) . toString ( 'base64' ) . split ( '/' ) . join ( '@' ) ; }
2019-11-15 17:36:41 -05:00
function getRandomLowerCase ( len ) { var r = '' , random = obj . crypto . randomBytes ( len ) ; for ( var i = 0 ; i < len ; i ++ ) { r += String . fromCharCode ( 97 + ( random [ i ] % 26 ) ) ; } return r ; }
2019-06-21 20:18:48 -04:00
2020-03-13 23:39:21 -04:00
// Generate a 8 digit integer with even random probability for each value.
function getRandomEightDigitInteger ( ) { var bigInt ; do { bigInt = parent . crypto . randomBytes ( 4 ) . readUInt32BE ( 0 ) ; } while ( bigInt >= 4200000000 ) ; return bigInt % 100000000 ; }
2019-08-07 17:25:23 -04:00
// Clean a IPv6 address that encodes a IPv4 address
2019-09-20 13:41:31 -04:00
function cleanRemoteAddr ( addr ) { if ( typeof addr != 'string' ) { return null ; } if ( addr . indexOf ( '::ffff:' ) == 0 ) { return addr . substring ( 7 ) ; } else { return addr ; } }
2019-08-07 17:25:23 -04:00
// Record a new entry in a recording log
2019-08-13 14:49:05 -04:00
function recordingEntry ( fd , type , flags , data , func , tag ) {
2019-08-07 17:25:23 -04:00
try {
if ( typeof data == 'string' ) {
// String write
var blockData = Buffer . from ( data ) , header = Buffer . alloc ( 16 ) ; // Header: Type (2) + Flags (2) + Size(4) + Time(8)
header . writeInt16BE ( type , 0 ) ; // Type (1 = Header, 2 = Network Data)
header . writeInt16BE ( flags , 2 ) ; // Flags (1 = Binary, 2 = User)
header . writeInt32BE ( blockData . length , 4 ) ; // Size
header . writeIntBE ( new Date ( ) , 10 , 6 ) ; // Time
var block = Buffer . concat ( [ header , blockData ] ) ;
2019-08-13 14:49:05 -04:00
obj . fs . write ( fd , block , 0 , block . length , function ( ) { func ( fd , tag ) ; } ) ;
2019-08-07 17:25:23 -04:00
} else {
// Binary write
var header = Buffer . alloc ( 16 ) ; // Header: Type (2) + Flags (2) + Size(4) + Time(8)
header . writeInt16BE ( type , 0 ) ; // Type (1 = Header, 2 = Network Data)
header . writeInt16BE ( flags | 1 , 2 ) ; // Flags (1 = Binary, 2 = User)
header . writeInt32BE ( data . length , 4 ) ; // Size
header . writeIntBE ( new Date ( ) , 10 , 6 ) ; // Time
var block = Buffer . concat ( [ header , data ] ) ;
2019-08-13 14:49:05 -04:00
obj . fs . write ( fd , block , 0 , block . length , function ( ) { func ( fd , tag ) ; } ) ;
2019-08-07 17:25:23 -04:00
}
2019-08-13 14:49:05 -04:00
} catch ( ex ) { console . log ( ex ) ; func ( fd , tag ) ; }
2019-08-07 17:25:23 -04:00
}
2019-11-18 20:31:42 -05:00
// This is the invalid login throttling code
obj . badLoginTable = { } ;
obj . badLoginTableLastClean = 0 ;
if ( parent . config . settings == null ) { parent . config . settings = { } ; }
2020-02-18 13:57:39 -05:00
if ( parent . config . settings . maxinvalidlogin !== false ) {
if ( typeof parent . config . settings . maxinvalidlogin != 'object' ) { parent . config . settings . maxinvalidlogin = { time : 10 , count : 10 } ; }
if ( typeof parent . config . settings . maxinvalidlogin . time != 'number' ) { parent . config . settings . maxinvalidlogin . time = 10 ; }
if ( typeof parent . config . settings . maxinvalidlogin . count != 'number' ) { parent . config . settings . maxinvalidlogin . count = 10 ; }
if ( ( typeof parent . config . settings . maxinvalidlogin . coolofftime != 'number' ) || ( parent . config . settings . maxinvalidlogin . coolofftime < 1 ) ) { parent . config . settings . maxinvalidlogin . coolofftime = null ; }
}
2019-11-18 20:31:42 -05:00
obj . setbadLogin = function ( ip ) { // Set an IP address that just did a bad login request
2020-02-18 13:57:39 -05:00
if ( parent . config . settings . maxinvalidlogin === false ) return ;
2019-11-18 20:31:42 -05:00
if ( typeof ip == 'object' ) { ip = cleanRemoteAddr ( ip . ip ) ; }
2020-01-10 19:25:02 -05:00
var splitip = ip . split ( '.' ) ;
if ( splitip . length == 4 ) { ip = ( splitip [ 0 ] + '.' + splitip [ 1 ] + '.' + splitip [ 2 ] + '.*' ) ; }
2019-11-18 20:31:42 -05:00
if ( ++ obj . badLoginTableLastClean > 100 ) { obj . cleanBadLoginTable ( ) ; }
2019-11-19 16:33:52 -05:00
if ( typeof obj . badLoginTable [ ip ] == 'number' ) { if ( obj . badLoginTable [ ip ] < Date . now ( ) ) { delete obj . badLoginTable [ ip ] ; } else { return ; } } // Check cooloff period
2019-11-18 20:31:42 -05:00
if ( obj . badLoginTable [ ip ] == null ) { obj . badLoginTable [ ip ] = [ Date . now ( ) ] ; } else { obj . badLoginTable [ ip ] . push ( Date . now ( ) ) ; }
2019-11-19 16:33:52 -05:00
if ( ( obj . badLoginTable [ ip ] . length >= parent . config . settings . maxinvalidlogin . count ) && ( parent . config . settings . maxinvalidlogin . coolofftime != null ) ) {
obj . badLoginTable [ ip ] = Date . now ( ) + ( parent . config . settings . maxinvalidlogin . coolofftime * 60000 ) ; // Move to cooloff period
}
2019-11-18 20:31:42 -05:00
}
obj . checkAllowLogin = function ( ip ) { // Check if an IP address is allowed to login
2020-02-18 13:57:39 -05:00
if ( parent . config . settings . maxinvalidlogin === false ) return true ;
2019-11-18 20:31:42 -05:00
if ( typeof ip == 'object' ) { ip = cleanRemoteAddr ( ip . ip ) ; }
2020-01-10 19:25:02 -05:00
var splitip = ip . split ( '.' ) ;
if ( splitip . length == 4 ) { ip = ( splitip [ 0 ] + '.' + splitip [ 1 ] + '.' + splitip [ 2 ] + '.*' ) ; } // If this is IPv4, keep only the 3 first
2019-11-18 20:31:42 -05:00
var cutoffTime = Date . now ( ) - ( parent . config . settings . maxinvalidlogin . time * 60000 ) ; // Time in minutes
var ipTable = obj . badLoginTable [ ip ] ;
if ( ipTable == null ) return true ;
2019-11-19 16:33:52 -05:00
if ( typeof ipTable == 'number' ) { if ( obj . badLoginTable [ ip ] < Date . now ( ) ) { delete obj . badLoginTable [ ip ] ; } else { return false ; } } // Check cooloff period
2019-11-18 20:31:42 -05:00
while ( ( ipTable . length > 0 ) && ( ipTable [ 0 ] < cutoffTime ) ) { ipTable . shift ( ) ; }
if ( ipTable . length == 0 ) { delete obj . badLoginTable [ ip ] ; return true ; }
return ( ipTable . length < parent . config . settings . maxinvalidlogin . count ) ; // No more than x bad logins in x minutes
}
obj . cleanBadLoginTable = function ( ) { // Clean up the IP address login blockage table, we do this occasionaly.
2020-02-18 13:57:39 -05:00
if ( parent . config . settings . maxinvalidlogin === false ) return ;
2019-11-18 20:31:42 -05:00
var cutoffTime = Date . now ( ) - ( parent . config . settings . maxinvalidlogin . time * 60000 ) ; // Time in minutes
2019-11-19 16:33:52 -05:00
for ( var ip in obj . badLoginTable ) {
2019-11-18 20:31:42 -05:00
var ipTable = obj . badLoginTable [ ip ] ;
2019-11-19 16:33:52 -05:00
if ( typeof ipTable == 'number' ) {
if ( obj . badLoginTable [ ip ] < Date . now ( ) ) { delete obj . badLoginTable [ ip ] ; } // Check cooloff period
} else {
while ( ( ipTable . length > 0 ) && ( ipTable [ 0 ] < cutoffTime ) ) { ipTable . shift ( ) ; }
if ( ipTable . length == 0 ) { delete obj . badLoginTable [ ip ] ; }
}
2019-11-18 20:31:42 -05:00
}
obj . badLoginTableLastClean = 0 ;
}
2017-08-28 12:27:45 -04:00
return obj ;
2018-08-30 15:05:23 -04:00
} ;