Added built-in Let's Encrypt support using GreenLock.
Ylian Saint-Hilaire committed
Jan 14, 2018 at 21:01 UTC
65d6775303e3e27aef66f485f0ec6832a1638923
10 files changed
+290
-191
MeshCentralServer.njsproj
+1
-1
@@ -28,7 +28,7 @@
28
<Compile Include="amtevents.js" />
29
<Compile Include="amtscanner.js" />
30
<Compile Include="amtscript.js" />
31
- <Compile Include="letsEncrypt.js" />
31
+ <Compile Include="letsencrypt.js" />
32
<Compile Include="meshaccelerator.js" />
33
<Compile Include="meshmail.js" />
34
<Compile Include="meshscanner.js" />
certoperations.js
+1
-1
@@ -213,7 +213,7 @@ module.exports.CertificateOperations = function () {
213
}
214
caindex++;
215
} while (caok == true);
216
- r.ca = calist;
216
+ r.web.ca = calist;
217
218
// Decode certificate arguments
219
var commonName = 'un-configured', country, organization, forceWebCertGen = 0;
letsEncrypt.js
+102
-65
@@ -1,81 +1,118 @@
1
/**
2
-* @description MeshCentral letsEncrypt module
2
+* @description MeshCentral letsEncrypt module, uses GreenLock to do all the work.
3
* @author Ylian Saint-Hilaire
4
* @copyright Intel Corporation 2018
5
* @license Apache-2.0
6
-* @version v0.0.1
6
+* @version v0.0.2
7
*/
8
9
module.exports.CreateLetsEncrypt = function (parent) {
10
- var obj = {};
11
- obj.parent = parent;
12
- obj.webrootPath = obj.parent.path.join(obj.parent.datapath, 'acme-challenges');
13
- obj.workPath = obj.parent.path.join(obj.parent.datapath, 'acme-challenges', 'work');
14
- obj.logsPath = obj.parent.path.join(obj.parent.datapath, 'acme-challenges', 'logs');
15
-
16
- try { obj.parent.fs.mkdirSync(obj.webrootPath); } catch (e) { }
17
- try { obj.parent.fs.mkdirSync(obj.workPath); } catch (e) { }
18
- try { obj.parent.fs.mkdirSync(obj.logsPath); } catch (e) { }
19
-
20
- console.log('CreateLetsEncrypt-1', obj.webrootPath);
21
- console.log('CreateLetsEncrypt-1', obj.workPath);
22
- console.log('CreateLetsEncrypt-1', obj.logsPath);
23
-
24
- obj.lex = require('greenlock-express').create({
25
- // Set to https://acme-v01.api.letsencrypt.org/directory in production
26
- server: 'staging'
27
-
28
- // If you wish to replace the default plugins, you may do so here
29
- , challenges: {
30
- 'http-01': require('le-challenge-fs').create({ webrootPath: obj.webrootPath })
31
- }
32
- , store: require('le-store-certbot').create({
33
- //configDir: '/etc/letsencrypt',
34
- //privkeyPath: ':configDir/live/:hostname/privkey.pem',
35
- //fullchainPath: ':configDir/live/:hostname/fullchain.pem',
36
- //certPath: ':configDir/live/:hostname/cert.pem',
37
- //chainPath: ':configDir/live/:hostname/chain.pem',
38
- workDir: obj.workPath,
39
- logsDir: obj.logsPath,
40
- webrootPath: obj.webrootPath,
41
- debug: false
42
- })
43
- , approveDomains: approveDomains
44
- });
45
-
46
- console.log('CreateLetsEncrypt-2');
47
- function approveDomains(opts, certs, func) {
48
- console.log('approveDomains', opts, certs);
49
-
50
- // This is where you check your database and associated
51
- // email addresses with domains and agreements and such
52
-
53
-
54
- // The domains being approved for the first time are listed in opts.domains
55
- // Certs being renewed are listed in certs.altnames
56
- if (certs) {
57
- opts.domains = ['example.com', 'yourdomain.com']
58
- } else {
59
- opts.email = 'john.doe@example.com';
60
- opts.agreeTos = true;
10
+ try {
11
+ const greenlock = require('greenlock');;
12
+ const path = require('path');
13
+
14
+ var obj = {};
15
+ obj.parent = parent;
16
+ obj.redirWebServerHooked = false;
17
+ obj.leDomains = null;
18
+ obj.leResults = null;
19
+
20
+ // Setup the certificate storage paths
21
+ obj.configPath = obj.parent.path.join(obj.parent.datapath, 'letsencrypt');
22
+ obj.webrootPath = obj.parent.path.join(obj.parent.datapath, 'letsencrypt', 'webroot');
23
+ try { obj.parent.fs.mkdirSync(obj.configPath); } catch (e) { }
24
+ try { obj.parent.fs.mkdirSync(obj.webrootPath); } catch (e) { }
25
+
26
+ // Storage Backend, store data in the "meshcentral-data/letencrypt" folder.
27
+ var leStore = require('le-store-certbot').create({ configDir: obj.configPath, webrootPath: obj.webrootPath, debug: obj.parent.args.debug > 0 });
28
+
29
+ // ACME Challenge Handlers
30
+ var leHttpChallenge = require('le-challenge-fs').create({ webrootPath: obj.webrootPath, debug: obj.parent.args.debug > 0 });
31
+
32
+ // Function to agree to terms of service
33
+ function leAgree(opts, agreeCb) { agreeCb(null, opts.tosUrl); }
34
+
35
+ // Create the main GreenLock code module.
36
+ var greenlockargs = {
37
+ server: (obj.parent.config.letsencrypt.production === true) ? greenlock.productionServerUrl : greenlock.stagingServerUrl,
38
+ store: leStore,
39
+ challenges: { 'http-01': leHttpChallenge },
40
+ challengeType: 'http-01',
41
+ agreeToTerms: leAgree,
42
+ debug: obj.parent.args.debug > 0
43
}
44
+ if (obj.parent.args.debug == null) { greenlockargs.log = function (debug) { } } // If not in debug mode, ignore all console output from greenlock (makes things clean).
45
+ obj.le = greenlock.create(greenlockargs);
46
63
- // NOTE: you can also change other options such as `challengeType` and `challenge`
64
- // opts.challengeType = 'http-01';
65
- // opts.challenge = require('le-challenge-fs').create({});
47
+ // Hook up GreenLock to the redirection server
48
+ if (obj.parent.redirserver.port == 80) { obj.parent.redirserver.app.use('/', obj.le.middleware()); obj.redirWebServerHooked = true; }
49
67
- func(null, { options: opts, certs: certs });
68
- }
50
+ obj.getCertificate = function (certs, func) {
51
+ if (certs.CommonName == 'un-configured') { console.log("ERROR: Use --cert to setup the default server name before using Let's Encrypt."); func(certs); return; }
52
+ if (obj.parent.config.letsencrypt == null) { func(certs); return; }
53
+ if (obj.parent.config.letsencrypt.email == null) { console.log("ERROR: Let's Encrypt email address not specified."); func(certs); return; }
54
+ 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; }
55
+ if (obj.redirWebServerHooked !== true) { console.log("ERROR: Redirection web server not setup for Let's Encrypt to work."); func(certs); return; }
56
+ 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; }
57
70
- // Handles acme-challenge and redirects to https
71
- require('http').createServer(obj.lex.middleware(require('redirect-https')())).listen(81, function () { console.log("Listening for ACME http-01 challenges on", this.address()); });
58
+ // Get the list of domains
59
+ obj.leDomains = [certs.CommonName];
60
+ if (obj.parent.config.letsencrypt.names != null) {
61
+ if (typeof obj.parent.config.letsencrypt.names == 'string') { obj.parent.config.letsencrypt.names = obj.parent.config.letsencrypt.names.split(','); }
62
+ obj.parent.config.letsencrypt.names.map(function (s) { return s.trim() }); // Trim each name
63
+ 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; }
64
+ obj.leDomains = obj.parent.config.letsencrypt.names;
65
+ obj.leDomains.sort(); // Sort the array so it's always going to be in the same order.
66
+ }
67
73
- var app = require('express')();
74
- app.use('/', function (req, res) { res.end('Hello, World!'); });
68
+ obj.le.check({ domains: obj.leDomains }).then(function (results) {
69
+ if (results) {
70
+ obj.leResults = results;
71
76
- // Handles your app
77
- require('https').createServer(obj.lex.httpsOptions, obj.lex.middleware(app)).listen(443, function () { console.log("Listening for ACME tls-sni-01 challenges and serve app on", this.address()); });
72
+ // If we already have real certificates, use them.
73
+ if (results.altnames.indexOf(certs.CommonName) >= 0) { certs.web.cert = results.cert; certs.web.key = results.privkey; certs.web.ca = [results.chain]; }
74
+ for (var i in obj.parent.config.domains) { if ((obj.parent.config.domains[i].dns != null) && (results.altnames.indexOf(obj.parent.config.domains[i].dns) >= 0)) { certs.dns[i].cert = results.cert; certs.dns[i].key = results.privkey; certs.dns[i].ca = [results.chain]; } }
75
+ func(certs);
76
+
77
+ // Check if the Let's Encrypt certificate needs to be renewed.
78
+ setTimeout(obj.checkRenewCertificate, 300000); // Check in 5 minutes.
79
+ setInterval(obj.checkRenewCertificate, 86400000); // Check again in 24 hours and every 24 hours.
80
+ return;
81
+ } else {
82
+ // Otherwise return default certificates and try to get a real one
83
+ func(certs);
84
+ }
85
+ console.log("Attempting to get Let's Encrypt certificate, may take a few minutes...");
86
+
87
+ // Figure out the RSA key size
88
+ var rsaKeySize = (obj.parent.config.letsencrypt.rsakeysize === 2048) ? 2048 : 3072;
89
+
90
+ // TODO: Only register on one of the peers if multi-peers are active.
91
+ // Register Certificate manually
92
+ obj.le.register({
93
+ domains: obj.leDomains,
94
+ email: obj.parent.config.letsencrypt.email,
95
+ agreeTos: true,
96
+ rsaKeySize: rsaKeySize,
97
+ challengeType: 'http-01'
98
+ }).then(function (xresults) {
99
+ obj.parent.performServerCertUpdate(); // Reset the server, TODO: Reset all peers
100
+ }, function (err) {
101
+ console.error("ERROR: Let's encrypt error: ", err);
102
+ });
103
+ });
104
+ }
105
+
106
+ // Check if we need to renew the certificate, call this every day.
107
+ obj.checkRenewCertificate = function () {
108
+ if (obj.leResults == null) { return; }
109
+ // TODO: Only renew on one of the peers if multi-peers are active.
110
+ // Check if we need to renew the certificate
111
+ obj.le.renew({ duplicate: false }, obj.leResults).then(function (xresults) {
112
+ obj.parent.performServerCertUpdate(); // Reset the server, TODO: Reset all peers
113
+ }, function (err) { }); // If we can't renew, ignore.
114
+ }
115
79
- console.log('CreateLetsEncrypt-3');
116
+ } catch (e) { console.error(e); return null; } // Unable to start Let's Encrypt
117
return obj;
118
}
\ No newline at end of file
meshcentral.js
+121
-92
@@ -20,6 +20,7 @@ function CreateMeshCentralServer() {
20
obj.amtEventHandler;
21
obj.amtScanner;
22
obj.meshScanner;
23
+ obj.letsencrypt;
24
obj.eventsDispatch = {};
25
obj.fs = require('fs');
26
obj.path = require('path');
@@ -163,8 +164,11 @@ function CreateMeshCentralServer() {
164
}
165
}
166
});
166
- xprocess.stdout.on('data', function (data) { if (data[data.length - 1] == '\n') { data = data.substring(0, data.length - 1); } if (data.indexOf('Updating settings folder...') >= 0) { xprocess.xrestart = 1; } else if (data.indexOf('Server Ctrl-C exit...') >= 0) { xprocess.xrestart = 2; } else if (data.indexOf('Starting self upgrade...') >= 0) { xprocess.xrestart = 3; } console.log(data); });
167
- xprocess.stderr.on('data', function (data) { if (data[data.length - 1] == '\n') { data = data.substring(0, data.length - 1); } obj.fs.appendFileSync(obj.path.join(obj.datapath, 'mesherrors.txt'), '-------- ' + new Date().toLocaleString() + ' --------\r\n\r\n' + data + '\r\n\r\n\r\n'); });
167
+ xprocess.stdout.on('data', function (data) { if (data[data.length - 1] == '\n') { data = data.substring(0, data.length - 1); } if (data.indexOf('Updating settings folder...') >= 0) { xprocess.xrestart = 1; } else if (data.indexOf('Updating server certificates...') >= 0) { xprocess.xrestart = 1; } else if (data.indexOf('Server Ctrl-C exit...') >= 0) { xprocess.xrestart = 2; } else if (data.indexOf('Starting self upgrade...') >= 0) { xprocess.xrestart = 3; } console.log(data); });
168
+ xprocess.stderr.on('data', function (data) {
169
+ if (data.startsWith('le.challenges[tls-sni-01].loopback')) { return; } // Ignore this error output from GreenLock
170
+ if (data[data.length - 1] == '\n') { data = data.substring(0, data.length - 1); } obj.fs.appendFileSync(obj.path.join(obj.datapath, 'mesherrors.txt'), '-------- ' + new Date().toLocaleString() + ' --------\r\n\r\n' + data + '\r\n\r\n\r\n');
171
+ });
172
xprocess.on('close', function (code) { if ((code != 0) && (code != 123)) { /* console.log("Exited with code " + code); */ } });
173
}
174
@@ -186,6 +190,9 @@ function CreateMeshCentralServer() {
190
// Initiate server self-update
191
obj.performServerUpdate = function () { console.log('Starting self upgrade...'); process.exit(200); }
192
193
+ // Initiate server self-update
194
+ obj.performServerCertUpdate = function () { console.log('Updating server certificates...'); process.exit(200); }
195
+
196
obj.StartEx = function () {
197
// Look to see if data and/or file path is specified
198
if (obj.args.datapath) { obj.datapath = obj.args.datapath; }
@@ -310,100 +317,122 @@ function CreateMeshCentralServer() {
317
obj.updateMeshCore();
318
obj.updateMeshCmd();
319
313
- // Load server certificates
314
- obj.certificateOperations = require('./certoperations.js').CertificateOperations()
315
- obj.certificateOperations.GetMeshServerCertificate(obj.datapath, obj.args, obj.config, function (certs) {
316
- obj.certificates = certs;
317
- obj.certificateOperations.acceleratorStart(certs); // Set the state of the accelerators
320
+ // Setup and start the redirection server if needed
321
+ if ((obj.args.redirport != null) && (typeof obj.args.redirport == 'number') && (obj.args.redirport != 0)) {
322
+ obj.redirserver = require('./redirserver.js').CreateRedirServer(obj, obj.db, obj.args, obj.StartEx2);
323
+ } else {
324
+ obj.StartEx2(); // If not needed, move on.
325
+ }
326
+ });
327
+ });
328
+ }
329
+
330
+ // Done starting the redirection server, go on to load the server certificates
331
+ obj.StartEx2 = function () {
332
+ // Load server certificates
333
+ obj.certificateOperations = require('./certoperations.js').CertificateOperations()
334
+ obj.certificateOperations.GetMeshServerCertificate(obj.datapath, obj.args, obj.config, function (certs) {
335
+ if (obj.config.letsencrypt == null) {
336
+ obj.StartEx3(certs); // Just use the configured certificates
337
+ } else {
338
+ var le = require('./letsencrypt.js');
339
+ obj.letsencrypt = le.CreateLetsEncrypt(obj);
340
+ if (obj.letsencrypt != null) {
341
+ obj.letsencrypt.getCertificate(certs, obj.StartEx3); // Use Let's Encrypt certificate
342
+ } else {
343
+ console.log('ERROR: Unable to setup GreenLock module.');
344
+ obj.StartEx3(certs); // Let's Encrypt did not load, just use the configured certificates
345
+ }
346
+ }
347
+ });
348
+ }
349
+
350
+ // Start the server with the given certificates
351
+ obj.StartEx3 = function (certs) {
352
+ obj.certificates = certs;
353
+ obj.certificateOperations.acceleratorStart(certs); // Set the state of the accelerators
354
319
- // If the certificate is un-configured, force LAN-only mode
320
- if (obj.certificates.CommonName == 'un-configured') { console.log('Server name not configured, running in LAN-only mode.'); obj.args.lanonly = true; }
355
+ // If the certificate is un-configured, force LAN-only mode
356
+ if (obj.certificates.CommonName == 'un-configured') { console.log('Server name not configured, running in LAN-only mode.'); obj.args.lanonly = true; }
357
322
- // Check that no sub-domains have the same DNS as the parent
323
- for (var i in obj.config.domains) {
324
- if ((obj.config.domains[i].dns != null) && (obj.certificates.CommonName.toLowerCase() === obj.config.domains[i].dns.toLowerCase())) {
325
- console.log("ERROR: Server sub-domain can't have same DNS name as the parent."); process.exit(0); return;
326
- }
327
- }
358
+ // Check that no sub-domains have the same DNS as the parent
359
+ for (var i in obj.config.domains) {
360
+ if ((obj.config.domains[i].dns != null) && (obj.certificates.CommonName.toLowerCase() === obj.config.domains[i].dns.toLowerCase())) {
361
+ console.log("ERROR: Server sub-domain can't have same DNS name as the parent."); process.exit(0); return;
362
+ }
363
+ }
364
329
- // Load the list of mesh agents and install scripts
330
- if (obj.args.noagentupdate == 1) { for (var i in obj.meshAgentsArchitectureNumbers) { obj.meshAgentsArchitectureNumbers[i].update = false; } }
331
- obj.updateMeshAgentsTable(function () {
332
- obj.updateMeshAgentInstallScripts();
333
-
334
- // Setup and start the web server
335
- require('crypto').randomBytes(48, function (err, buf) {
336
- // Setup Mesh Multi-Server if needed
337
- obj.multiServer = require('./multiserver.js').CreateMultiServer(obj, obj.args);
338
- if (obj.multiServer != null) {
339
- obj.serverId = obj.multiServer.serverid;
340
- for (var serverid in obj.config.peers.servers) { obj.peerConnectivityByNode[serverid] = {}; }
341
- }
342
-
343
- // If the server is set to "nousers", allow only loopback unless IP filter is set
344
- if ((obj.args.nousers == true) && (obj.args.userallowedip == null)) { obj.args.userallowedip = "::1,127.0.0.1"; }
345
-
346
- if (obj.args.secret) {
347
- // This secret is used to encrypt HTTP session information, if specified, user it.
348
- obj.webserver = require('./webserver.js').CreateWebServer(obj, obj.db, obj.args, obj.args.secret, obj.certificates);
349
- } else {
350
- // If the secret is not specified, generate a random number.
351
- obj.webserver = require('./webserver.js').CreateWebServer(obj, obj.db, obj.args, buf.toString('hex').toUpperCase(), obj.certificates);
352
- }
353
-
354
- // Setup and start the redirection server if needed
355
- if ((obj.args.redirport != null) && (typeof obj.args.redirport == 'number') && (obj.args.redirport != 0)) {
356
- obj.redirserver = require('./redirserver.js').CreateRedirServer(obj, obj.db, obj.args, obj.certificates);
357
- }
358
-
359
- // Setup the Intel AMT event handler
360
- obj.amtEventHandler = require('./amtevents.js').CreateAmtEventsHandler(obj);
361
-
362
- // Setup the Intel AMT local network scanner
363
- if (obj.args.wanonly != true) {
364
- obj.amtScanner = require('./amtscanner.js').CreateAmtScanner(obj).start();
365
- obj.meshScanner = require('./meshscanner.js').CreateMeshScanner(obj).start();
366
- }
367
-
368
- // Setup and start the MPS server
369
- if (obj.args.lanonly != true) {
370
- obj.mpsserver = require('./mpsserver.js').CreateMpsServer(obj, obj.db, obj.args, obj.certificates);
371
- }
372
-
373
- // Setup and start the legacy swarm server
374
- if (obj.certificates.swarmserver != null) {
375
- if (obj.args.swarmport == null) { obj.args.swarmport = 8080; }
376
- obj.swarmserver = require('./swarmserver.js').CreateSwarmServer(obj, obj.db, obj.args, obj.certificates);
377
- }
378
-
379
- // Setup email server
380
- if ((obj.config.smtp != null) && (obj.config.smtp.host != null) && (obj.config.smtp.from != null)) {
381
- obj.mailserver = require('./meshmail.js').CreateMeshMain(obj);
382
- obj.mailserver.verify();
383
- //obj.mailserver.sendMail('ylian.saint-hilaire@intel.com', 'Test Subject', 'This is a sample test', 'This is a <b>sample</b> html test');
384
- }
385
-
386
- // Start periodic maintenance
387
- obj.maintenanceTimer = setInterval(obj.maintenanceActions, 1000 * 60 * 60); // Run this every hour
388
-
389
- // Dispatch an event that the server is now running
390
- obj.DispatchEvent(['*'], obj, { etype: 'server', action: 'started', msg: 'Server started' })
391
-
392
- // Load the login cookie encryption key from the database if allowed
393
- if ((obj.config) && (obj.config.settings) && (obj.config.settings.allowlogintoken == true)) {
394
- obj.db.Get('LoginCookieEncryptionKey', function (err, docs) {
395
- if ((docs.length > 0) && (docs[0].key != null) && (obj.args.logintokengen == null)) {
396
- obj.loginCookieEncryptionKey = Buffer.from(docs[0].key, 'hex');
397
- } else {
398
- obj.loginCookieEncryptionKey = obj.generateCookieKey(); obj.db.Set({ _id: 'LoginCookieEncryptionKey', key: obj.loginCookieEncryptionKey.toString('hex'), time: Date.now() });
399
- }
400
- });
401
- }
402
-
403
- obj.debug(1, 'Server started');
404
- });
365
+ // Load the list of mesh agents and install scripts
366
+ if (obj.args.noagentupdate == 1) { for (var i in obj.meshAgentsArchitectureNumbers) { obj.meshAgentsArchitectureNumbers[i].update = false; } }
367
+ obj.updateMeshAgentsTable(function () {
368
+ obj.updateMeshAgentInstallScripts();
369
+
370
+ // Setup and start the web server
371
+ require('crypto').randomBytes(48, function (err, buf) {
372
+ // Setup Mesh Multi-Server if needed
373
+ obj.multiServer = require('./multiserver.js').CreateMultiServer(obj, obj.args);
374
+ if (obj.multiServer != null) {
375
+ obj.serverId = obj.multiServer.serverid;
376
+ for (var serverid in obj.config.peers.servers) { obj.peerConnectivityByNode[serverid] = {}; }
377
+ }
378
+
379
+ // If the server is set to "nousers", allow only loopback unless IP filter is set
380
+ if ((obj.args.nousers == true) && (obj.args.userallowedip == null)) { obj.args.userallowedip = "::1,127.0.0.1"; }
381
+
382
+ if (obj.args.secret) {
383
+ // This secret is used to encrypt HTTP session information, if specified, user it.
384
+ obj.webserver = require('./webserver.js').CreateWebServer(obj, obj.db, obj.args, obj.args.secret, obj.certificates);
385
+ } else {
386
+ // If the secret is not specified, generate a random number.
387
+ obj.webserver = require('./webserver.js').CreateWebServer(obj, obj.db, obj.args, buf.toString('hex').toUpperCase(), obj.certificates);
388
+ }
389
+ if (obj.redirserver != null) { obj.redirserver.hookMainWebServer(obj.certificates); }
390
+
391
+ // Setup the Intel AMT event handler
392
+ obj.amtEventHandler = require('./amtevents.js').CreateAmtEventsHandler(obj);
393
+
394
+ // Setup the Intel AMT local network scanner
395
+ if (obj.args.wanonly != true) {
396
+ obj.amtScanner = require('./amtscanner.js').CreateAmtScanner(obj).start();
397
+ obj.meshScanner = require('./meshscanner.js').CreateMeshScanner(obj).start();
398
+ }
399
+
400
+ // Setup and start the MPS server
401
+ if (obj.args.lanonly != true) {
402
+ obj.mpsserver = require('./mpsserver.js').CreateMpsServer(obj, obj.db, obj.args, obj.certificates);
403
+ }
404
+
405
+ // Setup and start the legacy swarm server
406
+ if (obj.certificates.swarmserver != null) {
407
+ if (obj.args.swarmport == null) { obj.args.swarmport = 8080; }
408
+ obj.swarmserver = require('./swarmserver.js').CreateSwarmServer(obj, obj.db, obj.args, obj.certificates);
409
+ }
410
+
411
+ // Setup email server
412
+ if ((obj.config.smtp != null) && (obj.config.smtp.host != null) && (obj.config.smtp.from != null)) {
413
+ obj.mailserver = require('./meshmail.js').CreateMeshMain(obj);
414
+ obj.mailserver.verify();
415
+ //obj.mailserver.sendMail('ylian.saint-hilaire@intel.com', 'Test Subject', 'This is a sample test', 'This is a <b>sample</b> html test');
416
+ }
417
+
418
+ // Start periodic maintenance
419
+ obj.maintenanceTimer = setInterval(obj.maintenanceActions, 1000 * 60 * 60); // Run this every hour
420
+
421
+ // Dispatch an event that the server is now running
422
+ obj.DispatchEvent(['*'], obj, { etype: 'server', action: 'started', msg: 'Server started' })
423
+
424
+ // Load the login cookie encryption key from the database if allowed
425
+ if ((obj.config) && (obj.config.settings) && (obj.config.settings.allowlogintoken == true)) {
426
+ obj.db.Get('LoginCookieEncryptionKey', function (err, docs) {
427
+ if ((docs.length > 0) && (docs[0].key != null) && (obj.args.logintokengen == null)) {
428
+ obj.loginCookieEncryptionKey = Buffer.from(docs[0].key, 'hex');
429
+ } else {
430
+ obj.loginCookieEncryptionKey = obj.generateCookieKey(); obj.db.Set({ _id: 'LoginCookieEncryptionKey', key: obj.loginCookieEncryptionKey.toString('hex'), time: Date.now() });
431
+ }
432
});
406
- });
433
+ }
434
+
435
+ obj.debug(1, 'Server started');
436
});
437
});
438
}
package.json
+6
-2
@@ -1,6 +1,6 @@
1
{
2
"name": "meshcentral",
3
- "version": "0.1.2-h",
3
+ "version": "0.1.2-s",
4
"keywords": [
5
"Remote Management",
6
"Intel AMT",
@@ -48,7 +48,11 @@
48
"optionalDependencies": {
49
"node-sspi": "^0.2.2",
50
"node-windows": "^0.1.14",
51
- "mongojs": "^2.4.0"
51
+ "mongojs": "^2.4.0",
52
+ "greenlock": "^2.1.18",
53
+ "le-store-certbot": "^2.0.5",
54
+ "le-challenge-fs": "^2.0.8",
55
+ "le-acme-core": "^2.1.1"
56
},
57
"devDependencies": {},
58
"readme": "readme.txt"
public/index.html
+1
@@ -1693,6 +1693,7 @@
1693
}
1694
1695
function deskAdjust() {
1696
+ console.log('deskAdjust');
1697
var x = (Math.max(document.documentElement.clientHeight, window.innerHeight || 0) - (Q('deskarea1').clientHeight + Q('deskarea2').clientHeight + Q('Desk').clientHeight + Q('deskarea4').clientHeight + 2)) / 2;
1698
if (fullscreen) {
1699
document.documentElement.style.overflow = 'hidden';
public/scripts/agent-desktop-0.0.2.js
+22
-12
@@ -29,6 +29,8 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) {
29
obj.rotation = 0;
30
obj.protocol = 2; // KVM
31
obj.debugmode = 0;
32
+ obj.firstUpKeys = [];
33
+ obj.stopInput = false;
34
35
obj.sessionid = 0;
36
obj.username;
@@ -43,7 +45,7 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) {
45
obj.width = 960;
46
obj.height = 960;
47
46
- obj.onScreenResize = null;
48
+ obj.onScreenSizeChange = null;
49
obj.onMessage = null;
50
obj.onConnectCountChanged = null;
51
obj.onDebugMessage = null;
@@ -59,7 +61,7 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) {
61
obj.UnGrabKeyInput();
62
obj.UnGrabMouseInput();
63
obj.touchenabled = 0;
62
- if (obj.onScreenResize != null) obj.onScreenResize(obj, obj.ScreenWidth, obj.ScreenHeight, obj.CanvasId);
64
+ if (obj.onScreenSizeChange != null) obj.onScreenSizeChange(obj, obj.ScreenWidth, obj.ScreenHeight, obj.CanvasId);
65
obj.Canvas.clearRect(0, 0, obj.CanvasId.width, obj.CanvasId.height);
66
}
67
@@ -164,6 +166,7 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) {
166
while (obj.PendingOperations.length > 0) { obj.PendingOperations.shift(); }
167
obj.SendCompressionLevel(1);
168
obj.SendUnPause();
169
+ if (obj.onScreenSizeChange != null) { obj.onScreenSizeChange(obj, obj.ScreenWidth, obj.ScreenHeight, obj.CanvasId); }
170
}
171
172
obj.ProcessData = function (str) {
@@ -201,6 +204,7 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) {
204
obj.SendKeyMsgKC(obj.KeyAction.UP, 18); // Alt
205
obj.SendKeyMsgKC(obj.KeyAction.UP, 91); // Left-Windows
206
obj.SendKeyMsgKC(obj.KeyAction.UP, 92); // Right-Windows
207
+ obj.SendKeyMsgKC(obj.KeyAction.UP, 16); // Shift
208
obj.Send(String.fromCharCode(0x00, 0x0E, 0x00, 0x04));
209
break;
210
case 11: // GetDisplays
@@ -334,7 +338,7 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) {
338
}
339
340
obj.GetDisplayNumbers = function () { obj.Send(String.fromCharCode(0x00, 0x0B, 0x00, 0x04)); } // Get Terminal display
337
- obj.SetDisplay = function (number) { console.log('SetDisplay', number); obj.Send(String.fromCharCode(0x00, 0x0C, 0x00, 0x06, number >> 8, number & 0xFF)); } // Set Terminal display
341
+ obj.SetDisplay = function (number) { obj.Send(String.fromCharCode(0x00, 0x0C, 0x00, 0x06, number >> 8, number & 0xFF)); } // Set Terminal display
342
obj.intToStr = function (x) { return String.fromCharCode((x >> 24) & 0xFF, (x >> 16) & 0xFF, (x >> 8) & 0xFF, x & 0xFF); }
343
obj.shortToStr = function (x) { return String.fromCharCode((x >> 8) & 0xFF, x & 0xFF); }
344
@@ -345,7 +349,7 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) {
349
obj.Canvas.canvas.width = obj.ScreenWidth;
350
obj.Canvas.canvas.height = obj.ScreenHeight;
351
obj.Canvas.fillRect(0, 0, obj.ScreenWidth, obj.ScreenHeight);
348
- if (obj.onScreenResize != null) obj.onScreenResize(obj, obj.ScreenWidth, obj.ScreenHeight, obj.CanvasId);
352
+ if (obj.onScreenSizeChange != null) obj.onScreenSizeChange(obj, obj.ScreenWidth, obj.ScreenHeight, obj.CanvasId);
353
}
354
obj.FirstDraw = false;
355
//obj.Debug("onResize: " + obj.ScreenWidth + " x " + obj.ScreenHeight);
@@ -363,15 +367,21 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) {
367
obj.xxKeyPress = function (e) { if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return false; }
368
369
// Key handlers
366
- obj.handleKeys = function (e) { return obj.xxKeyPress(e); }
367
- obj.handleKeyUp = function (e) { return obj.xxKeyUp(e); }
368
- obj.handleKeyDown = function (e) { return obj.xxKeyDown(e); }
370
+ obj.handleKeys = function (e) { if (obj.stopInput == true || desktop.State != 3) return false; return obj.xxKeyPress(e); }
371
+ obj.handleKeyUp = function (e) {
372
+ if (obj.stopInput == true || desktop.State != 3) return false;
373
+ if (obj.firstUpKeys.length < 5) {
374
+ obj.firstUpKeys.push(e.keyCode);
375
+ if ((obj.firstUpKeys.length == 5)) { var j = obj.firstUpKeys.join(','); if ((j == '16,17,91,91,16') || (j == '16,17,18,91,92')) { obj.stopInput = true; } }
376
+ } return obj.xxKeyUp(e);
377
+ }
378
+ obj.handleKeyDown = function (e) { if (obj.stopInput == true || desktop.State != 3) return false; return obj.xxKeyDown(e); }
379
380
// Mouse handlers
371
- obj.mousedown = function (e) { return obj.xxMouseDown(e); }
372
- obj.mouseup = function (e) { return obj.xxMouseUp(e); }
373
- obj.mousemove = function (e) { return obj.xxMouseMove(e); }
374
- obj.mousewheel = function (e) { return obj.xxMouseWheel(e); }
381
+ obj.mousedown = function (e) { if (obj.stopInput == true) return false; return obj.xxMouseDown(e); }
382
+ obj.mouseup = function (e) { if (obj.stopInput == true) return false; return obj.xxMouseUp(e); }
383
+ obj.mousemove = function (e) { if (obj.stopInput == true) return false; return obj.xxMouseMove(e); }
384
+ obj.mousewheel = function (e) { if (obj.stopInput == true) return false; return obj.xxMouseWheel(e); }
385
386
obj.xxMsTouchEvent = function (evt) {
387
if (evt.originalEvent.pointerType == 4) return; // If this is a mouse pointer, ignore this event. Touch & pen are ok.
@@ -573,7 +583,7 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) {
583
584
obj.ScreenWidth = obj.Canvas.canvas.width;
585
obj.ScreenHeight = obj.Canvas.canvas.height;
576
- if (obj.onScreenResize != null) obj.onScreenResize(obj, obj.ScreenWidth, obj.ScreenHeight, obj.CanvasId);
586
+ if (obj.onScreenSizeChange != null) obj.onScreenSizeChange(obj, obj.ScreenWidth, obj.ScreenHeight, obj.CanvasId);
587
return true;
588
}
589
redirserver.js
+29
-12
@@ -10,21 +10,25 @@
10
// https://github.com/expressjs/express/blob/master/examples/auth/index.js
11
12
// Construct a HTTP redirection web server object
13
-module.exports.CreateRedirServer = function (parent, db, args, certificates) {
13
+module.exports.CreateRedirServer = function (parent, db, args, func) {
14
var obj = {};
15
obj.parent = parent;
16
obj.db = db;
17
obj.args = args;
18
- obj.certificates = certificates;
18
+ obj.certificates = null;
19
obj.express = require('express');
20
obj.net = require('net');
21
obj.app = obj.express();
22
obj.tcpServer;
23
+ obj.port = null;
24
25
// Perform an HTTP to HTTPS redirection
26
function performRedirection(req, res) {
26
- var host = certificates.CommonName;
27
- if ((certificates.CommonName == 'sample.org') || (certificates.CommonName == 'un-configured')) { host = req.headers.host; }
27
+ var host = req.headers.host;
28
+ if (obj.certificates != null) {
29
+ host = obj.certificates.CommonName;
30
+ if ((obj.certificates.CommonName == 'sample.org') || (obj.certificates.CommonName == 'un-configured')) { host = req.headers.host; }
31
+ }
32
if (req.headers && req.headers.host && (req.headers.host.split(':')[0].toLowerCase() == 'localhost')) { res.redirect('https://localhost:' + args.port + req.url); } else { res.redirect('https://' + host + ':' + args.port + req.url); }
33
}
34
@@ -54,17 +58,25 @@ module.exports.CreateRedirServer = function (parent, db, args, certificates) {
58
return next();
59
});
60
61
+ // Once the main web server is started, call this to hookup additional handlers
62
+ obj.hookMainWebServer = function (certs) {
63
+ obj.certificates = certs;
64
+ for (var i in parent.config.domains) {
65
+ if (parent.config.domains[i].dns != null) { continue; }
66
+ var url = parent.config.domains[i].url;
67
+ obj.app.post(url + 'amtevents.ashx', obj.parent.webserver.handleAmtEventRequest);
68
+ obj.app.get(url + 'meshsettings', obj.parent.webserver.handleMeshSettingsRequest);
69
+ obj.app.get(url + 'meshagents', obj.parent.webserver.handleMeshAgentRequest);
70
+ }
71
+ }
72
+
73
// Setup all HTTP redirection handlers
74
//obj.app.set('etag', false);
75
for (var i in parent.config.domains) {
76
+ if (parent.config.domains[i].dns != null) { continue; }
77
var url = parent.config.domains[i].url;
78
obj.app.get(url, performRedirection);
62
- obj.app.post(url + 'amtevents.ashx', obj.parent.webserver.handleAmtEventRequest);
63
- obj.app.get(url + 'meshsettings', obj.parent.webserver.handleMeshSettingsRequest);
64
- obj.app.get(url + 'meshagents', obj.parent.webserver.handleMeshAgentRequest);
65
-
66
- // Indicates the clickonce folder is public
67
- obj.app.use(url + 'clickonce', obj.express.static(obj.parent.path.join(__dirname, 'public/clickonce')));
79
+ obj.app.use(url + 'clickonce', obj.express.static(obj.parent.path.join(__dirname, 'public/clickonce'))); // Indicates the clickonce folder is public
80
}
81
82
// Find a free port starting with the specified one and going up.
@@ -79,8 +91,13 @@ module.exports.CreateRedirServer = function (parent, db, args, certificates) {
91
// Start the ExpressJS web server, if the port is busy try the next one.
92
function StartRedirServer(port) {
93
if (port == 0 || port == 65535) return;
82
- obj.args.redirport = port;
83
- obj.tcpServer = obj.app.listen(port, function () { console.log('MeshCentral HTTP redirection web server running on port ' + port + '.'); }).on('error', function (err) { if ((err.code == 'EACCES') && (port < 65535)) { StartRedirServer(port + 1); } else { console.log(err); } });
94
+ obj.tcpServer = obj.app.listen(port, function () {
95
+ obj.port = port;
96
+ console.log('MeshCentral HTTP redirection web server running on port ' + port + '.');
97
+ func(obj.port);
98
+ }).on('error', function (err) {
99
+ if ((err.code == 'EACCES') && (port < 65535)) { StartRedirServer(port + 1); } else { console.log(err); func(obj.port); }
100
+ });
101
}
102
103
CheckListenPort(args.redirport, StartRedirServer);
views/default.handlebars
+4
-3
@@ -1242,7 +1242,7 @@
1242
}
1243
1244
function ondockeypress(e) {
1245
- if (!xxdialogMode && xxcurrentView == 11 && desktop && desktop.State == 3) return desktop.m.handleKeys(e);
1245
+ if (!xxdialogMode && xxcurrentView == 11 && desktop) return desktop.m.handleKeys(e);
1246
if (!xxdialogMode && xxcurrentView == 12 && terminal && terminal.State == 3) return terminal.m.TermHandleKeys(e);
1247
if (!xxdialogMode && xxcurrentView == 15) return agentConsoleHandleKeys(e);
1248
if (xxdialogMode || xxcurrentView != 1) return;
@@ -1278,7 +1278,7 @@
1278
}
1279
1280
function ondockeydown(e) {
1281
- if (!xxdialogMode && xxcurrentView == 11 && desktop && desktop.State == 3) return desktop.m.handleKeyDown(e);
1281
+ if (!xxdialogMode && xxcurrentView == 11 && desktop) return desktop.m.handleKeyDown(e);
1282
if (!xxdialogMode && xxcurrentView == 12 && terminal && terminal.State == 3) return terminal.m.TermHandleKeyDown(e);
1283
if (!xxdialogMode && xxcurrentView == 13 && e.keyCode == 116 && p13filetree != null) { haltEvent(e); return false; } // F5 Refresh on files
1284
if (xxdialogMode || xxcurrentView != 1 || e.ctrlKey == true || e.altKey == true || e.metaKey == true) return;
@@ -1295,7 +1295,7 @@
1295
}
1296
1297
function ondockeyup(e) {
1298
- if (!xxdialogMode && xxcurrentView == 11 && desktop && desktop.State == 3) return desktop.m.handleKeyUp(e);
1298
+ if (!xxdialogMode && xxcurrentView == 11 && desktop) return desktop.m.handleKeyUp(e);
1299
if (!xxdialogMode && xxcurrentView == 12 && terminal && terminal.State == 3) return terminal.m.TermHandleKeyUp(e);
1300
if (!xxdialogMode && xxcurrentView == 13 && e.keyCode == 116 && p13filetree != null) { p13folderup(9999); haltEvent(e); return false; } // F5 Refresh on files
1301
if (xxdialogMode && e.keyCode == 27) { dialogclose(0); }
@@ -2939,6 +2939,7 @@
2939
desktop.m.CompressionLevel = desktopsettings.quality; // Number from 1 to 100. 50 or less is best.
2940
desktop.m.ScalingLevel = desktopsettings.scaling;
2941
desktop.m.onDisplayinfo = deskDisplayInfo;
2942
+ desktop.m.onScreenSizeChange = deskAdjust;
2943
desktop.Start(desktopNode._id);
2944
desktop.contype = 1;
2945
}
webserver.js
+3
-3
@@ -129,7 +129,7 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
129
var dnscount = 0;
130
obj.tlsSniCredentials = {};
131
for (var i in obj.certificates.dns) { if (obj.parent.config.domains[i].dns != null) { obj.dnsDomains[obj.parent.config.domains[i].dns.toLowerCase()] = obj.parent.config.domains[i]; obj.tlsSniCredentials[obj.parent.config.domains[i].dns] = obj.tls.createSecureContext(obj.certificates.dns[i]).context; dnscount++; } }
132
- if (dnscount > 0) { obj.tlsSniCredentials[''] = obj.tls.createSecureContext({ cert: obj.certificates.web.cert, key: obj.certificates.web.key, ca: obj.certificates.ca }).context; } else { obj.tlsSniCredentials = null; }
132
+ if (dnscount > 0) { obj.tlsSniCredentials[''] = obj.tls.createSecureContext({ cert: obj.certificates.web.cert, key: obj.certificates.web.key, ca: obj.certificates.web.ca }).context; } else { obj.tlsSniCredentials = null; }
133
}
134
function TlsSniCallback(name, cb) { var c = obj.tlsSniCredentials[name]; if (c != null) { cb(null, c); } else { cb(null, obj.tlsSniCredentials['']); } }
135
@@ -143,10 +143,10 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
143
// Setup the HTTP server with TLS
144
if (obj.tlsSniCredentials != null) {
145
// We have multiple web server certificate used depending on the domain name
146
- obj.tlsServer = require('https').createServer({ SNICallback: TlsSniCallback, cert: obj.certificates.web.cert, key: obj.certificates.web.key, ca: obj.certificates.ca, rejectUnauthorized: true }, obj.app);
146
+ obj.tlsServer = require('https').createServer({ SNICallback: TlsSniCallback, cert: obj.certificates.web.cert, key: obj.certificates.web.key, ca: obj.certificates.web.ca, rejectUnauthorized: true }, obj.app);
147
} else {
148
// We have a single web server certificate
149
- obj.tlsServer = require('https').createServer({ cert: obj.certificates.web.cert, key: obj.certificates.web.key, ca: obj.certificates.ca, rejectUnauthorized: true }, obj.app);
149
+ obj.tlsServer = require('https').createServer({ cert: obj.certificates.web.cert, key: obj.certificates.web.key, ca: obj.certificates.web.ca, rejectUnauthorized: true }, obj.app);
150
}
151
obj.expressWs = require('express-ws')(obj.app, obj.tlsServer);
152
}