Added first support for running state-less.
Ylian Saint-Hilaire committed
Feb 2, 2019 at 14:54 UTC
a597d7a50361ef126d9036370d7211f36ae4a1c8
5 files changed
+385
-214
certoperations.js
+49
-31
@@ -14,9 +14,10 @@
14
/*jshint esversion: 6 */
15
"use strict";
16
17
-module.exports.CertificateOperations = function () {
17
+module.exports.CertificateOperations = function (parent) {
18
var obj = {};
19
20
+ obj.parent = parent;
21
obj.fs = require("fs");
22
obj.forge = require("node-forge");
23
obj.crypto = require("crypto");
@@ -24,7 +25,6 @@ module.exports.CertificateOperations = function () {
25
obj.pki = obj.forge.pki;
26
obj.dirExists = function (filePath) { try { return obj.fs.statSync(filePath).isDirectory(); } catch (err) { return false; } };
27
obj.getFilesizeInBytes = function (filename) { try { return obj.fs.statSync(filename).size; } catch (err) { return -1; } };
27
- obj.fileExists = function (filePath) { try { return obj.fs.statSync(filePath).isFile(); } catch (err) { return false; } };
28
29
// Return the certificate of the remote HTTPS server
30
obj.loadCertificate = function (url, tag, func) {
@@ -51,6 +51,22 @@ module.exports.CertificateOperations = function () {
51
} else { func(url, null, tag); }
52
};
53
54
+ // Check if a configuration file exists
55
+ obj.fileExists = function (filename) {
56
+ if ((parent.configurationFiles != null) && (parent.configurationFiles[filename] != null)) { return true; }
57
+ var filePath = parent.getConfigFilePath(filename);
58
+ try { return obj.fs.statSync(filePath).isFile(); } catch (err) { return false; }
59
+ };
60
+
61
+ // Load a configuration file
62
+ obj.fileLoad = function (filename, encoding) {
63
+ if ((parent.configurationFiles != null) && (parent.configurationFiles[filename] != null)) {
64
+ return fixEndOfLines(parent.configurationFiles[filename].toString());
65
+ } else {
66
+ return fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath(filename), encoding));
67
+ }
68
+ }
69
+
70
// Return the SHA384 hash of the certificate public key
71
obj.getPublicKeyHash = function (cert) {
72
var publickey = obj.pki.certificateFromPem(cert).publicKey;
@@ -156,7 +172,7 @@ module.exports.CertificateOperations = function () {
172
}
173
174
// Returns the web server TLS certificate and private key, if not present, create demonstration ones.
159
- obj.GetMeshServerCertificate = function (parent, args, config, func) {
175
+ obj.GetMeshServerCertificate = function (args, config, func) {
176
var i = 0;
177
var certargs = args.cert;
178
var mpscertargs = args.mpscert;
@@ -174,54 +190,54 @@ module.exports.CertificateOperations = function () {
190
var rcount = 0;
191
192
// If the root certificate already exist, load it
177
- if (obj.fileExists(parent.getConfigFilePath("root-cert-public.crt")) && obj.fileExists(parent.getConfigFilePath("root-cert-private.key"))) {
178
- var rootCertificate = fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath("root-cert-public.crt"), "utf8"));
179
- var rootPrivateKey = fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath("root-cert-private.key"), "utf8"));
193
+ if (obj.fileExists("root-cert-public.crt") && obj.fileExists("root-cert-private.key")) {
194
+ var rootCertificate = obj.fileLoad("root-cert-public.crt", "utf8");
195
+ var rootPrivateKey = obj.fileLoad("root-cert-private.key", "utf8");
196
r.root = { cert: rootCertificate, key: rootPrivateKey };
197
rcount++;
198
}
199
200
if (args.tlsoffload) {
201
// If the web certificate already exist, load it. Load just the certificate since we are in TLS offload situation
186
- if (obj.fileExists(parent.getConfigFilePath("webserver-cert-public.crt"))) {
187
- r.web = { cert: fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath("webserver-cert-public.crt"), "utf8")) };
202
+ if (obj.fileExists("webserver-cert-public.crt")) {
203
+ r.web = { cert: obj.fileLoad("webserver-cert-public.crt", "utf8") };
204
rcount++;
205
}
206
} else {
207
// If the web certificate already exist, load it. Load both certificate and private key
192
- if (obj.fileExists(parent.getConfigFilePath("webserver-cert-public.crt")) && obj.fileExists(parent.getConfigFilePath("webserver-cert-private.key"))) {
193
- r.web = { cert: fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath("webserver-cert-public.crt"), "utf8")), key: fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath("webserver-cert-private.key"), "utf8")) };
208
+ if (obj.fileExists("webserver-cert-public.crt") && obj.fileExists("webserver-cert-private.key")) {
209
+ r.web = { cert: obj.fileLoad("webserver-cert-public.crt", "utf8"), key: obj.fileLoad("webserver-cert-private.key", "utf8") };
210
rcount++;
211
}
212
}
213
214
// If the mps certificate already exist, load it
199
- if (obj.fileExists(parent.getConfigFilePath("mpsserver-cert-public.crt")) && obj.fileExists(parent.getConfigFilePath("mpsserver-cert-private.key"))) {
200
- r.mps = { cert: fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath("mpsserver-cert-public.crt")), "utf8"), key: fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath("mpsserver-cert-private.key"), "utf8")) };
215
+ if (obj.fileExists("mpsserver-cert-public.crt") && obj.fileExists("mpsserver-cert-private.key")) {
216
+ r.mps = { cert: obj.fileLoad("mpsserver-cert-public.crt", "utf8"), key: obj.fileLoad("mpsserver-cert-private.key", "utf8") };
217
rcount++;
218
}
219
220
// If the agent certificate already exist, load it
205
- if (obj.fileExists(parent.getConfigFilePath("agentserver-cert-public.crt")) && obj.fileExists(parent.getConfigFilePath("agentserver-cert-private.key"))) {
206
- r.agent = { cert: fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath("agentserver-cert-public.crt")), "utf8"), key: fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath("agentserver-cert-private.key"), "utf8")) };
221
+ if (obj.fileExists("agentserver-cert-public.crt") && obj.fileExists("agentserver-cert-private.key")) {
222
+ r.agent = { cert: obj.fileLoad("agentserver-cert-public.crt", "utf8"), key: obj.fileLoad("agentserver-cert-private.key", "utf8") };
223
rcount++;
224
}
225
226
// If the swarm server certificate exist, load it (This is an optional certificate)
211
- if (obj.fileExists(parent.getConfigFilePath("swarmserver-cert-public.crt")) && obj.fileExists(parent.getConfigFilePath("swarmserver-cert-private.key"))) {
212
- r.swarmserver = { cert: fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath("swarmserver-cert-public.crt"), "utf8")), key: fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath("swarmserver-cert-private.key"), "utf8")) };
227
+ if (obj.fileExists("swarmserver-cert-public.crt") && obj.fileExists("swarmserver-cert-private.key")) {
228
+ r.swarmserver = { cert: obj.fileLoad("swarmserver-cert-public.crt", "utf8"), key: obj.fileLoad("swarmserver-cert-private.key", "utf8") };
229
}
230
231
// If the swarm server root certificate exist, load it (This is an optional certificate)
216
- if (obj.fileExists(parent.getConfigFilePath("swarmserverroot-cert-public.crt"))) {
217
- r.swarmserverroot = { cert: fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath("swarmserverroot-cert-public.crt"), "utf8")) };
232
+ if (obj.fileExists("swarmserverroot-cert-public.crt")) {
233
+ r.swarmserverroot = { cert: obj.fileLoad("swarmserverroot-cert-public.crt", "utf8") };
234
}
235
236
// If CA certificates are present, load them
237
do {
238
caok = false;
223
- if (obj.fileExists(parent.getConfigFilePath("webserver-cert-chain" + caindex + ".crt"))) {
224
- calist.push(fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath("webserver-cert-chain" + caindex + ".crt"), "utf8")));
239
+ if (obj.fileExists("webserver-cert-chain" + caindex + ".crt")) {
240
+ calist.push(obj.fileLoad("webserver-cert-chain" + caindex + ".crt", "utf8"));
241
caok = true;
242
}
243
caindex++;
@@ -259,24 +275,24 @@ module.exports.CertificateOperations = function () {
275
dnsname = config.domains[i].dns;
276
if (args.tlsoffload) {
277
// If the web certificate already exist, load it. Load just the certificate since we are in TLS offload situation
262
- if (obj.fileExists(parent.getConfigFilePath("webserver-" + i + "-cert-public.crt"))) {
263
- r.dns[i] = { cert: fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath("webserver-" + i + "-cert-public.crt"), "utf8")) };
278
+ if (obj.fileExists("webserver-" + i + "-cert-public.crt")) {
279
+ r.dns[i] = { cert: obj.fileLoad("webserver-" + i + "-cert-public.crt", "utf8") };
280
config.domains[i].certs = r.dns[i];
281
} else {
282
console.log("WARNING: File \"webserver-" + i + "-cert-public.crt\" missing, domain \"" + i + "\" will not work correctly.");
283
}
284
} else {
285
// If the web certificate already exist, load it. Load both certificate and private key
270
- if (obj.fileExists(parent.getConfigFilePath("webserver-" + i + "-cert-public.crt")) && obj.fileExists(parent.getConfigFilePath("webserver-" + i + "-cert-private.key"))) {
271
- r.dns[i] = { cert: fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath("webserver-" + i + "-cert-public.crt"), "utf8")), key: fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath("webserver-" + i + "-cert-private.key"), "utf8")) };
286
+ if (obj.fileExists("webserver-" + i + "-cert-public.crt") && obj.fileExists("webserver-" + i + "-cert-private.key")) {
287
+ r.dns[i] = { cert: obj.fileLoad("webserver-" + i + "-cert-public.crt", "utf8"), key: obj.fileLoad("webserver-" + i + "-cert-private.key", "utf8") };
288
config.domains[i].certs = r.dns[i];
289
// If CA certificates are present, load them
290
caindex = 1;
291
r.dns[i].ca = [];
292
do {
293
caok = false;
278
- if (obj.fileExists(parent.getConfigFilePath("webserver-" + i + "-cert-chain" + caindex + ".crt"))) {
279
- r.dns[i].ca.push(fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath("webserver-" + i + "-cert-chain" + caindex + ".crt"), "utf8")));
294
+ if (obj.fileExists("webserver-" + i + "-cert-chain" + caindex + ".crt")) {
295
+ r.dns[i].ca.push(obj.fileLoad("webserver-" + i + "-cert-chain" + caindex + ".crt", "utf8"));
296
caok = true;
297
}
298
caindex++;
@@ -319,6 +335,8 @@ module.exports.CertificateOperations = function () {
335
if (r.AmtMpsName != mpsCommonName) { forceMpsCertGen = 1; }
336
}
337
}
338
+ if (parent.configurationFiles != null) { console.log("Error: Database missing some certificates."); process.exit(0); return null; }
339
+
340
console.log("Generating certificates, may take a few minutes...");
341
parent.updateServerState("state", "generatingcertificates");
342
@@ -406,7 +424,7 @@ module.exports.CertificateOperations = function () {
424
dnsname = config.domains[i].dns;
425
if (!args.tlsoffload) {
426
// If the web certificate does not exist, create it
409
- if ((obj.fileExists(parent.getConfigFilePath("webserver-" + i + "-cert-public.crt")) === false) || (obj.fileExists(parent.getConfigFilePath("webserver-" + i + "-cert-private.key")) === false)) {
427
+ if ((obj.fileExists("webserver-" + i + "-cert-public.crt") === false) || (obj.fileExists("webserver-" + i + "-cert-private.key") === false)) {
428
console.log("Generating HTTPS certificate for " + i + "...");
429
var xwebCertAndKey = obj.IssueWebServerCertificate(rootCertAndKey, false, dnsname, country, organization, null, strongCertificate);
430
var xwebCertificate = obj.pki.certificateToPem(xwebCertAndKey.cert);
@@ -421,7 +439,7 @@ module.exports.CertificateOperations = function () {
439
r.dns[i].ca = [];
440
do {
441
caok = false;
424
- if (obj.fileExists(parent.getConfigFilePath("webserver-" + i + "-cert-chain" + caindex + ".crt"))) {
442
+ if (obj.fileExists("webserver-" + i + "-cert-chain" + caindex + ".crt")) {
443
r.dns[i].ca.push(fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath("webserver-" + i + "-cert-chain" + caindex + ".crt"), "utf8")));
444
caok = true;
445
}
@@ -433,12 +451,12 @@ module.exports.CertificateOperations = function () {
451
}
452
453
// If the swarm server certificate exist, load it (This is an optional certificate)
436
- if (obj.fileExists(parent.getConfigFilePath("swarmserver-cert-public.crt")) && obj.fileExists(parent.getConfigFilePath("swarmserver-cert-private.key"))) {
454
+ if (obj.fileExists("swarmserver-cert-public.crt") && obj.fileExists("swarmserver-cert-private.key")) {
455
r.swarmserver = { cert: fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath("swarmserver-cert-public.crt"), "utf8")), key: fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath("swarmserver-cert-private.key"), "utf8")) };
456
}
457
458
// If the swarm server root certificate exist, load it (This is an optional certificate)
441
- if (obj.fileExists(parent.getConfigFilePath("swarmserverroot-cert-public.crt"))) {
459
+ if (obj.fileExists("swarmserverroot-cert-public.crt")) {
460
r.swarmserverroot = { cert: fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath("swarmserverroot-cert-public.crt"), "utf8")) };
461
}
462
@@ -448,7 +466,7 @@ module.exports.CertificateOperations = function () {
466
r.web.ca = [];
467
do {
468
caok = false;
451
- if (obj.fileExists(parent.getConfigFilePath("webserver-cert-chain" + caindex + ".crt"))) {
469
+ if (obj.fileExists("webserver-cert-chain" + caindex + ".crt")) {
470
r.web.ca.push(fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath("webserver-cert-chain" + caindex + ".crt"), "utf8")));
471
caok = true;
472
}
db.js
+52
-6
@@ -176,14 +176,60 @@ module.exports.CreateDB = function (parent) {
176
obj.getLocalAmtNodes = function (func) { obj.file.find({ type: 'node', host: { $exists: true, $ne: null }, intelamt: { $exists: true } }, func); };
177
obj.getAmtUuidNode = function (meshid, uuid, func) { obj.file.find({ type: 'node', meshid: meshid, 'intelamt.uuid': uuid }, func); };
178
179
- // Read a file from the database
180
- obj.getFile = function (path, func) { obj.Get('cfile/' + path, func); }
179
+ // Read a configuration file from the database
180
+ obj.getConfigFile = function (path, func) { obj.Get('cfile/' + path, func); }
181
182
- // Write a file to the database
183
- obj.setFile = function (path, data, func) { obj.Set({ _id: 'cfile/' + path, type: 'cfile', data: data.toString('base64') }, func); }
182
+ // Write a configuration file to the database
183
+ obj.setConfigFile = function (path, data, func) { obj.Set({ _id: 'cfile/' + path, type: 'cfile', data: data.toString('base64') }, func); }
184
185
- // List all files
186
- obj.listFiles = function (func) { obj.file.find({ type: 'cfile' }).sort({ _id: 1 }).exec(func); }
185
+ // List all configuration files
186
+ obj.listConfigFiles = function (func) { obj.file.find({ type: 'cfile' }).sort({ _id: 1 }).exec(func); }
187
+
188
+ // Get all configuration files
189
+ obj.getAllConfigFiles = function (password, func) {
190
+ obj.file.find({ type: 'cfile' }, function (err, docs) {
191
+ if (err != null) { func(null); return; }
192
+ var r = null;
193
+ for (var i = 0; i < docs.length; i++) {
194
+ var name = docs[i]._id.split('/')[1];
195
+ var data = obj.decryptData(password, docs[i].data);
196
+ if (data != null) { if (r == null) { r = {}; } r[name] = data; }
197
+ }
198
+ func(r);
199
+ });
200
+ }
201
+
202
+ // Get encryption key
203
+ obj.getEncryptDataKey = function (password) {
204
+ if (typeof password != 'string') return null;
205
+ return obj.parent.crypto.createHash('sha384').update(password).digest("raw").slice(0, 32);
206
+ }
207
+
208
+ // Encrypt data
209
+ obj.encryptData = function (password, plaintext) {
210
+ var key = obj.getEncryptDataKey(password);
211
+ if (key == null) return null;
212
+ const iv = obj.parent.crypto.randomBytes(16);
213
+ const aes = obj.parent.crypto.createCipheriv('aes-256-cbc', key, iv);
214
+ var ciphertext = aes.update(plaintext);
215
+ ciphertext = Buffer.concat([iv, ciphertext, aes.final()]);
216
+ return ciphertext.toString('base64');
217
+ }
218
+
219
+ // Decrypt data
220
+ obj.decryptData = function (password, ciphertext) {
221
+ try {
222
+ var key = obj.getEncryptDataKey(password);
223
+ if (key == null) return null;
224
+ const ciphertextBytes = Buffer.from(ciphertext, 'base64');
225
+ const iv = ciphertextBytes.slice(0, 16);
226
+ const data = ciphertextBytes.slice(16);
227
+ const aes = obj.parent.crypto.createDecipheriv('aes-256-cbc', key, iv);
228
+ var plaintextBytes = Buffer.from(aes.update(data));
229
+ plaintextBytes = Buffer.concat([plaintextBytes, aes.final()]);
230
+ return plaintextBytes;
231
+ } catch (ex) { return null; }
232
+ }
233
234
// Get the number of records in the database for various types, this is the slow NeDB way. TODO: MongoDB can use group() to do this faster.
235
obj.getStats = function (func) {
meshcentral.js
+230
-149
@@ -40,6 +40,7 @@ function CreateMeshCentralServer(config, args) {
40
obj.platform = require('os').platform();
41
obj.args = args;
42
obj.common = require('./common.js');
43
+ obj.configurationFiles = null;
44
obj.certificates = null;
45
obj.connectivityByNode = {}; // This object keeps a list of all connected CIRA and agents, by nodeid->value (value: 1 = Agent, 2 = CIRA, 4 = AmtDirect)
46
obj.peerConnectivityByNode = {}; // This object keeps a list of all connected CIRA and agents of peers, by serverid->nodeid->value (value: 1 = Agent, 2 = CIRA, 4 = AmtDirect)
@@ -95,7 +96,7 @@ function CreateMeshCentralServer(config, args) {
96
try { require('./pass').hash('test', function () { }); } catch (e) { console.log('Old version of node, must upgrade.'); return; } // TODO: Not sure if this test works or not.
97
98
// Check for invalid arguments
98
- var validArguments = ['_', 'notls', 'user', 'port', 'aliasport', 'mpsport', 'mpsaliasport', 'redirport', 'cert', 'mpscert', 'deletedomain', 'deletedefaultdomain', 'showall', 'showusers', 'shownodes', 'showmeshes', 'showevents', 'showpower', 'clearpower', 'showiplocations', 'help', 'exactports', 'install', 'uninstall', 'start', 'stop', 'restart', 'debug', 'filespath', 'datapath', 'noagentupdate', 'launch', 'noserverbackup', 'mongodb', 'mongodbcol', 'wanonly', 'lanonly', 'nousers', 'mpsdebug', 'mpspass', 'ciralocalfqdn', 'dbexport', 'dbexportmin', 'dbimport', 'dbencryptkey', 'selfupdate', 'tlsoffload', 'userallowedip', 'userblockedip', 'swarmallowedip', 'agentallowedip', 'agentblockedip', 'fastcert', 'swarmport', 'swarmdebug', 'logintoken', 'logintokenkey', 'logintokengen', 'logintokengen', 'mailtokengen', 'admin', 'unadmin', 'sessionkey', 'sessiontime', 'minify', 'minifycore', 'dblistconfigfiles', 'dbshowconfigfile', 'dbpushconfigfiles', 'dbpullconfigfiles', 'dbdeleteconfigfiles'];
99
+ var validArguments = ['_', 'notls', 'user', 'port', 'aliasport', 'mpsport', 'mpsaliasport', 'redirport', 'cert', 'mpscert', 'deletedomain', 'deletedefaultdomain', 'showall', 'showusers', 'shownodes', 'showmeshes', 'showevents', 'showpower', 'clearpower', 'showiplocations', 'help', 'exactports', 'install', 'uninstall', 'start', 'stop', 'restart', 'debug', 'filespath', 'datapath', 'noagentupdate', 'launch', 'noserverbackup', 'mongodb', 'mongodbcol', 'wanonly', 'lanonly', 'nousers', 'mpsdebug', 'mpspass', 'ciralocalfqdn', 'dbexport', 'dbexportmin', 'dbimport', 'dbencryptkey', 'selfupdate', 'tlsoffload', 'userallowedip', 'userblockedip', 'swarmallowedip', 'agentallowedip', 'agentblockedip', 'fastcert', 'swarmport', 'swarmdebug', 'logintoken', 'logintokenkey', 'logintokengen', 'logintokengen', 'mailtokengen', 'admin', 'unadmin', 'sessionkey', 'sessiontime', 'minify', 'minifycore', 'dblistconfigfiles', 'dbshowconfigfile', 'dbpushconfigfiles', 'dbpullconfigfiles', 'dbdeleteconfigfiles', 'configkey', 'loadconfigfromdb'];
100
for (var arg in obj.args) { obj.args[arg.toLocaleLowerCase()] = obj.args[arg]; if (validArguments.indexOf(arg.toLocaleLowerCase()) == -1) { console.log('Invalid argument "' + arg + '", use --help.'); return; } }
101
if (obj.args.mongodb == true) { console.log('Must specify: --mongodb [connectionstring] \r\nSee https://docs.mongodb.com/manual/reference/connection-string/ for MongoDB connection string.'); return; }
102
for (i in obj.config.settings) { obj.args[i] = obj.config.settings[i]; } // Place all settings into arguments, arguments have already been placed into settings so arguments take precedence.
@@ -217,58 +218,12 @@ function CreateMeshCentralServer(config, args) {
218
// Initiate server self-update
219
obj.performServerCertUpdate = function () { console.log('Updating server certificates...'); process.exit(200); };
220
221
+ // Look for easy command line instructions and do them here.
222
obj.StartEx = function () {
223
var i;
224
//var wincmd = require('node-windows');
225
//wincmd.list(function (svc) { console.log(svc); }, true);
226
225
- // If we are targetting a specific version, update now.
226
- if (typeof obj.args.selfupdate == 'string') {
227
- obj.args.selfupdate = obj.args.selfupdate.toLowerCase();
228
- if (obj.currentVer !== obj.args.selfupdate) { obj.performServerUpdate(); return; } // We are targetting a specific version, run self update now.
229
- }
230
-
231
- // Write the server state
232
- obj.updateServerState('state', 'starting');
233
-
234
- // Look to see if data and/or file path is specified
235
- if (obj.args.datapath) { obj.datapath = obj.args.datapath; }
236
- if (obj.args.filespath) { obj.filespath = obj.args.filespath; }
237
-
238
- // Read environment variables. For a subset of arguments, we allow them to be read from environment variables.
239
- var xenv = ['user', 'port', 'mpsport', 'mpsaliasport', 'redirport', 'exactport', 'debug'];
240
- for (i in xenv) { if ((obj.args[xenv[i]] == null) && (process.env['mesh' + xenv[i]])) { obj.args[xenv[i]] = obj.common.toNumber(process.env['mesh' + xenv[i]]); } }
241
-
242
- // Validate the domains, this is used for multi-hosting
243
- if (obj.config.domains == null) { obj.config.domains = {}; }
244
- if (obj.config.domains[''] == null) { obj.config.domains[''] = {}; }
245
- if (obj.config.domains[''].dns != null) { console.log("ERROR: Default domain can't have a DNS name."); return; }
246
- var xdomains = {}; for (i in obj.config.domains) { if (obj.config.domains[i].title == null) { obj.config.domains[i].title = 'MeshCentral'; } if (obj.config.domains[i].title2 == null) { obj.config.domains[i].title2 = '2.0 Beta 2'; } xdomains[i.toLowerCase()] = obj.config.domains[i]; } obj.config.domains = xdomains;
247
- var bannedDomains = ['public', 'private', 'images', 'scripts', 'styles', 'views']; // List of banned domains
248
- for (i in obj.config.domains) { for (var j in bannedDomains) { if (i == bannedDomains[j]) { console.log("ERROR: Domain '" + i + "' is not allowed domain name in ./data/config.json."); return; } } }
249
- for (i in obj.config.domains) {
250
- if (obj.config.domains[i].dns == null) { obj.config.domains[i].url = (i == '') ? '/' : ('/' + i + '/'); } else { obj.config.domains[i].url = '/'; }
251
- obj.config.domains[i].id = i;
252
- if (typeof obj.config.domains[i].userallowedip == 'string') { if (obj.config.domains[i].userallowedip == '') { obj.config.domains[i].userallowedip = null; } else { obj.config.domains[i].userallowedip = obj.config.domains[i].userallowedip.split(','); } }
253
- if (typeof obj.config.domains[i].userblockedip == 'string') { if (obj.config.domains[i].userblockedip == '') { obj.config.domains[i].userblockedip = null; } else { obj.config.domains[i].userblockedip = obj.config.domains[i].userallowedip.split(','); } }
254
- if (typeof obj.config.domains[i].agentallowedip == 'string') { if (obj.config.domains[i].agentallowedip == '') { obj.config.domains[i].agentallowedip = null; } else { obj.config.domains[i].agentallowedip = obj.config.domains[i].agentallowedip.split(','); } }
255
- if (typeof obj.config.domains[i].agentblockedip == 'string') { if (obj.config.domains[i].agentblockedip == '') { obj.config.domains[i].agentblockedip = null; } else { obj.config.domains[i].agentblockedip = obj.config.domains[i].agentblockedip.split(','); } }
256
- }
257
-
258
- // Log passed arguments into Windows Service Log
259
- //if (obj.servicelog != null) { var s = ''; for (i in obj.args) { if (i != '_') { if (s.length > 0) { s += ', '; } s += i + "=" + obj.args[i]; } } logInfoEvent('MeshServer started with arguments: ' + s); }
260
-
261
- // Look at passed in arguments
262
- if ((obj.args.user != null) && (typeof obj.args.user != 'string')) { delete obj.args.user; }
263
- if ((obj.args.ciralocalfqdn != null) && ((obj.args.lanonly == true) || (obj.args.wanonly == true))) { console.log("WARNING: CIRA local FQDN's ignored when server in LAN-only or WAN-only mode."); }
264
- if ((obj.args.ciralocalfqdn != null) && (obj.args.ciralocalfqdn.split(',').length > 4)) { console.log("WARNING: Can't have more than 4 CIRA local FQDN's. Ignoring value."); obj.args.ciralocalfqdn = null; }
265
- if (obj.args.ignoreagenthashcheck === true) { console.log("WARNING: Agent hash checking is being skipped, this is unsafe."); }
266
- if (obj.args.port == null || typeof obj.args.port != 'number') { if (obj.args.notls == null) { obj.args.port = 443; } else { obj.args.port = 80; } }
267
- if (obj.args.aliasport != null && (typeof obj.args.aliasport != 'number')) obj.args.aliasport = null;
268
- if (obj.args.mpsport == null || typeof obj.args.mpsport != 'number') obj.args.mpsport = 4433;
269
- if (obj.args.mpsaliasport != null && (typeof obj.args.mpsaliasport != 'number')) obj.args.mpsaliasport = null;
270
- if (obj.args.notls == null && obj.args.redirport == null) obj.args.redirport = 80;
271
- if (obj.args.minifycore === 0) obj.args.minifycore = false;
227
if (typeof obj.args.userallowedip == 'string') { if (obj.args.userallowedip == '') { obj.args.userallowedip = null; } else { obj.args.userallowedip = obj.args.userallowedip.split(','); } }
228
if (typeof obj.args.userblockedip == 'string') { if (obj.args.userblockedip == '') { obj.args.userblockedip = null; } else { obj.args.userblockedip = obj.args.userblockedip.split(','); } }
229
if (typeof obj.args.agentallowedip == 'string') { if (obj.args.agentallowedip == '') { obj.args.agentallowedip = null; } else { obj.args.agentallowedip = obj.args.agentallowedip.split(','); } }
@@ -291,37 +246,62 @@ function CreateMeshCentralServer(config, args) {
246
if (obj.args.showiplocations) { obj.db.GetAllType('iploc', function (err, docs) { console.log(docs); process.exit(); }); return; }
247
if (obj.args.logintoken) { obj.getLoginToken(obj.args.logintoken, function (r) { console.log(r); process.exit(); }); return; }
248
if (obj.args.logintokenkey) { obj.showLoginTokenKey(function (r) { console.log(r); process.exit(); }); return; }
294
- if (obj.args.dblistconfigfiles) { obj.db.GetAllType('cfile', function (err, docs) { if (err == null) { if (docs.length == 0) { console.log('No files found.'); } else { for (var i in docs) { console.log(docs[i]._id.split('/')[1] + ', ' + Buffer.from(docs[i].data, 'base64').length + ' bytes.'); } } } else { console.log('Unable to read from database.'); } process.exit(); }); return; }
295
- if (obj.args.dbshowconfigfile) { obj.db.getFile(obj.args.dbshowconfigfile, function (err, docs) { if (err == null) { if (docs.length == 0) { console.log('File not found.'); } else { console.log(Buffer.from(docs[0].data, 'base64').toString()); } } else { console.log('Unable to read from database.'); } process.exit(); }); return; }
296
- if (obj.args.dbdeleteconfigfiles) { console.log('Delating all configuration files from the database...'); obj.db.RemoveAllOfType('cfile', function () { console.log('Done.'); process.exit(); }); } // Delete all configuration files from database
249
+
250
+ // Show a list of all configuration files in the database
251
+ if (obj.args.dblistconfigfiles) {
252
+ obj.db.GetAllType('cfile', function (err, docs) { if (err == null) { if (docs.length == 0) { console.log('No files found.'); } else { for (var i in docs) { console.log(docs[i]._id.split('/')[1] + ', ' + Buffer.from(docs[i].data, 'base64').length + ' bytes.'); } } } else { console.log('Unable to read from database.'); } process.exit(); }); return;
253
+ }
254
+
255
+ // Display the content of a configuration file in the database
256
+ if (obj.args.dbshowconfigfile) {
257
+ if (typeof obj.args.configkey != 'string') { console.log('Error, --configkey is required.'); process.exit(); return; }
258
+ obj.db.getConfigFile(obj.args.dbshowconfigfile, function (err, docs) {
259
+ if (err == null) {
260
+ if (docs.length == 0) { console.log('File not found.'); } else {
261
+ var data = obj.db.decryptData(obj.args.configkey, docs[0].data);
262
+ if (data == null) { console.log('Invalid config key.'); } else { console.log(data); }
263
+ }
264
+ } else { console.log('Unable to read from database.'); }
265
+ process.exit();
266
+ }); return;
267
+ }
268
+
269
+ // Delete all configuration files from database
270
+ if (obj.args.dbdeleteconfigfiles) {
271
+ console.log('Deleting all configuration files from the database...'); obj.db.RemoveAllOfType('cfile', function () { console.log('Done.'); process.exit(); });
272
+ }
273
274
// Push all relevent files from meshcentral-data into the database
275
if (obj.args.dbpushconfigfiles) {
276
+ if (typeof obj.args.configkey != 'string') { console.log('Error, --configkey is required.'); process.exit(); return; }
277
if (typeof obj.args.dbpushconfigfiles != 'string') {
278
console.log('Usage: --dbpulldatafiles (path) This will import files from folder into the database');
279
console.log(' --dbpulldatafiles * This will import files from meshcentral-data into the db.');
280
process.exit();
281
} else {
305
- if (obj.args.dbpushconfigfiles == '*') { obj.args.dbpushconfigfiles = obj.datapath; }
306
- obj.fs.readdir(obj.datapath, (err, files) => {
307
- var lockCount = 1
308
- for (var i in files) {
309
- const file = files[i];
310
- if (file.endsWith('.json') || file.endsWith('.key') || file.endsWith('.crt')) {
311
- const path = obj.path.join(obj.args.dbpushconfigfiles, files[i]), binary = Buffer.from(obj.fs.readFileSync(path, { encoding: 'binary' }), 'binary');
312
- console.log('Pushing ' + file + ', ' + binary.length + ' bytes.');
313
- lockCount++;
314
- obj.db.setFile(file, binary, function () { if ((--lockCount) == 0) { console.log('Done.'); process.exit(); } });
282
+ obj.db.RemoveAllOfType('cfile', function () {
283
+ if (obj.args.dbpushconfigfiles == '*') { obj.args.dbpushconfigfiles = obj.datapath; }
284
+ obj.fs.readdir(obj.datapath, (err, files) => {
285
+ var lockCount = 1
286
+ for (var i in files) {
287
+ const file = files[i];
288
+ if ((file == 'config.json') || file.endsWith('.key') || file.endsWith('.crt') || (file == 'terms.txt') || file.endsWith('.jpg') || file.endsWith('.png')) {
289
+ const path = obj.path.join(obj.args.dbpushconfigfiles, files[i]), binary = Buffer.from(obj.fs.readFileSync(path, { encoding: 'binary' }), 'binary');
290
+ console.log('Pushing ' + file + ', ' + binary.length + ' bytes.');
291
+ lockCount++;
292
+ obj.db.setConfigFile(file, obj.db.encryptData(obj.args.configkey, binary), function () { if ((--lockCount) == 0) { console.log('Done.'); process.exit(); } });
293
+ }
294
}
316
- }
317
- if (--lockCount == 0) { process.exit(); }
318
- })
295
+ if (--lockCount == 0) { process.exit(); }
296
+ });
297
+ });
298
}
299
return;
300
}
301
302
// Pull all database files into meshcentral-data
303
if (obj.args.dbpullconfigfiles) {
304
+ if (typeof obj.args.configkey != 'string') { console.log('Error, --configkey is required.'); process.exit(); return; }
305
if (typeof obj.args.dbpullconfigfiles != 'string') {
306
console.log('Usage: --dbpulldatafiles (path)');
307
process.exit();
@@ -332,9 +312,13 @@ function CreateMeshCentralServer(config, args) {
312
console.log('File not found.');
313
} else {
314
for (var i in docs) {
335
- const file = docs[i]._id.split('/')[1], binary = Buffer.from(docs[i].data, 'base64');
336
- obj.fs.writeFileSync(obj.path.join(obj.args.dbpullconfigfiles, file), binary);
337
- console.log('Pulling ' + file + ', ' + binary.length + ' bytes.');
315
+ const file = docs[i]._id.split('/')[1], binary = obj.db.decryptData(obj.args.configkey, docs[i].data);
316
+ if (binary == null) {
317
+ console.log('Invalid config key.');
318
+ } else {
319
+ obj.fs.writeFileSync(obj.path.join(obj.args.dbpullconfigfiles, file), binary);
320
+ console.log('Pulling ' + file + ', ' + binary.length + ' bytes.');
321
+ }
322
}
323
}
324
} else {
@@ -380,101 +364,196 @@ function CreateMeshCentralServer(config, args) {
364
return;
365
}
366
383
- // Clear old event entries and power entires
384
- obj.db.clearOldEntries('event', 30); // Clear all event entires that are older than 30 days.
385
- obj.db.clearOldEntries('power', 10); // Clear all event entires that are older than 10 days. If a node is connected longer than 10 days, current power state will be used for everything.
386
-
387
- // Setup a site administrator
388
- if ((obj.args.admin) && (typeof obj.args.admin == 'string')) {
389
- var adminname = obj.args.admin.split('/');
390
- if (adminname.length == 1) { adminname = 'user//' + adminname[0]; }
391
- else if (adminname.length == 2) { adminname = 'user/' + adminname[0] + '/' + adminname[1]; }
392
- else { console.log('Invalid administrator name.'); process.exit(); return; }
393
- obj.db.Get(adminname, function (err, user) {
394
- if (user.length != 1) { console.log('Invalid user name.'); process.exit(); return; }
395
- user[0].siteadmin = 0xFFFFFFFF;
396
- obj.db.Set(user[0], function () {
397
- if (user[0].domain == '') { console.log('User ' + user[0].name + ' set to site administrator.'); } else { console.log('User ' + user[0].name + ' of domain ' + user[0].domain + ' set to site administrator.'); }
367
+ // Load configuration for database if needed
368
+ if (obj.args.loadconfigfromdb) {
369
+ var key = null;
370
+ if (typeof obj.args.configkey == 'string') { key = obj.args.configkey; }
371
+ else if (typeof obj.args.loadconfigfromdb == 'string') { key = obj.args.loadconfigfromdb; }
372
+ if (key == null) { console.log('Error, --configkey is required.'); process.exit(); return; }
373
+ obj.db.getAllConfigFiles(key, function (configFiles) {
374
+ if (configFiles == null) { console.log('Error, no configuration files found or invalid configkey.'); process.exit(); return; }
375
+ if (!configFiles['config.json']) { console.log('Error, could not file config.json from database.'); process.exit(); return; }
376
+ obj.configurationFiles = configFiles;
377
+
378
+ // Parse the new configuration file
379
+ var config2 = null;
380
+ try { config2 = JSON.parse(configFiles['config.json']); } catch (ex) { console.log('Error, unable to parse config.json from database.'); process.exit(); return; }
381
+
382
+ // Set the command line arguments to the config file if they are not present
383
+ if (!config2.settings) { config2.settings = {}; }
384
+ for (i in args) { config2.settings[i] = args[i]; }
385
+
386
+ // Lower case all keys in the config file
387
+ try {
388
+ require('./common.js').objKeysToLower(config2);
389
+ } catch (ex) {
390
+ console.log('CRITICAL ERROR: Unable to access the file \"./common.js\".\r\nCheck folder & file permissions.');
391
process.exit();
392
return;
400
- });
401
- });
402
- return;
403
- }
393
+ }
394
405
- // Remove a site administrator
406
- if ((obj.args.unadmin) && (typeof obj.args.unadmin == 'string')) {
407
- var adminname = obj.args.unadmin.split('/');
408
- if (adminname.length == 1) { adminname = 'user//' + adminname[0]; }
409
- else if (adminname.length == 2) { adminname = 'user/' + adminname[0] + '/' + adminname[1]; }
410
- else { console.log('Invalid administrator name.'); process.exit(); return; }
411
- obj.db.Get(adminname, function (err, user) {
412
- if (user.length != 1) { console.log('Invalid user name.'); process.exit(); return; }
413
- if (user[0].siteadmin) { delete user[0].siteadmin; }
414
- obj.db.Set(user[0], function () {
415
- if (user[0].domain == '') { console.log('User ' + user[0].name + ' is not a site administrator.'); } else { console.log('User ' + user[0].name + ' of domain ' + user[0].domain + ' is not a site administrator.'); }
416
- process.exit();
417
- return;
418
- });
395
+ // Grad some of the values from the original config.json file if present.
396
+ config2['mongodb'] = config['mongodb'];
397
+ config2['mongodbcol'] = config['mongodbcol'];
398
+ config2['dbencryptkey'] = config['dbencryptkey'];
399
+
400
+ // We got a new config.json from the database, let's use it.
401
+ config = obj.config = config2;
402
+ obj.StartEx1b();
403
});
420
- return;
404
+ } else {
405
+ config = obj.config = getConfig(true);
406
+ obj.StartEx1b();
407
}
408
+ });
409
+ };
410
423
- // Perform other database cleanup
424
- obj.db.cleanup();
411
+ // Time to start the serverf or real.
412
+ obj.StartEx1b = function () {
413
+ var i;
414
426
- // Set all nodes to power state of unknown (0)
427
- if (obj.multiServer == null) {
428
- obj.db.file.insert({ type: 'power', time: Date.now(), node: '*', power: 0, s: 1 });
429
- } else {
430
- obj.db.file.insert({ type: 'power', time: Date.now(), node: '*', power: 0, s: 1, server: obj.multiServer.serverid });
431
- }
415
+ // If we are targetting a specific version, update now.
416
+ if (typeof obj.args.selfupdate == 'string') {
417
+ obj.args.selfupdate = obj.args.selfupdate.toLowerCase();
418
+ if (obj.currentVer !== obj.args.selfupdate) { obj.performServerUpdate(); return; } // We are targetting a specific version, run self update now.
419
+ }
420
433
- // Read or setup database configuration values
434
- obj.db.Get('dbconfig', function (err, dbconfig) {
435
- if (dbconfig.length == 1) { obj.dbconfig = dbconfig[0]; } else { obj.dbconfig = { _id: 'dbconfig', version: 1 }; }
436
- if (obj.dbconfig.amtWsEventSecret == null) { obj.crypto.randomBytes(32, function (err, buf) { obj.dbconfig.amtWsEventSecret = buf.toString('hex'); obj.db.Set(obj.dbconfig); }); }
437
-
438
- // This is used by the user to create a username/password for a Intel AMT WSMAN event subscription
439
- if (obj.args.getwspass) {
440
- if (obj.args.getwspass.length == 64) {
441
- obj.crypto.randomBytes(6, function (err, buf) {
442
- while (obj.dbconfig.amtWsEventSecret == null) { process.nextTick(); }
443
- var username = buf.toString('hex');
444
- var nodeid = obj.args.getwspass;
445
- var pass = obj.crypto.createHash('sha384').update(username.toLowerCase() + ":" + nodeid + ":" + obj.dbconfig.amtWsEventSecret).digest("base64").substring(0, 12).split("/").join("x").split("\\").join("x");
446
- console.log('--- Intel(r) AMT WSMAN eventing credentials ---');
447
- console.log('Username: ' + username);
448
- console.log('Password: ' + pass);
449
- console.log('Argument: ' + nodeid);
450
- process.exit();
451
- });
452
- } else {
453
- console.log('Invalid NodeID.');
454
- process.exit();
455
- }
421
+ // Write the server state
422
+ obj.updateServerState('state', 'starting');
423
+
424
+ // Look to see if data and/or file path is specified
425
+ if (obj.args.datapath) { obj.datapath = obj.args.datapath; }
426
+ if (obj.args.filespath) { obj.filespath = obj.args.filespath; }
427
+
428
+ // Read environment variables. For a subset of arguments, we allow them to be read from environment variables.
429
+ var xenv = ['user', 'port', 'mpsport', 'mpsaliasport', 'redirport', 'exactport', 'debug'];
430
+ for (i in xenv) { if ((obj.args[xenv[i]] == null) && (process.env['mesh' + xenv[i]])) { obj.args[xenv[i]] = obj.common.toNumber(process.env['mesh' + xenv[i]]); } }
431
+
432
+ // Validate the domains, this is used for multi-hosting
433
+ if (obj.config.domains == null) { obj.config.domains = {}; }
434
+ if (obj.config.domains[''] == null) { obj.config.domains[''] = {}; }
435
+ if (obj.config.domains[''].dns != null) { console.log("ERROR: Default domain can't have a DNS name."); return; }
436
+ var xdomains = {}; for (i in obj.config.domains) { if (obj.config.domains[i].title == null) { obj.config.domains[i].title = 'MeshCentral'; } if (obj.config.domains[i].title2 == null) { obj.config.domains[i].title2 = '2.0 Beta 2'; } xdomains[i.toLowerCase()] = obj.config.domains[i]; } obj.config.domains = xdomains;
437
+ var bannedDomains = ['public', 'private', 'images', 'scripts', 'styles', 'views']; // List of banned domains
438
+ for (i in obj.config.domains) { for (var j in bannedDomains) { if (i == bannedDomains[j]) { console.log("ERROR: Domain '" + i + "' is not allowed domain name in ./data/config.json."); return; } } }
439
+ for (i in obj.config.domains) {
440
+ if (obj.config.domains[i].dns == null) { obj.config.domains[i].url = (i == '') ? '/' : ('/' + i + '/'); } else { obj.config.domains[i].url = '/'; }
441
+ obj.config.domains[i].id = i;
442
+ if (typeof obj.config.domains[i].userallowedip == 'string') { if (obj.config.domains[i].userallowedip == '') { obj.config.domains[i].userallowedip = null; } else { obj.config.domains[i].userallowedip = obj.config.domains[i].userallowedip.split(','); } }
443
+ if (typeof obj.config.domains[i].userblockedip == 'string') { if (obj.config.domains[i].userblockedip == '') { obj.config.domains[i].userblockedip = null; } else { obj.config.domains[i].userblockedip = obj.config.domains[i].userallowedip.split(','); } }
444
+ if (typeof obj.config.domains[i].agentallowedip == 'string') { if (obj.config.domains[i].agentallowedip == '') { obj.config.domains[i].agentallowedip = null; } else { obj.config.domains[i].agentallowedip = obj.config.domains[i].agentallowedip.split(','); } }
445
+ if (typeof obj.config.domains[i].agentblockedip == 'string') { if (obj.config.domains[i].agentblockedip == '') { obj.config.domains[i].agentblockedip = null; } else { obj.config.domains[i].agentblockedip = obj.config.domains[i].agentblockedip.split(','); } }
446
+ }
447
+
448
+ // Log passed arguments into Windows Service Log
449
+ //if (obj.servicelog != null) { var s = ''; for (i in obj.args) { if (i != '_') { if (s.length > 0) { s += ', '; } s += i + "=" + obj.args[i]; } } logInfoEvent('MeshServer started with arguments: ' + s); }
450
+
451
+ // Look at passed in arguments
452
+ if ((obj.args.user != null) && (typeof obj.args.user != 'string')) { delete obj.args.user; }
453
+ if ((obj.args.ciralocalfqdn != null) && ((obj.args.lanonly == true) || (obj.args.wanonly == true))) { console.log("WARNING: CIRA local FQDN's ignored when server in LAN-only or WAN-only mode."); }
454
+ if ((obj.args.ciralocalfqdn != null) && (obj.args.ciralocalfqdn.split(',').length > 4)) { console.log("WARNING: Can't have more than 4 CIRA local FQDN's. Ignoring value."); obj.args.ciralocalfqdn = null; }
455
+ if (obj.args.ignoreagenthashcheck === true) { console.log("WARNING: Agent hash checking is being skipped, this is unsafe."); }
456
+ if (obj.args.port == null || typeof obj.args.port != 'number') { if (obj.args.notls == null) { obj.args.port = 443; } else { obj.args.port = 80; } }
457
+ if (obj.args.aliasport != null && (typeof obj.args.aliasport != 'number')) obj.args.aliasport = null;
458
+ if (obj.args.mpsport == null || typeof obj.args.mpsport != 'number') obj.args.mpsport = 4433;
459
+ if (obj.args.mpsaliasport != null && (typeof obj.args.mpsaliasport != 'number')) obj.args.mpsaliasport = null;
460
+ if (obj.args.notls == null && obj.args.redirport == null) obj.args.redirport = 80;
461
+ if (obj.args.minifycore === 0) obj.args.minifycore = false;
462
+
463
+ // Clear old event entries and power entires
464
+ obj.db.clearOldEntries('event', 30); // Clear all event entires that are older than 30 days.
465
+ obj.db.clearOldEntries('power', 10); // Clear all event entires that are older than 10 days. If a node is connected longer than 10 days, current power state will be used for everything.
466
+
467
+ // Setup a site administrator
468
+ if ((obj.args.admin) && (typeof obj.args.admin == 'string')) {
469
+ var adminname = obj.args.admin.split('/');
470
+ if (adminname.length == 1) { adminname = 'user//' + adminname[0]; }
471
+ else if (adminname.length == 2) { adminname = 'user/' + adminname[0] + '/' + adminname[1]; }
472
+ else { console.log('Invalid administrator name.'); process.exit(); return; }
473
+ obj.db.Get(adminname, function (err, user) {
474
+ if (user.length != 1) { console.log('Invalid user name.'); process.exit(); return; }
475
+ user[0].siteadmin = 0xFFFFFFFF;
476
+ obj.db.Set(user[0], function () {
477
+ if (user[0].domain == '') { console.log('User ' + user[0].name + ' set to site administrator.'); } else { console.log('User ' + user[0].name + ' of domain ' + user[0].domain + ' set to site administrator.'); }
478
+ process.exit();
479
return;
457
- }
480
+ });
481
+ });
482
+ return;
483
+ }
484
+
485
+ // Remove a site administrator
486
+ if ((obj.args.unadmin) && (typeof obj.args.unadmin == 'string')) {
487
+ var adminname = obj.args.unadmin.split('/');
488
+ if (adminname.length == 1) { adminname = 'user//' + adminname[0]; }
489
+ else if (adminname.length == 2) { adminname = 'user/' + adminname[0] + '/' + adminname[1]; }
490
+ else { console.log('Invalid administrator name.'); process.exit(); return; }
491
+ obj.db.Get(adminname, function (err, user) {
492
+ if (user.length != 1) { console.log('Invalid user name.'); process.exit(); return; }
493
+ if (user[0].siteadmin) { delete user[0].siteadmin; }
494
+ obj.db.Set(user[0], function () {
495
+ if (user[0].domain == '') { console.log('User ' + user[0].name + ' is not a site administrator.'); } else { console.log('User ' + user[0].name + ' of domain ' + user[0].domain + ' is not a site administrator.'); }
496
+ process.exit();
497
+ return;
498
+ });
499
+ });
500
+ return;
501
+ }
502
459
- // Load the default meshcore and meshcmd
460
- obj.updateMeshCore();
461
- obj.updateMeshCmd();
503
+ // Perform other database cleanup
504
+ obj.db.cleanup();
505
+
506
+ // Set all nodes to power state of unknown (0)
507
+ if (obj.multiServer == null) {
508
+ obj.db.file.insert({ type: 'power', time: Date.now(), node: '*', power: 0, s: 1 });
509
+ } else {
510
+ obj.db.file.insert({ type: 'power', time: Date.now(), node: '*', power: 0, s: 1, server: obj.multiServer.serverid });
511
+ }
512
463
- // Setup and start the redirection server if needed. We must start the redirection server before Let's Encrypt.
464
- if ((obj.args.redirport != null) && (typeof obj.args.redirport == 'number') && (obj.args.redirport != 0)) {
465
- obj.redirserver = require('./redirserver.js').CreateRedirServer(obj, obj.db, obj.args, obj.StartEx2);
513
+ // Read or setup database configuration values
514
+ obj.db.Get('dbconfig', function (err, dbconfig) {
515
+ if (dbconfig.length == 1) { obj.dbconfig = dbconfig[0]; } else { obj.dbconfig = { _id: 'dbconfig', version: 1 }; }
516
+ if (obj.dbconfig.amtWsEventSecret == null) { obj.crypto.randomBytes(32, function (err, buf) { obj.dbconfig.amtWsEventSecret = buf.toString('hex'); obj.db.Set(obj.dbconfig); }); }
517
+
518
+ // This is used by the user to create a username/password for a Intel AMT WSMAN event subscription
519
+ if (obj.args.getwspass) {
520
+ if (obj.args.getwspass.length == 64) {
521
+ obj.crypto.randomBytes(6, function (err, buf) {
522
+ while (obj.dbconfig.amtWsEventSecret == null) { process.nextTick(); }
523
+ var username = buf.toString('hex');
524
+ var nodeid = obj.args.getwspass;
525
+ var pass = obj.crypto.createHash('sha384').update(username.toLowerCase() + ":" + nodeid + ":" + obj.dbconfig.amtWsEventSecret).digest("base64").substring(0, 12).split("/").join("x").split("\\").join("x");
526
+ console.log('--- Intel(r) AMT WSMAN eventing credentials ---');
527
+ console.log('Username: ' + username);
528
+ console.log('Password: ' + pass);
529
+ console.log('Argument: ' + nodeid);
530
+ process.exit();
531
+ });
532
} else {
467
- obj.StartEx2(); // If not needed, move on.
533
+ console.log('Invalid NodeID.');
534
+ process.exit();
535
}
469
- });
536
+ return;
537
+ }
538
+
539
+ // Load the default meshcore and meshcmd
540
+ obj.updateMeshCore();
541
+ obj.updateMeshCmd();
542
+
543
+ // Setup and start the redirection server if needed. We must start the redirection server before Let's Encrypt.
544
+ if ((obj.args.redirport != null) && (typeof obj.args.redirport == 'number') && (obj.args.redirport != 0)) {
545
+ obj.redirserver = require('./redirserver.js').CreateRedirServer(obj, obj.db, obj.args, obj.StartEx2);
546
+ } else {
547
+ obj.StartEx2(); // If not needed, move on.
548
+ }
549
});
471
- };
550
+ }
551
552
// Done starting the redirection server, go on to load the server certificates
553
obj.StartEx2 = function () {
554
// Load server certificates
476
- obj.certificateOperations = require('./certoperations.js').CertificateOperations();
477
- obj.certificateOperations.GetMeshServerCertificate(obj, obj.args, obj.config, function (certs) {
555
+ obj.certificateOperations = require('./certoperations.js').CertificateOperations(obj);
556
+ obj.certificateOperations.GetMeshServerCertificate(obj.args, obj.config, function (certs) {
557
if ((obj.config.letsencrypt == null) || (obj.redirserver == null)) {
558
obj.StartEx3(certs); // Just use the configured certificates
559
} else {
@@ -1355,7 +1434,7 @@ function CreateMeshCentralServer(config, args) {
1434
}
1435
1436
// Return the server configuration
1358
-function getConfig() {
1437
+function getConfig(createSampleConfig) {
1438
// Figure out the datapath location
1439
var i;
1440
var fs = require('fs');
@@ -1378,9 +1457,11 @@ function getConfig() {
1457
if (config.domains == null) { config.domains = {}; }
1458
for (i in config.domains) { if ((i.split('/').length > 1) || (i.split(' ').length > 1)) { console.log("ERROR: Error in config.json, domain names can't have spaces or /."); return null; } }
1459
} else {
1381
- // Copy the "sample-config.json" to give users a starting point
1382
- var sampleConfigPath = path.join(__dirname, 'sample-config.json');
1383
- if (fs.existsSync(sampleConfigPath)) { fs.createReadStream(sampleConfigPath).pipe(fs.createWriteStream(configFilePath)); }
1460
+ if (createSampleConfig === true) {
1461
+ // Copy the "sample-config.json" to give users a starting point
1462
+ var sampleConfigPath = path.join(__dirname, 'sample-config.json');
1463
+ if (fs.existsSync(sampleConfigPath)) { fs.createReadStream(sampleConfigPath).pipe(fs.createWriteStream(configFilePath)); }
1464
+ }
1465
}
1466
1467
// Set the command line arguments to the config file if they are not present
@@ -1431,7 +1512,7 @@ function mainStart(args) {
1512
// Check for any missing modules.
1513
InstallModules(['minimist'], function () {
1514
// Get the server configuration
1434
- var config = getConfig();
1515
+ var config = getConfig(false);
1516
if (config == null) { process.exit(); }
1517
1518
// Check is Windows SSPI will be used
package.json
+1
-1
@@ -1,6 +1,6 @@
1
{
2
"name": "meshcentral",
3
- "version": "0.2.7-g",
3
+ "version": "0.2.7-h",
4
"keywords": [
5
"Remote Management",
6
"Intel AMT",
webserver.js
+53
-27
@@ -873,31 +873,44 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
873
const domain = checkUserIpAddress(req, res);
874
if (domain == null) return;
875
876
- // See if there is a terms.txt file in meshcentral-data
877
- var p = obj.path.join(obj.parent.datapath, 'terms.txt');
878
- if (obj.fs.existsSync(p)) {
879
- obj.fs.readFile(p, 'utf8', function (err, data) {
880
- if (err != null) { res.sendStatus(404); return; }
876
+ // See if term.txt was loaded from the database
877
+ if ((parent.configurationFiles != null) && (parent.configurationFiles['terms.txt'] != null)) {
878
+ // Send the terms from the database
879
+ res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' });
880
+ if (req.session && req.session.userid) {
881
+ if (req.session.domainid != domain.id) { req.session = null; res.redirect(domain.url); return; } // Check is the session is for the correct domain
882
+ var user = obj.users[req.session.userid];
883
+ res.render(obj.path.join(obj.parent.webViewsPath, isMobileBrowser(req) ? 'terms-mobile' : 'terms'), { title: domain.title, title2: domain.title2, terms: encodeURIComponent(parent.configurationFiles['terms.txt'].toString()), logoutControl: 'Welcome ' + user.name + '. <a href=' + domain.url + 'logout?' + Math.random() + ' style=color:white>Logout</a>' });
884
+ } else {
885
+ res.render(obj.path.join(obj.parent.webViewsPath, isMobileBrowser(req) ? 'terms-mobile' : 'terms'), { title: domain.title, title2: domain.title2, terms: encodeURIComponent(parent.configurationFiles['terms.txt'].toString()) });
886
+ }
887
+ } else {
888
+ // See if there is a terms.txt file in meshcentral-data
889
+ var p = obj.path.join(obj.parent.datapath, 'terms.txt');
890
+ if (obj.fs.existsSync(p)) {
891
+ obj.fs.readFile(p, 'utf8', function (err, data) {
892
+ if (err != null) { res.sendStatus(404); return; }
893
882
- // Send the terms
894
+ // Send the terms from terms.txt
895
+ res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' });
896
+ if (req.session && req.session.userid) {
897
+ if (req.session.domainid != domain.id) { req.session = null; res.redirect(domain.url); return; } // Check is the session is for the correct domain
898
+ var user = obj.users[req.session.userid];
899
+ res.render(obj.path.join(obj.parent.webViewsPath, isMobileBrowser(req) ? 'terms-mobile' : 'terms'), { title: domain.title, title2: domain.title2, terms: encodeURIComponent(data), logoutControl: 'Welcome ' + user.name + '. <a href=' + domain.url + 'logout?' + Math.random() + ' style=color:white>Logout</a>' });
900
+ } else {
901
+ res.render(obj.path.join(obj.parent.webViewsPath, isMobileBrowser(req) ? 'terms-mobile' : 'terms'), { title: domain.title, title2: domain.title2, terms: encodeURIComponent(data) });
902
+ }
903
+ });
904
+ } else {
905
+ // Send the default terms
906
res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' });
907
if (req.session && req.session.userid) {
908
if (req.session.domainid != domain.id) { req.session = null; res.redirect(domain.url); return; } // Check is the session is for the correct domain
909
var user = obj.users[req.session.userid];
887
- res.render(obj.path.join(obj.parent.webViewsPath, isMobileBrowser(req) ? 'terms-mobile' : 'terms'), { title: domain.title, title2: domain.title2, terms: encodeURIComponent(data), logoutControl: 'Welcome ' + user.name + '. <a href=' + domain.url + 'logout?' + Math.random() + ' style=color:white>Logout</a>' });
910
+ res.render(obj.path.join(obj.parent.webViewsPath, isMobileBrowser(req) ? 'terms-mobile' : 'terms'), { title: domain.title, title2: domain.title2, logoutControl: 'Welcome ' + user.name + '. <a href=' + domain.url + 'logout?' + Math.random() + ' style=color:white>Logout</a>' });
911
} else {
889
- res.render(obj.path.join(obj.parent.webViewsPath, isMobileBrowser(req) ? 'terms-mobile' : 'terms'), { title: domain.title, title2: domain.title2, terms: encodeURIComponent(data) });
912
+ res.render(obj.path.join(obj.parent.webViewsPath, isMobileBrowser(req) ? 'terms-mobile' : 'terms'), { title: domain.title, title2: domain.title2 });
913
}
891
- });
892
- } else {
893
- // Send the terms
894
- res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' });
895
- if (req.session && req.session.userid) {
896
- if (req.session.domainid != domain.id) { req.session = null; res.redirect(domain.url); return; } // Check is the session is for the correct domain
897
- var user = obj.users[req.session.userid];
898
- res.render(obj.path.join(obj.parent.webViewsPath, isMobileBrowser(req) ? 'terms-mobile' : 'terms'), { title: domain.title, title2: domain.title2, logoutControl: 'Welcome ' + user.name + '. <a href=' + domain.url + 'logout?' + Math.random() + ' style=color:white>Logout</a>' });
899
- } else {
900
- res.render(obj.path.join(obj.parent.webViewsPath, isMobileBrowser(req) ? 'terms-mobile' : 'terms'), { title: domain.title, title2: domain.title2 });
914
}
915
}
916
}
@@ -1032,8 +1045,15 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
1045
1046
res.set({ 'Cache-Control': 'max-age=86400' }); // 1 day
1047
if ((domain != null) && domain.titlepicture) {
1035
- try { res.sendFile(obj.path.join(obj.parent.datapath, domain.titlepicture)); } catch (e) {
1036
- try { res.sendFile(obj.path.join(obj.parent.webPublicPath, 'images/logoback.png')); } catch (e) { res.sendStatus(404); }
1048
+ if ((parent.configurationFiles != null) && (parent.configurationFiles[domain.titlepicture] != null)) {
1049
+ // Use the logo in the database
1050
+ res.set({ 'Content-Type': 'image/jpeg' });
1051
+ res.send(parent.configurationFiles[domain.titlepicture]);
1052
+ } else {
1053
+ // Use the logo on file
1054
+ try { res.sendFile(obj.path.join(obj.parent.datapath, domain.titlepicture)); } catch (e) {
1055
+ try { res.sendFile(obj.path.join(obj.parent.webPublicPath, 'images/logoback.png')); } catch (e) { res.sendStatus(404); }
1056
+ }
1057
}
1058
} else {
1059
try { res.sendFile(obj.path.join(obj.parent.webPublicPath, 'images/logoback.png')); } catch (e) { res.sendStatus(404); }
@@ -1975,14 +1995,20 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
1995
1996
// Server picture
1997
obj.app.get(url + 'serverpic.ashx', function (req, res) {
1978
- // Check if we have "server.png" in the data folder, if so, use that.
1979
- var p = obj.path.join(obj.parent.datapath, 'server.jpg');
1980
- if (obj.fs.existsSync(p)) {
1981
- // Use the data folder server picture
1982
- try { res.sendFile(p); } catch (e) { res.sendStatus(404); }
1998
+ // Check if we have "server.jpg" in the data folder, if so, use that.
1999
+ if ((parent.configurationFiles != null) && (parent.configurationFiles['server.jpg'] != null)) {
2000
+ res.set({ 'Content-Type': 'image/jpeg' });
2001
+ res.send(parent.configurationFiles['server.jpg']);
2002
} else {
1984
- // Use the default server picture
1985
- try { res.sendFile(obj.path.join(obj.parent.webPublicPath, 'images/server-200.jpg')); } catch (e) { res.sendStatus(404); }
2003
+ // Check if we have "server.jpg" in the data folder, if so, use that.
2004
+ var p = obj.path.join(obj.parent.datapath, 'server.jpg');
2005
+ if (obj.fs.existsSync(p)) {
2006
+ // Use the data folder server picture
2007
+ try { res.sendFile(p); } catch (e) { res.sendStatus(404); }
2008
+ } else {
2009
+ // Use the default server picture
2010
+ try { res.sendFile(obj.path.join(obj.parent.webPublicPath, 'images/server-200.jpg')); } catch (e) { res.sendStatus(404); }
2011
+ }
2012
}
2013
});
2014