Merge branch 'master' into plugin-admin

This commit is contained in:
Ryan Blenis 2019-11-22 14:26:44 -05:00
commit 8e35f432c8
30 changed files with 884 additions and 412 deletions

3
.greenlockrc Normal file
View File

@ -0,0 +1,3 @@
{
"manager": "C:\\Users\\Default.DESKTOP-M9I88C9\\Desktop\\AmtWebApp\\meshcentral\\letsencrypt.js"
}

Binary file not shown.

View File

@ -1788,6 +1788,78 @@ function createMeshCore(agent) {
response = 'Available commands: \r\n' + fin + '.'; response = 'Available commands: \r\n' + fin + '.';
break; break;
} }
case 'wallpaper':
if (process.platform != 'win32' && !(process.platform == 'linux' && require('linux-gnome-helpers').available))
{
response = 'wallpaper command not supported on this platform'
}
else
{
if (args['_'].length != 1)
{
response = 'Proper usage: wallpaper (GET|TOGGLE)'; // Display usage
}
else
{
switch (args['_'][0].toUpperCase())
{
default:
response = 'Proper usage: wallpaper (GET|TOGGLE)'; // Display usage
break;
case 'GET':
case 'TOGGLE':
if (process.platform == 'win32')
{
var id = require('user-sessions').getProcessOwnerName(process.pid).tsid == 0 ? 1 : 0;
var child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0='], { type: id });
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
child.stderr.on('data', function () { });
child.waitExit();
var current = child.stdout.str.trim();
if (args['_'][0].toUpperCase() == 'GET')
{
response = current;
break;
}
if (current != '')
{
require('MeshAgent')._wallpaper = current;
response = 'Wallpaper cleared';
}
else
{
response = 'Wallpaper restored';
}
child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0=', current != '' ? '""' : require('MeshAgent')._wallpaper], { type: id });
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
child.stderr.on('data', function () { });
child.waitExit();
}
else
{
var id = require('user-sessions').consoleUid();
var current = require('linux-gnome-helpers').getDesktopWallpaper(id);
if (args['_'][0].toUpperCase() == 'GET')
{
response = current;
break;
}
if (current != '/dev/null')
{
require('MeshAgent')._wallpaper = current;
response = 'Wallpaper cleared';
}
else
{
response = 'Wallpaper restored';
}
require('linux-gnome-helpers').setDesktopWallpaper(id, current != '/dev/null' ? undefined : require('MeshAgent')._wallpaper);
}
break;
}
}
}
break;
case 'safemode': case 'safemode':
if (process.platform != 'win32') if (process.platform != 'win32')
{ {

View File

@ -313,7 +313,7 @@ module.exports.CertificateOperations = function (parent) {
cert.setIssuer(attrs); cert.setIssuer(attrs);
// Create a root certificate // Create a root certificate
//cert.setExtensions([{ name: "basicConstraints", cA: true }, { name: "nsCertType", sslCA: true, emailCA: true, objCA: true }, { name: "subjectKeyIdentifier" }]); //cert.setExtensions([{ name: "basicConstraints", cA: true }, { name: "nsCertType", sslCA: true, emailCA: true, objCA: true }, { name: "subjectKeyIdentifier" }]);
cert.setExtensions([{ name: "basicConstraints", cA: true }, { name: "subjectKeyIdentifier" }]); cert.setExtensions([{ name: "basicConstraints", cA: true }, { name: "subjectKeyIdentifier" }, { name: "keyUsage", keyCertSign: true }]);
cert.sign(keys.privateKey, obj.forge.md.sha384.create()); cert.sign(keys.privateKey, obj.forge.md.sha384.create());
return { cert: cert, key: keys.privateKey }; return { cert: cert, key: keys.privateKey };
@ -418,6 +418,21 @@ module.exports.CertificateOperations = function (parent) {
var rootPrivateKey = obj.fileLoad("root-cert-private.key", "utf8"); var rootPrivateKey = obj.fileLoad("root-cert-private.key", "utf8");
r.root = { cert: rootCertificate, key: rootPrivateKey }; r.root = { cert: rootCertificate, key: rootPrivateKey };
rcount++; rcount++;
// Check if the root certificate has the "Certificate Signing (04)" Key usage.
// This option is required for newer versions of Intel AMT for CIRA/WS-EVENTS.
var xroot = obj.pki.certificateFromPem(rootCertificate);
var xext = xroot.getExtension("keyUsage");
if ((xext == null) || (xext.keyCertSign !== true)) {
// We need to fix this certificate
console.log('Fixing root certificate to add signing key usage...');
obj.fs.writeFileSync(parent.getConfigFilePath("root-cert-public-backup.crt"), rootCertificate);
xroot.setExtensions([{ name: "basicConstraints", cA: true }, { name: "subjectKeyIdentifier" }, { name: "keyUsage", keyCertSign: true }]);
var xrootPrivateKey = obj.pki.privateKeyFromPem(rootPrivateKey);
xroot.sign(xrootPrivateKey, obj.forge.md.sha384.create());
r.root.cert = obj.pki.certificateToPem(xroot);
try { obj.fs.writeFileSync(parent.getConfigFilePath("root-cert-public.crt"), r.root.cert); } catch (ex) { }
}
} }
if (args.tlsoffload) { if (args.tlsoffload) {

16
db.js
View File

@ -687,7 +687,13 @@ module.exports.CreateDB = function (parent, func) {
// TODO: Starting in MongoDB 4.0.3, you should use countDocuments() instead of count() that is deprecated. We should detect MongoDB version and switch. // TODO: Starting in MongoDB 4.0.3, you should use countDocuments() instead of count() that is deprecated. We should detect MongoDB version and switch.
// https://docs.mongodb.com/manual/reference/method/db.collection.countDocuments/ // https://docs.mongodb.com/manual/reference/method/db.collection.countDocuments/
//obj.isMaxType = function (max, type, domainid, func) { if (max == null) { func(false); } else { obj.file.countDocuments({ type: type, domain: domainid }, function (err, count) { func((err != null) || (count > max)); }); } } //obj.isMaxType = function (max, type, domainid, func) { if (max == null) { func(false); } else { obj.file.countDocuments({ type: type, domain: domainid }, function (err, count) { func((err != null) || (count > max)); }); } }
obj.isMaxType = function (max, type, domainid, func) { if (max == null) { func(false); } else { obj.file.count({ type: type, domain: domainid }, function (err, count) { func((err != null) || (count > max), count); }); } } obj.isMaxType = function (max, type, domainid, func) {
if (obj.eventsfile.countDocuments) {
if (max == null) { func(false); } else { obj.file.countDocuments({ type: type, domain: domainid }, function (err, count) { func((err != null) || (count > max), count); }); }
} else {
if (max == null) { func(false); } else { obj.file.count({ type: type, domain: domainid }, function (err, count) { func((err != null) || (count > max), count); }); }
}
}
// Database actions on the events collection // Database actions on the events collection
obj.GetAllEvents = function (func) { obj.eventsfile.find({}).toArray(func); }; obj.GetAllEvents = function (func) { obj.eventsfile.find({}).toArray(func); };
@ -703,6 +709,13 @@ module.exports.CreateDB = function (parent, func) {
obj.GetNodeEventsSelfWithLimit = function (nodeid, domain, userid, limit, func) { obj.eventsfile.find({ domain: domain, nodeid: nodeid, userid: { $in: [userid, null] } }).project({ type: 0, etype: 0, _id: 0, domain: 0, ids: 0, node: 0, nodeid: 0 }).sort({ time: -1 }).limit(limit).toArray(func); }; obj.GetNodeEventsSelfWithLimit = function (nodeid, domain, userid, limit, func) { obj.eventsfile.find({ domain: domain, nodeid: nodeid, userid: { $in: [userid, null] } }).project({ type: 0, etype: 0, _id: 0, domain: 0, ids: 0, node: 0, nodeid: 0 }).sort({ time: -1 }).limit(limit).toArray(func); };
obj.RemoveAllEvents = function (domain) { obj.eventsfile.deleteMany({ domain: domain }, { multi: true }); }; obj.RemoveAllEvents = function (domain) { obj.eventsfile.deleteMany({ domain: domain }, { multi: true }); };
obj.RemoveAllNodeEvents = function (domain, nodeid) { obj.eventsfile.deleteMany({ domain: domain, nodeid: nodeid }, { multi: true }); }; obj.RemoveAllNodeEvents = function (domain, nodeid) { obj.eventsfile.deleteMany({ domain: domain, nodeid: nodeid }, { multi: true }); };
obj.GetFailedLoginCount = function (username, domainid, lastlogin, func) {
if (obj.eventsfile.countDocuments) {
obj.eventsfile.countDocuments({ action: 'authfail', username: username, domain: domainid, time: { "$gte": lastlogin } }, function (err, count) { func((err == null) ? count : 0); });
} else {
obj.eventsfile.count({ action: 'authfail', username: username, domain: domainid, time: { "$gte": lastlogin } }, function (err, count) { func((err == null) ? count : 0); });
}
}
// Database actions on the power collection // Database actions on the power collection
obj.getAllPower = function (func) { obj.powerfile.find({}).toArray(func); }; obj.getAllPower = function (func) { obj.powerfile.find({}).toArray(func); };
@ -852,6 +865,7 @@ module.exports.CreateDB = function (parent, func) {
obj.GetNodeEventsSelfWithLimit = function (nodeid, domain, userid, limit, func) { if (obj.databaseType == 1) { obj.eventsfile.find({ domain: domain, nodeid: nodeid, userid: { $in: [userid, null] } }, { type: 0, etype: 0, _id: 0, domain: 0, ids: 0, node: 0, nodeid: 0 }).sort({ time: -1 }).limit(limit).exec(func); } else { obj.eventsfile.find({ domain: domain, nodeid: nodeid }, { type: 0, etype: 0, _id: 0, domain: 0, ids: 0, node: 0, nodeid: 0 }).sort({ time: -1 }).limit(limit, func); } }; obj.GetNodeEventsSelfWithLimit = function (nodeid, domain, userid, limit, func) { if (obj.databaseType == 1) { obj.eventsfile.find({ domain: domain, nodeid: nodeid, userid: { $in: [userid, null] } }, { type: 0, etype: 0, _id: 0, domain: 0, ids: 0, node: 0, nodeid: 0 }).sort({ time: -1 }).limit(limit).exec(func); } else { obj.eventsfile.find({ domain: domain, nodeid: nodeid }, { type: 0, etype: 0, _id: 0, domain: 0, ids: 0, node: 0, nodeid: 0 }).sort({ time: -1 }).limit(limit, func); } };
obj.RemoveAllEvents = function (domain) { obj.eventsfile.remove({ domain: domain }, { multi: true }); }; obj.RemoveAllEvents = function (domain) { obj.eventsfile.remove({ domain: domain }, { multi: true }); };
obj.RemoveAllNodeEvents = function (domain, nodeid) { obj.eventsfile.remove({ domain: domain, nodeid: nodeid }, { multi: true }); }; obj.RemoveAllNodeEvents = function (domain, nodeid) { obj.eventsfile.remove({ domain: domain, nodeid: nodeid }, { multi: true }); };
obj.GetFailedLoginCount = function (username, domainid, lastlogin, func) { obj.eventsfile.count({ action: 'authfail', username: username, domain: domainid, time: { "$gte": lastlogin } }, function (err, count) { func((err == null) ? count : 0); }); }
// Database actions on the power collection // Database actions on the power collection
obj.getAllPower = function (func) { obj.powerfile.find({}, func); }; obj.getAllPower = function (func) { obj.powerfile.find({}, func); };

View File

@ -12,62 +12,128 @@
/*jshint node: true */ /*jshint node: true */
/*jshint strict: false */ /*jshint strict: false */
/*jshint esversion: 6 */ /*jshint esversion: 6 */
"use strict"; 'use strict';
module.exports.CreateLetsEncrypt = function (parent) { module.exports.CreateLetsEncrypt = function (parent) {
try { try {
// Get the GreenLock version
var greenLockVersion = null;
try { greenLockVersion = require('greenlock/package.json').version; } catch (ex) { }
if (greenLockVersion == null) {
parent.debug('cert', "Initializing Let's Encrypt support");
} else {
parent.debug('cert', "Initializing Let's Encrypt support, using GreenLock v" + greenLockVersion);
}
// Check the current node version and support for generateKeyPair
if (require('crypto').generateKeyPair == null) { return null; }
if (Number(process.version.match(/^v(\d+\.\d+)/)[1]) < 10) { return null; }
// Try to delete the "./ursa-optional" or "./node_modules/ursa-optional" folder if present. // Try to delete the "./ursa-optional" or "./node_modules/ursa-optional" folder if present.
// This is an optional module that GreenLock uses that causes issues. // This is an optional module that GreenLock uses that causes issues.
try { try {
const fs = require('fs'); const fs = require('fs');
if (fs.existsSync(obj.path.join(__dirname, 'ursa-optional'))) { fs.unlinkSync(obj.path.join(__dirname, 'ursa-optional')); } if (fs.existsSync(parent.path.join(__dirname, 'ursa-optional'))) { fs.unlinkSync(obj.path.join(__dirname, 'ursa-optional')); }
if (fs.existsSync(obj.path.join(__dirname, 'node_modules', 'ursa-optional'))) { fs.unlinkSync(obj.path.join(__dirname, 'node_modules', 'ursa-optional')); } if (fs.existsSync(parent.path.join(__dirname, 'node_modules', 'ursa-optional'))) { fs.unlinkSync(obj.path.join(__dirname, 'node_modules', 'ursa-optional')); }
} catch (ex) { } } catch (ex) { }
// Get GreenLock setup and running. // Get GreenLock setup and running.
const greenlock = require('greenlock'); const greenlock = require('greenlock');
var obj = {}; var obj = {};
obj.parent = parent; obj.parent = parent;
obj.path = require('path');
obj.redirWebServerHooked = false; obj.redirWebServerHooked = false;
obj.leDomains = null; obj.leDomains = null;
obj.leResults = null; obj.leResults = null;
obj.leResultsStaging = null;
obj.performRestart = false; // Indicates we need to restart the server
obj.performMoveToProduction = false; // Indicates we just got a staging certificate and need to move to production
obj.runAsProduction = false; // This starts at false and moves to true if staging cert is ok.
// Setup the certificate storage paths // Setup the certificate storage paths
obj.configPath = obj.parent.path.join(obj.parent.datapath, 'letsencrypt'); obj.configPath = obj.path.join(obj.parent.datapath, 'letsencrypt3');
obj.webrootPath = obj.parent.path.join(obj.parent.datapath, 'letsencrypt', 'webroot');
try { obj.parent.fs.mkdirSync(obj.configPath); } catch (e) { } try { obj.parent.fs.mkdirSync(obj.configPath); } catch (e) { }
try { obj.parent.fs.mkdirSync(obj.webrootPath); } catch (e) { } obj.configPathStaging = obj.path.join(obj.parent.datapath, 'letsencrypt3-staging');
try { obj.parent.fs.mkdirSync(obj.configPathStaging); } catch (e) { }
// Storage Backend, store data in the "meshcentral-data/letencrypt" folder. // Setup Let's Encrypt default configuration
var leStore = require('le-store-certbot').create({ configDir: obj.configPath, webrootPath: obj.webrootPath, debug: obj.parent.args.debug > 0 }); obj.leDefaults = { agreeToTerms: true, store: { module: 'greenlock-store-fs', basePath: obj.configPath } };
obj.leDefaultsStaging = { agreeToTerms: true, store: { module: 'greenlock-store-fs', basePath: obj.configPathStaging } };
// ACME Challenge Handlers // Get package and maintainer email
var leHttpChallenge = require('le-challenge-fs').create({ webrootPath: obj.webrootPath, debug: obj.parent.args.debug > 0 }); const pkg = require('./package.json');
var maintainerEmail = null;
if (typeof pkg.author == 'string') {
// Older NodeJS
maintainerEmail = pkg.author;
var i = maintainerEmail.indexOf('<');
if (i >= 0) { maintainerEmail = maintainerEmail.substring(i + 1); }
var i = maintainerEmail.indexOf('>');
if (i >= 0) { maintainerEmail = maintainerEmail.substring(0, i); }
} else if (typeof pkg.author == 'object') {
// Latest NodeJS
maintainerEmail = pkg.author.email;
}
// Function to agree to terms of service // Check if we need to be in debug mode
function leAgree(opts, agreeCb) { agreeCb(null, opts.tosUrl); } var ledebug = false;
try { ledebug = ((obj.parent.args.debug != null) || (obj.parent.args.debug.indexOf('cert'))); } catch (ex) { }
// Create the main GreenLock code module. // Create the main GreenLock code module for production.
var greenlockargs = { var greenlockargs = {
version: 'draft-12', parent: obj,
server: (obj.parent.config.letsencrypt.production === true) ? 'https://acme-v02.api.letsencrypt.org/directory' : 'https://acme-staging-v02.api.letsencrypt.org/directory', packageRoot: __dirname,
store: leStore, packageAgent: pkg.name + '/' + pkg.version,
challenges: { 'http-01': leHttpChallenge }, manager: obj.path.join(__dirname, 'letsencrypt.js'),
challengeType: 'http-01', maintainerEmail: maintainerEmail,
agreeToTerms: leAgree, notify: function (ev, args) { if (typeof args == 'string') { parent.debug('cert', ev + ': ' + args); } else { parent.debug('cert', ev + ': ' + JSON.stringify(args)); } },
debug: obj.parent.args.debug > 0 staging: false,
debug: ledebug
}; };
if (obj.parent.args.debug == null) { greenlockargs.log = function (debug) { }; } // If not in debug mode, ignore all console output from greenlock (makes things clean). if (obj.parent.args.debug == null) { greenlockargs.log = function (debug) { }; } // If not in debug mode, ignore all console output from greenlock (makes things clean).
obj.le = greenlock.create(greenlockargs); obj.le = greenlock.create(greenlockargs);
// Create the main GreenLock code module for staging.
var greenlockargsstaging = {
parent: obj,
packageRoot: __dirname,
packageAgent: pkg.name + '/' + pkg.version,
manager: obj.path.join(__dirname, 'letsencrypt.js'),
maintainerEmail: maintainerEmail,
notify: function (ev, args) { if (typeof args == 'string') { parent.debug('cert', 'Notify: ' + ev + ': ' + args); } else { parent.debug('cert', 'Notify: ' + ev + ': ' + JSON.stringify(args)); } },
staging: true,
debug: ledebug
};
if (obj.parent.args.debug == null) { greenlockargsstaging.log = function (debug) { }; } // If not in debug mode, ignore all console output from greenlock (makes things clean).
obj.leStaging = greenlock.create(greenlockargsstaging);
// Hook up GreenLock to the redirection server // Hook up GreenLock to the redirection server
if (obj.parent.redirserver.port == 80) { obj.parent.redirserver.app.use('/', obj.le.middleware()); obj.redirWebServerHooked = true; } if (obj.parent.config.settings.rediraliasport === 80) { obj.redirWebServerHooked = true; }
else if ((obj.parent.config.settings.rediraliasport == null) && (obj.parent.redirserver.port == 80)) { obj.redirWebServerHooked = true; }
// Respond to a challenge
obj.challenge = function (token, hostname, func) {
if (obj.runAsProduction === true) {
// Production
parent.debug('cert', "Challenge " + hostname + "/" + token);
obj.le.challenges.get({ type: 'http-01', servername: hostname, token: token })
.then(function (results) { func(results.keyAuthorization); })
.catch(function (e) { console.log('LE-ERROR', e); func(null); }); // unexpected error, not related to renewal
} else {
// Staging
parent.debug('cert', "Challenge " + hostname + "/" + token);
obj.leStaging.challenges.get({ type: 'http-01', servername: hostname, token: token })
.then(function (results) { func(results.keyAuthorization); })
.catch(function (e) { console.log('LE-ERROR', e); func(null); }); // unexpected error, not related to renewal
}
}
obj.getCertificate = function(certs, func) { obj.getCertificate = function(certs, func) {
parent.debug('cert', "Getting certs from local store");
if (certs.CommonName.indexOf('.') == -1) { console.log("ERROR: Use --cert to setup the default server name before using Let's Encrypt."); func(certs); return; } if (certs.CommonName.indexOf('.') == -1) { console.log("ERROR: Use --cert to setup the default server name before using Let's Encrypt."); func(certs); return; }
if (obj.parent.config.letsencrypt == null) { func(certs); return; } if (obj.parent.config.letsencrypt == null) { func(certs); return; }
if (obj.parent.config.letsencrypt.email == null) { console.log("ERROR: Let's Encrypt email address not specified."); func(certs); return; } if (obj.parent.config.letsencrypt.email == null) { console.log("ERROR: Let's Encrypt email address not specified."); func(certs); return; }
if ((obj.parent.redirserver == null) || (obj.parent.redirserver.port !== 80)) { console.log("ERROR: Redirection web server must be active on port 80 for Let's Encrypt to work."); func(certs); return; } if ((obj.parent.redirserver == null) || ((typeof obj.parent.config.settings.rediraliasport === 'number') && (obj.parent.config.settings.rediraliasport !== 80)) || ((obj.parent.config.settings.rediraliasport == null) && (obj.parent.redirserver.port !== 80))) { console.log("ERROR: Redirection web server must be active on port 80 for Let's Encrypt to work."); func(certs); return; }
if (obj.redirWebServerHooked !== true) { console.log("ERROR: Redirection web server not setup for Let's Encrypt to work."); func(certs); return; } if (obj.redirWebServerHooked !== true) { console.log("ERROR: Redirection web server not setup for Let's Encrypt to work."); func(certs); return; }
if ((obj.parent.config.letsencrypt.rsakeysize != null) && (obj.parent.config.letsencrypt.rsakeysize !== 2048) && (obj.parent.config.letsencrypt.rsakeysize !== 3072)) { console.log("ERROR: Invalid Let's Encrypt certificate key size, must be 2048 or 3072."); func(certs); return; } if ((obj.parent.config.letsencrypt.rsakeysize != null) && (obj.parent.config.letsencrypt.rsakeysize !== 2048) && (obj.parent.config.letsencrypt.rsakeysize !== 3072)) { console.log("ERROR: Invalid Let's Encrypt certificate key size, must be 2048 or 3072."); func(certs); return; }
@ -78,70 +144,160 @@ module.exports.CreateLetsEncrypt = function (parent) {
obj.parent.config.letsencrypt.names.map(function (s) { return s.trim(); }); // Trim each name obj.parent.config.letsencrypt.names.map(function (s) { return s.trim(); }); // Trim each name
if ((typeof obj.parent.config.letsencrypt.names != 'object') || (obj.parent.config.letsencrypt.names.length == null)) { console.log("ERROR: Let's Encrypt names must be an array in config.json."); func(certs); return; } if ((typeof obj.parent.config.letsencrypt.names != 'object') || (obj.parent.config.letsencrypt.names.length == null)) { console.log("ERROR: Let's Encrypt names must be an array in config.json."); func(certs); return; }
obj.leDomains = obj.parent.config.letsencrypt.names; obj.leDomains = obj.parent.config.letsencrypt.names;
obj.leDomains.sort(); // Sort the array so it's always going to be in the same order.
} }
obj.le.check({ domains: obj.leDomains }).then(function (results) { if (obj.parent.config.letsencrypt.production !== true) {
if (results) { // We are in staging mode, just go ahead
obj.leResults = results; obj.getCertificateEx(certs, func);
} else {
// We are really in production mode
if (obj.runAsProduction === true) {
// Staging cert check must have been done already, move to production
obj.getCertificateEx(certs, func);
} else {
// Perform staging certificate check
parent.debug('cert', "Checking staging certificate " + obj.leDomains[0] + "...");
obj.leStaging.get({ servername: obj.leDomains[0] })
.then(function (results) {
if (results != null) {
// We have a staging certificate, move to production for real
parent.debug('cert', "Staging certificate is present, moving to production...");
obj.runAsProduction = true;
obj.getCertificateEx(certs, func);
} else {
// No staging certificate
parent.debug('cert', "No staging certificate present");
func(certs);
setTimeout(obj.checkRenewCertificate, 10000); // Check the certificate in 10 seconds.
}
})
.catch(function (e) {
// No staging certificate
parent.debug('cert', "No staging certificate present");
func(certs);
setTimeout(obj.checkRenewCertificate, 10000); // Check the certificate in 10 seconds.
});
}
}
}
// If we already have real certificates, use them. obj.getCertificateEx = function (certs, func) {
if (results.altnames.indexOf(certs.CommonName) >= 0) { // Get the Let's Encrypt certificate from our own storage
certs.web.cert = results.cert; const xle = (obj.runAsProduction === true)? obj.le : obj.leStaging;
certs.web.key = results.privkey; xle.get({ servername: obj.leDomains[0] })
certs.web.ca = [results.chain]; .then(function (results) {
// If we already have real certificates, use them
if (results) {
if (results.site.altnames.indexOf(certs.CommonName) >= 0) {
certs.web.cert = results.pems.cert;
certs.web.key = results.pems.privkey;
certs.web.ca = [results.pems.chain];
} }
for (var i in obj.parent.config.domains) { for (var i in obj.parent.config.domains) {
if ((obj.parent.config.domains[i].dns != null) && (obj.parent.certificateOperations.compareCertificateNames(results.altnames, obj.parent.config.domains[i].dns))) { if ((obj.parent.config.domains[i].dns != null) && (obj.parent.certificateOperations.compareCertificateNames(results.site.altnames, obj.parent.config.domains[i].dns))) {
certs.dns[i].cert = results.cert; certs.dns[i].cert = results.pems.cert;
certs.dns[i].key = results.privkey; certs.dns[i].key = results.pems.privkey;
certs.dns[i].ca = [results.chain]; certs.dns[i].ca = [results.pems.chain];
} }
} }
}
parent.debug('cert', "Got certs from local store (" + (obj.runAsProduction ? "Production" : "Staging") + ")");
func(certs); func(certs);
// Check if the Let's Encrypt certificate needs to be renewed. // Check if the Let's Encrypt certificate needs to be renewed.
setTimeout(obj.checkRenewCertificate, 60000); // Check in 1 minute. setTimeout(obj.checkRenewCertificate, 60000); // Check in 1 minute.
setInterval(obj.checkRenewCertificate, 86400000); // Check again in 24 hours and every 24 hours. setInterval(obj.checkRenewCertificate, 86400000); // Check again in 24 hours and every 24 hours.
return; return;
} else { })
// Otherwise return default certificates and try to get a real one .catch(function (e) {
parent.debug('cert', "Unable to get certs from local store (" + (obj.runAsProduction ? "Production" : "Staging") + ")");
setTimeout(obj.checkRenewCertificate, 10000); // Check the certificate in 10 seconds.
func(certs); func(certs);
});
} }
console.log("Attempting to get Let's Encrypt certificate, may take a few minutes...");
// Figure out the RSA key size
var rsaKeySize = (obj.parent.config.letsencrypt.rsakeysize === 2048) ? 2048 : 3072;
// TODO: Only register on one of the peers if multi-peers are active.
// Register Certificate manually
obj.le.register({
domains: obj.leDomains,
email: obj.parent.config.letsencrypt.email,
agreeTos: true,
rsaKeySize: rsaKeySize,
challengeType: 'http-01',
renewWithin: 45 * 24 * 60 * 60 * 1000, // Certificate renewal may begin at this time (45 days)
renewBy: 60 * 24 * 60 * 60 * 1000 // Certificate renewal should happen by this time (60 days)
}).then(function (xresults) {
obj.parent.performServerCertUpdate(); // Reset the server, TODO: Reset all peers
}, function (err) {
console.error("ERROR: Let's encrypt error: ", err);
});
});
};
// Check if we need to renew the certificate, call this every day. // Check if we need to renew the certificate, call this every day.
obj.checkRenewCertificate = function () { obj.checkRenewCertificate = function () {
if (obj.leResults == null) { return; } parent.debug('cert', "Checking certificate for " + obj.leDomains[0] + " (" + (obj.runAsProduction ? "Production" : "Staging") + ")");
// TODO: Only renew on one of the peers if multi-peers are active.
// Check if we need to renew the certificate // Setup renew options
obj.le.renew({ duplicate: false, domains: obj.leDomains, email: obj.parent.config.letsencrypt.email }, obj.leResults).then(function (xresults) { obj.certCheckStart = Date.now();
obj.parent.performServerCertUpdate(); // Reset the server, TODO: Reset all peers const xle = (obj.runAsProduction === true) ? obj.le : obj.leStaging;
}, function (err) { }); // If we can't renew, ignore. var renewOptions = { servername: obj.leDomains[0], altnames: obj.leDomains };
}; try {
xle.renew(renewOptions)
.then(function (results) {
if ((results == null) || (typeof results != 'object') || (results.length == 0) || (results[0].error != null)) {
parent.debug('cert', "Unable to get a certificate (" + (obj.runAsProduction ? "Production" : "Staging") + ", " + (Date.now() - obj.certCheckStart) + "ms): " + JSON.stringify(results));
} else {
parent.debug('cert', "Checks completed (" + (obj.runAsProduction ? "Production" : "Staging") + ", " + (Date.now() - obj.certCheckStart) + "ms): " + JSON.stringify(results));
if (obj.performRestart === true) { parent.debug('cert', "Certs changed, restarting..."); obj.parent.performServerCertUpdate(); } // Reset the server, TODO: Reset all peers
else if (obj.performMoveToProduction == true) {
parent.debug('cert', "Staging certificate received, moving to production...");
obj.runAsProduction = true;
obj.performMoveToProduction = false;
obj.performRestart = true;
setTimeout(obj.checkRenewCertificate, 10000); // Check the certificate in 10 seconds.
}
}
})
.catch(function (ex) {
parent.debug('cert', "checkCertificate exception: (" + JSON.stringify(ex) + ")");
console.log(ex);
});
} catch (ex) {
parent.debug('cert', "checkCertificate main exception: (" + JSON.stringify(ex) + ")");
console.log(ex);
}
}
return obj; return obj;
} catch (ex) { console.log(ex); } // Unable to start Let's Encrypt } catch (ex) { console.log(ex); } // Unable to start Let's Encrypt
return null; return null;
}; };
// GreenLock v3 Manager
module.exports.create = function (options) {
var manager = { parent: options.parent };
manager.find = async function (options) {
//console.log('LE-FIND', options);
return Promise.resolve([{ subject: options.servername, altnames: options.altnames }]);
};
manager.set = function (options) {
manager.parent.parent.debug('cert', "Certificate has been set: " + JSON.stringify(options));
if (manager.parent.parent.config.letsencrypt.production == manager.parent.runAsProduction) { manager.parent.performRestart = true; }
else if ((manager.parent.parent.config.letsencrypt.production === true) && (manager.parent.runAsProduction === false)) { manager.parent.performMoveToProduction = true; }
return null;
};
manager.remove = function (options) {
manager.parent.parent.debug('cert', "Certificate has been removed: " + JSON.stringify(options));
if (manager.parent.parent.config.letsencrypt.production == manager.parent.runAsProduction) { manager.parent.performRestart = true; }
else if ((manager.parent.parent.config.letsencrypt.production === true) && (manager.parent.runAsProduction === false)) { manager.parent.performMoveToProduction = true; }
return null;
};
// set the global config
manager.defaults = async function (options) {
var r;
if (manager.parent.runAsProduction === true) {
// Production
//console.log('LE-DEFAULTS-Production', options);
if (options != null) { for (var i in options) { if (manager.parent.leDefaults[i] == null) { manager.parent.leDefaults[i] = options[i]; } } }
r = manager.parent.leDefaults;
r.subscriberEmail = manager.parent.parent.config.letsencrypt.email;
r.sites = { mainsite: { subject: manager.parent.leDomains[0], altnames: manager.parent.leDomains } };
} else {
// Staging
//console.log('LE-DEFAULTS-Staging', options);
if (options != null) { for (var i in options) { if (manager.parent.leDefaultsStaging[i] == null) { manager.parent.leDefaultsStaging[i] = options[i]; } } }
r = manager.parent.leDefaultsStaging;
r.subscriberEmail = manager.parent.parent.config.letsencrypt.email;
r.sites = { mainsite: { subject: manager.parent.leDomains[0], altnames: manager.parent.leDomains } };
}
return r;
};
return manager;
};

View File

@ -117,7 +117,7 @@ function CreateMeshCentralServer(config, args) {
try { require('./pass').hash('test', function () { }, 0); } catch (e) { console.log('Old version of node, must upgrade.'); return; } // TODO: Not sure if this test works or not. try { require('./pass').hash('test', function () { }, 0); } catch (e) { console.log('Old version of node, must upgrade.'); return; } // TODO: Not sure if this test works or not.
// Check for invalid arguments // Check for invalid arguments
var validArguments = ['_', 'notls', 'user', 'port', 'aliasport', 'mpsport', 'mpsaliasport', 'redirport', 'cert', 'mpscert', 'deletedomain', 'deletedefaultdomain', 'showall', 'showusers', 'shownodes', 'showmeshes', 'showevents', 'showpower', 'clearpower', 'showiplocations', 'help', 'exactports', 'install', 'uninstall', 'start', 'stop', 'restart', 'debug', 'filespath', 'datapath', 'noagentupdate', 'launch', 'noserverbackup', 'mongodb', 'mongodbcol', 'wanonly', 'lanonly', 'nousers', 'mpspass', 'ciralocalfqdn', 'dbexport', 'dbexportmin', 'dbimport', 'dbmerge', 'dbencryptkey', 'selfupdate', 'tlsoffload', 'userallowedip', 'userblockedip', 'swarmallowedip', 'agentallowedip', 'agentblockedip', 'fastcert', 'swarmport', 'logintoken', 'logintokenkey', 'logintokengen', 'logintokengen', 'mailtokengen', 'admin', 'unadmin', 'sessionkey', 'sessiontime', 'minify', 'minifycore', 'dblistconfigfiles', 'dbshowconfigfile', 'dbpushconfigfiles', 'dbpullconfigfiles', 'dbdeleteconfigfiles', 'vaultpushconfigfiles', 'vaultpullconfigfiles', 'vaultdeleteconfigfiles', 'configkey', 'loadconfigfromdb', 'npmpath', 'memorytracking', 'serverid', 'recordencryptionrecode', 'vault', 'token', 'unsealkey', 'name']; var validArguments = ['_', 'notls', 'user', 'port', 'aliasport', 'mpsport', 'mpsaliasport', 'redirport', 'rediraliasport', 'cert', 'mpscert', 'deletedomain', 'deletedefaultdomain', 'showall', 'showusers', 'shownodes', 'showmeshes', 'showevents', 'showpower', 'clearpower', 'showiplocations', 'help', 'exactports', 'xinstall', 'xuninstall', 'install', 'uninstall', 'start', 'stop', 'restart', 'debug', 'filespath', 'datapath', 'noagentupdate', 'launch', 'noserverbackup', 'mongodb', 'mongodbcol', 'wanonly', 'lanonly', 'nousers', 'mpspass', 'ciralocalfqdn', 'dbexport', 'dbexportmin', 'dbimport', 'dbmerge', 'dbencryptkey', 'selfupdate', 'tlsoffload', 'userallowedip', 'userblockedip', 'swarmallowedip', 'agentallowedip', 'agentblockedip', 'fastcert', 'swarmport', 'logintoken', 'logintokenkey', 'logintokengen', 'logintokengen', 'mailtokengen', 'admin', 'unadmin', 'sessionkey', 'sessiontime', 'minify', 'minifycore', 'dblistconfigfiles', 'dbshowconfigfile', 'dbpushconfigfiles', 'dbpullconfigfiles', 'dbdeleteconfigfiles', 'vaultpushconfigfiles', 'vaultpullconfigfiles', 'vaultdeleteconfigfiles', 'configkey', 'loadconfigfromdb', 'npmpath', 'memorytracking', 'serverid', 'recordencryptionrecode', 'vault', 'token', 'unsealkey', 'name', 'log'];
for (var arg in obj.args) { obj.args[arg.toLocaleLowerCase()] = obj.args[arg]; if (validArguments.indexOf(arg.toLocaleLowerCase()) == -1) { console.log('Invalid argument "' + arg + '", use --help.'); return; } } for (var arg in obj.args) { obj.args[arg.toLocaleLowerCase()] = obj.args[arg]; if (validArguments.indexOf(arg.toLocaleLowerCase()) == -1) { console.log('Invalid argument "' + arg + '", use --help.'); return; } }
if (obj.args.mongodb == true) { console.log('Must specify: --mongodb [connectionstring] \r\nSee https://docs.mongodb.com/manual/reference/connection-string/ for MongoDB connection string.'); return; } if (obj.args.mongodb == true) { console.log('Must specify: --mongodb [connectionstring] \r\nSee https://docs.mongodb.com/manual/reference/connection-string/ for MongoDB connection string.'); return; }
for (i in obj.config.settings) { obj.args[i] = obj.config.settings[i]; } // Place all settings into arguments, arguments have already been placed into settings so arguments take precedence. for (i in obj.config.settings) { obj.args[i] = obj.config.settings[i]; } // Place all settings into arguments, arguments have already been placed into settings so arguments take precedence.
@ -144,9 +144,10 @@ function CreateMeshCentralServer(config, args) {
return; return;
} }
if (obj.service != null) {
// Check if we need to install, start, stop, remove ourself as a background service // Check if we need to install, start, stop, remove ourself as a background service
if ((obj.service != null) && ((obj.args.install == true) || (obj.args.uninstall == true) || (obj.args.start == true) || (obj.args.stop == true) || (obj.args.restart == true))) { if (((obj.args.xinstall == true) || (obj.args.xuninstall == true) || (obj.args.start == true) || (obj.args.stop == true) || (obj.args.restart == true))) {
var env = [], xenv = ['user', 'port', 'aliasport', 'mpsport', 'mpsaliasport', 'redirport', 'exactport', 'debug']; var env = [], xenv = ['user', 'port', 'aliasport', 'mpsport', 'mpsaliasport', 'redirport', 'exactport', 'rediraliasport', 'debug'];
for (i in xenv) { if (obj.args[xenv[i]] != null) { env.push({ name: 'mesh' + xenv[i], value: obj.args[xenv[i]] }); } } // Set some args as service environement variables. for (i in xenv) { if (obj.args[xenv[i]] != null) { env.push({ name: 'mesh' + xenv[i], value: obj.args[xenv[i]] }); } } // Set some args as service environement variables.
var svc = new obj.service({ name: 'MeshCentral', description: 'MeshCentral Remote Management Server', script: obj.path.join(__dirname, 'winservice.js'), env: env, wait: 2, grow: 0.5 }); var svc = new obj.service({ name: 'MeshCentral', description: 'MeshCentral Remote Management Server', script: obj.path.join(__dirname, 'winservice.js'), env: env, wait: 2, grow: 0.5 });
svc.on('install', function () { console.log('MeshCentral service installed.'); svc.start(); }); svc.on('install', function () { console.log('MeshCentral service installed.'); svc.start(); });
@ -156,13 +157,42 @@ function CreateMeshCentralServer(config, args) {
svc.on('alreadyinstalled', function () { console.log('MeshCentral service already installed.'); process.exit(); }); svc.on('alreadyinstalled', function () { console.log('MeshCentral service already installed.'); process.exit(); });
svc.on('invalidinstallation', function () { console.log('Invalid MeshCentral service installation.'); process.exit(); }); svc.on('invalidinstallation', function () { console.log('Invalid MeshCentral service installation.'); process.exit(); });
if (obj.args.install == true) { try { svc.install(); } catch (e) { logException(e); } } if (obj.args.xinstall == true) { try { svc.install(); } catch (e) { logException(e); } }
if (obj.args.stop == true || obj.args.restart == true) { try { svc.stop(); } catch (e) { logException(e); } } if (obj.args.stop == true || obj.args.restart == true) { try { svc.stop(); } catch (e) { logException(e); } }
if (obj.args.start == true || obj.args.restart == true) { try { svc.start(); } catch (e) { logException(e); } } if (obj.args.start == true || obj.args.restart == true) { try { svc.start(); } catch (e) { logException(e); } }
if (obj.args.uninstall == true) { try { svc.uninstall(); } catch (e) { logException(e); } } if (obj.args.xuninstall == true) { try { svc.uninstall(); } catch (e) { logException(e); } }
return; return;
} }
// Windows service install using the external winservice.js
if (obj.args.install == true) {
console.log('Installing MeshCentral as Windows Service...');
if (obj.fs.existsSync(obj.path.join(__dirname, '../WinService')) == false) { try { obj.fs.mkdirSync(obj.path.join(__dirname, '../WinService')); } catch (ex) { console.log('ERROR: Unable to create WinService folder: ' + ex); process.exit(); return; } }
try { obj.fs.createReadStream(obj.path.join(__dirname, 'winservice.js')).pipe(obj.fs.createWriteStream(obj.path.join(__dirname, '../WinService/winservice.js'))); } catch (ex) { console.log('ERROR: Unable to copy winservice.js: ' + ex); process.exit(); return; }
require('child_process').exec('node winservice.js --install', { maxBuffer: 512000, timeout: 120000, cwd: obj.path.join(__dirname, '../WinService') }, function (error, stdout, stderr) {
if ((error != null) && (error != '')) { console.log('ERROR: Unable to install MeshCentral as a service: ' + error); process.exit(); return; }
console.log(stdout);
});
return;
} else if (obj.args.uninstall == true) {
console.log('Uninstalling MeshCentral Windows Service...');
if (obj.fs.existsSync(obj.path.join(__dirname, '../WinService')) == true) {
require('child_process').exec('node winservice.js --uninstall', { maxBuffer: 512000, timeout: 120000, cwd: obj.path.join(__dirname, '../WinService') }, function (error, stdout, stderr) {
if ((error != null) && (error != '')) { console.log('ERROR: Unable to uninstall MeshCentral service: ' + error); process.exit(); return; }
console.log(stdout);
try { obj.fs.unlinkSync(obj.path.join(__dirname, '../WinService/winservice.js')); } catch (ex) { }
try { obj.fs.rmdirSync(obj.path.join(__dirname, '../WinService')); } catch (ex) { }
});
} else {
require('child_process').exec('node winservice.js --uninstall', { maxBuffer: 512000, timeout: 120000, cwd: __dirname }, function (error, stdout, stderr) {
if ((error != null) && (error != '')) { console.log('ERROR: Unable to uninstall MeshCentral service: ' + error); process.exit(); return; }
console.log(stdout);
});
}
return;
}
}
// If "--launch" is in the arguments, launch now // If "--launch" is in the arguments, launch now
if (obj.args.launch) { if (obj.args.launch) {
if (obj.args.vault) { obj.StartVault(); } else { obj.StartEx(); } if (obj.args.vault) { obj.StartVault(); } else { obj.StartEx(); }
@ -677,7 +707,7 @@ function CreateMeshCentralServer(config, args) {
} }
// Read environment variables. For a subset of arguments, we allow them to be read from environment variables. // Read environment variables. For a subset of arguments, we allow them to be read from environment variables.
var xenv = ['user', 'port', 'mpsport', 'mpsaliasport', 'redirport', 'exactport', 'debug']; var xenv = ['user', 'port', 'mpsport', 'mpsaliasport', 'redirport', 'rediraliasport', 'exactport', 'debug'];
for (i in xenv) { if ((obj.args[xenv[i]] == null) && (process.env['mesh' + xenv[i]])) { obj.args[xenv[i]] = obj.common.toNumber(process.env['mesh' + xenv[i]]); } } for (i in xenv) { if ((obj.args[xenv[i]] == null) && (process.env['mesh' + xenv[i]])) { obj.args[xenv[i]] = obj.common.toNumber(process.env['mesh' + xenv[i]]); } }
// Validate the domains, this is used for multi-hosting // Validate the domains, this is used for multi-hosting
@ -735,6 +765,7 @@ function CreateMeshCentralServer(config, args) {
if (obj.args.aliasport != null && (typeof obj.args.aliasport != 'number')) obj.args.aliasport = null; if (obj.args.aliasport != null && (typeof obj.args.aliasport != 'number')) obj.args.aliasport = null;
if (obj.args.mpsport == null || typeof obj.args.mpsport != 'number') obj.args.mpsport = 4433; if (obj.args.mpsport == null || typeof obj.args.mpsport != 'number') obj.args.mpsport = 4433;
if (obj.args.mpsaliasport != null && (typeof obj.args.mpsaliasport != 'number')) obj.args.mpsaliasport = null; if (obj.args.mpsaliasport != null && (typeof obj.args.mpsaliasport != 'number')) obj.args.mpsaliasport = null;
if (obj.args.rediraliasport != null && (typeof obj.args.rediraliasport != 'number')) obj.args.rediraliasport = null;
if (obj.args.notls == null && obj.args.redirport == null) obj.args.redirport = 80; if (obj.args.notls == null && obj.args.redirport == null) obj.args.redirport = 80;
if (obj.args.minifycore === 0) obj.args.minifycore = false; if (obj.args.minifycore === 0) obj.args.minifycore = false;
if (typeof args.agentidletimeout != 'number') { args.agentidletimeout = 150000; } else { args.agentidletimeout *= 1000 } // Default agent idle timeout is 2m, 30sec. if (typeof args.agentidletimeout != 'number') { args.agentidletimeout = 150000; } else { args.agentidletimeout *= 1000 } // Default agent idle timeout is 2m, 30sec.
@ -830,7 +861,9 @@ function CreateMeshCentralServer(config, args) {
// Load server certificates // Load server certificates
obj.certificateOperations = require('./certoperations.js').CertificateOperations(obj); obj.certificateOperations = require('./certoperations.js').CertificateOperations(obj);
obj.certificateOperations.GetMeshServerCertificate(obj.args, obj.config, function (certs) { obj.certificateOperations.GetMeshServerCertificate(obj.args, obj.config, function (certs) {
if ((obj.config.letsencrypt == null) || (obj.redirserver == null)) { // Get the current node version
const nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]);
if ((nodeVersion < 8) || (require('crypto').generateKeyPair == null) || (obj.config.letsencrypt == null) || (obj.redirserver == null)) {
obj.StartEx3(certs); // Just use the configured certificates obj.StartEx3(certs); // Just use the configured certificates
} else { } else {
var le = require('./letsencrypt.js'); var le = require('./letsencrypt.js');
@ -1831,6 +1864,27 @@ function CreateMeshCentralServer(config, args) {
// Send event to console // Send event to console
if ((obj.debugSources != null) && ((obj.debugSources == '*') || (obj.debugSources.indexOf(source) >= 0))) { console.log(source.toUpperCase() + ':', ...args); } if ((obj.debugSources != null) && ((obj.debugSources == '*') || (obj.debugSources.indexOf(source) >= 0))) { console.log(source.toUpperCase() + ':', ...args); }
// Send event to log file
if (obj.config.settings && obj.config.settings.log) {
if (typeof obj.args.log == 'string') { obj.args.log = obj.args.log.split(','); }
if (obj.args.log.indexOf(source) >= 0) {
const d = new Date();
if (obj.xxLogFile == null) {
try {
obj.xxLogFile = obj.fs.openSync(obj.getConfigFilePath('log.txt'), 'a+', 666);
obj.fs.writeSync(obj.xxLogFile, '---- Log start at ' + new Date().toLocaleString() + ' ----\r\n');
obj.xxLogDateStr = d.toLocaleDateString();
} catch (ex) { }
}
if (obj.xxLogFile != null) {
try {
if (obj.xxLogDateStr != d.toLocaleDateString()) { obj.xxLogDateStr = d.toLocaleDateString(); obj.fs.writeSync(obj.xxLogFile, '---- ' + d.toLocaleDateString() + ' ----\r\n'); }
obj.fs.writeSync(obj.xxLogFile, new Date().toLocaleTimeString() + ' - ' + source + ': ' + Array.prototype.slice.call(...args).join('') + '\r\n');
} catch (ex) { }
}
}
}
// Send the event to logged in administrators // Send the event to logged in administrators
if ((obj.debugRemoteSources != null) && ((obj.debugRemoteSources == '*') || (obj.debugRemoteSources.indexOf(source) >= 0))) { if ((obj.debugRemoteSources != null) && ((obj.debugRemoteSources == '*') || (obj.debugRemoteSources.indexOf(source) >= 0))) {
var sendcount = 0; var sendcount = 0;
@ -1932,8 +1986,17 @@ function InstallModules(modules, func) {
var missingModules = []; var missingModules = [];
if (modules.length > 0) { if (modules.length > 0) {
for (var i in modules) { for (var i in modules) {
// Modules may contain a version tag (foobar@1.0.0), remove it so the module can be found using require
var moduleName = modules[i].split("@", 1)[0];
try { try {
var xxmodule = require(modules[i]); if (moduleName == 'greenlock') {
// Check if we have GreenLock v3
delete require.cache[require.resolve('greenlock')]; // Clear the require cache
if (typeof require('greenlock').challengeType == 'string') { missingModules.push(modules[i]); }
} else {
// For all other modules, do the check here.
require(moduleName);
}
} catch (e) { } catch (e) {
if (previouslyInstalledModules[modules[i]] !== true) { missingModules.push(modules[i]); } if (previouslyInstalledModules[modules[i]] !== true) { missingModules.push(modules[i]); }
} }
@ -1943,7 +2006,6 @@ function InstallModules(modules, func) {
} }
// Check if a module is present and install it if missing // Check if a module is present and install it if missing
var InstallModuleChildProcess = null;
function InstallModule(modulename, func, tag1, tag2) { function InstallModule(modulename, func, tag1, tag2) {
console.log('Installing ' + modulename + '...'); console.log('Installing ' + modulename + '...');
var child_process = require('child_process'); var child_process = require('child_process');
@ -1952,9 +2014,7 @@ function InstallModule(modulename, func, tag1, tag2) {
// Get the working directory // Get the working directory
if ((__dirname.endsWith('/node_modules/meshcentral')) || (__dirname.endsWith('\\node_modules\\meshcentral')) || (__dirname.endsWith('/node_modules/meshcentral/')) || (__dirname.endsWith('\\node_modules\\meshcentral\\'))) { parentpath = require('path').join(__dirname, '../..'); } if ((__dirname.endsWith('/node_modules/meshcentral')) || (__dirname.endsWith('\\node_modules\\meshcentral')) || (__dirname.endsWith('/node_modules/meshcentral/')) || (__dirname.endsWith('\\node_modules\\meshcentral\\'))) { parentpath = require('path').join(__dirname, '../..'); }
// Looks like we need to keep a global reference to the child process object for this to work correctly. child_process.exec(`npm install --no-optional ${modulename}`, { maxBuffer: 512000, timeout: 120000, cwd: parentpath }, function (error, stdout, stderr) {
InstallModuleChildProcess = child_process.exec('npm install --no-optional --save ' + modulename, { maxBuffer: 512000, timeout: 10000, cwd: parentpath }, function (error, stdout, stderr) {
InstallModuleChildProcess = null;
if ((error != null) && (error != '')) { if ((error != null) && (error != '')) {
console.log('ERROR: Unable to install required module "' + modulename + '". MeshCentral may not have access to npm, or npm may not have suffisent rights to load the new module. Try "npm install ' + modulename + '" to manualy install this module.\r\n'); console.log('ERROR: Unable to install required module "' + modulename + '". MeshCentral may not have access to npm, or npm may not have suffisent rights to load the new module. Try "npm install ' + modulename + '" to manualy install this module.\r\n');
process.exit(); process.exit();
@ -2002,20 +2062,20 @@ function mainStart() {
if (config.domains[i].auth == 'ldap') { ldap = true; } if (config.domains[i].auth == 'ldap') { ldap = true; }
} }
// Get the current node version
var nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]);
// Build the list of required modules // Build the list of required modules
var modules = ['ws', 'cbor', 'nedb', 'https', 'yauzl', 'xmldom', 'ipcheck', 'express', 'archiver', 'multiparty', 'node-forge', 'express-ws', 'compression', 'body-parser', 'connect-redis', 'cookie-session', 'express-handlebars']; var modules = ['ws', 'cbor', 'nedb', 'https', 'yauzl', 'xmldom', 'ipcheck', 'express', 'archiver', 'multiparty', 'node-forge', 'express-ws', 'compression', 'body-parser', 'connect-redis', 'cookie-session', 'express-handlebars'];
if (require('os').platform() == 'win32') { modules.push('node-windows'); if (sspi == true) { modules.push('node-sspi'); } } // Add Windows modules if (require('os').platform() == 'win32') { modules.push('node-windows'); if (sspi == true) { modules.push('node-sspi'); } } // Add Windows modules
if (ldap == true) { modules.push('ldapauth-fork'); } if (ldap == true) { modules.push('ldapauth-fork'); }
if (config.letsencrypt != null) { modules.push('greenlock'); modules.push('le-store-certbot'); modules.push('le-challenge-fs'); modules.push('le-acme-core'); } // Add Greenlock Modules if (config.letsencrypt != null) { if ((nodeVersion < 10) || (require('crypto').generateKeyPair == null)) { if (!args.launch) { console.log("WARNING: Let's Encrypt support requires Node v10.12.0 or higher."); } } else { modules.push('greenlock'); } } // Add Greenlock Module
if (config.settings.mqtt != null) { modules.push('aedes'); } // Add MQTT Modules if (config.settings.mqtt != null) { modules.push('aedes'); } // Add MQTT Modules
if (config.settings.mongodb != null) { modules.push('mongodb'); } // Add MongoDB, official driver. if (config.settings.mongodb != null) { modules.push('mongodb'); } // Add MongoDB, official driver.
if (config.settings.vault != null) { modules.push('node-vault'); } // Add official HashiCorp's Vault module. if (config.settings.vault != null) { modules.push('node-vault'); } // Add official HashiCorp's Vault module.
else if (config.settings.xmongodb != null) { modules.push('mongojs'); } // Add MongoJS, old driver. else if (config.settings.xmongodb != null) { modules.push('mongojs'); } // Add MongoJS, old driver.
if (config.smtp != null) { modules.push('nodemailer'); } // Add SMTP support if (config.smtp != null) { modules.push('nodemailer'); } // Add SMTP support
// Get the current node version
var nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]);
// If running NodeJS < 8, install "util.promisify" // If running NodeJS < 8, install "util.promisify"
if (nodeVersion < 8) { modules.push('util.promisify'); } if (nodeVersion < 8) { modules.push('util.promisify'); }

View File

@ -360,6 +360,14 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
try { ws.send(JSON.stringify({ action: 'traceinfo', traceSources: parent.parent.debugRemoteSources })); } catch (ex) { } try { ws.send(JSON.stringify({ action: 'traceinfo', traceSources: parent.parent.debugRemoteSources })); } catch (ex) { }
} }
// See how many times bad login attempts where made since the last login
const lastLoginTime = parent.users[user._id].pastlogin;
if (lastLoginTime != null) {
db.GetFailedLoginCount(user.name, user.domain, new Date(lastLoginTime * 1000), function (count) {
if (count > 0) { try { ws.send(JSON.stringify({ action: 'msg', type: 'notify', title: "Security Warning", tag: 'ServerNotify', value: "There has been " + count + " failed login attempts on this account since the last login." })); } catch (ex) { } delete user.pastlogin; }
});
}
// We are all set, start receiving data // We are all set, start receiving data
ws._socket.resume(); ws._socket.resume();
}); });
@ -681,14 +689,31 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
case 'help': { case 'help': {
r = 'Available commands: help, info, versions, args, resetserver, showconfig, usersessions, tasklimiter, setmaxtasks, cores,\r\n' r = 'Available commands: help, info, versions, args, resetserver, showconfig, usersessions, tasklimiter, setmaxtasks, cores,\r\n'
r += 'migrationagents, agentstats, webstats, mpsstats, swarmstats, acceleratorsstats, updatecheck, serverupdate, nodeconfig,\r\n'; r += 'migrationagents, agentstats, webstats, mpsstats, swarmstats, acceleratorsstats, updatecheck, serverupdate, nodeconfig,\r\n';
r += 'heapdump, relays, autobackup, backupconfig, dupagents, dispatchtable.'; r += 'heapdump, relays, autobackup, backupconfig, dupagents, dispatchtable, badlogins.';
break;
}
case 'badlogins': {
if (typeof parent.parent.config.settings.maxinvalidlogin.coolofftime == 'number') {
r = "Max is " + parent.parent.config.settings.maxinvalidlogin.count + " bad login(s) in " + parent.parent.config.settings.maxinvalidlogin.time + " minute(s), " + parent.parent.config.settings.maxinvalidlogin.coolofftime + " minute(s) cooloff.\r\n";
} else {
r = "Max is " + parent.parent.config.settings.maxinvalidlogin.count + " bad login(s) in " + parent.parent.config.settings.maxinvalidlogin.time + " minute(s).\r\n";
}
var badLoginCount = 0;
parent.cleanBadLoginTable();
for (var i in parent.badLoginTable) {
badLoginCount++;
if (typeof parent.badLoginTable[i] == 'number') {
r += "Cooloff for " + Math.floor((parent.badLoginTable[i] - Date.now()) / 60000) + " minute(s)\r\n";
} else {
r += (i + ' - ' + parent.badLoginTable[i].length + " entries\r\n");
}
}
if (badLoginCount == 0) { r += 'No bad logins.'; }
break; break;
} }
case 'dispatchtable': { case 'dispatchtable': {
r = ''; r = '';
for (var i in parent.parent.eventsDispatch) { for (var i in parent.parent.eventsDispatch) { r += (i + ', ' + parent.parent.eventsDispatch[i].length + '\r\n'); }
r += (i + ', ' + parent.parent.eventsDispatch[i].length + '\r\n');
}
break; break;
} }
case 'dupagents': { case 'dupagents': {

View File

@ -1,6 +1,6 @@
{ {
"name": "meshcentral", "name": "meshcentral",
"version": "0.4.3-s", "version": "0.4.4-r",
"keywords": [ "keywords": [
"Remote Management", "Remote Management",
"Intel AMT", "Intel AMT",
@ -30,22 +30,19 @@
"dependencies": { "dependencies": {
"archiver": "^3.0.0", "archiver": "^3.0.0",
"body-parser": "^1.19.0", "body-parser": "^1.19.0",
"cbor": "4.1.5", "cbor": "^4.1.5",
"compression": "^1.7.4", "compression": "^1.7.4",
"connect-redis": "^3.4.1", "connect-redis": "^3.4.1",
"cookie-session": "^2.0.0-beta.3", "cookie-session": "^2.0.0-beta.3",
"express": "^4.17.0", "express": "^4.17.0",
"express-handlebars": "^3.1.0", "express-handlebars": "^3.1.0",
"express-ws": "^4.0.0", "express-ws": "^4.0.0",
"html-minifier": "^4.0.0",
"ipcheck": "^0.1.0", "ipcheck": "^0.1.0",
"meshcentral": "*", "meshcentral": "*",
"minify-js": "0.0.4",
"minimist": "^1.2.0", "minimist": "^1.2.0",
"multiparty": "^4.2.1", "multiparty": "^4.2.1",
"nedb": "^1.8.0", "nedb": "^1.8.0",
"node-forge": "^0.8.4", "node-forge": "^0.8.4",
"node-vault": "^0.9.11",
"ws": "^6.2.1", "ws": "^6.2.1",
"xmldom": "^0.1.27", "xmldom": "^0.1.27",
"yauzl": "^2.10.0" "yauzl": "^2.10.0"

File diff suppressed because one or more lines are too long

View File

@ -23,11 +23,12 @@ module.exports.CreateRedirServer = function (parent, db, args, func) {
obj.db = db; obj.db = db;
obj.args = args; obj.args = args;
obj.certificates = null; obj.certificates = null;
obj.express = require("express"); obj.express = require('express');
obj.net = require("net"); obj.net = require('net');
obj.app = obj.express(); obj.app = obj.express();
obj.tcpServer = null; obj.tcpServer = null;
obj.port = null; obj.port = null;
const leChallengePrefix = '/.well-known/acme-challenge/';
// Perform an HTTP to HTTPS redirection // Perform an HTTP to HTTPS redirection
function performRedirection(req, res) { function performRedirection(req, res) {
@ -49,14 +50,14 @@ module.exports.CreateRedirServer = function (parent, db, args, func) {
*/ */
// Renter the terms of service. // Renter the terms of service.
obj.app.get("/MeshServerRootCert.cer", function (req, res) { obj.app.get('/MeshServerRootCert.cer', function (req, res) {
// The redirection server starts before certificates are loaded, make sure to handle the case where no certificate is loaded now. // The redirection server starts before certificates are loaded, make sure to handle the case where no certificate is loaded now.
if (obj.certificates != null) { if (obj.certificates != 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=\"" + obj.certificates.RootName + ".cer\"" }); res.set({ 'Cache-Control': "no-cache, no-store, must-revalidate", "Pragma": "no-cache", "Expires": "0", "Content-Type": "application/octet-stream", "Content-Disposition": "attachment; filename=\"" + obj.certificates.RootName + ".cer\"" });
var rootcert = obj.certificates.root.cert; var rootcert = obj.certificates.root.cert;
var i = rootcert.indexOf("-----BEGIN CERTIFICATE-----\r\n"); var i = rootcert.indexOf('-----BEGIN CERTIFICATE-----\r\n');
if (i >= 0) { rootcert = rootcert.substring(i + 29); } if (i >= 0) { rootcert = rootcert.substring(i + 29); }
i = rootcert.indexOf("-----END CERTIFICATE-----"); i = rootcert.indexOf('-----END CERTIFICATE-----');
if (i >= 0) { rootcert = rootcert.substring(i, 0); } if (i >= 0) { rootcert = rootcert.substring(i, 0); }
res.send(Buffer.from(rootcert, "base64")); res.send(Buffer.from(rootcert, "base64"));
} else { } else {
@ -66,9 +67,17 @@ module.exports.CreateRedirServer = function (parent, db, args, func) {
// Add HTTP security headers to all responses // Add HTTP security headers to all responses
obj.app.use(function (req, res, next) { obj.app.use(function (req, res, next) {
res.removeHeader("X-Powered-By"); parent.debug('webrequest', req.url + ' (RedirServer)');
res.set({ "strict-transport-security": "max-age=60000; 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: \"self\" \"unsafe-inline\"" }); res.removeHeader('X-Powered-By');
if ((parent.letsencrypt != null) && (req.url.startsWith(leChallengePrefix))) {
// Let's Encrypt Support
parent.letsencrypt.challenge(req.url.slice(leChallengePrefix.length), getCleanHostname(req), function (response) { if (response == null) { res.sendStatus(404); } else { res.send(response); } });
} else {
// Everything else
res.set({ 'strict-transport-security': "max-age=60000; 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: \"self\" \"unsafe-inline\"" });
return next(); return next();
}
}); });
// Once the main web server is started, call this to hookup additional handlers // Once the main web server is started, call this to hookup additional handlers
@ -125,6 +134,17 @@ module.exports.CreateRedirServer = function (parent, db, args, func) {
}); });
} }
// Get the remote hostname correctly
const servernameRe = /^[a-z0-9\.\-]+$/i;
function getHostname(req) { return req.hostname || req.headers['x-forwarded-host'] || (req.headers.host || ''); };
function getCleanHostname(req) {
var servername = getHostname(req).toLowerCase().replace(/:.*/, '');
try { req.hostname = servername; } catch (e) { } // read-only express property
if (req.headers['x-forwarded-host']) { req.headers['x-forwarded-host'] = servername; }
try { req.headers.host = servername; } catch (e) { }
return (servernameRe.test(servername) && -1 === servername.indexOf('..') && servername) || '';
};
CheckListenPort(args.redirport, StartRedirServer); CheckListenPort(args.redirport, StartRedirServer);
return obj; return obj;

View File

@ -44,6 +44,7 @@
"_TlsOffload": true, "_TlsOffload": true,
"_MpsTlsOffload": true, "_MpsTlsOffload": true,
"_No2FactorAuth": true, "_No2FactorAuth": true,
"_Log": "main,web,webrequest,cert",
"_WebRtConfig": { "_WebRtConfig": {
"iceServers": [ "iceServers": [
{ "urls": "stun:stun.services.mozilla.com" }, { "urls": "stun:stun.services.mozilla.com" },
@ -58,7 +59,9 @@
}, },
"_Redirects": { "_Redirects": {
"meshcommander": "https://www.meshcommander.com/" "meshcommander": "https://www.meshcommander.com/"
} },
"__MaxInvalidLogin": "Time in minutes, max amount of bad logins from a source IP in the time before logins are rejected.",
"MaxInvalidLogin": { "time": 10, "count": 10, "coolofftime": 10 }
}, },
"_domains": { "_domains": {
"": { "": {

View File

@ -462,7 +462,7 @@ function InstallModule(modulename, func, tag1, tag2) {
if ((__dirname.endsWith('/node_modules/meshcentral')) || (__dirname.endsWith('\\node_modules\\meshcentral')) || (__dirname.endsWith('/node_modules/meshcentral/')) || (__dirname.endsWith('\\node_modules\\meshcentral\\'))) { parentpath = require('path').join(__dirname, '../..'); } if ((__dirname.endsWith('/node_modules/meshcentral')) || (__dirname.endsWith('\\node_modules\\meshcentral')) || (__dirname.endsWith('/node_modules/meshcentral/')) || (__dirname.endsWith('\\node_modules\\meshcentral\\'))) { parentpath = require('path').join(__dirname, '../..'); }
// Looks like we need to keep a global reference to the child process object for this to work correctly. // Looks like we need to keep a global reference to the child process object for this to work correctly.
InstallModuleChildProcess = child_process.exec('npm install --no-optional --save ' + modulename, { maxBuffer: 512000, timeout: 10000, cwd: parentpath }, function (error, stdout, stderr) { InstallModuleChildProcess = child_process.exec('npm install --no-optional --save ' + modulename, { maxBuffer: 512000, timeout: 120000, cwd: parentpath }, function (error, stdout, stderr) {
InstallModuleChildProcess = null; InstallModuleChildProcess = null;
if ((error != null) && (error != '')) { if ((error != null) && (error != '')) {
console.log('ERROR: Unable to install required module "' + modulename + '". May not have access to npm, or npm may not have suffisent rights to load the new module. Try "npm install ' + modulename + '" to manualy install this module.\r\n'); console.log('ERROR: Unable to install required module "' + modulename + '". May not have access to npm, or npm may not have suffisent rights to load the new module. Try "npm install ' + modulename + '" to manualy install this module.\r\n');

View File

@ -6498,10 +6498,10 @@
if (meshrights & 8) { if (meshrights & 8) {
Q('p20remotecontrol').checked = true; Q('p20remotecontrol').checked = true;
if (meshrights & 256) { Q('p20remoteview').checked = true; } if (meshrights & 256) { Q('p20remoteview').checked = true; }
if (meshrights & 512) { Q('p20remotelimitedinput').checked = true; } if (meshrights & 512) { Q('p20noterminal').checked = true; }
if (meshrights & 1024) { Q('p20noterminal').checked = true; } if (meshrights & 1024) { Q('p20nofiles').checked = true; }
if (meshrights & 2048) { Q('p20nofiles').checked = true; } if (meshrights & 2048) { Q('p20noamt').checked = true; }
if (meshrights & 4096) { Q('p20noamt').checked = true; } if (meshrights & 4096) { Q('p20remotelimitedinput').checked = true; }
} }
if (meshrights & 16) { Q('p20meshagentconsole').checked = true; } if (meshrights & 16) { Q('p20meshagentconsole').checked = true; }
if (meshrights & 32) { Q('p20meshserverfiles').checked = true; } if (meshrights & 32) { Q('p20meshserverfiles').checked = true; }
@ -8197,6 +8197,7 @@
x += '<div><label><input type=checkbox id=p41c4 ' + ((serverTraceSources.indexOf('peer') >= 0) ? 'checked' : '') + '>' + "MeshCentral Server Peering" + '</label></div>'; x += '<div><label><input type=checkbox id=p41c4 ' + ((serverTraceSources.indexOf('peer') >= 0) ? 'checked' : '') + '>' + "MeshCentral Server Peering" + '</label></div>';
x += '<div><label><input type=checkbox id=p41c15 ' + ((serverTraceSources.indexOf('agent') >= 0) ? 'checked' : '') + '>' + "MeshAgent traffic" + '</label></div>'; x += '<div><label><input type=checkbox id=p41c15 ' + ((serverTraceSources.indexOf('agent') >= 0) ? 'checked' : '') + '>' + "MeshAgent traffic" + '</label></div>';
x += '<div><label><input type=checkbox id=p41c14 ' + ((serverTraceSources.indexOf('agentupdate') >= 0) ? 'checked' : '') + '>' + "MeshAgent update" + '</label></div>'; x += '<div><label><input type=checkbox id=p41c14 ' + ((serverTraceSources.indexOf('agentupdate') >= 0) ? 'checked' : '') + '>' + "MeshAgent update" + '</label></div>';
x += '<div><label><input type=checkbox id=p41c16 ' + ((serverTraceSources.indexOf('cert') >= 0) ? 'checked' : '') + '>' + "Server Certificate" + '</label></div>';
x += '<div style="width:100%;border-bottom:1px solid gray;margin-bottom:5px;margin-top:5px"><b>' + "Web Server" + '</b></div>'; x += '<div style="width:100%;border-bottom:1px solid gray;margin-bottom:5px;margin-top:5px"><b>' + "Web Server" + '</b></div>';
x += '<div><label><input type=checkbox id=p41c5 ' + ((serverTraceSources.indexOf('web') >= 0) ? 'checked' : '') + '>' + "Web Server" + '</label></div>'; x += '<div><label><input type=checkbox id=p41c5 ' + ((serverTraceSources.indexOf('web') >= 0) ? 'checked' : '') + '>' + "Web Server" + '</label></div>';
x += '<div><label><input type=checkbox id=p41c6 ' + ((serverTraceSources.indexOf('webrequest') >= 0) ? 'checked' : '') + '>' + "Web Server Requests" + '</label></div>'; x += '<div><label><input type=checkbox id=p41c6 ' + ((serverTraceSources.indexOf('webrequest') >= 0) ? 'checked' : '') + '>' + "Web Server Requests" + '</label></div>';
@ -8213,8 +8214,8 @@
} }
function setServerTracingEx(b) { function setServerTracingEx(b) {
var sources = [], allsources = ['cookie', 'dispatch', 'main', 'peer', 'web', 'webrequest', 'relay', 'webrelaydata', 'webrelay', 'mps', 'mpscmd', 'swarm', 'swarmcmd', 'agentupdate', 'agent']; var sources = [], allsources = ['cookie', 'dispatch', 'main', 'peer', 'web', 'webrequest', 'relay', 'webrelaydata', 'webrelay', 'mps', 'mpscmd', 'swarm', 'swarmcmd', 'agentupdate', 'agent', 'cert'];
if (b == 1) { for (var i = 1; i < 16; i++) { try { if (Q('p41c' + i).checked) { sources.push(allsources[i - 1]); } } catch (ex) { } } } if (b == 1) { for (var i = 1; i < 17; i++) { try { if (Q('p41c' + i).checked) { sources.push(allsources[i - 1]); } } catch (ex) { } } }
meshserver.send({ action: 'traceinfo', traceSources: sources }); meshserver.send({ action: 'traceinfo', traceSources: sources });
} }

File diff suppressed because one or more lines are too long

View File

@ -3221,6 +3221,7 @@
x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20editnotes>' + "Edit Device Notes" + '</label><br>'; x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20editnotes>' + "Edit Device Notes" + '</label><br>';
x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20limitevents>' + "Show Only Own Events" + '</label><br>'; x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20limitevents>' + "Show Only Own Events" + '</label><br>';
x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20chatnotify>' + "Chat & Notify" + '</label><br>'; x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20chatnotify>' + "Chat & Notify" + '</label><br>';
x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20uninstall>' + "Uninstall Agent" + '</label><br>';
x += '</div>'; x += '</div>';
setDialogMode(2, "Add User to Mesh", 3, p20showAddMeshUserDialogEx, x); setDialogMode(2, "Add User to Mesh", 3, p20showAddMeshUserDialogEx, x);
p20validateAddMeshUserDialog(); p20validateAddMeshUserDialog();
@ -3229,20 +3230,24 @@
function p20validateAddMeshUserDialog() { function p20validateAddMeshUserDialog() {
var meshrights = currentMesh.links[userinfo._id].rights; var meshrights = currentMesh.links[userinfo._id].rights;
QE('idx_dlgOkButton', (Q('dp20username').value.length > 0)); var nc = !Q('p20fulladmin').checked;
QE('p20fulladmin', meshrights == 0xFFFFFFFF); QE('p20fulladmin', meshrights == 0xFFFFFFFF);
QE('p20editmesh', (!Q('p20fulladmin').checked) && (meshrights == 0xFFFFFFFF)); QE('p20editmesh', nc && (meshrights == 0xFFFFFFFF));
QE('p20manageusers', !Q('p20fulladmin').checked); QE('p20manageusers', nc);
QE('p20managecomputers', !Q('p20fulladmin').checked); QE('p20managecomputers', nc);
QE('p20remotecontrol', !Q('p20fulladmin').checked); QE('p20remotecontrol', nc);
QE('p20meshagentconsole', !Q('p20fulladmin').checked); QE('p20meshagentconsole', nc);
QE('p20meshserverfiles', !Q('p20fulladmin').checked); QE('p20meshserverfiles', nc);
QE('p20wakedevices', !Q('p20fulladmin').checked); QE('p20wakedevices', nc);
QE('p20editnotes', !Q('p20fulladmin').checked); QE('p20editnotes', nc);
QE('p20remoteview', !Q('p20fulladmin').checked && Q('p20remotecontrol').checked); QE('p20limitevents', nc);
QE('p20noterminal', !Q('p20fulladmin').checked && Q('p20remotecontrol').checked); QE('p20remoteview', nc && Q('p20remotecontrol').checked);
QE('p20nofiles', !Q('p20fulladmin').checked && Q('p20remotecontrol').checked); QE('p20remotelimitedinput', nc && Q('p20remotecontrol').checked && !Q('p20remoteview').checked);
QE('p20noamt', !Q('p20fulladmin').checked && Q('p20remotecontrol').checked); QE('p20noterminal', nc && Q('p20remotecontrol').checked);
QE('p20nofiles', nc && Q('p20remotecontrol').checked);
QE('p20noamt', nc && Q('p20remotecontrol').checked);
QE('p20chatnotify', nc);
QE('p20uninstall', nc);
} }
function p20showAddMeshUserDialogEx() { function p20showAddMeshUserDialogEx() {
@ -3260,8 +3265,14 @@
if (Q('p20noterminal').checked == true) meshadmin += 512; if (Q('p20noterminal').checked == true) meshadmin += 512;
if (Q('p20nofiles').checked == true) meshadmin += 1024; if (Q('p20nofiles').checked == true) meshadmin += 1024;
if (Q('p20noamt').checked == true) meshadmin += 2048; if (Q('p20noamt').checked == true) meshadmin += 2048;
if (Q('p20remotelimitedinput').checked == true) meshadmin += 4096;
if (Q('p20limitevents').checked == true) meshadmin += 8192;
if (Q('p20chatnotify').checked == true) meshadmin += 16384;
if (Q('p20uninstall').checked == true) meshadmin += 32768;
} }
meshserver.send({ action: 'addmeshuser', meshid: currentMesh._id, meshname: currentMesh.name, username: Q('dp20username').value, meshadmin: meshadmin }); var users = Q('dp20username').value.split(','), users2 = [];
for (var i in users) { users2.push(users[i].trim()); }
meshserver.send({ action: 'addmeshuser', meshid: currentMesh._id, meshname: currentMesh.name, usernames: users2, meshadmin: meshadmin });
} }
function p20viewuser(userid) { function p20viewuser(userid) {
@ -3284,6 +3295,7 @@
if (((meshrights & 8) != 0) && ((meshrights & 4096) != 0) && ((meshrights & 256) == 0)) r.push("Limited Input"); if (((meshrights & 8) != 0) && ((meshrights & 4096) != 0) && ((meshrights & 256) == 0)) r.push("Limited Input");
if ((meshrights & 8192) != 0) r.push("Self Events Only"); if ((meshrights & 8192) != 0) r.push("Self Events Only");
if ((meshrights & 16384) != 0) r.push("Chat & Notify"); if ((meshrights & 16384) != 0) r.push("Chat & Notify");
if ((meshrights & 32768) != 0) r.push("Uninstall");
} }
if (r.length == 0) { r.push("No Rights"); } if (r.length == 0) { r.push("No Rights"); }
var buttons = 1, x = addHtmlValue("User", EscapeHtml(decodeURIComponent(userid.split('/')[2]))); var buttons = 1, x = addHtmlValue("User", EscapeHtml(decodeURIComponent(userid.split('/')[2])));

View File

@ -7555,10 +7555,10 @@
if (meshrights & 8) { if (meshrights & 8) {
Q('p20remotecontrol').checked = true; Q('p20remotecontrol').checked = true;
if (meshrights & 256) { Q('p20remoteview').checked = true; } if (meshrights & 256) { Q('p20remoteview').checked = true; }
if (meshrights & 512) { Q('p20remotelimitedinput').checked = true; } if (meshrights & 512) { Q('p20noterminal').checked = true; }
if (meshrights & 1024) { Q('p20noterminal').checked = true; } if (meshrights & 1024) { Q('p20nofiles').checked = true; }
if (meshrights & 2048) { Q('p20nofiles').checked = true; } if (meshrights & 2048) { Q('p20noamt').checked = true; }
if (meshrights & 4096) { Q('p20noamt').checked = true; } if (meshrights & 4096) { Q('p20remotelimitedinput').checked = true; }
} }
if (meshrights & 16) { Q('p20meshagentconsole').checked = true; } if (meshrights & 16) { Q('p20meshagentconsole').checked = true; }
if (meshrights & 32) { Q('p20meshserverfiles').checked = true; } if (meshrights & 32) { Q('p20meshserverfiles').checked = true; }
@ -9254,6 +9254,7 @@
x += '<div><label><input type=checkbox id=p41c4 ' + ((serverTraceSources.indexOf('peer') >= 0) ? 'checked' : '') + '>' + "MeshCentral Server Peering" + '</label></div>'; x += '<div><label><input type=checkbox id=p41c4 ' + ((serverTraceSources.indexOf('peer') >= 0) ? 'checked' : '') + '>' + "MeshCentral Server Peering" + '</label></div>';
x += '<div><label><input type=checkbox id=p41c15 ' + ((serverTraceSources.indexOf('agent') >= 0) ? 'checked' : '') + '>' + "MeshAgent traffic" + '</label></div>'; x += '<div><label><input type=checkbox id=p41c15 ' + ((serverTraceSources.indexOf('agent') >= 0) ? 'checked' : '') + '>' + "MeshAgent traffic" + '</label></div>';
x += '<div><label><input type=checkbox id=p41c14 ' + ((serverTraceSources.indexOf('agentupdate') >= 0) ? 'checked' : '') + '>' + "MeshAgent update" + '</label></div>'; x += '<div><label><input type=checkbox id=p41c14 ' + ((serverTraceSources.indexOf('agentupdate') >= 0) ? 'checked' : '') + '>' + "MeshAgent update" + '</label></div>';
x += '<div><label><input type=checkbox id=p41c16 ' + ((serverTraceSources.indexOf('cert') >= 0) ? 'checked' : '') + '>' + "Server Certificate" + '</label></div>';
x += '<div style="width:100%;border-bottom:1px solid gray;margin-bottom:5px;margin-top:5px"><b>' + "Web Server" + '</b></div>'; x += '<div style="width:100%;border-bottom:1px solid gray;margin-bottom:5px;margin-top:5px"><b>' + "Web Server" + '</b></div>';
x += '<div><label><input type=checkbox id=p41c5 ' + ((serverTraceSources.indexOf('web') >= 0) ? 'checked' : '') + '>' + "Web Server" + '</label></div>'; x += '<div><label><input type=checkbox id=p41c5 ' + ((serverTraceSources.indexOf('web') >= 0) ? 'checked' : '') + '>' + "Web Server" + '</label></div>';
x += '<div><label><input type=checkbox id=p41c6 ' + ((serverTraceSources.indexOf('webrequest') >= 0) ? 'checked' : '') + '>' + "Web Server Requests" + '</label></div>'; x += '<div><label><input type=checkbox id=p41c6 ' + ((serverTraceSources.indexOf('webrequest') >= 0) ? 'checked' : '') + '>' + "Web Server Requests" + '</label></div>';
@ -9270,8 +9271,8 @@
} }
function setServerTracingEx(b) { function setServerTracingEx(b) {
var sources = [], allsources = ['cookie', 'dispatch', 'main', 'peer', 'web', 'webrequest', 'relay', 'webrelaydata', 'webrelay', 'mps', 'mpscmd', 'swarm', 'swarmcmd', 'agentupdate', 'agent']; var sources = [], allsources = ['cookie', 'dispatch', 'main', 'peer', 'web', 'webrequest', 'relay', 'webrelaydata', 'webrelay', 'mps', 'mpscmd', 'swarm', 'swarmcmd', 'agentupdate', 'agent', 'cert'];
if (b == 1) { for (var i = 1; i < 16; i++) { try { if (Q('p41c' + i).checked) { sources.push(allsources[i - 1]); } } catch (ex) { } } } if (b == 1) { for (var i = 1; i < 17; i++) { try { if (Q('p41c' + i).checked) { sources.push(allsources[i - 1]); } } catch (ex) { } } }
meshserver.send({ action: 'traceinfo', traceSources: sources }); meshserver.send({ action: 'traceinfo', traceSources: sources });
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -46,9 +46,7 @@
<div id=loginpanel style="background-color:#979797;border-radius:16px;width:260px;padding:16px;text-align:center;clear:both;display:none"> <div id=loginpanel style="background-color:#979797;border-radius:16px;width:260px;padding:16px;text-align:center;clear:both;display:none">
<form method=post> <form method=post>
<input type=hidden name=action value=login /> <input type=hidden name=action value=login />
<div id=message1> <div id=message1></div>
{{{message}}}
</div>
<div> <div>
<b>Log In</b> <b>Log In</b>
</div> </div>
@ -80,9 +78,7 @@
<div style="background-color:#979797;border-radius:16px;width:260px;padding:16px;text-align:center;clear:both;position:relative"> <div style="background-color:#979797;border-radius:16px;width:260px;padding:16px;text-align:center;clear:both;position:relative">
<form method=post> <form method=post>
<input type=hidden name=action value=createaccount /> <input type=hidden name=action value=createaccount />
<div id=message2> <div id=message2></div>
{{{message}}}
</div>
<div> <div>
<b>Account Creation</b> <b>Account Creation</b>
</div> </div>
@ -127,9 +123,7 @@
<div id=resetpanel style="background-color:#979797;border-radius:16px;width:260px;padding:16px;text-align:center;display:none;clear:both"> <div id=resetpanel style="background-color:#979797;border-radius:16px;width:260px;padding:16px;text-align:center;display:none;clear:both">
<form method=post> <form method=post>
<input type=hidden name=action value=resetaccount /> <input type=hidden name=action value=resetaccount />
<div id=message3> <div id=message3></div>
{{{message}}}
</div>
<div> <div>
<b>Account Reset</b> <b>Account Reset</b>
</div> </div>
@ -153,9 +147,7 @@
<form method=post autocomplete=off> <form method=post autocomplete=off>
<input type=hidden name=action value=tokenlogin /> <input type=hidden name=action value=tokenlogin />
<input type=hidden name=hwstate value="{{{hwstate}}}" /> <input type=hidden name=hwstate value="{{{hwstate}}}" />
<div id=message4> <div id=message4></div>
{{{message}}}
</div>
<table> <table>
<tr> <tr>
<td align=right width=100>Login token:</td> <td align=right width=100>Login token:</td>
@ -178,9 +170,7 @@
<div id=resettokenpanel style="background-color:#979797;border-radius:16px;width:260px;padding:16px;text-align:center;display:none;clear:both"> <div id=resettokenpanel style="background-color:#979797;border-radius:16px;width:260px;padding:16px;text-align:center;display:none;clear:both">
<form method=post autocomplete=off> <form method=post autocomplete=off>
<input type=hidden name=action value=resetaccount /> <input type=hidden name=action value=resetaccount />
<div id=message5> <div id=message5></div>
{{{message}}}
</div>
<table> <table>
<tr> <tr>
<td align=right width=100>Login token:</td> <td align=right width=100>Login token:</td>
@ -203,9 +193,7 @@
<div id=resetpasswordpanel style="position:relative;background-color:#979797;border-radius:16px;width:300px;padding:16px;text-align:center;display:none"> <div id=resetpasswordpanel style="position:relative;background-color:#979797;border-radius:16px;width:300px;padding:16px;text-align:center;display:none">
<form method=post> <form method=post>
<input type=hidden name=action value=resetpassword /> <input type=hidden name=action value=resetpassword />
<div id=message6> <div id=message6></div>
{{{message}}}
</div>
<div id="rpasswordPolicyCallout" style="left:-10px;width:100px;display:none;position:absolute;background-color:#FFC;border-radius:5px;padding:5px;box-shadow:0px 0px 15px #666;font-size:10px"></div> <div id="rpasswordPolicyCallout" style="left:-10px;width:100px;display:none;position:absolute;background-color:#FFC;border-radius:5px;padding:5px;box-shadow:0px 0px 15px #666;font-size:10px"></div>
<table> <table>
<tr> <tr>
@ -279,6 +267,20 @@
var hardwareKeyChallenge = decodeURIComponent('{{{hkey}}}'); var hardwareKeyChallenge = decodeURIComponent('{{{hkey}}}');
var currentpanel = 0; var currentpanel = 0;
// Display the right server message
var messageid = parseInt('{{{messageid}}}');
var okmessages = ['', "Hold on, reset mail sent."];
var failmessages = ["Unable to create account.", "Account limit reached.", "Existing account with this email address.", "Invalid account creation token.", "Username already exists.", "Password rejected, use a different one.", "Invalid email.", "Account not found.", "Invalid token, try again.", "Unable to sent email.", "Account locked.", "Access denied.", "Login failed, check username and password.", "Password change requested.", "IP address blocked, try again later."];
if (messageid > 0) {
var msg = '';
if ((messageid < 100) && (messageid < okmessages.length)) { msg = okmessages[messageid]; }
else if ((messageid >= 100) && ((messageid - 100) < failmessages.length)) { msg = failmessages[messageid - 100]; }
if (msg != '') {
if (messageid >= 100) { msg = ('<span class="msg error"><b style=color:#8C001A>' + msg + '<b></span><br /><br />'); } else { msg = ('<span class="msg success"><b>' + msg + '</b></span><br /><br />'); }
for (var i = 1; i < 7; i++) { QH('message' + i, msg); }
}
}
// If URL arguments are provided, add them to form posts // If URL arguments are provided, add them to form posts
if (window.location.href.indexOf('?') > 0) { if (window.location.href.indexOf('?') > 0) {
var urlargs = window.location.href.substring(window.location.href.indexOf('?')); var urlargs = window.location.href.substring(window.location.href.indexOf('?'));

View File

@ -43,9 +43,7 @@
<div id=loginpanel style="display:none"> <div id=loginpanel style="display:none">
<form method=post> <form method=post>
<input type=hidden name=action value=login /> <input type=hidden name=action value=login />
<div id=message1> <div id=message1></div>
{{{message}}}
</div>
<div> <div>
<b>Log In</b> <b>Log In</b>
</div> </div>
@ -76,9 +74,7 @@
<div id=createpanel style="display:none;position:relative"> <div id=createpanel style="display:none;position:relative">
<form method=post> <form method=post>
<input type=hidden name=action value=createaccount /> <input type=hidden name=action value=createaccount />
<div id=message2> <div id=message2></div>
{{{message}}}
</div>
<div> <div>
<b>Account Creation</b> <b>Account Creation</b>
</div> </div>
@ -122,9 +118,7 @@
<div id=resetpanel style="display:none"> <div id=resetpanel style="display:none">
<form method=post> <form method=post>
<input type=hidden name=action value=resetaccount /> <input type=hidden name=action value=resetaccount />
<div id=message3> <div id=message3></div>
{{{message}}}
</div>
<div> <div>
<b>Account Reset</b> <b>Account Reset</b>
</div> </div>
@ -148,9 +142,7 @@
<form method=post autocomplete=off> <form method=post autocomplete=off>
<input type=hidden name=action value=tokenlogin /> <input type=hidden name=action value=tokenlogin />
<input type=hidden name=hwstate value="{{{hwstate}}}" /> <input type=hidden name=hwstate value="{{{hwstate}}}" />
<div id=message4> <div id=message4></div>
{{{message}}}
</div>
<table> <table>
<tr> <tr>
<td align=right width=100>Login token:</td> <td align=right width=100>Login token:</td>
@ -172,9 +164,7 @@
<div id=resettokenpanel style="display:none"> <div id=resettokenpanel style="display:none">
<form method=post> <form method=post>
<input type=hidden name=action value=resetaccount /> <input type=hidden name=action value=resetaccount />
<div id=message5> <div id=message5></div>
{{{message}}}
</div>
<table> <table>
<tr> <tr>
<td align=right width=100>Login token:</td> <td align=right width=100>Login token:</td>
@ -196,9 +186,7 @@
<div id=resetpasswordpanel style="display:none;position:relative"> <div id=resetpasswordpanel style="display:none;position:relative">
<form method=post> <form method=post>
<input type=hidden name=action value=resetpassword /> <input type=hidden name=action value=resetpassword />
<div id=message6> <div id=message6></div>
{{{message}}}
</div>
<div id="rpasswordPolicyCallout" style="display:none"></div> <div id="rpasswordPolicyCallout" style="display:none"></div>
<table> <table>
<tr> <tr>
@ -276,6 +264,20 @@
var nightMode = (getstore('_nightMode', '0') == '1'); var nightMode = (getstore('_nightMode', '0') == '1');
var publicKeyCredentialRequestOptions = null; var publicKeyCredentialRequestOptions = null;
// Display the right server message
var messageid = parseInt('{{{messageid}}}');
var okmessages = ['', "Hold on, reset mail sent."];
var failmessages = ["Unable to create account.", "Account limit reached.", "Existing account with this email address.", "Invalid account creation token.", "Username already exists.", "Password rejected, use a different one.", "Invalid email.", "Account not found.", "Invalid token, try again.", "Unable to sent email.", "Account locked.", "Access denied.", "Login failed, check username and password.", "Password change requested.", "IP address blocked, try again later."];
if (messageid > 0) {
var msg = '';
if ((messageid < 100) && (messageid < okmessages.length)) { msg = okmessages[messageid]; }
else if ((messageid >= 100) && ((messageid - 100) < failmessages.length)) { msg = failmessages[messageid - 100]; }
if (msg != '') {
if (messageid >= 100) { msg = ('<span class="msg error"><b style=color:#8C001A>' + msg + '<b></span><br /><br />'); } else { msg = ('<span class="msg success"><b>' + msg + '</b></span><br /><br />'); }
for (var i = 1; i < 7; i++) { QH('message' + i, msg); }
}
}
// If URL arguments are provided, add them to form posts // If URL arguments are provided, add them to form posts
if (window.location.href.indexOf('?') > 0) { if (window.location.href.indexOf('?') > 0) {
var urlargs = window.location.href.substring(window.location.href.indexOf('?')); var urlargs = window.location.href.substring(window.location.href.indexOf('?'));
@ -353,9 +355,7 @@
QE('tokenOkButton', true); QE('tokenOkButton', true);
Q('tokenOkButton').click(); Q('tokenOkButton').click();
}, },
function (error) { function (error) { console.log('credentials-get error', error); }
console.log('credentials-get error', error);
}
); );
} }
} }

View File

@ -6498,10 +6498,10 @@
if (meshrights & 8) { if (meshrights & 8) {
Q('p20remotecontrol').checked = true; Q('p20remotecontrol').checked = true;
if (meshrights & 256) { Q('p20remoteview').checked = true; } if (meshrights & 256) { Q('p20remoteview').checked = true; }
if (meshrights & 512) { Q('p20remotelimitedinput').checked = true; } if (meshrights & 512) { Q('p20noterminal').checked = true; }
if (meshrights & 1024) { Q('p20noterminal').checked = true; } if (meshrights & 1024) { Q('p20nofiles').checked = true; }
if (meshrights & 2048) { Q('p20nofiles').checked = true; } if (meshrights & 2048) { Q('p20noamt').checked = true; }
if (meshrights & 4096) { Q('p20noamt').checked = true; } if (meshrights & 4096) { Q('p20remotelimitedinput').checked = true; }
} }
if (meshrights & 16) { Q('p20meshagentconsole').checked = true; } if (meshrights & 16) { Q('p20meshagentconsole').checked = true; }
if (meshrights & 32) { Q('p20meshserverfiles').checked = true; } if (meshrights & 32) { Q('p20meshserverfiles').checked = true; }
@ -8197,6 +8197,7 @@
x += '<div><label><input type=checkbox id=p41c4 ' + ((serverTraceSources.indexOf('peer') >= 0) ? 'checked' : '') + '>' + "MeshCentral Server Peering" + '</label></div>'; x += '<div><label><input type=checkbox id=p41c4 ' + ((serverTraceSources.indexOf('peer') >= 0) ? 'checked' : '') + '>' + "MeshCentral Server Peering" + '</label></div>';
x += '<div><label><input type=checkbox id=p41c15 ' + ((serverTraceSources.indexOf('agent') >= 0) ? 'checked' : '') + '>' + "MeshAgent traffic" + '</label></div>'; x += '<div><label><input type=checkbox id=p41c15 ' + ((serverTraceSources.indexOf('agent') >= 0) ? 'checked' : '') + '>' + "MeshAgent traffic" + '</label></div>';
x += '<div><label><input type=checkbox id=p41c14 ' + ((serverTraceSources.indexOf('agentupdate') >= 0) ? 'checked' : '') + '>' + "MeshAgent update" + '</label></div>'; x += '<div><label><input type=checkbox id=p41c14 ' + ((serverTraceSources.indexOf('agentupdate') >= 0) ? 'checked' : '') + '>' + "MeshAgent update" + '</label></div>';
x += '<div><label><input type=checkbox id=p41c16 ' + ((serverTraceSources.indexOf('cert') >= 0) ? 'checked' : '') + '>' + "Server Certificate" + '</label></div>';
x += '<div style="width:100%;border-bottom:1px solid gray;margin-bottom:5px;margin-top:5px"><b>' + "Serveur Web" + '</b></div>'; x += '<div style="width:100%;border-bottom:1px solid gray;margin-bottom:5px;margin-top:5px"><b>' + "Serveur Web" + '</b></div>';
x += '<div><label><input type=checkbox id=p41c5 ' + ((serverTraceSources.indexOf('web') >= 0) ? 'checked' : '') + '>' + "Serveur Web" + '</label></div>'; x += '<div><label><input type=checkbox id=p41c5 ' + ((serverTraceSources.indexOf('web') >= 0) ? 'checked' : '') + '>' + "Serveur Web" + '</label></div>';
x += '<div><label><input type=checkbox id=p41c6 ' + ((serverTraceSources.indexOf('webrequest') >= 0) ? 'checked' : '') + '>' + "Demandes de serveur Web" + '</label></div>'; x += '<div><label><input type=checkbox id=p41c6 ' + ((serverTraceSources.indexOf('webrequest') >= 0) ? 'checked' : '') + '>' + "Demandes de serveur Web" + '</label></div>';
@ -8213,8 +8214,8 @@
} }
function setServerTracingEx(b) { function setServerTracingEx(b) {
var sources = [], allsources = ['cookie', 'dispatch', 'main', 'peer', 'web', 'webrequest', 'relay', 'webrelaydata', 'webrelay', 'mps', 'mpscmd', 'swarm', 'swarmcmd', 'agentupdate', 'agent']; var sources = [], allsources = ['cookie', 'dispatch', 'main', 'peer', 'web', 'webrequest', 'relay', 'webrelaydata', 'webrelay', 'mps', 'mpscmd', 'swarm', 'swarmcmd', 'agentupdate', 'agent', 'cert'];
if (b == 1) { for (var i = 1; i < 16; i++) { try { if (Q('p41c' + i).checked) { sources.push(allsources[i - 1]); } } catch (ex) { } } } if (b == 1) { for (var i = 1; i < 17; i++) { try { if (Q('p41c' + i).checked) { sources.push(allsources[i - 1]); } } catch (ex) { } } }
meshserver.send({ action: 'traceinfo', traceSources: sources }); meshserver.send({ action: 'traceinfo', traceSources: sources });
} }

File diff suppressed because one or more lines are too long

View File

@ -3219,6 +3219,7 @@
x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20editnotes>' + "Edit Device Notes" + '</label><br>'; x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20editnotes>' + "Edit Device Notes" + '</label><br>';
x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20limitevents>' + "Show Only Own Events" + '</label><br>'; x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20limitevents>' + "Show Only Own Events" + '</label><br>';
x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20chatnotify>' + "Chat & Notify" + '</label><br>'; x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20chatnotify>' + "Chat & Notify" + '</label><br>';
x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20uninstall>' + "Uninstall Agent" + '</label><br>';
x += '</div>'; x += '</div>';
setDialogMode(2, "Ajouter un utilisateur au groupe", 3, p20showAddMeshUserDialogEx, x); setDialogMode(2, "Ajouter un utilisateur au groupe", 3, p20showAddMeshUserDialogEx, x);
p20validateAddMeshUserDialog(); p20validateAddMeshUserDialog();
@ -3227,20 +3228,24 @@
function p20validateAddMeshUserDialog() { function p20validateAddMeshUserDialog() {
var meshrights = currentMesh.links[userinfo._id].rights; var meshrights = currentMesh.links[userinfo._id].rights;
QE('idx_dlgOkButton', (Q('dp20username').value.length > 0)); var nc = !Q('p20fulladmin').checked;
QE('p20fulladmin', meshrights == 0xFFFFFFFF); QE('p20fulladmin', meshrights == 0xFFFFFFFF);
QE('p20editmesh', (!Q('p20fulladmin').checked) && (meshrights == 0xFFFFFFFF)); QE('p20editmesh', nc && (meshrights == 0xFFFFFFFF));
QE('p20manageusers', !Q('p20fulladmin').checked); QE('p20manageusers', nc);
QE('p20managecomputers', !Q('p20fulladmin').checked); QE('p20managecomputers', nc);
QE('p20remotecontrol', !Q('p20fulladmin').checked); QE('p20remotecontrol', nc);
QE('p20meshagentconsole', !Q('p20fulladmin').checked); QE('p20meshagentconsole', nc);
QE('p20meshserverfiles', !Q('p20fulladmin').checked); QE('p20meshserverfiles', nc);
QE('p20wakedevices', !Q('p20fulladmin').checked); QE('p20wakedevices', nc);
QE('p20editnotes', !Q('p20fulladmin').checked); QE('p20editnotes', nc);
QE('p20remoteview', !Q('p20fulladmin').checked && Q('p20remotecontrol').checked); QE('p20limitevents', nc);
QE('p20noterminal', !Q('p20fulladmin').checked && Q('p20remotecontrol').checked); QE('p20remoteview', nc && Q('p20remotecontrol').checked);
QE('p20nofiles', !Q('p20fulladmin').checked && Q('p20remotecontrol').checked); QE('p20remotelimitedinput', nc && Q('p20remotecontrol').checked && !Q('p20remoteview').checked);
QE('p20noamt', !Q('p20fulladmin').checked && Q('p20remotecontrol').checked); QE('p20noterminal', nc && Q('p20remotecontrol').checked);
QE('p20nofiles', nc && Q('p20remotecontrol').checked);
QE('p20noamt', nc && Q('p20remotecontrol').checked);
QE('p20chatnotify', nc);
QE('p20uninstall', nc);
} }
function p20showAddMeshUserDialogEx() { function p20showAddMeshUserDialogEx() {
@ -3258,8 +3263,14 @@
if (Q('p20noterminal').checked == true) meshadmin += 512; if (Q('p20noterminal').checked == true) meshadmin += 512;
if (Q('p20nofiles').checked == true) meshadmin += 1024; if (Q('p20nofiles').checked == true) meshadmin += 1024;
if (Q('p20noamt').checked == true) meshadmin += 2048; if (Q('p20noamt').checked == true) meshadmin += 2048;
if (Q('p20remotelimitedinput').checked == true) meshadmin += 4096;
if (Q('p20limitevents').checked == true) meshadmin += 8192;
if (Q('p20chatnotify').checked == true) meshadmin += 16384;
if (Q('p20uninstall').checked == true) meshadmin += 32768;
} }
meshserver.send({ action: 'addmeshuser', meshid: currentMesh._id, meshname: currentMesh.name, username: Q('dp20username').value, meshadmin: meshadmin }); var users = Q('dp20username').value.split(','), users2 = [];
for (var i in users) { users2.push(users[i].trim()); }
meshserver.send({ action: 'addmeshuser', meshid: currentMesh._id, meshname: currentMesh.name, usernames: users2, meshadmin: meshadmin });
} }
function p20viewuser(userid) { function p20viewuser(userid) {
@ -3282,6 +3293,7 @@
if (((meshrights & 8) != 0) && ((meshrights & 4096) != 0) && ((meshrights & 256) == 0)) r.push("Limited Input"); if (((meshrights & 8) != 0) && ((meshrights & 4096) != 0) && ((meshrights & 256) == 0)) r.push("Limited Input");
if ((meshrights & 8192) != 0) r.push("Self Events Only"); if ((meshrights & 8192) != 0) r.push("Self Events Only");
if ((meshrights & 16384) != 0) r.push("Chat & Notify"); if ((meshrights & 16384) != 0) r.push("Chat & Notify");
if ((meshrights & 32768) != 0) r.push("Uninstall");
} }
if (r.length == 0) { r.push("No Rights"); } if (r.length == 0) { r.push("No Rights"); }
var buttons = 1, x = addHtmlValue("User", EscapeHtml(decodeURIComponent(userid.split('/')[2]))); var buttons = 1, x = addHtmlValue("User", EscapeHtml(decodeURIComponent(userid.split('/')[2])));

View File

@ -7478,10 +7478,10 @@
if (meshrights & 8) { if (meshrights & 8) {
Q('p20remotecontrol').checked = true; Q('p20remotecontrol').checked = true;
if (meshrights & 256) { Q('p20remoteview').checked = true; } if (meshrights & 256) { Q('p20remoteview').checked = true; }
if (meshrights & 512) { Q('p20remotelimitedinput').checked = true; } if (meshrights & 512) { Q('p20noterminal').checked = true; }
if (meshrights & 1024) { Q('p20noterminal').checked = true; } if (meshrights & 1024) { Q('p20nofiles').checked = true; }
if (meshrights & 2048) { Q('p20nofiles').checked = true; } if (meshrights & 2048) { Q('p20noamt').checked = true; }
if (meshrights & 4096) { Q('p20noamt').checked = true; } if (meshrights & 4096) { Q('p20remotelimitedinput').checked = true; }
} }
if (meshrights & 16) { Q('p20meshagentconsole').checked = true; } if (meshrights & 16) { Q('p20meshagentconsole').checked = true; }
if (meshrights & 32) { Q('p20meshserverfiles').checked = true; } if (meshrights & 32) { Q('p20meshserverfiles').checked = true; }
@ -9177,6 +9177,7 @@
x += '<div><label><input type=checkbox id=p41c4 ' + ((serverTraceSources.indexOf('peer') >= 0) ? 'checked' : '') + '>' + "MeshCentral Server Peering" + '</label></div>'; x += '<div><label><input type=checkbox id=p41c4 ' + ((serverTraceSources.indexOf('peer') >= 0) ? 'checked' : '') + '>' + "MeshCentral Server Peering" + '</label></div>';
x += '<div><label><input type=checkbox id=p41c15 ' + ((serverTraceSources.indexOf('agent') >= 0) ? 'checked' : '') + '>' + "MeshAgent traffic" + '</label></div>'; x += '<div><label><input type=checkbox id=p41c15 ' + ((serverTraceSources.indexOf('agent') >= 0) ? 'checked' : '') + '>' + "MeshAgent traffic" + '</label></div>';
x += '<div><label><input type=checkbox id=p41c14 ' + ((serverTraceSources.indexOf('agentupdate') >= 0) ? 'checked' : '') + '>' + "MeshAgent update" + '</label></div>'; x += '<div><label><input type=checkbox id=p41c14 ' + ((serverTraceSources.indexOf('agentupdate') >= 0) ? 'checked' : '') + '>' + "MeshAgent update" + '</label></div>';
x += '<div><label><input type=checkbox id=p41c16 ' + ((serverTraceSources.indexOf('cert') >= 0) ? 'checked' : '') + '>' + "Server Certificate" + '</label></div>';
x += '<div style="width:100%;border-bottom:1px solid gray;margin-bottom:5px;margin-top:5px"><b>' + "Serveur Web" + '</b></div>'; x += '<div style="width:100%;border-bottom:1px solid gray;margin-bottom:5px;margin-top:5px"><b>' + "Serveur Web" + '</b></div>';
x += '<div><label><input type=checkbox id=p41c5 ' + ((serverTraceSources.indexOf('web') >= 0) ? 'checked' : '') + '>' + "Serveur Web" + '</label></div>'; x += '<div><label><input type=checkbox id=p41c5 ' + ((serverTraceSources.indexOf('web') >= 0) ? 'checked' : '') + '>' + "Serveur Web" + '</label></div>';
x += '<div><label><input type=checkbox id=p41c6 ' + ((serverTraceSources.indexOf('webrequest') >= 0) ? 'checked' : '') + '>' + "Demandes de serveur Web" + '</label></div>'; x += '<div><label><input type=checkbox id=p41c6 ' + ((serverTraceSources.indexOf('webrequest') >= 0) ? 'checked' : '') + '>' + "Demandes de serveur Web" + '</label></div>';
@ -9193,8 +9194,8 @@
} }
function setServerTracingEx(b) { function setServerTracingEx(b) {
var sources = [], allsources = ['cookie', 'dispatch', 'main', 'peer', 'web', 'webrequest', 'relay', 'webrelaydata', 'webrelay', 'mps', 'mpscmd', 'swarm', 'swarmcmd', 'agentupdate', 'agent']; var sources = [], allsources = ['cookie', 'dispatch', 'main', 'peer', 'web', 'webrequest', 'relay', 'webrelaydata', 'webrelay', 'mps', 'mpscmd', 'swarm', 'swarmcmd', 'agentupdate', 'agent', 'cert'];
if (b == 1) { for (var i = 1; i < 16; i++) { try { if (Q('p41c' + i).checked) { sources.push(allsources[i - 1]); } } catch (ex) { } } } if (b == 1) { for (var i = 1; i < 17; i++) { try { if (Q('p41c' + i).checked) { sources.push(allsources[i - 1]); } } catch (ex) { } } }
meshserver.send({ action: 'traceinfo', traceSources: sources }); meshserver.send({ action: 'traceinfo', traceSources: sources });
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -44,9 +44,7 @@
<div id="loginpanel" style="background-color:#979797;border-radius:16px;width:260px;padding:16px;text-align:center;clear:both;display:none"> <div id="loginpanel" style="background-color:#979797;border-radius:16px;width:260px;padding:16px;text-align:center;clear:both;display:none">
<form method="post"> <form method="post">
<input type="hidden" name="action" value="login"> <input type="hidden" name="action" value="login">
<div id="message1"> <div id="message1"></div>
{{{message}}}
</div>
<div> <div>
<b>Log In</b> <b>Log In</b>
</div> </div>
@ -78,9 +76,7 @@
<div style="background-color:#979797;border-radius:16px;width:260px;padding:16px;text-align:center;clear:both;position:relative"> <div style="background-color:#979797;border-radius:16px;width:260px;padding:16px;text-align:center;clear:both;position:relative">
<form method="post"> <form method="post">
<input type="hidden" name="action" value="createaccount"> <input type="hidden" name="action" value="createaccount">
<div id="message2"> <div id="message2"></div>
{{{message}}}
</div>
<div> <div>
<b>Account Creation</b> <b>Account Creation</b>
</div> </div>
@ -125,9 +121,7 @@
<div id="resetpanel" style="background-color:#979797;border-radius:16px;width:260px;padding:16px;text-align:center;display:none;clear:both"> <div id="resetpanel" style="background-color:#979797;border-radius:16px;width:260px;padding:16px;text-align:center;display:none;clear:both">
<form method="post"> <form method="post">
<input type="hidden" name="action" value="resetaccount"> <input type="hidden" name="action" value="resetaccount">
<div id="message3"> <div id="message3"></div>
{{{message}}}
</div>
<div> <div>
<b>Account Reset</b> <b>Account Reset</b>
</div> </div>
@ -151,9 +145,7 @@
<form method="post" autocomplete="off"> <form method="post" autocomplete="off">
<input type="hidden" name="action" value="tokenlogin"> <input type="hidden" name="action" value="tokenlogin">
<input type="hidden" name="hwstate" value="{{{hwstate}}}"> <input type="hidden" name="hwstate" value="{{{hwstate}}}">
<div id="message4"> <div id="message4"></div>
{{{message}}}
</div>
<table> <table>
<tbody><tr> <tbody><tr>
<td align="right" width="100">Login token:</td> <td align="right" width="100">Login token:</td>
@ -176,9 +168,7 @@
<div id="resettokenpanel" style="background-color:#979797;border-radius:16px;width:260px;padding:16px;text-align:center;display:none;clear:both"> <div id="resettokenpanel" style="background-color:#979797;border-radius:16px;width:260px;padding:16px;text-align:center;display:none;clear:both">
<form method="post" autocomplete="off"> <form method="post" autocomplete="off">
<input type="hidden" name="action" value="resetaccount"> <input type="hidden" name="action" value="resetaccount">
<div id="message5"> <div id="message5"></div>
{{{message}}}
</div>
<table> <table>
<tbody><tr> <tbody><tr>
<td align="right" width="100">Login token:</td> <td align="right" width="100">Login token:</td>
@ -201,9 +191,7 @@
<div id="resetpasswordpanel" style="position:relative;background-color:#979797;border-radius:16px;width:300px;padding:16px;text-align:center;display:none"> <div id="resetpasswordpanel" style="position:relative;background-color:#979797;border-radius:16px;width:300px;padding:16px;text-align:center;display:none">
<form method="post"> <form method="post">
<input type="hidden" name="action" value="resetpassword"> <input type="hidden" name="action" value="resetpassword">
<div id="message6"> <div id="message6"></div>
{{{message}}}
</div>
<div id="rpasswordPolicyCallout" style="left:-10px;width:100px;display:none;position:absolute;background-color:#FFC;border-radius:5px;padding:5px;box-shadow:0px 0px 15px #666;font-size:10px"></div> <div id="rpasswordPolicyCallout" style="left:-10px;width:100px;display:none;position:absolute;background-color:#FFC;border-radius:5px;padding:5px;box-shadow:0px 0px 15px #666;font-size:10px"></div>
<table> <table>
<tbody><tr> <tbody><tr>
@ -277,6 +265,20 @@
var hardwareKeyChallenge = decodeURIComponent('{{{hkey}}}'); var hardwareKeyChallenge = decodeURIComponent('{{{hkey}}}');
var currentpanel = 0; var currentpanel = 0;
// Display the right server message
var messageid = parseInt('{{{messageid}}}');
var okmessages = ['', "Hold on, reset mail sent."];
var failmessages = ["Unable to create account.", "Account limit reached.", "Existing account with this email address.", "Invalid account creation token.", "Username already exists.", "Password rejected, use a different one.", "Invalid email.", "Account not found.", "Invalid token, try again.", "Unable to sent email.", "Account locked.", "Access denied.", "Login failed, check username and password.", "Password change requested.", "IP address blocked, try again later."];
if (messageid > 0) {
var msg = '';
if ((messageid < 100) && (messageid < okmessages.length)) { msg = okmessages[messageid]; }
else if ((messageid >= 100) && ((messageid - 100) < failmessages.length)) { msg = failmessages[messageid - 100]; }
if (msg != '') {
if (messageid >= 100) { msg = ('<span class="msg error"><b style=color:#8C001A>' + msg + '<b></span><br /><br />'); } else { msg = ('<span class="msg success"><b>' + msg + '</b></span><br /><br />'); }
for (var i = 1; i < 7; i++) { QH('message' + i, msg); }
}
}
// If URL arguments are provided, add them to form posts // If URL arguments are provided, add them to form posts
if (window.location.href.indexOf('?') > 0) { if (window.location.href.indexOf('?') > 0) {
var urlargs = window.location.href.substring(window.location.href.indexOf('?')); var urlargs = window.location.href.substring(window.location.href.indexOf('?'));

View File

@ -41,9 +41,7 @@
<div id="loginpanel" style="display:none"> <div id="loginpanel" style="display:none">
<form method="post"> <form method="post">
<input type="hidden" name="action" value="login"> <input type="hidden" name="action" value="login">
<div id="message1"> <div id="message1"></div>
{{{message}}}
</div>
<div> <div>
<b>Log In</b> <b>Log In</b>
</div> </div>
@ -74,9 +72,7 @@
<div id="createpanel" style="display:none;position:relative"> <div id="createpanel" style="display:none;position:relative">
<form method="post"> <form method="post">
<input type="hidden" name="action" value="createaccount"> <input type="hidden" name="action" value="createaccount">
<div id="message2"> <div id="message2"></div>
{{{message}}}
</div>
<div> <div>
<b>Account Creation</b> <b>Account Creation</b>
</div> </div>
@ -120,9 +116,7 @@
<div id="resetpanel" style="display:none"> <div id="resetpanel" style="display:none">
<form method="post"> <form method="post">
<input type="hidden" name="action" value="resetaccount"> <input type="hidden" name="action" value="resetaccount">
<div id="message3"> <div id="message3"></div>
{{{message}}}
</div>
<div> <div>
<b>Account Reset</b> <b>Account Reset</b>
</div> </div>
@ -146,9 +140,7 @@
<form method="post" autocomplete="off"> <form method="post" autocomplete="off">
<input type="hidden" name="action" value="tokenlogin"> <input type="hidden" name="action" value="tokenlogin">
<input type="hidden" name="hwstate" value="{{{hwstate}}}"> <input type="hidden" name="hwstate" value="{{{hwstate}}}">
<div id="message4"> <div id="message4"></div>
{{{message}}}
</div>
<table> <table>
<tbody><tr> <tbody><tr>
<td align="right" width="100">Login token:</td> <td align="right" width="100">Login token:</td>
@ -170,9 +162,7 @@
<div id="resettokenpanel" style="display:none"> <div id="resettokenpanel" style="display:none">
<form method="post"> <form method="post">
<input type="hidden" name="action" value="resetaccount"> <input type="hidden" name="action" value="resetaccount">
<div id="message5"> <div id="message5"></div>
{{{message}}}
</div>
<table> <table>
<tbody><tr> <tbody><tr>
<td align="right" width="100">Login token:</td> <td align="right" width="100">Login token:</td>
@ -194,9 +184,7 @@
<div id="resetpasswordpanel" style="display:none;position:relative"> <div id="resetpasswordpanel" style="display:none;position:relative">
<form method="post"> <form method="post">
<input type="hidden" name="action" value="resetpassword"> <input type="hidden" name="action" value="resetpassword">
<div id="message6"> <div id="message6"></div>
{{{message}}}
</div>
<div id="rpasswordPolicyCallout" style="display:none"></div> <div id="rpasswordPolicyCallout" style="display:none"></div>
<table> <table>
<tbody><tr> <tbody><tr>
@ -274,6 +262,20 @@
var nightMode = (getstore('_nightMode', '0') == '1'); var nightMode = (getstore('_nightMode', '0') == '1');
var publicKeyCredentialRequestOptions = null; var publicKeyCredentialRequestOptions = null;
// Display the right server message
var messageid = parseInt('{{{messageid}}}');
var okmessages = ['', "Hold on, reset mail sent."];
var failmessages = ["Unable to create account.", "Account limit reached.", "Existing account with this email address.", "Invalid account creation token.", "Username already exists.", "Password rejected, use a different one.", "Invalid email.", "Account not found.", "Invalid token, try again.", "Unable to sent email.", "Account locked.", "Access denied.", "Login failed, check username and password.", "Password change requested.", "IP address blocked, try again later."];
if (messageid > 0) {
var msg = '';
if ((messageid < 100) && (messageid < okmessages.length)) { msg = okmessages[messageid]; }
else if ((messageid >= 100) && ((messageid - 100) < failmessages.length)) { msg = failmessages[messageid - 100]; }
if (msg != '') {
if (messageid >= 100) { msg = ('<span class="msg error"><b style=color:#8C001A>' + msg + '<b></span><br /><br />'); } else { msg = ('<span class="msg success"><b>' + msg + '</b></span><br /><br />'); }
for (var i = 1; i < 7; i++) { QH('message' + i, msg); }
}
}
// If URL arguments are provided, add them to form posts // If URL arguments are provided, add them to form posts
if (window.location.href.indexOf('?') > 0) { if (window.location.href.indexOf('?') > 0) {
var urlargs = window.location.href.substring(window.location.href.indexOf('?')); var urlargs = window.location.href.substring(window.location.href.indexOf('?'));
@ -351,9 +353,7 @@
QE('tokenOkButton', true); QE('tokenOkButton', true);
Q('tokenOkButton').click(); Q('tokenOkButton').click();
}, },
function (error) { function (error) { console.log('credentials-get error', error); }
console.log('credentials-get error', error);
}
); );
} }
} }

View File

@ -434,7 +434,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (req.session.userid) { if (req.session.userid) {
next(); next();
} else { } else {
req.session.error = 'Access denied!'; req.session.messageid = 111; // Access denied.
res.redirect(domain.url + 'login'); res.redirect(domain.url + 'login');
} }
}; };
@ -496,11 +496,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
function handleLogoutRequest(req, res) { function handleLogoutRequest(req, res) {
const domain = checkUserIpAddress(req, res); const domain = checkUserIpAddress(req, res);
if ((domain == null) || (domain.auth == 'sspi')) { if ((domain == null) || (domain.auth == 'sspi')) { parent.debug('web', 'handleLogoutRequest: failed checks.'); res.sendStatus(404); return; }
parent.debug('web', 'handleLogoutRequest: failed checks.'); if ((domain.loginkey != null) && (domain.loginkey != req.query.key)) { res.sendStatus(404); return; } // Check 3FA URL key
res.sendStatus(404);
return;
}
res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' }); 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 // Destroy the user's session to log them out will be re-created next request
@ -509,7 +506,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (user != null) { obj.parent.DispatchEvent(['*'], obj, { etype: 'user', userid: user._id, username: user.name, action: 'logout', msg: 'Account logout', domain: domain.id }); } if (user != null) { obj.parent.DispatchEvent(['*'], obj, { etype: 'user', userid: user._id, username: user.name, action: 'logout', msg: 'Account logout', domain: domain.id }); }
} }
req.session = null; req.session = null;
res.redirect(domain.url); if (req.query.key != null) { res.redirect(domain.url + "?key=" + req.query.key); } else { res.redirect(domain.url); }
parent.debug('web', 'handleLogoutRequest: success.'); parent.debug('web', 'handleLogoutRequest: success.');
} }
@ -638,6 +635,17 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
function handleLoginRequest(req, res, direct) { function handleLoginRequest(req, res, direct) {
const domain = checkUserIpAddress(req, res); const domain = checkUserIpAddress(req, res);
if (domain == null) { parent.debug('web', 'handleLoginRequest: invalid domain'); res.sendStatus(404); return; } if (domain == null) { parent.debug('web', 'handleLoginRequest: invalid domain'); res.sendStatus(404); return; }
if ((domain.loginkey != null) && (domain.loginkey != req.query.key)) { res.sendStatus(404); return; } // Check 3FA URL key
// 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;
}
// Normally, use the body username/password. If this is a token, use the username/password in the session. // 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; var xusername = req.body.username, xpassword = req.body.password;
@ -657,8 +665,10 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// 2-step auth is required, but the token is not present or not valid. // 2-step auth is required, but the token is not present or not valid.
if ((req.body.token != null) || (req.body.hwtoken != null)) { 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. randomWaitTime = 2000 + (obj.crypto.randomBytes(2).readUInt16BE(0) % 4095); // This is a fail, wait a random time. 2 to 6 seconds.
req.session.error = '<b style=color:#8C001A>Invalid token, try again.</b>'; req.session.messageid = 108; // Invalid token, try again.
parent.debug('web', 'handleLoginRequest: invalid 2FA token'); parent.debug('web', 'handleLoginRequest: invalid 2FA token');
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) });
obj.setbadLogin(req);
} else { } else {
parent.debug('web', 'handleLoginRequest: 2FA token required'); parent.debug('web', 'handleLoginRequest: 2FA token required');
} }
@ -686,12 +696,19 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Login failed, wait a random delay // Login failed, wait a random delay
setTimeout(function () { setTimeout(function () {
// If the account is locked, display that. // If the account is locked, display that.
if (typeof xusername == 'string') {
var xuserid = 'user/' + domain.id + '/' + xusername.toLowerCase();
if (err == 'locked') { if (err == 'locked') {
parent.debug('web', 'handleLoginRequest: login failed, locked account'); parent.debug('web', 'handleLoginRequest: login failed, locked account');
req.session.error = '<b style=color:#8C001A>Account locked.</b>'; 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) });
obj.setbadLogin(req);
} else { } else {
parent.debug('web', 'handleLoginRequest: login failed, bad username and password'); parent.debug('web', 'handleLoginRequest: login failed, bad username and password');
req.session.error = '<b style=color:#8C001A>Login failed, check username and password.</b>'; 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) });
obj.setbadLogin(req);
}
} }
// Clean up login mode and display password hint if present. // Clean up login mode and display password hint if present.
@ -714,7 +731,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Request a password change // Request a password change
parent.debug('web', 'handleLoginRequest: login ok, password change requested'); parent.debug('web', 'handleLoginRequest: login ok, password change requested');
req.session.loginmode = '6'; req.session.loginmode = '6';
req.session.error = '<b style=color:#8C001A>Password change requested.</b>'; req.session.messageid = 113; // Password change requested.
req.session.resettokenusername = xusername; req.session.resettokenusername = xusername;
req.session.resettokenpassword = xpassword; req.session.resettokenpassword = xpassword;
if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); } if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
@ -722,6 +739,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
} }
// Save login time // Save login time
user.pastlogin = user.login;
user.login = Math.floor(Date.now() / 1000); user.login = Math.floor(Date.now() / 1000);
obj.db.SetUser(user); obj.db.SetUser(user);
@ -733,13 +751,11 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Regenerate session when signing in to prevent fixation // Regenerate session when signing in to prevent fixation
//req.session.regenerate(function () { //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 // Store the user's primary key in the session store to be retrieved, or in this case the entire user object
// req.session.success = 'Authenticated as ' + user.name + 'click to <a href="/logout">logout</a>. You may now access <a href="/restricted">/restricted</a>.';
delete req.session.loginmode; delete req.session.loginmode;
delete req.session.tokenusername; delete req.session.tokenusername;
delete req.session.tokenpassword; delete req.session.tokenpassword;
delete req.session.tokenemail; delete req.session.tokenemail;
delete req.session.success; delete req.session.messageid;
delete req.session.error;
delete req.session.passhint; delete req.session.passhint;
req.session.userid = userid; req.session.userid = userid;
req.session.domainid = domain.id; req.session.domainid = domain.id;
@ -772,11 +788,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
function handleCreateAccountRequest(req, res, direct) { function handleCreateAccountRequest(req, res, direct) {
const domain = checkUserIpAddress(req, res); const domain = checkUserIpAddress(req, res);
if ((domain == null) || (domain.auth == 'sspi') || (domain.auth == 'ldap')) { if ((domain == null) || (domain.auth == 'sspi') || (domain.auth == 'ldap')) { parent.debug('web', 'handleCreateAccountRequest: failed checks.'); res.sendStatus(404); return; }
parent.debug('web', 'handleCreateAccountRequest: failed checks.'); if ((domain.loginkey != null) && (domain.loginkey != req.query.key)) { res.sendStatus(404); return; } // Check 3FA URL key
res.sendStatus(404);
return;
}
// Always lowercase the email address // Always lowercase the email address
if (req.body.email) { req.body.email = req.body.email.toLowerCase(); } if (req.body.email) { req.body.email = req.body.email.toLowerCase(); }
@ -802,7 +815,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (i == -1) { if (i == -1) {
parent.debug('web', 'handleCreateAccountRequest: unable to create account (1)'); parent.debug('web', 'handleCreateAccountRequest: unable to create account (1)');
req.session.loginmode = '2'; req.session.loginmode = '2';
req.session.error = '<b style=color:#8C001A>Unable to create account.</b>'; req.session.messageid = 100; // Unable to create account.
if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); } if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
return; return;
} }
@ -811,7 +824,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (emailok == false) { if (emailok == false) {
parent.debug('web', 'handleCreateAccountRequest: unable to create account (2)'); parent.debug('web', 'handleCreateAccountRequest: unable to create account (2)');
req.session.loginmode = '2'; req.session.loginmode = '2';
req.session.error = '<b style=color:#8C001A>Unable to create account.</b>'; req.session.messageid = 100; // Unable to create account.
if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); } if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
return; return;
} }
@ -822,13 +835,13 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (maxExceed) { if (maxExceed) {
parent.debug('web', 'handleCreateAccountRequest: account limit reached'); parent.debug('web', 'handleCreateAccountRequest: account limit reached');
req.session.loginmode = '2'; req.session.loginmode = '2';
req.session.error = '<b style=color:#8C001A>Account limit reached.</b>'; req.session.messageid = 101; // Account limit reached.
if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); } if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
} else { } 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)) { 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)) {
parent.debug('web', 'handleCreateAccountRequest: unable to create account (3)'); parent.debug('web', 'handleCreateAccountRequest: unable to create account (3)');
req.session.loginmode = '2'; req.session.loginmode = '2';
req.session.error = '<b style=color:#8C001A>Unable to create account.</b>'; req.session.messageid = 100; // Unable to create account.
if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); } if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
} else { } else {
// Check if this email was already verified // Check if this email was already verified
@ -836,14 +849,14 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (docs.length > 0) { if (docs.length > 0) {
parent.debug('web', 'handleCreateAccountRequest: Existing account with this email address'); parent.debug('web', 'handleCreateAccountRequest: Existing account with this email address');
req.session.loginmode = '2'; req.session.loginmode = '2';
req.session.error = '<b style=color:#8C001A>Existing account with this email address.</b>'; req.session.messageid = 102; // Existing account with this email address.
if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); } if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
} else { } else {
// Check if there is domain.newAccountToken, check if supplied token is valid // Check if there is domain.newAccountToken, check if supplied token is valid
if ((domain.newaccountspass != null) && (domain.newaccountspass != '') && (req.body.anewaccountpass != domain.newaccountspass)) { if ((domain.newaccountspass != null) && (domain.newaccountspass != '') && (req.body.anewaccountpass != domain.newaccountspass)) {
parent.debug('web', 'handleCreateAccountRequest: Invalid account creation token'); parent.debug('web', 'handleCreateAccountRequest: Invalid account creation token');
req.session.loginmode = '2'; req.session.loginmode = '2';
req.session.error = '<b style=color:#8C001A>Invalid account creation token.</b>'; req.session.messageid = 103; // Invalid account creation token.
if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); } if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
return; return;
} }
@ -851,7 +864,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (obj.users['user/' + domain.id + '/' + req.body.username.toLowerCase()]) { if (obj.users['user/' + domain.id + '/' + req.body.username.toLowerCase()]) {
parent.debug('web', 'handleCreateAccountRequest: Username already exists'); parent.debug('web', 'handleCreateAccountRequest: Username already exists');
req.session.loginmode = '2'; req.session.loginmode = '2';
req.session.error = '<b style=color:#8C001A>Username already exists.</b>'; req.session.messageid = 104; // Username already exists.
} else { } else {
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 }; 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 };
if (domain.newaccountsrights) { user.siteadmin = domain.newaccountsrights; } if (domain.newaccountsrights) { user.siteadmin = domain.newaccountsrights; }
@ -887,6 +900,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Called to process an account password reset // Called to process an account password reset
function handleResetPasswordRequest(req, res, direct) { function handleResetPasswordRequest(req, res, direct) {
const domain = checkUserIpAddress(req, res); const domain = checkUserIpAddress(req, res);
if ((domain.loginkey != null) && (domain.loginkey != req.query.key)) { res.sendStatus(404); return; } // Check 3FA URL key
// Check everything is ok // Check everything is ok
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')) { 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')) {
@ -897,8 +911,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
delete req.session.resettokenusername; delete req.session.resettokenusername;
delete req.session.resettokenpassword; delete req.session.resettokenpassword;
delete req.session.tokenemail; delete req.session.tokenemail;
delete req.session.success; delete req.session.messageid;
delete req.session.error;
delete req.session.passhint; delete req.session.passhint;
if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); } if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
return; return;
@ -914,7 +927,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (!obj.common.checkPasswordRequirements(req.body.rpassword1, domain.passwordrequirements)) { if (!obj.common.checkPasswordRequirements(req.body.rpassword1, domain.passwordrequirements)) {
parent.debug('web', 'handleResetPasswordRequest: password rejected, use a different one (1)'); parent.debug('web', 'handleResetPasswordRequest: password rejected, use a different one (1)');
req.session.loginmode = '6'; req.session.loginmode = '6';
req.session.error = '<b style=color:#8C001A>Password rejected, use a different one.</b>'; req.session.messageid = 105; // Password rejected, use a different one.
if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); } if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
return; return;
} }
@ -925,7 +938,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// This is the same password, request a password change again // This is the same password, request a password change again
parent.debug('web', 'handleResetPasswordRequest: password rejected, use a different one (2)'); parent.debug('web', 'handleResetPasswordRequest: password rejected, use a different one (2)');
req.session.loginmode = '6'; req.session.loginmode = '6';
req.session.error = '<b style=color:#8C001A>Password rejected, use a different one.</b>'; req.session.messageid = 105; // Password rejected, use a different one.
if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); } if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
} else { } else {
// Update the password, use a different salt. // Update the password, use a different salt.
@ -959,8 +972,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
delete req.session.resettokenusername; delete req.session.resettokenusername;
delete req.session.resettokenpassword; delete req.session.resettokenpassword;
delete req.session.tokenemail; delete req.session.tokenemail;
delete req.session.success; delete req.session.messageid;
delete req.session.error;
delete req.session.passhint; delete req.session.passhint;
if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); } if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
return; return;
@ -971,11 +983,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Called to process an account reset request // Called to process an account reset request
function handleResetAccountRequest(req, res, direct) { function handleResetAccountRequest(req, res, direct) {
const domain = checkUserIpAddress(req, res); const domain = checkUserIpAddress(req, res);
if ((domain == null) || (domain.auth == 'sspi') || (domain.auth == 'ldap') || (obj.args.lanonly == true) || (obj.parent.certificates.CommonName == null) || (obj.parent.certificates.CommonName.indexOf('.') == -1)) { if ((domain == null) || (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; }
parent.debug('web', 'handleResetAccountRequest: check failed'); if ((domain.loginkey != null) && (domain.loginkey != req.query.key)) { res.sendStatus(404); return; } // Check 3FA URL key
res.sendStatus(404);
return;
}
// Always lowercase the email address // Always lowercase the email address
if (req.body.email) { req.body.email = req.body.email.toLowerCase(); } if (req.body.email) { req.body.email = req.body.email.toLowerCase(); }
@ -988,14 +997,14 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (!email || checkEmail(email) == false) { if (!email || checkEmail(email) == false) {
parent.debug('web', 'handleResetAccountRequest: Invalid email'); parent.debug('web', 'handleResetAccountRequest: Invalid email');
req.session.loginmode = '3'; req.session.loginmode = '3';
req.session.error = '<b style=color:#8C001A>Invalid email.</b>'; req.session.messageid = 106; // Invalid email.
if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); } if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
} else { } else {
obj.db.GetUserWithVerifiedEmail(domain.id, email, function (err, docs) { obj.db.GetUserWithVerifiedEmail(domain.id, email, function (err, docs) {
if ((err != null) || (docs.length == 0)) { if ((err != null) || (docs.length == 0)) {
parent.debug('web', 'handleResetAccountRequest: Account not found'); parent.debug('web', 'handleResetAccountRequest: Account not found');
req.session.loginmode = '3'; req.session.loginmode = '3';
req.session.error = '<b style=color:#8C001A>Account not found.</b>'; req.session.messageid = 107; // Account not found.
if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); } if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
} else { } else {
// 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. // 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.
@ -1009,7 +1018,11 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (i == 0) { if (i == 0) {
// 2-step auth is required, but the token is not present or not valid. // 2-step auth is required, but the token is not present or not valid.
parent.debug('web', 'handleResetAccountRequest: Invalid 2FA token, try again'); parent.debug('web', 'handleResetAccountRequest: Invalid 2FA token, try again');
if ((req.body.token != null) || (req.body.hwtoken != null)) { req.session.error = '<b style=color:#8C001A>Invalid token, try again.</b>'; } if ((req.body.token != null) || (req.body.hwtoken != null)) {
req.session.messageid = 108; // Invalid token, try again.
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) });
obj.setbadLogin(req);
}
req.session.loginmode = '5'; req.session.loginmode = '5';
req.session.tokenemail = email; req.session.tokenemail = email;
if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); } if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
@ -1022,14 +1035,14 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (i == 0) { if (i == 0) {
parent.debug('web', 'handleResetAccountRequest: Hold on, reset mail sent.'); parent.debug('web', 'handleResetAccountRequest: Hold on, reset mail sent.');
req.session.loginmode = '1'; req.session.loginmode = '1';
req.session.error = '<b style=color:darkgreen>Hold on, reset mail sent.</b>'; req.session.messageid = 1; // Hold on, reset mail sent.
if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); } if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
} }
} else { } else {
if (i == 0) { if (i == 0) {
parent.debug('web', 'handleResetAccountRequest: Unable to sent email.'); parent.debug('web', 'handleResetAccountRequest: Unable to sent email.');
req.session.loginmode = '3'; req.session.loginmode = '3';
req.session.error = '<b style=color:#8C001A>Unable to sent email.</b>'; req.session.messageid = 109; // Unable to sent email.
if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); } if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
} }
} }
@ -1042,14 +1055,14 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (i == 0) { if (i == 0) {
parent.debug('web', 'handleResetAccountRequest: Hold on, reset mail sent.'); parent.debug('web', 'handleResetAccountRequest: Hold on, reset mail sent.');
req.session.loginmode = '1'; req.session.loginmode = '1';
req.session.error = '<b style=color:darkgreen>Hold on, reset mail sent.</b>'; req.session.messageid = 1; // Hold on, reset mail sent.
if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); } if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
} }
} else { } else {
if (i == 0) { if (i == 0) {
parent.debug('web', 'handleResetAccountRequest: Unable to sent email.'); parent.debug('web', 'handleResetAccountRequest: Unable to sent email.');
req.session.loginmode = '3'; req.session.loginmode = '3';
req.session.error = '<b style=color:#8C001A>Unable to sent email.</b>'; req.session.messageid = 109; // Unable to sent email.
if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); } if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
} }
} }
@ -1063,11 +1076,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Called to process a web based email verification request // Called to process a web based email verification request
function handleCheckMailRequest(req, res) { function handleCheckMailRequest(req, res) {
const domain = checkUserIpAddress(req, res); const domain = checkUserIpAddress(req, res);
if ((domain == null) || (domain.auth == 'sspi') || (domain.auth == 'ldap')) { if ((domain == null) || (domain.auth == 'sspi') || (domain.auth == 'ldap')) { parent.debug('web', 'handleCheckMailRequest: failed checks.'); res.sendStatus(404); return; }
parent.debug('web', 'handleCheckMailRequest: failed checks.'); if ((domain.loginkey != null) && (domain.loginkey != req.query.key)) { res.sendStatus(404); return; } // Check 3FA URL key
res.sendStatus(404);
return;
}
if (req.query.c != null) { if (req.query.c != null) {
var cookie = obj.parent.decodeCookie(req.query.c, obj.parent.mailserver.mailCookieEncryptionKey, 30); var cookie = obj.parent.decodeCookie(req.query.c, obj.parent.mailserver.mailCookieEncryptionKey, 30);
@ -1168,11 +1178,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Called to process an agent invite request // Called to process an agent invite request
function handleAgentInviteRequest(req, res) { function handleAgentInviteRequest(req, res) {
const domain = getDomain(req); const domain = getDomain(req);
if ((domain == null) || ((req.query.m == null) && (req.query.c == null))) { if ((domain == null) || ((req.query.m == null) && (req.query.c == null))) { parent.debug('web', 'handleAgentInviteRequest: failed checks.'); res.sendStatus(404); return; }
parent.debug('web', 'handleAgentInviteRequest: failed checks.'); if ((domain.loginkey != null) && (domain.loginkey != req.query.key)) { res.sendStatus(404); return; } // Check 3FA URL key
res.sendStatus(404);
return;
}
if (req.query.c != null) { if (req.query.c != null) {
// A cookie is specified in the query string, use that // A cookie is specified in the query string, use that
var cookie = obj.parent.decodeCookie(req.query.c, obj.parent.invitationLinkEncryptionKey); var cookie = obj.parent.decodeCookie(req.query.c, obj.parent.invitationLinkEncryptionKey);
@ -1198,11 +1206,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
function handleDeleteAccountRequest(req, res, direct) { function handleDeleteAccountRequest(req, res, direct) {
parent.debug('web', 'handleDeleteAccountRequest()'); parent.debug('web', 'handleDeleteAccountRequest()');
const domain = checkUserIpAddress(req, res); const domain = checkUserIpAddress(req, res);
if ((domain == null) || (domain.auth == 'sspi') || (domain.auth == 'ldap')) { if ((domain == null) || (domain.auth == 'sspi') || (domain.auth == 'ldap')) { parent.debug('web', 'handleDeleteAccountRequest: failed checks.'); res.sendStatus(404); return; }
parent.debug('web', 'handleDeleteAccountRequest: failed checks.'); if ((domain.loginkey != null) && (domain.loginkey != req.query.key)) { res.sendStatus(404); return; } // Check 3FA URL key
res.sendStatus(404);
return;
}
var user = null; var user = null;
if (req.body.authcookie) { if (req.body.authcookie) {
@ -1288,11 +1293,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Handle password changes // Handle password changes
function handlePasswordChangeRequest(req, res, direct) { function handlePasswordChangeRequest(req, res, direct) {
const domain = checkUserIpAddress(req, res); const domain = checkUserIpAddress(req, res);
if ((domain == null) || (domain.auth == 'sspi') || (domain.auth == 'ldap')) { if ((domain == null) || (domain.auth == 'sspi') || (domain.auth == 'ldap')) { parent.debug('web', 'handlePasswordChangeRequest: failed checks (1).'); res.sendStatus(404); return; }
parent.debug('web', 'handlePasswordChangeRequest: failed checks (1).'); if ((domain.loginkey != null) && (domain.loginkey != req.query.key)) { res.sendStatus(404); return; } // Check 3FA URL key
res.sendStatus(404);
return;
}
// Check if the user is logged and we have all required parameters // Check if the user is logged and we have all required parameters
if (!req.session || !req.session.userid || !req.body.apassword0 || !req.body.apassword1 || (req.body.apassword1 != req.body.apassword2) || (req.session.domainid != domain.id)) { if (!req.session || !req.session.userid || !req.body.apassword0 || !req.body.apassword1 || (req.body.apassword1 != req.body.apassword2) || (req.session.domainid != domain.id)) {
@ -1333,6 +1335,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
function handleRootRequest(req, res, direct) { function handleRootRequest(req, res, direct) {
const domain = checkUserIpAddress(req, res); const domain = checkUserIpAddress(req, res);
if (domain == null) { parent.debug('web', 'handleRootRequest: invalid domain.'); res.sendStatus(404); return; } if (domain == null) { parent.debug('web', 'handleRootRequest: invalid domain.'); res.sendStatus(404); return; }
if ((domain.loginkey != null) && (domain.loginkey != req.query.key)) { res.sendStatus(404); return; } // Check 3FA URL key
if (!obj.args) { parent.debug('web', 'handleRootRequest: no obj.args.'); res.sendStatus(500); return; } if (!obj.args) { parent.debug('web', 'handleRootRequest: no obj.args.'); res.sendStatus(500); return; }
if ((domain.sspi != null) && ((req.query.login == null) || (obj.parent.loginCookieEncryptionKey == null))) { if ((domain.sspi != null) && ((req.query.login == null) || (obj.parent.loginCookieEncryptionKey == null))) {
@ -1466,7 +1469,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
delete req.session.domainid; delete req.session.domainid;
delete req.session.currentNode; delete req.session.currentNode;
delete req.session.passhint; delete req.session.passhint;
req.session.error = '<b style=color:#8C001A>Account locked.</b>'; req.session.messageid = 110; // Account locked.
res.redirect(domain.url + getQueryPortion(req)); // BAD*** res.redirect(domain.url + getQueryPortion(req)); // BAD***
return; return;
} }
@ -1519,7 +1522,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
const authRelayCookie = obj.parent.encodeCookie({ ruserid: user._id, domainid: domain.id }, obj.parent.loginCookieEncryptionKey); const authRelayCookie = obj.parent.encodeCookie({ ruserid: user._id, domainid: domain.id }, obj.parent.loginCookieEncryptionKey);
// Send the master web application // Send the master web application
if ((!obj.args.user) && (obj.args.nousers != true) && (nologout == false)) { logoutcontrol += ' <a href=' + domain.url + 'logout?' + Math.random() + ' style=color:white>Logout</a>'; } // If a default user is in use or no user mode, don't display the logout button var extras = (req.query.key != null) ? ('&key=' + req.query.key) : '';
if ((!obj.args.user) && (obj.args.nousers != true) && (nologout == false)) { logoutcontrol += ' <a href=' + domain.url + 'logout?' + Math.random() + extras + ' style=color:white>Logout</a>'; } // If a default user is in use or no user mode, don't display the logout button
var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified
// Clean up the U2F challenge if needed // Clean up the U2F challenge if needed
@ -1578,18 +1582,13 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
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. 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.
// Format an error message if needed // Format an error message if needed
var err = null, msg = null, passhint = null; var passhint = null, msgid = 0;
if (req.session != null) { if (req.session != null) {
err = req.session.error; msgid = req.session.messageid;
msg = req.session.success;
if ((domain.passwordrequirements != null) && (domain.passwordrequirements.hint === true)) { passhint = EscapeHtml(req.session.passhint); } if ((domain.passwordrequirements != null) && (domain.passwordrequirements.hint === true)) { passhint = EscapeHtml(req.session.passhint); }
delete req.session.error; delete req.session.messageid;
delete req.session.success;
delete req.session.passhint; delete req.session.passhint;
} }
var message = '';
if (err != null) message = '<p class="msg error">' + err + '</p>';
if (msg != null) message = '<p class="msg success">' + msg + '</p>';
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')) 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'))
// Check if we are allowed to create new users using the login screen // Check if we are allowed to create new users using the login screen
@ -1601,12 +1600,16 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (hardwareKeyChallenge) { hwstate = obj.parent.encodeCookie({ u: req.session.tokenusername, p: req.session.tokenpassword, c: req.session.u2fchallenge }, obj.parent.loginCookieEncryptionKey) } if (hardwareKeyChallenge) { hwstate = obj.parent.encodeCookie({ u: req.session.tokenusername, p: req.session.tokenpassword, c: req.session.u2fchallenge }, obj.parent.loginCookieEncryptionKey) }
// Render the login page // Render the login page
render(req, res, getRenderPage('login', req), { loginmode: loginmode, rootCertLink: getRootCertLink(), domainurl: domain.url, title: domain.title, title2: domain.title2, 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), message: message, passhint: passhint, welcometext: domain.welcometext ? encodeURIComponent(domain.welcometext).split('\'').join('\\\'') : null, hwstate: hwstate }); render(req, res, getRenderPage('login', req), { loginmode: loginmode, rootCertLink: getRootCertLink(), domainurl: domain.url, title: domain.title, title2: domain.title2, 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 });
} }
// Handle a post request on the root // Handle a post request on the root
function handleRootPostRequest(req, res) { function handleRootPostRequest(req, res) {
const domain = checkUserIpAddress(req, res);
if (domain == null) { parent.debug('web', 'handleTermsRequest: Bad domain'); res.sendStatus(404); return; }
if ((domain.loginkey != null) && (domain.loginkey != req.query.key)) { res.sendStatus(404); return; } // Check 3FA URL key
parent.debug('web', 'handleRootPostRequest, action: ' + req.body.action); parent.debug('web', 'handleRootPostRequest, action: ' + req.body.action);
switch (req.body.action) { switch (req.body.action) {
case 'login': { handleLoginRequest(req, res, true); break; } case 'login': { handleLoginRequest(req, res, true); break; }
case 'tokenlogin': { case 'tokenlogin': {
@ -1647,11 +1650,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Render the terms of service. // Render the terms of service.
function handleTermsRequest(req, res) { function handleTermsRequest(req, res) {
const domain = checkUserIpAddress(req, res); const domain = checkUserIpAddress(req, res);
if (domain == null) { if (domain == null) { parent.debug('web', 'handleTermsRequest: Bad domain'); res.sendStatus(404); return; }
parent.debug('web', 'handleTermsRequest: Bad domain'); if ((domain.loginkey != null) && (domain.loginkey != req.query.key)) { res.sendStatus(404); return; } // Check 3FA URL key
res.sendStatus(404);
return;
}
// See if term.txt was loaded from the database // See if term.txt was loaded from the database
if ((parent.configurationFiles != null) && (parent.configurationFiles['terms.txt'] != null)) { if ((parent.configurationFiles != null) && (parent.configurationFiles['terms.txt'] != null)) {
@ -1661,7 +1661,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (req.session.domainid != domain.id) { req.session = null; res.redirect(domain.url + getQueryPortion(req)); return; } // Check is the session is for the correct domain if (req.session.domainid != domain.id) { req.session = null; res.redirect(domain.url + getQueryPortion(req)); return; } // Check is the session is for the correct domain
var user = obj.users[req.session.userid]; var user = obj.users[req.session.userid];
var logoutcontrol = 'Welcome ' + user.name + '.'; var logoutcontrol = 'Welcome ' + user.name + '.';
if ((domain.ldap == null) && (domain.sspi == null) && (obj.args.user == null) && (obj.args.nousers != true)) { logoutcontrol += ' <a href=' + domain.url + 'logout?' + Math.random() + ' style=color:white>Logout</a>'; } // If a default user is in use or no user mode, don't display the logout button 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)) { logoutcontrol += ' <a href=' + domain.url + 'logout?' + Math.random() + extras + ' style=color:white>Logout</a>'; } // If a default user is in use or no user mode, don't display the logout button
render(req, res, getRenderPage('terms', req), { title: domain.title, title2: domain.title2, domainurl: domain.url, terms: encodeURIComponent(parent.configurationFiles['terms.txt'].toString()), logoutControl: logoutcontrol }); render(req, res, getRenderPage('terms', req), { title: domain.title, title2: domain.title2, domainurl: domain.url, terms: encodeURIComponent(parent.configurationFiles['terms.txt'].toString()), logoutControl: logoutcontrol });
} else { } else {
render(req, res, getRenderPage('terms', req), { title: domain.title, title2: domain.title2, domainurl: domain.url, terms: encodeURIComponent(parent.configurationFiles['terms.txt'].toString()) }); render(req, res, getRenderPage('terms', req), { title: domain.title, title2: domain.title2, domainurl: domain.url, terms: encodeURIComponent(parent.configurationFiles['terms.txt'].toString()) });
@ -1679,7 +1680,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (req.session.domainid != domain.id) { req.session = null; res.redirect(domain.url + getQueryPortion(req)); return; } // Check is the session is for the correct domain if (req.session.domainid != domain.id) { req.session = null; res.redirect(domain.url + getQueryPortion(req)); return; } // Check is the session is for the correct domain
var user = obj.users[req.session.userid]; var user = obj.users[req.session.userid];
var logoutcontrol = 'Welcome ' + user.name + '.'; var logoutcontrol = 'Welcome ' + user.name + '.';
if ((domain.ldap == null) && (domain.sspi == null) && (obj.args.user == null) && (obj.args.nousers != true)) { logoutcontrol += ' <a href=' + domain.url + 'logout?' + Math.random() + ' style=color:white>Logout</a>'; } // If a default user is in use or no user mode, don't display the logout button 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)) { logoutcontrol += ' <a href=' + domain.url + 'logout?' + Math.random() + extras + ' style=color:white>Logout</a>'; } // If a default user is in use or no user mode, don't display the logout button
render(req, res, getRenderPage('terms', req), { title: domain.title, title2: domain.title2, domainurl: domain.url, terms: encodeURIComponent(data), logoutControl: logoutcontrol }); render(req, res, getRenderPage('terms', req), { title: domain.title, title2: domain.title2, domainurl: domain.url, terms: encodeURIComponent(data), logoutControl: logoutcontrol });
} else { } else {
render(req, res, getRenderPage('terms', req), { title: domain.title, title2: domain.title2, domainurl: domain.url, terms: encodeURIComponent(data) }); render(req, res, getRenderPage('terms', req), { title: domain.title, title2: domain.title2, domainurl: domain.url, terms: encodeURIComponent(data) });
@ -1693,7 +1695,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (req.session.domainid != domain.id) { req.session = null; res.redirect(domain.url + getQueryPortion(req)); return; } // Check is the session is for the correct domain if (req.session.domainid != domain.id) { req.session = null; res.redirect(domain.url + getQueryPortion(req)); return; } // Check is the session is for the correct domain
var user = obj.users[req.session.userid]; var user = obj.users[req.session.userid];
var logoutcontrol = 'Welcome ' + user.name + '.'; var logoutcontrol = 'Welcome ' + user.name + '.';
if ((domain.ldap == null) && (domain.sspi == null) && (obj.args.user == null) && (obj.args.nousers != true)) { logoutcontrol += ' <a href=' + domain.url + 'logout?' + Math.random() + ' style=color:white>Logout</a>'; } // If a default user is in use or no user mode, don't display the logout button 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)) { logoutcontrol += ' <a href=' + domain.url + 'logout?' + Math.random() + extras + ' style=color:white>Logout</a>'; } // If a default user is in use or no user mode, don't display the logout button
render(req, res, getRenderPage('terms', req), { title: domain.title, title2: domain.title2, domainurl: domain.url, logoutControl: logoutcontrol }); render(req, res, getRenderPage('terms', req), { title: domain.title, title2: domain.title2, domainurl: domain.url, logoutControl: logoutcontrol });
} else { } else {
render(req, res, getRenderPage('terms', req), { title: domain.title, title2: domain.title2, domainurl: domain.url }); render(req, res, getRenderPage('terms', req), { title: domain.title, title2: domain.title2, domainurl: domain.url });
@ -1726,6 +1729,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Returns the mesh server root certificate // Returns the mesh server root certificate
function handleRootCertRequest(req, res) { function handleRootCertRequest(req, res) {
const domain = getDomain(req);
if (domain == null) { parent.debug('web', 'handleRootCertRequest: no domain'); res.sendStatus(404); return; }
if ((domain.loginkey != null) && (domain.loginkey != req.query.key)) { res.sendStatus(404); return; } // Check 3FA URL key
if ((obj.userAllowedIp != null) && (checkIpAddressEx(req, res, obj.userAllowedIp, false) === false)) { parent.debug('web', 'handleRootCertRequest: invalid ip'); return; } // Check server-wide IP filter only. 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()'); parent.debug('web', 'handleRootCertRequest()');
try { try {
@ -1775,8 +1781,14 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
scriptFile.mescript = Buffer.from(scriptEngine.script_compile(runscript), 'binary').toString('base64'); scriptFile.mescript = Buffer.from(scriptEngine.script_compile(runscript), 'binary').toString('base64');
scriptFile.scriptText = runscript; scriptFile.scriptText = runscript;
// Randomize the environement detection
var randomDnsName;
do { randomDnsName = getRandomLowerCase(14); } while (randomDnsName == 'aabbccddeeffgg');
var text = JSON.stringify(scriptFile, null, ' ');
for (var i = 0; i < 5; i++) { text = text.replace('aabbccddeeffgg', randomDnsName); }
// Send the script // Send the script
func(Buffer.from(JSON.stringify(scriptFile, null, ' '))); func(Buffer.from(text));
}); });
} else { } else {
// Server name is a hostname // Server name is a hostname
@ -1800,14 +1812,24 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
scriptFile.mescript = Buffer.from(scriptEngine.script_compile(runscript), 'binary').toString('base64'); scriptFile.mescript = Buffer.from(scriptEngine.script_compile(runscript), 'binary').toString('base64');
scriptFile.scriptText = runscript; scriptFile.scriptText = runscript;
// Randomize the environement detection
var randomDnsName;
do { randomDnsName = getRandomLowerCase(14); } while (randomDnsName == 'aabbccddeeffgg');
var text = JSON.stringify(scriptFile, null, ' ');
for (var i = 0; i < 5; i++) { text = text.replace('aabbccddeeffgg', randomDnsName); }
// Send the script // Send the script
func(Buffer.from(JSON.stringify(scriptFile, null, ' '))); func(Buffer.from(text));
}); });
} }
} }
// Returns an mescript for Intel AMT configuration // Returns an mescript for Intel AMT configuration
function handleMeScriptRequest(req, res) { function handleMeScriptRequest(req, res) {
const domain = getDomain(req);
if (domain == null) { parent.debug('web', 'handleMeScriptRequest: no domain'); res.sendStatus(404); return; }
if ((domain.loginkey != null) && (domain.loginkey != req.query.key)) { 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. if ((obj.userAllowedIp != null) && (checkIpAddressEx(req, res, obj.userAllowedIp, false) === false)) { return; } // Check server-wide IP filter only.
if (req.query.type == 1) { if (req.query.type == 1) {
obj.getCiraConfigurationScript(req.query.meshid, function (script) { obj.getCiraConfigurationScript(req.query.meshid, function (script) {
@ -1835,6 +1857,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
function handleDownloadUserFiles(req, res) { function handleDownloadUserFiles(req, res) {
const domain = checkUserIpAddress(req, res); const domain = checkUserIpAddress(req, res);
if (domain == null) { res.sendStatus(404); return; } if (domain == null) { res.sendStatus(404); return; }
if ((domain.loginkey != null) && (domain.loginkey != req.query.key)) { res.sendStatus(404); return; } // Check 3FA URL key
if (obj.common.validateString(req.path, 1, 4096) == false) { res.sendStatus(404); return; } if (obj.common.validateString(req.path, 1, 4096) == false) { res.sendStatus(404); return; }
var domainname = 'domain', spliturl = decodeURIComponent(req.path).split('/'), filename = ''; var domainname = 'domain', spliturl = decodeURIComponent(req.path).split('/'), filename = '';
if ((spliturl.length < 3) || (obj.common.IsFilenameValid(spliturl[2]) == false) || (domain.userQuota == -1)) { res.sendStatus(404); return; } if ((spliturl.length < 3) || (obj.common.IsFilenameValid(spliturl[2]) == false) || (domain.userQuota == -1)) { res.sendStatus(404); return; }
@ -2788,6 +2812,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
function handleBackupRequest(req, res) { function handleBackupRequest(req, res) {
const domain = checkUserIpAddress(req, res); const domain = checkUserIpAddress(req, res);
if (domain == null) { res.sendStatus(404); return; } if (domain == null) { res.sendStatus(404); return; }
if ((domain.loginkey != null) && (domain.loginkey != req.query.key)) { res.sendStatus(404); return; } // Check 3FA URL key
if ((!req.session) || (req.session == null) || (!req.session.userid) || (obj.parent.args.noserverbackup == 1)) { res.sendStatus(401); return; } if ((!req.session) || (req.session == null) || (!req.session.userid) || (obj.parent.args.noserverbackup == 1)) { res.sendStatus(401); return; }
var user = obj.users[req.session.userid]; var user = obj.users[req.session.userid];
if ((user == null) || ((user.siteadmin & 1) == 0)) { res.sendStatus(401); return; } // Check if we have server backup rights if ((user == null) || ((user.siteadmin & 1) == 0)) { res.sendStatus(401); return; } // Check if we have server backup rights
@ -2820,6 +2845,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
function handleRestoreRequest(req, res) { function handleRestoreRequest(req, res) {
const domain = checkUserIpAddress(req, res); const domain = checkUserIpAddress(req, res);
if (domain == null) { res.sendStatus(404); return; } if (domain == null) { res.sendStatus(404); return; }
if ((domain.loginkey != null) && (domain.loginkey != req.query.key)) { res.sendStatus(404); return; } // Check 3FA URL key
if (obj.parent.args.noserverbackup == 1) { res.sendStatus(401); return; } if (obj.parent.args.noserverbackup == 1) { res.sendStatus(401); return; }
var authUserid = null; var authUserid = null;
if ((req.session != null) && (typeof req.session.userid == 'string')) { authUserid = req.session.userid; } if ((req.session != null) && (typeof req.session.userid == 'string')) { authUserid = req.session.userid; }
@ -3429,7 +3455,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
}); });
} }
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); }); //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); });
// Indicates to ExpressJS that the override public folder should be used to serve static files. // Indicates to ExpressJS that the override public folder should be used to serve static files.
if (obj.parent.webPublicOverridePath != null) { obj.app.use(url, obj.express.static(obj.parent.webPublicOverridePath, { maxAge: '1h' })); } if (obj.parent.webPublicOverridePath != null) { obj.app.use(url, obj.express.static(obj.parent.webPublicOverridePath, { maxAge: '1h' })); }
@ -3456,6 +3482,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Authenticates a session and forwards // Authenticates a session and forwards
function PerformWSSessionAuth(ws, req, noAuthOk, func) { function PerformWSSessionAuth(ws, req, noAuthOk, func) {
// 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; }
try { try {
// Hold this websocket until we are ready. // Hold this websocket until we are ready.
ws._socket.pause(); ws._socket.pause();
@ -3473,7 +3501,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if ((err == null) && (user)) { if ((err == null) && (user)) {
// Check if a 2nd factor is needed // Check if a 2nd factor is needed
if (checkUserOneTimePasswordRequired(domain, user) == true) { if (checkUserOneTimePasswordRequired(domain, user) == true) {
if (req.query.token) { if (typeof req.query.token != 'string') {
try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth', msg: 'tokenrequired' })); ws.close(); } catch (e) { } try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth', msg: 'tokenrequired' })); ws.close(); } catch (e) { }
} else { } else {
checkUserOneTimePassword(req, domain, user, req.query.token, null, function (result) { checkUserOneTimePassword(req, domain, user, req.query.token, null, function (result) {
@ -3497,6 +3525,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
} else { } else {
// If not authenticated, close the websocket connection // If not authenticated, close the websocket connection
parent.debug('web', 'ERR: Websocket bad user/pass auth'); parent.debug('web', 'ERR: Websocket bad user/pass auth');
//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) });
//obj.setbadLogin(req);
try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth', msg: 'noauth-2' })); ws.close(); } catch (e) { } try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth', msg: 'noauth-2' })); ws.close(); } catch (e) { }
} }
} }
@ -4005,6 +4035,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
function checkAmtPassword(p) { return (p.length > 7) && (/\d/.test(p)) && (/[a-z]/.test(p)) && (/[A-Z]/.test(p)) && (/\W/.test(p)); } 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; } function getRandomAmtPassword() { var p; do { p = Buffer.from(obj.crypto.randomBytes(9), 'binary').toString('base64').split('/').join('@'); } while (checkAmtPassword(p) == false); return p; }
function getRandomPassword() { return Buffer.from(obj.crypto.randomBytes(9), 'binary').toString('base64').split('/').join('@'); } function getRandomPassword() { return Buffer.from(obj.crypto.randomBytes(9), 'binary').toString('base64').split('/').join('@'); }
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; }
// Clean a IPv6 address that encodes a IPv4 address // Clean a IPv6 address that encodes a IPv4 address
function cleanRemoteAddr(addr) { if (typeof addr != 'string') { return null; } if (addr.indexOf('::ffff:') == 0) { return addr.substring(7); } else { return addr; } } function cleanRemoteAddr(addr) { if (typeof addr != 'string') { return null; } if (addr.indexOf('::ffff:') == 0) { return addr.substring(7); } else { return addr; } }
@ -4034,5 +4065,46 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
} catch (ex) { console.log(ex); func(fd, tag); } } catch (ex) { console.log(ex); func(fd, tag); }
} }
// This is the invalid login throttling code
obj.badLoginTable = {};
obj.badLoginTableLastClean = 0;
if (parent.config.settings == null) { parent.config.settings = {}; }
if (parent.config.settings.maxinvalidlogin == null) { 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; }
obj.setbadLogin = function (ip) { // Set an IP address that just did a bad login request
if (typeof ip == 'object') { ip = cleanRemoteAddr(ip.ip); }
if (++obj.badLoginTableLastClean > 100) { obj.cleanBadLoginTable(); }
if (typeof obj.badLoginTable[ip] == 'number') { if (obj.badLoginTable[ip] < Date.now()) { delete obj.badLoginTable[ip]; } else { return; } } // Check cooloff period
if (obj.badLoginTable[ip] == null) { obj.badLoginTable[ip] = [Date.now()]; } else { obj.badLoginTable[ip].push(Date.now()); }
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
}
}
obj.checkAllowLogin = function (ip) { // Check if an IP address is allowed to login
if (typeof ip == 'object') { ip = cleanRemoteAddr(ip.ip); }
var cutoffTime = Date.now() - (parent.config.settings.maxinvalidlogin.time * 60000); // Time in minutes
var ipTable = obj.badLoginTable[ip];
if (ipTable == null) return true;
if (typeof ipTable == 'number') { if (obj.badLoginTable[ip] < Date.now()) { delete obj.badLoginTable[ip]; } else { return false; } } // Check cooloff period
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.
var cutoffTime = Date.now() - (parent.config.settings.maxinvalidlogin.time * 60000); // Time in minutes
for (var ip in obj.badLoginTable) {
var ipTable = obj.badLoginTable[ip];
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]; }
}
}
obj.badLoginTableLastClean = 0;
}
return obj; return obj;
}; };