mirror of
https://github.com/Ylianst/MeshCentral.git
synced 2024-11-22 22:17:31 +03:00
Merge branch 'master' into plugin-admin
This commit is contained in:
commit
8e35f432c8
3
.greenlockrc
Normal file
3
.greenlockrc
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"manager": "C:\\Users\\Default.DESKTOP-M9I88C9\\Desktop\\AmtWebApp\\meshcentral\\letsencrypt.js"
|
||||
}
|
Binary file not shown.
@ -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')
|
||||
{
|
||||
|
@ -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
16
db.js
@ -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); };
|
||||
|
294
letsEncrypt.js
294
letsEncrypt.js
@ -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];
|
||||
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.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];
|
||||
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;
|
||||
};
|
@ -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,9 +144,10 @@ function CreateMeshCentralServer(config, args) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj.service != null) {
|
||||
// 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'];
|
||||
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(); });
|
||||
@ -156,13 +157,42 @@ function CreateMeshCentralServer(config, args) {
|
||||
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.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.uninstall == true) { try { svc.uninstall(); } 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
|
||||
if (obj.args.launch) {
|
||||
if (obj.args.vault) { obj.StartVault(); } else { obj.StartEx(); }
|
||||
@ -677,7 +707,7 @@ function CreateMeshCentralServer(config, args) {
|
||||
}
|
||||
|
||||
// Read environment variables. For a subset of arguments, we allow them to be read from environment variables.
|
||||
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'); }
|
||||
|
||||
|
33
meshuser.js
33
meshuser.js
@ -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': {
|
||||
|
@ -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
@ -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\"" });
|
||||
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;
|
||||
|
@ -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": {
|
||||
"": {
|
||||
|
@ -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');
|
||||
|
@ -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
@ -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])));
|
||||
|
@ -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
@ -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('?'));
|
||||
|
@ -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); }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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
@ -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])));
|
||||
|
@ -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
@ -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('?'));
|
||||
|
@ -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); }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
246
webserver.js
246
webserver.js
@ -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 (typeof xusername == 'string') {
|
||||
var xuserid = 'user/' + domain.id + '/' + xusername.toLowerCase();
|
||||
if (err == 'locked') {
|
||||
parent.debug('web', 'handleLoginRequest: login failed, locked account');
|
||||
req.session.error = '<b style=color:#8C001A>Account locked.</b>';
|
||||
req.session.messageid = 110; // Account locked.
|
||||
obj.parent.DispatchEvent(['*', 'server-users', xuserid], obj, { action: 'authfail', userid: xuserid, username: xusername, domain: domain.id, msg: 'User login attempt on locked account from ' + cleanRemoteAddr(req.ip) });
|
||||
obj.setbadLogin(req);
|
||||
} else {
|
||||
parent.debug('web', 'handleLoginRequest: login failed, bad username and password');
|
||||
req.session.error = '<b style=color:#8C001A>Login failed, check username and password.</b>';
|
||||
req.session.messageid = 112; // Login failed, check username and password.
|
||||
obj.parent.DispatchEvent(['*', 'server-users', xuserid], obj, { action: 'authfail', userid: xuserid, username: xusername, domain: domain.id, msg: 'Invalid user login attempt from ' + cleanRemoteAddr(req.ip) });
|
||||
obj.setbadLogin(req);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up login mode and display password hint if present.
|
||||
@ -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;
|
||||
};
|
Loading…
Reference in New Issue
Block a user