Merge branch 'master' into plugin-admin

This commit is contained in:
Ryan Blenis 2019-11-22 14:26:44 -05:00
commit a5007c995a
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 + '.';
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':
if (process.platform != 'win32')
{

View File

@ -313,7 +313,7 @@ module.exports.CertificateOperations = function (parent) {
cert.setIssuer(attrs);
// 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: "subjectKeyIdentifier" }]);
cert.setExtensions([{ name: "basicConstraints", cA: true }, { name: "subjectKeyIdentifier" }, { name: "keyUsage", keyCertSign: true }]);
cert.sign(keys.privateKey, obj.forge.md.sha384.create());
return { cert: cert, key: keys.privateKey };
@ -418,6 +418,21 @@ module.exports.CertificateOperations = function (parent) {
var rootPrivateKey = obj.fileLoad("root-cert-private.key", "utf8");
r.root = { cert: rootCertificate, key: rootPrivateKey };
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) {

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.
// 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.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
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.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.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
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.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.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
obj.getAllPower = function (func) { obj.powerfile.find({}, func); };

View File

@ -12,136 +12,292 @@
/*jshint node: true */
/*jshint strict: false */
/*jshint esversion: 6 */
"use strict";
'use strict';
module.exports.CreateLetsEncrypt = function (parent) {
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.
// This is an optional module that GreenLock uses that causes issues.
try {
const fs = require('fs');
if (fs.existsSync(obj.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, 'ursa-optional'))) { fs.unlinkSync(obj.path.join(__dirname, '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) { }
// Get GreenLock setup and running.
const greenlock = require('greenlock');
var obj = {};
obj.parent = parent;
obj.path = require('path');
obj.redirWebServerHooked = false;
obj.leDomains = 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
obj.configPath = obj.parent.path.join(obj.parent.datapath, 'letsencrypt');
obj.webrootPath = obj.parent.path.join(obj.parent.datapath, 'letsencrypt', 'webroot');
obj.configPath = obj.path.join(obj.parent.datapath, 'letsencrypt3');
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.
var leStore = require('le-store-certbot').create({ configDir: obj.configPath, webrootPath: obj.webrootPath, debug: obj.parent.args.debug > 0 });
// Setup Let's Encrypt default configuration
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
var leHttpChallenge = require('le-challenge-fs').create({ webrootPath: obj.webrootPath, debug: obj.parent.args.debug > 0 });
// Get package and maintainer email
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
function leAgree(opts, agreeCb) { agreeCb(null, opts.tosUrl); }
// Check if we need to be in debug mode
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 = {
version: 'draft-12',
server: (obj.parent.config.letsencrypt.production === true) ? 'https://acme-v02.api.letsencrypt.org/directory' : 'https://acme-staging-v02.api.letsencrypt.org/directory',
store: leStore,
challenges: { 'http-01': leHttpChallenge },
challengeType: 'http-01',
agreeToTerms: leAgree,
debug: obj.parent.args.debug > 0
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', ev + ': ' + args); } else { parent.debug('cert', ev + ': ' + JSON.stringify(args)); } },
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).
obj.le = greenlock.create(greenlockargs);
// Hook up GreenLock to the redirection server
if (obj.parent.redirserver.port == 80) { obj.parent.redirserver.app.use('/', obj.le.middleware()); obj.redirWebServerHooked = true; }
// 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);
obj.getCertificate = function (certs, func) {
// Hook up GreenLock to the redirection server
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) {
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 (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.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.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; }
// Get the list of domains
obj.leDomains = [certs.CommonName];
obj.leDomains = [ certs.CommonName ];
if (obj.parent.config.letsencrypt.names != null) {
if (typeof obj.parent.config.letsencrypt.names == 'string') { obj.parent.config.letsencrypt.names = obj.parent.config.letsencrypt.names.split(','); }
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; }
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 (results) {
obj.leResults = results;
if (obj.parent.config.letsencrypt.production !== true) {
// We are in staging mode, just go ahead
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.
if (results.altnames.indexOf(certs.CommonName) >= 0) {
certs.web.cert = results.cert;
certs.web.key = results.privkey;
certs.web.ca = [results.chain];
}
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))) {
certs.dns[i].cert = results.cert;
certs.dns[i].key = results.privkey;
certs.dns[i].ca = [results.chain];
obj.getCertificateEx = function (certs, func) {
// Get the Let's Encrypt certificate from our own storage
const xle = (obj.runAsProduction === true)? obj.le : obj.leStaging;
xle.get({ servername: obj.leDomains[0] })
.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) {
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.pems.cert;
certs.dns[i].key = results.pems.privkey;
certs.dns[i].ca = [results.pems.chain];
}
}
}
parent.debug('cert', "Got certs from local store (" + (obj.runAsProduction ? "Production" : "Staging") + ")");
func(certs);
// Check if the Let's Encrypt certificate needs to be renewed.
setTimeout(obj.checkRenewCertificate, 60000); // Check in 1 minute.
setInterval(obj.checkRenewCertificate, 86400000); // Check again in 24 hours and every 24 hours.
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);
}
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.
obj.checkRenewCertificate = function () {
if (obj.leResults == null) { return; }
// TODO: Only renew on one of the peers if multi-peers are active.
// Check if we need to renew the certificate
obj.le.renew({ duplicate: false, domains: obj.leDomains, email: obj.parent.config.letsencrypt.email }, obj.leResults).then(function (xresults) {
obj.parent.performServerCertUpdate(); // Reset the server, TODO: Reset all peers
}, function (err) { }); // If we can't renew, ignore.
};
parent.debug('cert', "Checking certificate for " + obj.leDomains[0] + " (" + (obj.runAsProduction ? "Production" : "Staging") + ")");
// Setup renew options
obj.certCheckStart = Date.now();
const xle = (obj.runAsProduction === true) ? obj.le : obj.leStaging;
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;
} catch (ex) { console.log(ex); } // Unable to start Let's Encrypt
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.
// 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; } }
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.
@ -144,23 +144,53 @@ function CreateMeshCentralServer(config, args) {
return;
}
// 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))) {
var env = [], xenv = ['user', 'port', 'aliasport', 'mpsport', 'mpsaliasport', 'redirport', 'exactport', '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.
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('uninstall', function () { console.log('MeshCentral service uninstalled.'); process.exit(); });
svc.on('start', function () { console.log('MeshCentral service started.'); process.exit(); });
svc.on('stop', function () { console.log('MeshCentral service stopped.'); if (obj.args.stop) { process.exit(); } if (obj.args.restart) { console.log('Holding 5 seconds...'); setTimeout(function () { svc.start(); }, 5000); } });
svc.on('alreadyinstalled', function () { console.log('MeshCentral service already installed.'); process.exit(); });
svc.on('invalidinstallation', function () { console.log('Invalid MeshCentral service installation.'); process.exit(); });
if (obj.service != null) {
// Check if we need to install, start, stop, remove ourself as a background service
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', '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.
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('uninstall', function () { console.log('MeshCentral service uninstalled.'); process.exit(); });
svc.on('start', function () { console.log('MeshCentral service started.'); process.exit(); });
svc.on('stop', function () { console.log('MeshCentral service stopped.'); if (obj.args.stop) { process.exit(); } if (obj.args.restart) { console.log('Holding 5 seconds...'); setTimeout(function () { svc.start(); }, 5000); } });
svc.on('alreadyinstalled', function () { console.log('MeshCentral service already installed.'); 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.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.uninstall == true) { try { svc.uninstall(); } catch (e) { logException(e); } }
return;
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.start == true || obj.args.restart == true) { try { svc.start(); } catch (e) { logException(e); } }
if (obj.args.xuninstall == true) { try { svc.uninstall(); } catch (e) { logException(e); } }
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
@ -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.
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]]); } }
// 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.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.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.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.
@ -830,7 +861,9 @@ function CreateMeshCentralServer(config, args) {
// Load server certificates
obj.certificateOperations = require('./certoperations.js').CertificateOperations(obj);
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
} else {
var le = require('./letsencrypt.js');
@ -1831,6 +1864,27 @@ function CreateMeshCentralServer(config, args) {
// Send event to console
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
if ((obj.debugRemoteSources != null) && ((obj.debugRemoteSources == '*') || (obj.debugRemoteSources.indexOf(source) >= 0))) {
var sendcount = 0;
@ -1932,8 +1986,17 @@ function InstallModules(modules, func) {
var missingModules = [];
if (modules.length > 0) {
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 {
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) {
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
var InstallModuleChildProcess = null;
function InstallModule(modulename, func, tag1, tag2) {
console.log('Installing ' + modulename + '...');
var child_process = require('child_process');
@ -1952,9 +2014,7 @@ function InstallModule(modulename, func, tag1, tag2) {
// 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, '../..'); }
// 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 = null;
child_process.exec(`npm install --no-optional ${modulename}`, { maxBuffer: 512000, timeout: 120000, cwd: parentpath }, function (error, stdout, stderr) {
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');
process.exit();
@ -2002,20 +2062,20 @@ function mainStart() {
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
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 (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.mongodb != null) { modules.push('mongodb'); } // Add MongoDB, official driver.
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.
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 (nodeVersion < 8) { modules.push('util.promisify'); }
@ -2028,7 +2088,7 @@ function mainStart() {
if (yubikey == true) { modules.push('yubikeyotp'); } // Add YubiKey OTP support
if (allsspi == false) { modules.push('otplib'); } // Google Authenticator support
}
// Install any missing modules and launch the server
InstallModules(modules, function () { meshserver = CreateMeshCentralServer(config, args); meshserver.Start(); });

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) { }
}
// 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
ws._socket.resume();
});
@ -681,14 +689,31 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
case 'help': {
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 += '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;
}
case 'dispatchtable': {
r = '';
for (var i in parent.parent.eventsDispatch) {
r += (i + ', ' + parent.parent.eventsDispatch[i].length + '\r\n');
}
for (var i in parent.parent.eventsDispatch) { r += (i + ', ' + parent.parent.eventsDispatch[i].length + '\r\n'); }
break;
}
case 'dupagents': {

View File

@ -1,6 +1,6 @@
{
"name": "meshcentral",
"version": "0.4.3-s",
"version": "0.4.4-r",
"keywords": [
"Remote Management",
"Intel AMT",
@ -30,22 +30,19 @@
"dependencies": {
"archiver": "^3.0.0",
"body-parser": "^1.19.0",
"cbor": "4.1.5",
"cbor": "^4.1.5",
"compression": "^1.7.4",
"connect-redis": "^3.4.1",
"cookie-session": "^2.0.0-beta.3",
"express": "^4.17.0",
"express-handlebars": "^3.1.0",
"express-ws": "^4.0.0",
"html-minifier": "^4.0.0",
"ipcheck": "^0.1.0",
"meshcentral": "*",
"minify-js": "0.0.4",
"minimist": "^1.2.0",
"multiparty": "^4.2.1",
"nedb": "^1.8.0",
"node-forge": "^0.8.4",
"node-vault": "^0.9.11",
"ws": "^6.2.1",
"xmldom": "^0.1.27",
"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.args = args;
obj.certificates = null;
obj.express = require("express");
obj.net = require("net");
obj.express = require('express');
obj.net = require('net');
obj.app = obj.express();
obj.tcpServer = null;
obj.port = null;
const leChallengePrefix = '/.well-known/acme-challenge/';
// Perform an HTTP to HTTPS redirection
function performRedirection(req, res) {
@ -49,14 +50,14 @@ module.exports.CreateRedirServer = function (parent, db, args, func) {
*/
// 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.
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 i = rootcert.indexOf("-----BEGIN CERTIFICATE-----\r\n");
var i = rootcert.indexOf('-----BEGIN CERTIFICATE-----\r\n');
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); }
res.send(Buffer.from(rootcert, "base64"));
} else {
@ -66,9 +67,17 @@ module.exports.CreateRedirServer = function (parent, db, args, func) {
// Add HTTP security headers to all responses
obj.app.use(function (req, res, next) {
res.removeHeader("X-Powered-By");
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();
parent.debug('webrequest', req.url + ' (RedirServer)');
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();
}
});
// 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);
return obj;

View File

@ -44,6 +44,7 @@
"_TlsOffload": true,
"_MpsTlsOffload": true,
"_No2FactorAuth": true,
"_Log": "main,web,webrequest,cert",
"_WebRtConfig": {
"iceServers": [
{ "urls": "stun:stun.services.mozilla.com" },
@ -58,7 +59,9 @@
},
"_Redirects": {
"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": {
"": {

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, '../..'); }
// 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;
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');

View File

@ -6498,10 +6498,10 @@
if (meshrights & 8) {
Q('p20remotecontrol').checked = true;
if (meshrights & 256) { Q('p20remoteview').checked = true; }
if (meshrights & 512) { Q('p20remotelimitedinput').checked = true; }
if (meshrights & 1024) { Q('p20noterminal').checked = true; }
if (meshrights & 2048) { Q('p20nofiles').checked = true; }
if (meshrights & 4096) { Q('p20noamt').checked = true; }
if (meshrights & 512) { Q('p20noterminal').checked = true; }
if (meshrights & 1024) { Q('p20nofiles').checked = true; }
if (meshrights & 2048) { Q('p20noamt').checked = true; }
if (meshrights & 4096) { Q('p20remotelimitedinput').checked = true; }
}
if (meshrights & 16) { Q('p20meshagentconsole').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=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=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><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>';
@ -8213,8 +8214,8 @@
}
function setServerTracingEx(b) {
var sources = [], allsources = ['cookie', 'dispatch', 'main', 'peer', 'web', 'webrequest', 'relay', 'webrelaydata', 'webrelay', 'mps', 'mpscmd', 'swarm', 'swarmcmd', 'agentupdate', 'agent'];
if (b == 1) { for (var i = 1; i < 16; i++) { try { if (Q('p41c' + i).checked) { sources.push(allsources[i - 1]); } } catch (ex) { } } }
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 < 17; i++) { try { if (Q('p41c' + i).checked) { sources.push(allsources[i - 1]); } } catch (ex) { } } }
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=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=p20uninstall>' + "Uninstall Agent" + '</label><br>';
x += '</div>';
setDialogMode(2, "Add User to Mesh", 3, p20showAddMeshUserDialogEx, x);
p20validateAddMeshUserDialog();
@ -3229,20 +3230,24 @@
function p20validateAddMeshUserDialog() {
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('p20editmesh', (!Q('p20fulladmin').checked) && (meshrights == 0xFFFFFFFF));
QE('p20manageusers', !Q('p20fulladmin').checked);
QE('p20managecomputers', !Q('p20fulladmin').checked);
QE('p20remotecontrol', !Q('p20fulladmin').checked);
QE('p20meshagentconsole', !Q('p20fulladmin').checked);
QE('p20meshserverfiles', !Q('p20fulladmin').checked);
QE('p20wakedevices', !Q('p20fulladmin').checked);
QE('p20editnotes', !Q('p20fulladmin').checked);
QE('p20remoteview', !Q('p20fulladmin').checked && Q('p20remotecontrol').checked);
QE('p20noterminal', !Q('p20fulladmin').checked && Q('p20remotecontrol').checked);
QE('p20nofiles', !Q('p20fulladmin').checked && Q('p20remotecontrol').checked);
QE('p20noamt', !Q('p20fulladmin').checked && Q('p20remotecontrol').checked);
QE('p20editmesh', nc && (meshrights == 0xFFFFFFFF));
QE('p20manageusers', nc);
QE('p20managecomputers', nc);
QE('p20remotecontrol', nc);
QE('p20meshagentconsole', nc);
QE('p20meshserverfiles', nc);
QE('p20wakedevices', nc);
QE('p20editnotes', nc);
QE('p20limitevents', nc);
QE('p20remoteview', nc && Q('p20remotecontrol').checked);
QE('p20remotelimitedinput', nc && Q('p20remotecontrol').checked && !Q('p20remoteview').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() {
@ -3260,8 +3265,14 @@
if (Q('p20noterminal').checked == true) meshadmin += 512;
if (Q('p20nofiles').checked == true) meshadmin += 1024;
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) {
@ -3284,6 +3295,7 @@
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 & 16384) != 0) r.push("Chat & Notify");
if ((meshrights & 32768) != 0) r.push("Uninstall");
}
if (r.length == 0) { r.push("No Rights"); }
var buttons = 1, x = addHtmlValue("User", EscapeHtml(decodeURIComponent(userid.split('/')[2])));

View File

@ -7555,10 +7555,10 @@
if (meshrights & 8) {
Q('p20remotecontrol').checked = true;
if (meshrights & 256) { Q('p20remoteview').checked = true; }
if (meshrights & 512) { Q('p20remotelimitedinput').checked = true; }
if (meshrights & 1024) { Q('p20noterminal').checked = true; }
if (meshrights & 2048) { Q('p20nofiles').checked = true; }
if (meshrights & 4096) { Q('p20noamt').checked = true; }
if (meshrights & 512) { Q('p20noterminal').checked = true; }
if (meshrights & 1024) { Q('p20nofiles').checked = true; }
if (meshrights & 2048) { Q('p20noamt').checked = true; }
if (meshrights & 4096) { Q('p20remotelimitedinput').checked = true; }
}
if (meshrights & 16) { Q('p20meshagentconsole').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=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=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><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>';
@ -9270,8 +9271,8 @@
}
function setServerTracingEx(b) {
var sources = [], allsources = ['cookie', 'dispatch', 'main', 'peer', 'web', 'webrequest', 'relay', 'webrelaydata', 'webrelay', 'mps', 'mpscmd', 'swarm', 'swarmcmd', 'agentupdate', 'agent'];
if (b == 1) { for (var i = 1; i < 16; i++) { try { if (Q('p41c' + i).checked) { sources.push(allsources[i - 1]); } } catch (ex) { } } }
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 < 17; i++) { try { if (Q('p41c' + i).checked) { sources.push(allsources[i - 1]); } } catch (ex) { } } }
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">
<form method=post>
<input type=hidden name=action value=login />
<div id=message1>
{{{message}}}
</div>
<div id=message1></div>
<div>
<b>Log In</b>
</div>
@ -80,9 +78,7 @@
<div style="background-color:#979797;border-radius:16px;width:260px;padding:16px;text-align:center;clear:both;position:relative">
<form method=post>
<input type=hidden name=action value=createaccount />
<div id=message2>
{{{message}}}
</div>
<div id=message2></div>
<div>
<b>Account Creation</b>
</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">
<form method=post>
<input type=hidden name=action value=resetaccount />
<div id=message3>
{{{message}}}
</div>
<div id=message3></div>
<div>
<b>Account Reset</b>
</div>
@ -153,9 +147,7 @@
<form method=post autocomplete=off>
<input type=hidden name=action value=tokenlogin />
<input type=hidden name=hwstate value="{{{hwstate}}}" />
<div id=message4>
{{{message}}}
</div>
<div id=message4></div>
<table>
<tr>
<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">
<form method=post autocomplete=off>
<input type=hidden name=action value=resetaccount />
<div id=message5>
{{{message}}}
</div>
<div id=message5></div>
<table>
<tr>
<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">
<form method=post>
<input type=hidden name=action value=resetpassword />
<div id=message6>
{{{message}}}
</div>
<div id=message6></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>
<tr>
@ -279,6 +267,20 @@
var hardwareKeyChallenge = decodeURIComponent('{{{hkey}}}');
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 (window.location.href.indexOf('?') > 0) {
var urlargs = window.location.href.substring(window.location.href.indexOf('?'));

View File

@ -43,9 +43,7 @@
<div id=loginpanel style="display:none">
<form method=post>
<input type=hidden name=action value=login />
<div id=message1>
{{{message}}}
</div>
<div id=message1></div>
<div>
<b>Log In</b>
</div>
@ -76,9 +74,7 @@
<div id=createpanel style="display:none;position:relative">
<form method=post>
<input type=hidden name=action value=createaccount />
<div id=message2>
{{{message}}}
</div>
<div id=message2></div>
<div>
<b>Account Creation</b>
</div>
@ -122,9 +118,7 @@
<div id=resetpanel style="display:none">
<form method=post>
<input type=hidden name=action value=resetaccount />
<div id=message3>
{{{message}}}
</div>
<div id=message3></div>
<div>
<b>Account Reset</b>
</div>
@ -148,9 +142,7 @@
<form method=post autocomplete=off>
<input type=hidden name=action value=tokenlogin />
<input type=hidden name=hwstate value="{{{hwstate}}}" />
<div id=message4>
{{{message}}}
</div>
<div id=message4></div>
<table>
<tr>
<td align=right width=100>Login token:</td>
@ -172,9 +164,7 @@
<div id=resettokenpanel style="display:none">
<form method=post>
<input type=hidden name=action value=resetaccount />
<div id=message5>
{{{message}}}
</div>
<div id=message5></div>
<table>
<tr>
<td align=right width=100>Login token:</td>
@ -196,9 +186,7 @@
<div id=resetpasswordpanel style="display:none;position:relative">
<form method=post>
<input type=hidden name=action value=resetpassword />
<div id=message6>
{{{message}}}
</div>
<div id=message6></div>
<div id="rpasswordPolicyCallout" style="display:none"></div>
<table>
<tr>
@ -276,6 +264,20 @@
var nightMode = (getstore('_nightMode', '0') == '1');
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 (window.location.href.indexOf('?') > 0) {
var urlargs = window.location.href.substring(window.location.href.indexOf('?'));
@ -353,9 +355,7 @@
QE('tokenOkButton', true);
Q('tokenOkButton').click();
},
function (error) {
console.log('credentials-get error', error);
}
function (error) { console.log('credentials-get error', error); }
);
}
}

View File

@ -6498,10 +6498,10 @@
if (meshrights & 8) {
Q('p20remotecontrol').checked = true;
if (meshrights & 256) { Q('p20remoteview').checked = true; }
if (meshrights & 512) { Q('p20remotelimitedinput').checked = true; }
if (meshrights & 1024) { Q('p20noterminal').checked = true; }
if (meshrights & 2048) { Q('p20nofiles').checked = true; }
if (meshrights & 4096) { Q('p20noamt').checked = true; }
if (meshrights & 512) { Q('p20noterminal').checked = true; }
if (meshrights & 1024) { Q('p20nofiles').checked = true; }
if (meshrights & 2048) { Q('p20noamt').checked = true; }
if (meshrights & 4096) { Q('p20remotelimitedinput').checked = true; }
}
if (meshrights & 16) { Q('p20meshagentconsole').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=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=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><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>';
@ -8213,8 +8214,8 @@
}
function setServerTracingEx(b) {
var sources = [], allsources = ['cookie', 'dispatch', 'main', 'peer', 'web', 'webrequest', 'relay', 'webrelaydata', 'webrelay', 'mps', 'mpscmd', 'swarm', 'swarmcmd', 'agentupdate', 'agent'];
if (b == 1) { for (var i = 1; i < 16; i++) { try { if (Q('p41c' + i).checked) { sources.push(allsources[i - 1]); } } catch (ex) { } } }
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 < 17; i++) { try { if (Q('p41c' + i).checked) { sources.push(allsources[i - 1]); } } catch (ex) { } } }
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=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=p20uninstall>' + "Uninstall Agent" + '</label><br>';
x += '</div>';
setDialogMode(2, "Ajouter un utilisateur au groupe", 3, p20showAddMeshUserDialogEx, x);
p20validateAddMeshUserDialog();
@ -3227,20 +3228,24 @@
function p20validateAddMeshUserDialog() {
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('p20editmesh', (!Q('p20fulladmin').checked) && (meshrights == 0xFFFFFFFF));
QE('p20manageusers', !Q('p20fulladmin').checked);
QE('p20managecomputers', !Q('p20fulladmin').checked);
QE('p20remotecontrol', !Q('p20fulladmin').checked);
QE('p20meshagentconsole', !Q('p20fulladmin').checked);
QE('p20meshserverfiles', !Q('p20fulladmin').checked);
QE('p20wakedevices', !Q('p20fulladmin').checked);
QE('p20editnotes', !Q('p20fulladmin').checked);
QE('p20remoteview', !Q('p20fulladmin').checked && Q('p20remotecontrol').checked);
QE('p20noterminal', !Q('p20fulladmin').checked && Q('p20remotecontrol').checked);
QE('p20nofiles', !Q('p20fulladmin').checked && Q('p20remotecontrol').checked);
QE('p20noamt', !Q('p20fulladmin').checked && Q('p20remotecontrol').checked);
QE('p20editmesh', nc && (meshrights == 0xFFFFFFFF));
QE('p20manageusers', nc);
QE('p20managecomputers', nc);
QE('p20remotecontrol', nc);
QE('p20meshagentconsole', nc);
QE('p20meshserverfiles', nc);
QE('p20wakedevices', nc);
QE('p20editnotes', nc);
QE('p20limitevents', nc);
QE('p20remoteview', nc && Q('p20remotecontrol').checked);
QE('p20remotelimitedinput', nc && Q('p20remotecontrol').checked && !Q('p20remoteview').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() {
@ -3258,8 +3263,14 @@
if (Q('p20noterminal').checked == true) meshadmin += 512;
if (Q('p20nofiles').checked == true) meshadmin += 1024;
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) {
@ -3282,6 +3293,7 @@
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 & 16384) != 0) r.push("Chat & Notify");
if ((meshrights & 32768) != 0) r.push("Uninstall");
}
if (r.length == 0) { r.push("No Rights"); }
var buttons = 1, x = addHtmlValue("User", EscapeHtml(decodeURIComponent(userid.split('/')[2])));

View File

@ -7478,10 +7478,10 @@
if (meshrights & 8) {
Q('p20remotecontrol').checked = true;
if (meshrights & 256) { Q('p20remoteview').checked = true; }
if (meshrights & 512) { Q('p20remotelimitedinput').checked = true; }
if (meshrights & 1024) { Q('p20noterminal').checked = true; }
if (meshrights & 2048) { Q('p20nofiles').checked = true; }
if (meshrights & 4096) { Q('p20noamt').checked = true; }
if (meshrights & 512) { Q('p20noterminal').checked = true; }
if (meshrights & 1024) { Q('p20nofiles').checked = true; }
if (meshrights & 2048) { Q('p20noamt').checked = true; }
if (meshrights & 4096) { Q('p20remotelimitedinput').checked = true; }
}
if (meshrights & 16) { Q('p20meshagentconsole').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=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=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><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>';
@ -9193,8 +9194,8 @@
}
function setServerTracingEx(b) {
var sources = [], allsources = ['cookie', 'dispatch', 'main', 'peer', 'web', 'webrequest', 'relay', 'webrelaydata', 'webrelay', 'mps', 'mpscmd', 'swarm', 'swarmcmd', 'agentupdate', 'agent'];
if (b == 1) { for (var i = 1; i < 16; i++) { try { if (Q('p41c' + i).checked) { sources.push(allsources[i - 1]); } } catch (ex) { } } }
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 < 17; i++) { try { if (Q('p41c' + i).checked) { sources.push(allsources[i - 1]); } } catch (ex) { } } }
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">
<form method="post">
<input type="hidden" name="action" value="login">
<div id="message1">
{{{message}}}
</div>
<div id="message1"></div>
<div>
<b>Log In</b>
</div>
@ -78,9 +76,7 @@
<div style="background-color:#979797;border-radius:16px;width:260px;padding:16px;text-align:center;clear:both;position:relative">
<form method="post">
<input type="hidden" name="action" value="createaccount">
<div id="message2">
{{{message}}}
</div>
<div id="message2"></div>
<div>
<b>Account Creation</b>
</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">
<form method="post">
<input type="hidden" name="action" value="resetaccount">
<div id="message3">
{{{message}}}
</div>
<div id="message3"></div>
<div>
<b>Account Reset</b>
</div>
@ -151,9 +145,7 @@
<form method="post" autocomplete="off">
<input type="hidden" name="action" value="tokenlogin">
<input type="hidden" name="hwstate" value="{{{hwstate}}}">
<div id="message4">
{{{message}}}
</div>
<div id="message4"></div>
<table>
<tbody><tr>
<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">
<form method="post" autocomplete="off">
<input type="hidden" name="action" value="resetaccount">
<div id="message5">
{{{message}}}
</div>
<div id="message5"></div>
<table>
<tbody><tr>
<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">
<form method="post">
<input type="hidden" name="action" value="resetpassword">
<div id="message6">
{{{message}}}
</div>
<div id="message6"></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>
<tbody><tr>
@ -277,6 +265,20 @@
var hardwareKeyChallenge = decodeURIComponent('{{{hkey}}}');
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 (window.location.href.indexOf('?') > 0) {
var urlargs = window.location.href.substring(window.location.href.indexOf('?'));

View File

@ -41,9 +41,7 @@
<div id="loginpanel" style="display:none">
<form method="post">
<input type="hidden" name="action" value="login">
<div id="message1">
{{{message}}}
</div>
<div id="message1"></div>
<div>
<b>Log In</b>
</div>
@ -74,9 +72,7 @@
<div id="createpanel" style="display:none;position:relative">
<form method="post">
<input type="hidden" name="action" value="createaccount">
<div id="message2">
{{{message}}}
</div>
<div id="message2"></div>
<div>
<b>Account Creation</b>
</div>
@ -120,9 +116,7 @@
<div id="resetpanel" style="display:none">
<form method="post">
<input type="hidden" name="action" value="resetaccount">
<div id="message3">
{{{message}}}
</div>
<div id="message3"></div>
<div>
<b>Account Reset</b>
</div>
@ -146,9 +140,7 @@
<form method="post" autocomplete="off">
<input type="hidden" name="action" value="tokenlogin">
<input type="hidden" name="hwstate" value="{{{hwstate}}}">
<div id="message4">
{{{message}}}
</div>
<div id="message4"></div>
<table>
<tbody><tr>
<td align="right" width="100">Login token:</td>
@ -170,9 +162,7 @@
<div id="resettokenpanel" style="display:none">
<form method="post">
<input type="hidden" name="action" value="resetaccount">
<div id="message5">
{{{message}}}
</div>
<div id="message5"></div>
<table>
<tbody><tr>
<td align="right" width="100">Login token:</td>
@ -194,9 +184,7 @@
<div id="resetpasswordpanel" style="display:none;position:relative">
<form method="post">
<input type="hidden" name="action" value="resetpassword">
<div id="message6">
{{{message}}}
</div>
<div id="message6"></div>
<div id="rpasswordPolicyCallout" style="display:none"></div>
<table>
<tbody><tr>
@ -274,6 +262,20 @@
var nightMode = (getstore('_nightMode', '0') == '1');
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 (window.location.href.indexOf('?') > 0) {
var urlargs = window.location.href.substring(window.location.href.indexOf('?'));
@ -351,9 +353,7 @@
QE('tokenOkButton', true);
Q('tokenOkButton').click();
},
function (error) {
console.log('credentials-get error', error);
}
function (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) {
next();
} else {
req.session.error = 'Access denied!';
req.session.messageid = 111; // Access denied.
res.redirect(domain.url + 'login');
}
};
@ -496,11 +496,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
function handleLogoutRequest(req, res) {
const domain = checkUserIpAddress(req, res);
if ((domain == null) || (domain.auth == 'sspi')) {
parent.debug('web', 'handleLogoutRequest: failed checks.');
res.sendStatus(404);
return;
}
if ((domain == null) || (domain.auth == 'sspi')) { parent.debug('web', 'handleLogoutRequest: failed checks.'); res.sendStatus(404); return; }
if ((domain.loginkey != null) && (domain.loginkey != req.query.key)) { res.sendStatus(404); return; } // Check 3FA URL key
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
@ -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 }); }
}
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.');
}
@ -638,6 +635,17 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
function handleLoginRequest(req, res, direct) {
const domain = checkUserIpAddress(req, res);
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.
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.
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.
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');
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 {
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
setTimeout(function () {
// If the account is locked, display that.
if (err == 'locked') {
parent.debug('web', 'handleLoginRequest: login failed, locked account');
req.session.error = '<b style=color:#8C001A>Account locked.</b>';
} else {
parent.debug('web', 'handleLoginRequest: login failed, bad username and password');
req.session.error = '<b style=color:#8C001A>Login failed, check username and password.</b>';
if (typeof xusername == 'string') {
var xuserid = 'user/' + domain.id + '/' + xusername.toLowerCase();
if (err == 'locked') {
parent.debug('web', 'handleLoginRequest: login failed, locked account');
req.session.messageid = 110; // Account locked.
obj.parent.DispatchEvent(['*', 'server-users', xuserid], obj, { action: 'authfail', userid: xuserid, username: xusername, domain: domain.id, msg: 'User login attempt on locked account from ' + cleanRemoteAddr(req.ip) });
obj.setbadLogin(req);
} else {
parent.debug('web', 'handleLoginRequest: login failed, bad username and password');
req.session.messageid = 112; // Login failed, check username and password.
obj.parent.DispatchEvent(['*', 'server-users', xuserid], obj, { action: 'authfail', userid: xuserid, username: xusername, domain: domain.id, msg: 'Invalid user login attempt from ' + cleanRemoteAddr(req.ip) });
obj.setbadLogin(req);
}
}
// 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
parent.debug('web', 'handleLoginRequest: login ok, password change requested');
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.resettokenpassword = xpassword;
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
user.pastlogin = user.login;
user.login = Math.floor(Date.now() / 1000);
obj.db.SetUser(user);
@ -733,13 +751,11 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Regenerate session when signing in to prevent fixation
//req.session.regenerate(function () {
// Store the user's primary key in the session store to be retrieved, or in this case the entire user object
// 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.tokenusername;
delete req.session.tokenpassword;
delete req.session.tokenemail;
delete req.session.success;
delete req.session.error;
delete req.session.messageid;
delete req.session.passhint;
req.session.userid = userid;
req.session.domainid = domain.id;
@ -772,11 +788,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
function handleCreateAccountRequest(req, res, direct) {
const domain = checkUserIpAddress(req, res);
if ((domain == null) || (domain.auth == 'sspi') || (domain.auth == 'ldap')) {
parent.debug('web', 'handleCreateAccountRequest: failed checks.');
res.sendStatus(404);
return;
}
if ((domain == null) || (domain.auth == 'sspi') || (domain.auth == 'ldap')) { parent.debug('web', 'handleCreateAccountRequest: failed checks.'); res.sendStatus(404); return; }
if ((domain.loginkey != null) && (domain.loginkey != req.query.key)) { res.sendStatus(404); return; } // Check 3FA URL key
// Always lowercase the email address
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) {
parent.debug('web', 'handleCreateAccountRequest: unable to create account (1)');
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)); }
return;
}
@ -811,7 +824,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (emailok == false) {
parent.debug('web', 'handleCreateAccountRequest: unable to create account (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)); }
return;
}
@ -822,13 +835,13 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (maxExceed) {
parent.debug('web', 'handleCreateAccountRequest: account limit reached');
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)); }
} 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)) {
parent.debug('web', 'handleCreateAccountRequest: unable to create account (3)');
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)); }
} else {
// Check if this email was already verified
@ -836,14 +849,14 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (docs.length > 0) {
parent.debug('web', 'handleCreateAccountRequest: Existing account with this email address');
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)); }
} else {
// Check if there is domain.newAccountToken, check if supplied token is valid
if ((domain.newaccountspass != null) && (domain.newaccountspass != '') && (req.body.anewaccountpass != domain.newaccountspass)) {
parent.debug('web', 'handleCreateAccountRequest: Invalid account creation token');
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)); }
return;
}
@ -851,7 +864,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (obj.users['user/' + domain.id + '/' + req.body.username.toLowerCase()]) {
parent.debug('web', 'handleCreateAccountRequest: Username already exists');
req.session.loginmode = '2';
req.session.error = '<b style=color:#8C001A>Username already exists.</b>';
req.session.messageid = 104; // Username already exists.
} 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 };
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
function handleResetPasswordRequest(req, res, direct) {
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
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.resettokenpassword;
delete req.session.tokenemail;
delete req.session.success;
delete req.session.error;
delete req.session.messageid;
delete req.session.passhint;
if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
return;
@ -914,7 +927,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (!obj.common.checkPasswordRequirements(req.body.rpassword1, domain.passwordrequirements)) {
parent.debug('web', 'handleResetPasswordRequest: password rejected, use a different one (1)');
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)); }
return;
}
@ -925,7 +938,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// This is the same password, request a password change again
parent.debug('web', 'handleResetPasswordRequest: password rejected, use a different one (2)');
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)); }
} else {
// 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.resettokenpassword;
delete req.session.tokenemail;
delete req.session.success;
delete req.session.error;
delete req.session.messageid;
delete req.session.passhint;
if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
return;
@ -971,11 +983,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Called to process an account reset request
function handleResetAccountRequest(req, res, direct) {
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)) {
parent.debug('web', 'handleResetAccountRequest: check failed');
res.sendStatus(404);
return;
}
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; }
if ((domain.loginkey != null) && (domain.loginkey != req.query.key)) { res.sendStatus(404); return; } // Check 3FA URL key
// Always lowercase the email address
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) {
parent.debug('web', 'handleResetAccountRequest: Invalid email');
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)); }
} else {
obj.db.GetUserWithVerifiedEmail(domain.id, email, function (err, docs) {
if ((err != null) || (docs.length == 0)) {
parent.debug('web', 'handleResetAccountRequest: Account not found');
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)); }
} 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.
@ -1009,7 +1018,11 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (i == 0) {
// 2-step auth is required, but the token is not present or not valid.
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.tokenemail = email;
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) {
parent.debug('web', 'handleResetAccountRequest: Hold on, reset mail sent.');
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)); }
}
} else {
if (i == 0) {
parent.debug('web', 'handleResetAccountRequest: Unable to sent email.');
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)); }
}
}
@ -1042,14 +1055,14 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (i == 0) {
parent.debug('web', 'handleResetAccountRequest: Hold on, reset mail sent.');
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)); }
}
} else {
if (i == 0) {
parent.debug('web', 'handleResetAccountRequest: Unable to sent email.');
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)); }
}
}
@ -1063,11 +1076,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Called to process a web based email verification request
function handleCheckMailRequest(req, res) {
const domain = checkUserIpAddress(req, res);
if ((domain == null) || (domain.auth == 'sspi') || (domain.auth == 'ldap')) {
parent.debug('web', 'handleCheckMailRequest: failed checks.');
res.sendStatus(404);
return;
}
if ((domain == null) || (domain.auth == 'sspi') || (domain.auth == 'ldap')) { parent.debug('web', 'handleCheckMailRequest: failed checks.'); res.sendStatus(404); return; }
if ((domain.loginkey != null) && (domain.loginkey != req.query.key)) { res.sendStatus(404); return; } // Check 3FA URL key
if (req.query.c != null) {
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
function handleAgentInviteRequest(req, res) {
const domain = getDomain(req);
if ((domain == null) || ((req.query.m == null) && (req.query.c == null))) {
parent.debug('web', 'handleAgentInviteRequest: failed checks.');
res.sendStatus(404);
return;
}
if ((domain == null) || ((req.query.m == null) && (req.query.c == null))) { parent.debug('web', 'handleAgentInviteRequest: failed checks.'); res.sendStatus(404); return; }
if ((domain.loginkey != null) && (domain.loginkey != req.query.key)) { res.sendStatus(404); return; } // Check 3FA URL key
if (req.query.c != null) {
// A cookie is specified in the query string, use that
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) {
parent.debug('web', 'handleDeleteAccountRequest()');
const domain = checkUserIpAddress(req, res);
if ((domain == null) || (domain.auth == 'sspi') || (domain.auth == 'ldap')) {
parent.debug('web', 'handleDeleteAccountRequest: failed checks.');
res.sendStatus(404);
return;
}
if ((domain == null) || (domain.auth == 'sspi') || (domain.auth == 'ldap')) { parent.debug('web', 'handleDeleteAccountRequest: failed checks.'); res.sendStatus(404); return; }
if ((domain.loginkey != null) && (domain.loginkey != req.query.key)) { res.sendStatus(404); return; } // Check 3FA URL key
var user = null;
if (req.body.authcookie) {
@ -1288,11 +1293,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Handle password changes
function handlePasswordChangeRequest(req, res, direct) {
const domain = checkUserIpAddress(req, res);
if ((domain == null) || (domain.auth == 'sspi') || (domain.auth == 'ldap')) {
parent.debug('web', 'handlePasswordChangeRequest: failed checks (1).');
res.sendStatus(404);
return;
}
if ((domain == null) || (domain.auth == 'sspi') || (domain.auth == 'ldap')) { parent.debug('web', 'handlePasswordChangeRequest: failed checks (1).'); res.sendStatus(404); return; }
if ((domain.loginkey != null) && (domain.loginkey != req.query.key)) { res.sendStatus(404); return; } // Check 3FA URL key
// 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)) {
@ -1333,6 +1335,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
function handleRootRequest(req, res, direct) {
const domain = checkUserIpAddress(req, res);
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 ((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.currentNode;
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***
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);
// 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
// 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.
// Format an error message if needed
var err = null, msg = null, passhint = null;
var passhint = null, msgid = 0;
if (req.session != null) {
err = req.session.error;
msg = req.session.success;
msgid = req.session.messageid;
if ((domain.passwordrequirements != null) && (domain.passwordrequirements.hint === true)) { passhint = EscapeHtml(req.session.passhint); }
delete req.session.error;
delete req.session.success;
delete req.session.messageid;
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'))
// 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) }
// 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
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);
switch (req.body.action) {
case 'login': { handleLoginRequest(req, res, true); break; }
case 'tokenlogin': {
@ -1647,11 +1650,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Render the terms of service.
function handleTermsRequest(req, res) {
const domain = checkUserIpAddress(req, res);
if (domain == null) {
parent.debug('web', 'handleTermsRequest: Bad domain');
res.sendStatus(404);
return;
}
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
// See if term.txt was loaded from the database
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
var user = obj.users[req.session.userid];
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 });
} else {
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
var user = obj.users[req.session.userid];
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 });
} else {
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
var user = obj.users[req.session.userid];
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 });
} else {
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
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.
parent.debug('web', 'handleRootCertRequest()');
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.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
func(Buffer.from(JSON.stringify(scriptFile, null, ' ')));
func(Buffer.from(text));
});
} else {
// 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.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
func(Buffer.from(JSON.stringify(scriptFile, null, ' ')));
func(Buffer.from(text));
});
}
}
// Returns an mescript for Intel AMT configuration
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 (req.query.type == 1) {
obj.getCiraConfigurationScript(req.query.meshid, function (script) {
@ -1835,6 +1857,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
function handleDownloadUserFiles(req, res) {
const domain = checkUserIpAddress(req, res);
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; }
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; }
@ -2788,6 +2812,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
function handleBackupRequest(req, res) {
const domain = checkUserIpAddress(req, res);
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; }
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
@ -2820,6 +2845,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
function handleRestoreRequest(req, res) {
const domain = checkUserIpAddress(req, res);
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; }
var authUserid = null;
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.
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
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 {
// Hold this websocket until we are ready.
ws._socket.pause();
@ -3473,7 +3501,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if ((err == null) && (user)) {
// Check if a 2nd factor is needed
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) { }
} else {
checkUserOneTimePassword(req, domain, user, req.query.token, null, function (result) {
@ -3497,6 +3525,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
} else {
// If not authenticated, close the websocket connection
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) { }
}
}
@ -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 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 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
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); }
}
// 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;
};