Fixed plugin version matching, 2-factor reuirement + skip, removed GreenLock completely.

Ylian Saint-Hilaire committed Mar 11, 2020 at 16:53 UTC d483872aa6d42e83085e8c28d20b18aaa2a42110
6 files changed +40 -374
letsEncrypt.js
+1 -315
@@ -14,325 +14,12 @@
14 /*jshint esversion: 6 */
15 'use strict';
16
17 -// GreenLock Implementation
18 -var globalLetsEncrypt = null;
19 -module.exports.CreateLetsEncrypt = function (parent) {
20 - try {
21 - // Get the GreenLock version
22 - var greenLockVersion = null;
23 - try { greenLockVersion = require('greenlock/package.json').version; } catch (ex) { }
24 - if (greenLockVersion == null) {
25 - parent.debug('cert', "Initializing Let's Encrypt support");
26 - } else {
27 - parent.debug('cert', "Initializing Let's Encrypt support, using GreenLock v" + greenLockVersion);
28 - }
29 -
30 - // Check the current node version and support for generateKeyPair
31 - if (require('crypto').generateKeyPair == null) { return null; }
32 - if (Number(process.version.match(/^v(\d+\.\d+)/)[1]) < 10) { return null; }
33 -
34 - // Try to delete the "./ursa-optional" or "./node_modules/ursa-optional" folder if present.
35 - // This is an optional module that GreenLock uses that causes issues.
36 - try {
37 - const fs = require('fs');
38 - if (fs.existsSync(parent.path.join(__dirname, 'ursa-optional'))) { fs.unlinkSync(obj.path.join(__dirname, 'ursa-optional')); }
39 - if (fs.existsSync(parent.path.join(__dirname, 'node_modules', 'ursa-optional'))) { fs.unlinkSync(obj.path.join(__dirname, 'node_modules', 'ursa-optional')); }
40 - } catch (ex) { }
41 -
42 - // Get GreenLock setup and running.
43 - const greenlock = require('greenlock');
44 - var obj = {};
45 - globalLetsEncrypt = obj;
46 - obj.parent = parent;
47 - obj.lib = 'greenlock';
48 - obj.path = require('path');
49 - obj.redirWebServerHooked = false;
50 - obj.leDomains = null;
51 - obj.leResults = null;
52 - obj.leResultsStaging = null;
53 - obj.performRestart = false; // Indicates we need to restart the server
54 - obj.performMoveToProduction = false; // Indicates we just got a staging certificate and need to move to production
55 - obj.runAsProduction = false; // This starts at false and moves to true if staging cert is ok.
56 -
57 - // Setup the certificate storage paths
58 - obj.configPath = obj.path.join(obj.parent.datapath, 'letsencrypt3');
59 - try { obj.parent.fs.mkdirSync(obj.configPath); } catch (e) { }
60 - obj.configPathStaging = obj.path.join(obj.parent.datapath, 'letsencrypt3-staging');
61 - try { obj.parent.fs.mkdirSync(obj.configPathStaging); } catch (e) { }
62 -
63 - // Setup Let's Encrypt default configuration
64 - obj.leDefaults = { agreeToTerms: true, store: { module: 'greenlock-store-fs', basePath: obj.configPath } };
65 - obj.leDefaultsStaging = { agreeToTerms: true, store: { module: 'greenlock-store-fs', basePath: obj.configPathStaging } };
66 -
67 - // Get package and maintainer email
68 - const pkg = require('./package.json');
69 - var maintainerEmail = null;
70 - if (typeof pkg.author == 'string') {
71 - // Older NodeJS
72 - maintainerEmail = pkg.author;
73 - var i = maintainerEmail.indexOf('<');
74 - if (i >= 0) { maintainerEmail = maintainerEmail.substring(i + 1); }
75 - var i = maintainerEmail.indexOf('>');
76 - if (i >= 0) { maintainerEmail = maintainerEmail.substring(0, i); }
77 - } else if (typeof pkg.author == 'object') {
78 - // Latest NodeJS
79 - maintainerEmail = pkg.author.email;
80 - }
81 -
82 - // Check if we need to be in debug mode
83 - var ledebug = false;
84 - try { ledebug = ((obj.parent.args.debug != null) || (obj.parent.args.debug.indexOf('cert'))); } catch (ex) { }
85 -
86 - // Create the main GreenLock code module for production.
87 - var greenlockargs = {
88 - parent: obj,
89 - packageRoot: __dirname,
90 - packageAgent: pkg.name + '/' + pkg.version,
91 - manager: obj.path.join(__dirname, 'letsencrypt.js'),
92 - maintainerEmail: maintainerEmail,
93 - notify: function (ev, args) { if (typeof args == 'string') { parent.debug('cert', ev + ': ' + args); } else { parent.debug('cert', ev + ': ' + JSON.stringify(args)); } },
94 - staging: false,
95 - debug: ledebug
96 - };
97 - if (obj.parent.args.debug == null) { greenlockargs.log = function (debug) { }; } // If not in debug mode, ignore all console output from greenlock (makes things clean).
98 - obj.le = greenlock.create(greenlockargs);
99 -
100 - // Create the main GreenLock code module for staging.
101 - var greenlockargsstaging = {
102 - parent: obj,
103 - packageRoot: __dirname,
104 - packageAgent: pkg.name + '/' + pkg.version,
105 - manager: obj.path.join(__dirname, 'letsencrypt.js'),
106 - maintainerEmail: maintainerEmail,
107 - notify: function (ev, args) { if (typeof args == 'string') { parent.debug('cert', 'Notify: ' + ev + ': ' + args); } else { parent.debug('cert', 'Notify: ' + ev + ': ' + JSON.stringify(args)); } },
108 - staging: true,
109 - debug: ledebug
110 - };
111 - if (obj.parent.args.debug == null) { greenlockargsstaging.log = function (debug) { }; } // If not in debug mode, ignore all console output from greenlock (makes things clean).
112 - obj.leStaging = greenlock.create(greenlockargsstaging);
113 -
114 - // Hook up GreenLock to the redirection server
115 - if (obj.parent.config.settings.rediraliasport === 80) { obj.redirWebServerHooked = true; }
116 - else if ((obj.parent.config.settings.rediraliasport == null) && (obj.parent.redirserver.port == 80)) { obj.redirWebServerHooked = true; }
117 -
118 - // Respond to a challenge
119 - obj.challenge = function (token, hostname, func) {
120 - if (obj.runAsProduction === true) {
121 - // Production
122 - parent.debug('cert', "Challenge " + hostname + "/" + token);
123 - obj.le.challenges.get({ type: 'http-01', servername: hostname, token: token })
124 - .then(function (results) { func(results.keyAuthorization); })
125 - .catch(function (e) { console.log('LE-ERROR', e); func(null); }); // unexpected error, not related to renewal
126 - } else {
127 - // Staging
128 - parent.debug('cert', "Challenge " + hostname + "/" + token);
129 - obj.leStaging.challenges.get({ type: 'http-01', servername: hostname, token: token })
130 - .then(function (results) { func(results.keyAuthorization); })
131 - .catch(function (e) { console.log('LE-ERROR', e); func(null); }); // unexpected error, not related to renewal
132 - }
133 - }
134 -
135 - obj.getCertificate = function(certs, func) {
136 - parent.debug('cert', "Getting certs from local store");
137 - if (certs.CommonName.indexOf('.') == -1) { console.log("ERROR: Use --cert to setup the default server name before using Let's Encrypt."); func(certs); return; }
138 - if (obj.parent.config.letsencrypt == null) { func(certs); return; }
139 - if (obj.parent.config.letsencrypt.email == null) { console.log("ERROR: Let's Encrypt email address not specified."); func(certs); return; }
140 - 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; }
141 - if (obj.redirWebServerHooked !== true) { console.log("ERROR: Redirection web server not setup for Let's Encrypt to work."); func(certs); return; }
142 - 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; }
143 -
144 - // Get the list of domains
145 - obj.leDomains = [ certs.CommonName ];
146 - if (obj.parent.config.letsencrypt.names != null) {
147 - if (typeof obj.parent.config.letsencrypt.names == 'string') { obj.parent.config.letsencrypt.names = obj.parent.config.letsencrypt.names.split(','); }
148 - obj.parent.config.letsencrypt.names.map(function (s) { return s.trim(); }); // Trim each name
149 - 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; }
150 - obj.leDomains = obj.parent.config.letsencrypt.names;
151 - }
152 -
153 - if (obj.parent.config.letsencrypt.production !== true) {
154 - // We are in staging mode, just go ahead
155 - obj.getCertificateEx(certs, func);
156 - } else {
157 - // We are really in production mode
158 - if (obj.runAsProduction === true) {
159 - // Staging cert check must have been done already, move to production
160 - obj.getCertificateEx(certs, func);
161 - } else {
162 - // Perform staging certificate check
163 - parent.debug('cert', "Checking staging certificate " + obj.leDomains[0] + "...");
164 - obj.leStaging.get({ servername: obj.leDomains[0] })
165 - .then(function (results) {
166 - if (results != null) {
167 - // We have a staging certificate, move to production for real
168 - parent.debug('cert', "Staging certificate is present, moving to production...");
169 - obj.runAsProduction = true;
170 - obj.getCertificateEx(certs, func);
171 - } else {
172 - // No staging certificate
173 - parent.debug('cert', "No staging certificate present");
174 - func(certs);
175 - setTimeout(obj.checkRenewCertificate, 10000); // Check the certificate in 10 seconds.
176 - }
177 - })
178 - .catch(function (e) {
179 - // No staging certificate
180 - parent.debug('cert', "No staging certificate present");
181 - func(certs);
182 - setTimeout(obj.checkRenewCertificate, 10000); // Check the certificate in 10 seconds.
183 - });
184 - }
185 - }
186 - }
187 -
188 - obj.getCertificateEx = function (certs, func) {
189 - // Get the Let's Encrypt certificate from our own storage
190 - const xle = (obj.runAsProduction === true)? obj.le : obj.leStaging;
191 - xle.get({ servername: obj.leDomains[0] })
192 - .then(function (results) {
193 - // If we already have real certificates, use them
194 - if (results) {
195 - if (results.site.altnames.indexOf(certs.CommonName) >= 0) {
196 - certs.web.cert = results.pems.cert;
197 - certs.web.key = results.pems.privkey;
198 - certs.web.ca = [results.pems.chain];
199 - }
200 - for (var i in obj.parent.config.domains) {
201 - if ((obj.parent.config.domains[i].dns != null) && (obj.parent.certificateOperations.compareCertificateNames(results.site.altnames, obj.parent.config.domains[i].dns))) {
202 - certs.dns[i].cert = results.pems.cert;
203 - certs.dns[i].key = results.pems.privkey;
204 - certs.dns[i].ca = [results.pems.chain];
205 - }
206 - }
207 - }
208 - parent.debug('cert', "Got certs from local store (" + (obj.runAsProduction ? "Production" : "Staging") + ")");
209 - func(certs);
210 -
211 - // Check if the Let's Encrypt certificate needs to be renewed.
212 - setTimeout(obj.checkRenewCertificate, 60000); // Check in 1 minute.
213 - setInterval(obj.checkRenewCertificate, 86400000); // Check again in 24 hours and every 24 hours.
214 - return;
215 - })
216 - .catch(function (e) {
217 - parent.debug('cert', "Unable to get certs from local store (" + (obj.runAsProduction ? "Production" : "Staging") + ")");
218 - setTimeout(obj.checkRenewCertificate, 10000); // Check the certificate in 10 seconds.
219 - func(certs);
220 - });
221 - }
222 -
223 - // Check if we need to renew the certificate, call this every day.
224 - obj.checkRenewCertificate = function () {
225 - parent.debug('cert', "Checking certificate for " + obj.leDomains[0] + " (" + (obj.runAsProduction ? "Production" : "Staging") + ")");
226 -
227 - // Setup renew options
228 - obj.certCheckStart = Date.now();
229 - const xle = (obj.runAsProduction === true) ? obj.le : obj.leStaging;
230 - var renewOptions = { servername: obj.leDomains[0], altnames: obj.leDomains };
231 - try {
232 - xle.renew(renewOptions)
233 - .then(function (results) {
234 - if ((results == null) || (typeof results != 'object') || (results.length == 0) || (results[0].error != null)) {
235 - parent.debug('cert', "Unable to get a certificate (" + (obj.runAsProduction ? "Production" : "Staging") + ", " + (Date.now() - obj.certCheckStart) + "ms): " + JSON.stringify(results));
236 - } else {
237 - parent.debug('cert', "Checks completed (" + (obj.runAsProduction ? "Production" : "Staging") + ", " + (Date.now() - obj.certCheckStart) + "ms): " + JSON.stringify(results));
238 - if (obj.performRestart === true) { parent.debug('cert', "Certs changed, restarting..."); obj.parent.performServerCertUpdate(); } // Reset the server, TODO: Reset all peers
239 - else if (obj.performMoveToProduction == true) {
240 - parent.debug('cert', "Staging certificate received, moving to production...");
241 - obj.runAsProduction = true;
242 - obj.performMoveToProduction = false;
243 - obj.performRestart = true;
244 - setTimeout(obj.checkRenewCertificate, 10000); // Check the certificate in 10 seconds.
245 - }
246 - }
247 - })
248 - .catch(function (ex) {
249 - parent.debug('cert', "checkCertificate exception: (" + JSON.stringify(ex) + ")");
250 - console.log(ex);
251 - });
252 - } catch (ex) {
253 - parent.debug('cert', "checkCertificate main exception: (" + JSON.stringify(ex) + ")");
254 - console.log(ex);
255 - return ex;
256 - }
257 - return null;
258 - }
259 -
260 - return obj;
261 - } catch (ex) { console.log(ex); } // Unable to start Let's Encrypt
262 - return null;
263 -};
264 -
265 -// GreenLock v3 Manager
266 -module.exports.create = function (options) {
267 - //console.log('xxx-create', options);
268 - var manager = { parent: globalLetsEncrypt };
269 - manager.find = async function (options) {
270 - try {
271 - // GreenLock sometimes has the bad behavior of adding a wildcard cert request, remove it here if needed.
272 - if ((options.wildname != null) && (options.wildname != '')) { options.wildname = ''; }
273 - if (options.altnames) {
274 - var altnames2 = [];
275 - for (var i in options.altnames) { if (options.altnames[i].indexOf('*') == -1) { altnames2.push(options.altnames[i]); } }
276 - options.altnames = altnames2;
277 - }
278 - if (options.servernames) {
279 - var servernames2 = [];
280 - for (var i in options.servernames) { if (options.servernames[i].indexOf('*') == -1) { servernames2.push(options.servernames[i]); } }
281 - options.servernames = servernames2;
282 - }
283 - } catch (ex) { console.log(ex); }
284 - return Promise.resolve([{ subject: options.servername, altnames: options.altnames }]);
285 - };
286 -
287 - manager.set = function (options) {
288 - //console.log('xxx-set', options);
289 - manager.parent.parent.debug('cert', "Certificate has been set: " + JSON.stringify(options));
290 - if (manager.parent.parent.config.letsencrypt.production == manager.parent.runAsProduction) { manager.parent.performRestart = true; }
291 - else if ((manager.parent.parent.config.letsencrypt.production === true) && (manager.parent.runAsProduction === false)) { manager.parent.performMoveToProduction = true; }
292 - return null;
293 - };
294 -
295 - manager.remove = function (options) {
296 - //console.log('xxx-remove', options);
297 - manager.parent.parent.debug('cert', "Certificate has been removed: " + JSON.stringify(options));
298 - if (manager.parent.parent.config.letsencrypt.production == manager.parent.runAsProduction) { manager.parent.performRestart = true; }
299 - else if ((manager.parent.parent.config.letsencrypt.production === true) && (manager.parent.runAsProduction === false)) { manager.parent.performMoveToProduction = true; }
300 - return null;
301 - };
302 -
303 - // set the global config
304 - manager.defaults = async function (options) {
305 - //console.log('xxx-defaults', options);
306 - var r;
307 - if (manager.parent.runAsProduction === true) {
308 - // Production
309 - //console.log('LE-DEFAULTS-Production', options);
310 - if (options != null) { for (var i in options) { if (manager.parent.leDefaults[i] == null) { manager.parent.leDefaults[i] = options[i]; } } }
311 - r = manager.parent.leDefaults;
312 - r.subscriberEmail = manager.parent.parent.config.letsencrypt.email;
313 - r.sites = { mainsite: { subject: manager.parent.leDomains[0], altnames: manager.parent.leDomains } };
314 - } else {
315 - // Staging
316 - //console.log('LE-DEFAULTS-Staging', options);
317 - if (options != null) { for (var i in options) { if (manager.parent.leDefaultsStaging[i] == null) { manager.parent.leDefaultsStaging[i] = options[i]; } } }
318 - r = manager.parent.leDefaultsStaging;
319 - r.subscriberEmail = manager.parent.parent.config.letsencrypt.email;
320 - r.sites = { mainsite: { subject: manager.parent.leDomains[0], altnames: manager.parent.leDomains } };
321 - }
322 - return r;
323 - };
324 -
325 - return manager;
326 -};
327 -
328 -
17 // ACME-Client Implementation
18 var globalLetsEncrypt = null;
331 -module.exports.CreateLetsEncrypt2 = function (parent) {
19 +module.exports.CreateLetsEncrypt = function (parent) {
20 const acme = require('acme-client');
21
22 var obj = {};
335 - obj.lib = 'acme-client';
23 obj.fs = require('fs');
24 obj.path = require('path');
25 obj.parent = parent;
@@ -530,7 +217,6 @@ module.exports.CreateLetsEncrypt2 = function (parent) {
217 // Return the status of this module
218 obj.getStats = function () {
219 var r = {
533 - lib: 'acme-client',
220 configOk: obj.configOk,
221 leDomains: obj.leDomains,
222 challenges: obj.challenges,
meshcentral.js
+5 -15
@@ -1041,7 +1041,7 @@ function CreateMeshCentralServer(config, args) {
1041 if ((obj.config) && (obj.config.settings) && (obj.config.settings.plugins != null)) {
1042 const nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]);
1043 if (nodeVersion < 7) {
1044 - addServerWarning("Plugin support requires Node v7.0 or higher.");
1044 + addServerWarning("Plugin support requires Node v7.x or higher.");
1045 delete obj.config.settings.plugins;
1046 } else {
1047 obj.pluginHandler = require('./pluginHandler.js').pluginHandler(obj);
@@ -1068,15 +1068,11 @@ function CreateMeshCentralServer(config, args) {
1068 obj.certificateOperations.GetMeshServerCertificate(obj.args, obj.config, function (certs) {
1069 // Get the current node version
1070 const nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]);
1071 - if ((obj.config.letsencrypt == null) || (obj.redirserver == null) || (nodeVersion < 8) || ((obj.config.letsencrypt.lib == 'greenlock') && (require('crypto').generateKeyPair == null))) {
1071 + if ((obj.config.letsencrypt == null) || (obj.redirserver == null) || (nodeVersion < 8)) {
1072 obj.StartEx3(certs); // Just use the configured certificates
1073 } else if ((obj.config.letsencrypt != null) && (obj.config.letsencrypt.nochecks == true)) {
1074 // Use Let's Encrypt with no checking
1075 - if (obj.config.letsencrypt.lib == 'greenlock') {
1076 - obj.letsencrypt = require('./letsencrypt.js').CreateLetsEncrypt(obj);
1077 - } else {
1078 - obj.letsencrypt = require('./letsencrypt.js').CreateLetsEncrypt2(obj);
1079 - }
1075 + obj.letsencrypt = require('./letsencrypt.js').CreateLetsEncrypt(obj);
1076 obj.letsencrypt.getCertificate(certs, obj.StartEx3); // Use Let's Encrypt with no checking, use at your own risk.
1077 } else {
1078 // Check Let's Encrypt settings
@@ -1088,14 +1084,8 @@ function CreateMeshCentralServer(config, args) {
1084 else if (obj.config.letsencrypt.email.trim() !== obj.config.letsencrypt.email) { leok = false; addServerWarning("Invalid Let's Encrypt email address."); }
1085 else {
1086 var le = require('./letsencrypt.js');
1091 - try {
1092 - if (obj.config.letsencrypt.lib == 'greenlock') {
1093 - obj.letsencrypt = le.CreateLetsEncrypt(obj);
1094 - } else {
1095 - obj.letsencrypt = le.CreateLetsEncrypt2(obj);
1096 - }
1097 - } catch (ex) { console.log(ex); }
1098 - if (obj.letsencrypt == null) { addServerWarning("Unable to setup GreenLock module."); leok = false; }
1087 + try { obj.letsencrypt = le.CreateLetsEncrypt(obj); } catch (ex) { console.log(ex); }
1088 + if (obj.letsencrypt == null) { addServerWarning("Unable to setup Let's Encrypt module."); leok = false; }
1089 }
1090 if (leok == true) {
1091 // Check that the email address domain MX resolves.
meshuser.js
+5 -34
@@ -703,26 +703,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
703 if (parent.parent.letsencrypt == null) {
704 r = "Let's Encrypt not in use.";
705 } else {
706 - if (parent.parent.letsencrypt.lib == 'greenlock') {
707 - var leinfo = {};
708 - var greenLockVersion = null;
709 - try { greenLockVersion = require('greenlock/package.json').version; } catch (ex) { }
710 - if (greenLockVersion) { leinfo.greenLockVer = greenLockVersion; }
711 - leinfo.redirWebServerHooked = parent.parent.letsencrypt.redirWebServerHooked;
712 - leinfo.leDomains = parent.parent.letsencrypt.leDomains;
713 - leinfo.leResults = parent.parent.letsencrypt.leResults;
714 - leinfo.leResultsStaging = parent.parent.letsencrypt.leResultsStaging;
715 - leinfo.performRestart = parent.parent.letsencrypt.performRestart;
716 - leinfo.performMoveToProduction = parent.parent.letsencrypt.performMoveToProduction;
717 - leinfo.runAsProduction = parent.parent.letsencrypt.runAsProduction;
718 - leinfo.leDefaults = parent.parent.letsencrypt.leDefaults;
719 - leinfo.leDefaultsStaging = parent.parent.letsencrypt.leDefaultsStaging;
720 - r = JSON.stringify(leinfo, null, 4);
721 - } else if (parent.parent.letsencrypt.lib == 'acme-client') {
722 - r = JSON.stringify(parent.parent.letsencrypt.getStats(), null, 4);
723 - } else {
724 - r = 'Unknown module';
725 - }
706 + r = JSON.stringify(parent.parent.letsencrypt.getStats(), null, 4);
707 }
708 break;
709 }
@@ -730,14 +711,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
711 if (parent.parent.letsencrypt == null) {
712 r = "Let's Encrypt not in use.";
713 } else {
733 - if (parent.parent.letsencrypt.lib == 'greenlock') {
734 - var err = parent.parent.letsencrypt.checkRenewCertificate();
735 - if (err == null) { r = "Called Let's Encrypt certificate check."; } else { r = err; }
736 - } else if (parent.parent.letsencrypt.lib == 'acme-client') {
737 - r = ["CertOK", "Request:NoCert", "Request:Expire", "Request:MissingNames"][parent.parent.letsencrypt.checkRenewCertificate()];
738 - } else {
739 - r = 'Unknown module';
740 - }
714 + r = ["CertOK", "Request:NoCert", "Request:Expire", "Request:MissingNames"][parent.parent.letsencrypt.checkRenewCertificate()];
715 }
716 break;
717 }
@@ -745,11 +719,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
719 if (parent.parent.letsencrypt == null) {
720 r = "Let's Encrypt not in use.";
721 } else {
748 - if (parent.parent.letsencrypt.lib == 'acme-client') {
749 - r = parent.parent.letsencrypt.events.join('\r\n');
750 - } else {
751 - r = 'Not supported';
752 - }
722 + r = parent.parent.letsencrypt.events.join('\r\n');
723 }
724 break;
725 }
@@ -845,6 +815,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
815 var info = process.memoryUsage();
816 info.dbType = ['None', 'NeDB', 'MongoJS', 'MongoDB'][parent.db.databaseType];
817 if (parent.db.databaseType == 3) { info.dbChangeStream = parent.db.changeStream; }
818 + if (parent.parent.pluginHandler != null) { info.plugins = []; for (var i in parent.parent.pluginHandler.plugins) { info.plugins.push(i); } }
819 try { info.nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]); } catch (ex) { }
820 try { info.currentVer = parent.parent.currentVer; } catch (ex) { }
821 try { info.platform = process.platform; } catch (ex) { }
@@ -3121,7 +3092,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
3092 }
3093 case 'otp-hkey-get':
3094 {
3124 - // Check is 2-step login is supported
3095 + // Check if 2-step login is supported
3096 const twoStepLoginSupported = ((parent.parent.config.settings.no2factorauth !== true) && (domain.auth != 'sspi') && (parent.parent.certificates.CommonName.indexOf('.') != -1) && (args.nousers !== true));
3097 if (twoStepLoginSupported == false) break;
3098
package.json
+1 -1
@@ -1,6 +1,6 @@
1 {
2 "name": "meshcentral",
3 - "version": "0.5.0-b",
3 + "version": "0.5.0-d",
4 "keywords": [
5 "Remote Management",
6 "Intel AMT",
pluginHandler.js
+18 -8
@@ -274,6 +274,19 @@ module.exports.pluginHandler = function (parent) {
274 })
275 };
276
277 + // MeshCentral doesn't adhere to semantic versioning (due to the -<alpha_char> at the end of the version)
278 + // Convert 1.2.3-a to 1.2.3-3 where the letter is converted to a number.
279 + function versionToNumber(ver) { var x = ver.split('-'); if (x.length != 2) return ver; x[1] = x[1].toLowerCase().charCodeAt(0) - 96; return x.join('.'); }
280 +
281 + // Check if the current version of MeshCentral is at least the minimal required.
282 + function checkMeshCentralVersion(current, minimal) {
283 + if (minimal.startsWith('>=')) { minimal = minimal.substring(2); }
284 + var c = versionToNumber(current).split('.'), m = versionToNumber(minimal).split('.');
285 + if (c.length != m.length) return false;
286 + 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; } }
287 + return true;
288 + }
289 +
290 obj.getPluginLatest = function () {
291 return new Promise(function (resolve, reject) {
292 parent.db.getPlugins(function (err, plugins) {
@@ -294,16 +307,12 @@ module.exports.pluginHandler = function (parent) {
307 });
308 if (curconf == null) reject("Some plugin configs could not be parsed");
309 var s = require('semver');
297 - // MeshCentral doesn't adhere to semantic versioning (due to the -<alpha_char> at the end of the version)
298 - // Convert the letter to ASCII for a "true" version number comparison
299 - var mcCurVer = parent.currentVer.replace(/-(.)$/, (m, p1) => { return ("000" + p1.charCodeAt(0)).substr(-3,3); });
300 - var piCompatVer = newconf.meshCentralCompat.replace(/-(.)\b/g, (m, p1) => { return ("000" + p1.charCodeAt(0)).substr(-3,3); });
310 latestRet.push({
311 'id': curconf._id,
312 'installedVersion': curconf.version,
313 'version': newconf.version,
314 'hasUpdate': s.gt(newconf.version, curconf.version),
306 - 'meshCentralCompat': s.satisfies(mcCurVer, piCompatVer),
315 + 'meshCentralCompat': checkMeshCentralVersion(parent.currentVer, newconf.meshCentralCompat),
316 'changelogUrl': curconf.changelogUrl,
317 'status': curconf.status
318 });
@@ -377,7 +386,7 @@ module.exports.pluginHandler = function (parent) {
386 response.pipe(file);
387 file.on('finish', function () {
388 file.close(function () {
380 - var yauzl = require("yauzl");
389 + var yauzl = require('yauzl');
390 if (!obj.fs.existsSync(obj.pluginPath)) {
391 obj.fs.mkdirSync(obj.pluginPath);
392 }
@@ -498,9 +507,10 @@ module.exports.pluginHandler = function (parent) {
507 obj.removePlugin = function (id, func) {
508 parent.db.getPlugin(id, function (err, docs) {
509 var plugin = docs[0];
501 - var rimraf = require('rimraf');
510 + var rimraf = null;
511 + try { rimraf = require('rimraf'); } catch (ex) { }
512 let pluginPath = obj.parent.path.join(obj.pluginPath, plugin.shortName);
503 - rimraf.sync(pluginPath);
513 + if (rimraf) rimraf.sync(pluginPath);
514 parent.db.deletePlugin(id, func);
515 delete obj.plugins[plugin.shortName];
516 });
webserver.js
+10 -1
@@ -1564,7 +1564,16 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
1564 if (domain.geolocation == true) { features += 0x00008000; } // Enable geo-location features
1565 if ((domain.passwordrequirements != null) && (domain.passwordrequirements.hint === true)) { features += 0x00010000; } // Enable password hints
1566 if (parent.config.settings.no2factorauth !== true) { features += 0x00020000; } // Enable WebAuthn/FIDO2 support
1567 - if ((obj.args.nousers != true) && (domain.passwordrequirements != null) && (domain.passwordrequirements.force2factor === true)) { features += 0x00040000; } // Force 2-factor auth
1567 + if ((obj.args.nousers != true) && (domain.passwordrequirements != null) && (domain.passwordrequirements.force2factor === true)) {
1568 + // Check if we can skip 2nd factor auth because of the source IP address
1569 + var skip2factor = false;
1570 + if ((req != null) && (req.ip != null) && (domain.passwordrequirements != null) && (domain.passwordrequirements.skip2factor != null)) {
1571 + for (var i in domain.passwordrequirements.skip2factor) {
1572 + if (require('ipcheck').match(req.ip, domain.passwordrequirements.skip2factor[i]) === true) { skip2factor = true; }
1573 + }
1574 + }
1575 + if (skip2factor == false) { features += 0x00040000; } // Force 2-factor auth
1576 + }
1577 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.
1578 if (domain.amtacmactivation) { features += 0x00100000; } // Intel AMT ACM activation/upgrade is possible
1579 if (domain.usernameisemail) { features += 0x00200000; } // Username is email address