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