From d483872aa6d42e83085e8c28d20b18aaa2a42110 Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Wed, 11 Mar 2020 16:53:09 -0700 Subject: [PATCH] Fixed plugin version matching, 2-factor reuirement + skip, removed GreenLock completely. --- letsEncrypt.js | 316 +---------------------------------------------- meshcentral.js | 20 +-- meshuser.js | 39 +----- package.json | 2 +- pluginHandler.js | 26 ++-- webserver.js | 11 +- 6 files changed, 40 insertions(+), 374 deletions(-) diff --git a/letsEncrypt.js b/letsEncrypt.js index d989dcfa..2a8ae3f8 100644 --- a/letsEncrypt.js +++ b/letsEncrypt.js @@ -14,325 +14,12 @@ /*jshint esversion: 6 */ 'use strict'; -// GreenLock Implementation -var globalLetsEncrypt = null; -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(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 = {}; - globalLetsEncrypt = obj; - obj.parent = parent; - obj.lib = 'greenlock'; - 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.path.join(obj.parent.datapath, 'letsencrypt3'); - try { obj.parent.fs.mkdirSync(obj.configPath); } catch (e) { } - obj.configPathStaging = obj.path.join(obj.parent.datapath, 'letsencrypt3-staging'); - try { obj.parent.fs.mkdirSync(obj.configPathStaging); } catch (e) { } - - // 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 } }; - - // 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; - } - - // 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 for production. - var greenlockargs = { - 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); - - // Create the main GreenLock code module for staging. - var greenlockargsstaging = { - parent: obj, - packageRoot: __dirname, - packageAgent: pkg.name + '/' + pkg.version, - manager: obj.path.join(__dirname, 'letsencrypt.js'), - maintainerEmail: maintainerEmail, - notify: function (ev, args) { if (typeof args == 'string') { parent.debug('cert', 'Notify: ' + ev + ': ' + args); } else { parent.debug('cert', 'Notify: ' + ev + ': ' + JSON.stringify(args)); } }, - staging: true, - debug: ledebug - }; - if (obj.parent.args.debug == null) { greenlockargsstaging.log = function (debug) { }; } // If not in debug mode, ignore all console output from greenlock (makes things clean). - obj.leStaging = greenlock.create(greenlockargsstaging); - - // Hook up GreenLock to the redirection server - 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) || ((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 ]; - 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; - } - - 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. - }); - } - } - } - - obj.getCertificateEx = function (certs, func) { - // Get the Let's Encrypt certificate from our own storage - const xle = (obj.runAsProduction === true)? obj.le : obj.leStaging; - xle.get({ servername: obj.leDomains[0] }) - .then(function (results) { - // If we already have real certificates, use them - if (results) { - if (results.site.altnames.indexOf(certs.CommonName) >= 0) { - certs.web.cert = results.pems.cert; - certs.web.key = results.pems.privkey; - certs.web.ca = [results.pems.chain]; - } - for (var i in obj.parent.config.domains) { - if ((obj.parent.config.domains[i].dns != null) && (obj.parent.certificateOperations.compareCertificateNames(results.site.altnames, obj.parent.config.domains[i].dns))) { - certs.dns[i].cert = results.pems.cert; - certs.dns[i].key = results.pems.privkey; - certs.dns[i].ca = [results.pems.chain]; - } - } - } - parent.debug('cert', "Got certs from local store (" + (obj.runAsProduction ? "Production" : "Staging") + ")"); - func(certs); - - // Check if the Let's Encrypt certificate needs to be renewed. - setTimeout(obj.checkRenewCertificate, 60000); // Check in 1 minute. - setInterval(obj.checkRenewCertificate, 86400000); // Check again in 24 hours and every 24 hours. - return; - }) - .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); - }); - } - - // Check if we need to renew the certificate, call this every day. - obj.checkRenewCertificate = function () { - 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 ex; - } - return null; - } - - return obj; - } catch (ex) { console.log(ex); } // Unable to start Let's Encrypt - return null; -}; - -// GreenLock v3 Manager -module.exports.create = function (options) { - //console.log('xxx-create', options); - var manager = { parent: globalLetsEncrypt }; - manager.find = async function (options) { - try { - // GreenLock sometimes has the bad behavior of adding a wildcard cert request, remove it here if needed. - if ((options.wildname != null) && (options.wildname != '')) { options.wildname = ''; } - if (options.altnames) { - var altnames2 = []; - for (var i in options.altnames) { if (options.altnames[i].indexOf('*') == -1) { altnames2.push(options.altnames[i]); } } - options.altnames = altnames2; - } - if (options.servernames) { - var servernames2 = []; - for (var i in options.servernames) { if (options.servernames[i].indexOf('*') == -1) { servernames2.push(options.servernames[i]); } } - options.servernames = servernames2; - } - } catch (ex) { console.log(ex); } - return Promise.resolve([{ subject: options.servername, altnames: options.altnames }]); - }; - - manager.set = function (options) { - //console.log('xxx-set', 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) { - //console.log('xxx-remove', 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) { - //console.log('xxx-defaults', 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; -}; - - // ACME-Client Implementation var globalLetsEncrypt = null; -module.exports.CreateLetsEncrypt2 = function (parent) { +module.exports.CreateLetsEncrypt = function (parent) { const acme = require('acme-client'); var obj = {}; - obj.lib = 'acme-client'; obj.fs = require('fs'); obj.path = require('path'); obj.parent = parent; @@ -530,7 +217,6 @@ module.exports.CreateLetsEncrypt2 = function (parent) { // Return the status of this module obj.getStats = function () { var r = { - lib: 'acme-client', configOk: obj.configOk, leDomains: obj.leDomains, challenges: obj.challenges, diff --git a/meshcentral.js b/meshcentral.js index e65fbfa6..c250dff8 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -1041,7 +1041,7 @@ function CreateMeshCentralServer(config, args) { if ((obj.config) && (obj.config.settings) && (obj.config.settings.plugins != null)) { const nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]); if (nodeVersion < 7) { - addServerWarning("Plugin support requires Node v7.0 or higher."); + addServerWarning("Plugin support requires Node v7.x or higher."); delete obj.config.settings.plugins; } else { obj.pluginHandler = require('./pluginHandler.js').pluginHandler(obj); @@ -1068,15 +1068,11 @@ function CreateMeshCentralServer(config, args) { obj.certificateOperations.GetMeshServerCertificate(obj.args, obj.config, function (certs) { // Get the current node version const nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]); - if ((obj.config.letsencrypt == null) || (obj.redirserver == null) || (nodeVersion < 8) || ((obj.config.letsencrypt.lib == 'greenlock') && (require('crypto').generateKeyPair == null))) { + if ((obj.config.letsencrypt == null) || (obj.redirserver == null) || (nodeVersion < 8)) { obj.StartEx3(certs); // Just use the configured certificates } else if ((obj.config.letsencrypt != null) && (obj.config.letsencrypt.nochecks == true)) { // Use Let's Encrypt with no checking - if (obj.config.letsencrypt.lib == 'greenlock') { - obj.letsencrypt = require('./letsencrypt.js').CreateLetsEncrypt(obj); - } else { - obj.letsencrypt = require('./letsencrypt.js').CreateLetsEncrypt2(obj); - } + obj.letsencrypt = require('./letsencrypt.js').CreateLetsEncrypt(obj); obj.letsencrypt.getCertificate(certs, obj.StartEx3); // Use Let's Encrypt with no checking, use at your own risk. } else { // Check Let's Encrypt settings @@ -1088,14 +1084,8 @@ function CreateMeshCentralServer(config, args) { else if (obj.config.letsencrypt.email.trim() !== obj.config.letsencrypt.email) { leok = false; addServerWarning("Invalid Let's Encrypt email address."); } else { var le = require('./letsencrypt.js'); - try { - if (obj.config.letsencrypt.lib == 'greenlock') { - obj.letsencrypt = le.CreateLetsEncrypt(obj); - } else { - obj.letsencrypt = le.CreateLetsEncrypt2(obj); - } - } catch (ex) { console.log(ex); } - if (obj.letsencrypt == null) { addServerWarning("Unable to setup GreenLock module."); leok = false; } + try { obj.letsencrypt = le.CreateLetsEncrypt(obj); } catch (ex) { console.log(ex); } + if (obj.letsencrypt == null) { addServerWarning("Unable to setup Let's Encrypt module."); leok = false; } } if (leok == true) { // Check that the email address domain MX resolves. diff --git a/meshuser.js b/meshuser.js index 0f51f16d..0fec0eb8 100644 --- a/meshuser.js +++ b/meshuser.js @@ -703,26 +703,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use if (parent.parent.letsencrypt == null) { r = "Let's Encrypt not in use."; } else { - if (parent.parent.letsencrypt.lib == 'greenlock') { - var leinfo = {}; - var greenLockVersion = null; - try { greenLockVersion = require('greenlock/package.json').version; } catch (ex) { } - if (greenLockVersion) { leinfo.greenLockVer = greenLockVersion; } - leinfo.redirWebServerHooked = parent.parent.letsencrypt.redirWebServerHooked; - leinfo.leDomains = parent.parent.letsencrypt.leDomains; - leinfo.leResults = parent.parent.letsencrypt.leResults; - leinfo.leResultsStaging = parent.parent.letsencrypt.leResultsStaging; - leinfo.performRestart = parent.parent.letsencrypt.performRestart; - leinfo.performMoveToProduction = parent.parent.letsencrypt.performMoveToProduction; - leinfo.runAsProduction = parent.parent.letsencrypt.runAsProduction; - leinfo.leDefaults = parent.parent.letsencrypt.leDefaults; - leinfo.leDefaultsStaging = parent.parent.letsencrypt.leDefaultsStaging; - r = JSON.stringify(leinfo, null, 4); - } else if (parent.parent.letsencrypt.lib == 'acme-client') { - r = JSON.stringify(parent.parent.letsencrypt.getStats(), null, 4); - } else { - r = 'Unknown module'; - } + r = JSON.stringify(parent.parent.letsencrypt.getStats(), null, 4); } break; } @@ -730,14 +711,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use if (parent.parent.letsencrypt == null) { r = "Let's Encrypt not in use."; } else { - if (parent.parent.letsencrypt.lib == 'greenlock') { - var err = parent.parent.letsencrypt.checkRenewCertificate(); - if (err == null) { r = "Called Let's Encrypt certificate check."; } else { r = err; } - } else if (parent.parent.letsencrypt.lib == 'acme-client') { - r = ["CertOK", "Request:NoCert", "Request:Expire", "Request:MissingNames"][parent.parent.letsencrypt.checkRenewCertificate()]; - } else { - r = 'Unknown module'; - } + r = ["CertOK", "Request:NoCert", "Request:Expire", "Request:MissingNames"][parent.parent.letsencrypt.checkRenewCertificate()]; } break; } @@ -745,11 +719,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use if (parent.parent.letsencrypt == null) { r = "Let's Encrypt not in use."; } else { - if (parent.parent.letsencrypt.lib == 'acme-client') { - r = parent.parent.letsencrypt.events.join('\r\n'); - } else { - r = 'Not supported'; - } + r = parent.parent.letsencrypt.events.join('\r\n'); } break; } @@ -845,6 +815,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use var info = process.memoryUsage(); info.dbType = ['None', 'NeDB', 'MongoJS', 'MongoDB'][parent.db.databaseType]; if (parent.db.databaseType == 3) { info.dbChangeStream = parent.db.changeStream; } + if (parent.parent.pluginHandler != null) { info.plugins = []; for (var i in parent.parent.pluginHandler.plugins) { info.plugins.push(i); } } try { info.nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]); } catch (ex) { } try { info.currentVer = parent.parent.currentVer; } catch (ex) { } try { info.platform = process.platform; } catch (ex) { } @@ -3121,7 +3092,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use } case 'otp-hkey-get': { - // Check is 2-step login is supported + // Check if 2-step login is supported const twoStepLoginSupported = ((parent.parent.config.settings.no2factorauth !== true) && (domain.auth != 'sspi') && (parent.parent.certificates.CommonName.indexOf('.') != -1) && (args.nousers !== true)); if (twoStepLoginSupported == false) break; diff --git a/package.json b/package.json index 22f3b696..326d71e6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.5.0-b", + "version": "0.5.0-d", "keywords": [ "Remote Management", "Intel AMT", diff --git a/pluginHandler.js b/pluginHandler.js index a6779f7a..ae165cb9 100644 --- a/pluginHandler.js +++ b/pluginHandler.js @@ -274,6 +274,19 @@ module.exports.pluginHandler = function (parent) { }) }; + // MeshCentral doesn't adhere to semantic versioning (due to the - at the end of the version) + // Convert 1.2.3-a to 1.2.3-3 where the letter is converted to a number. + function versionToNumber(ver) { var x = ver.split('-'); if (x.length != 2) return ver; x[1] = x[1].toLowerCase().charCodeAt(0) - 96; return x.join('.'); } + + // Check if the current version of MeshCentral is at least the minimal required. + function checkMeshCentralVersion(current, minimal) { + if (minimal.startsWith('>=')) { minimal = minimal.substring(2); } + var c = versionToNumber(current).split('.'), m = versionToNumber(minimal).split('.'); + if (c.length != m.length) return false; + for (var i = 0; i < c.length; i++) { var cx = parseInt(c[i]), cm = parseInt(m[i]); if (cx > cm) { return true; } if (cx < cm) { return false; } } + return true; + } + obj.getPluginLatest = function () { return new Promise(function (resolve, reject) { parent.db.getPlugins(function (err, plugins) { @@ -294,16 +307,12 @@ module.exports.pluginHandler = function (parent) { }); if (curconf == null) reject("Some plugin configs could not be parsed"); var s = require('semver'); - // MeshCentral doesn't adhere to semantic versioning (due to the - at the end of the version) - // Convert the letter to ASCII for a "true" version number comparison - var mcCurVer = parent.currentVer.replace(/-(.)$/, (m, p1) => { return ("000" + p1.charCodeAt(0)).substr(-3,3); }); - var piCompatVer = newconf.meshCentralCompat.replace(/-(.)\b/g, (m, p1) => { return ("000" + p1.charCodeAt(0)).substr(-3,3); }); latestRet.push({ 'id': curconf._id, 'installedVersion': curconf.version, 'version': newconf.version, 'hasUpdate': s.gt(newconf.version, curconf.version), - 'meshCentralCompat': s.satisfies(mcCurVer, piCompatVer), + 'meshCentralCompat': checkMeshCentralVersion(parent.currentVer, newconf.meshCentralCompat), 'changelogUrl': curconf.changelogUrl, 'status': curconf.status }); @@ -377,7 +386,7 @@ module.exports.pluginHandler = function (parent) { response.pipe(file); file.on('finish', function () { file.close(function () { - var yauzl = require("yauzl"); + var yauzl = require('yauzl'); if (!obj.fs.existsSync(obj.pluginPath)) { obj.fs.mkdirSync(obj.pluginPath); } @@ -498,9 +507,10 @@ module.exports.pluginHandler = function (parent) { obj.removePlugin = function (id, func) { parent.db.getPlugin(id, function (err, docs) { var plugin = docs[0]; - var rimraf = require('rimraf'); + var rimraf = null; + try { rimraf = require('rimraf'); } catch (ex) { } let pluginPath = obj.parent.path.join(obj.pluginPath, plugin.shortName); - rimraf.sync(pluginPath); + if (rimraf) rimraf.sync(pluginPath); parent.db.deletePlugin(id, func); delete obj.plugins[plugin.shortName]; }); diff --git a/webserver.js b/webserver.js index 29f2c636..9ab2e4ab 100644 --- a/webserver.js +++ b/webserver.js @@ -1564,7 +1564,16 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { if (domain.geolocation == true) { features += 0x00008000; } // Enable geo-location features if ((domain.passwordrequirements != null) && (domain.passwordrequirements.hint === true)) { features += 0x00010000; } // Enable password hints if (parent.config.settings.no2factorauth !== true) { features += 0x00020000; } // Enable WebAuthn/FIDO2 support - if ((obj.args.nousers != true) && (domain.passwordrequirements != null) && (domain.passwordrequirements.force2factor === true)) { features += 0x00040000; } // Force 2-factor auth + if ((obj.args.nousers != true) && (domain.passwordrequirements != null) && (domain.passwordrequirements.force2factor === true)) { + // Check if we can skip 2nd factor auth because of the source IP address + var skip2factor = false; + if ((req != null) && (req.ip != null) && (domain.passwordrequirements != null) && (domain.passwordrequirements.skip2factor != null)) { + for (var i in domain.passwordrequirements.skip2factor) { + if (require('ipcheck').match(req.ip, domain.passwordrequirements.skip2factor[i]) === true) { skip2factor = true; } + } + } + if (skip2factor == false) { features += 0x00040000; } // Force 2-factor auth + } if ((domain.auth == 'sspi') || (domain.auth == 'ldap')) { features += 0x00080000; } // LDAP or SSPI in use, warn that users must login first before adding a user to a group. if (domain.amtacmactivation) { features += 0x00100000; } // Intel AMT ACM activation/upgrade is possible if (domain.usernameisemail) { features += 0x00200000; } // Username is email address