diff --git a/certoperations.js b/certoperations.js index 8b0f17ce..0379336a 100644 --- a/certoperations.js +++ b/certoperations.js @@ -14,9 +14,10 @@ /*jshint esversion: 6 */ "use strict"; -module.exports.CertificateOperations = function () { +module.exports.CertificateOperations = function (parent) { var obj = {}; + obj.parent = parent; obj.fs = require("fs"); obj.forge = require("node-forge"); obj.crypto = require("crypto"); @@ -24,7 +25,6 @@ module.exports.CertificateOperations = function () { obj.pki = obj.forge.pki; obj.dirExists = function (filePath) { try { return obj.fs.statSync(filePath).isDirectory(); } catch (err) { return false; } }; obj.getFilesizeInBytes = function (filename) { try { return obj.fs.statSync(filename).size; } catch (err) { return -1; } }; - obj.fileExists = function (filePath) { try { return obj.fs.statSync(filePath).isFile(); } catch (err) { return false; } }; // Return the certificate of the remote HTTPS server obj.loadCertificate = function (url, tag, func) { @@ -51,6 +51,22 @@ module.exports.CertificateOperations = function () { } else { func(url, null, tag); } }; + // Check if a configuration file exists + obj.fileExists = function (filename) { + if ((parent.configurationFiles != null) && (parent.configurationFiles[filename] != null)) { return true; } + var filePath = parent.getConfigFilePath(filename); + try { return obj.fs.statSync(filePath).isFile(); } catch (err) { return false; } + }; + + // Load a configuration file + obj.fileLoad = function (filename, encoding) { + if ((parent.configurationFiles != null) && (parent.configurationFiles[filename] != null)) { + return fixEndOfLines(parent.configurationFiles[filename].toString()); + } else { + return fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath(filename), encoding)); + } + } + // Return the SHA384 hash of the certificate public key obj.getPublicKeyHash = function (cert) { var publickey = obj.pki.certificateFromPem(cert).publicKey; @@ -156,7 +172,7 @@ module.exports.CertificateOperations = function () { } // Returns the web server TLS certificate and private key, if not present, create demonstration ones. - obj.GetMeshServerCertificate = function (parent, args, config, func) { + obj.GetMeshServerCertificate = function (args, config, func) { var i = 0; var certargs = args.cert; var mpscertargs = args.mpscert; @@ -174,54 +190,54 @@ module.exports.CertificateOperations = function () { var rcount = 0; // If the root certificate already exist, load it - if (obj.fileExists(parent.getConfigFilePath("root-cert-public.crt")) && obj.fileExists(parent.getConfigFilePath("root-cert-private.key"))) { - var rootCertificate = fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath("root-cert-public.crt"), "utf8")); - var rootPrivateKey = fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath("root-cert-private.key"), "utf8")); + if (obj.fileExists("root-cert-public.crt") && obj.fileExists("root-cert-private.key")) { + var rootCertificate = obj.fileLoad("root-cert-public.crt", "utf8"); + var rootPrivateKey = obj.fileLoad("root-cert-private.key", "utf8"); r.root = { cert: rootCertificate, key: rootPrivateKey }; rcount++; } if (args.tlsoffload) { // If the web certificate already exist, load it. Load just the certificate since we are in TLS offload situation - if (obj.fileExists(parent.getConfigFilePath("webserver-cert-public.crt"))) { - r.web = { cert: fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath("webserver-cert-public.crt"), "utf8")) }; + if (obj.fileExists("webserver-cert-public.crt")) { + r.web = { cert: obj.fileLoad("webserver-cert-public.crt", "utf8") }; rcount++; } } else { // If the web certificate already exist, load it. Load both certificate and private key - if (obj.fileExists(parent.getConfigFilePath("webserver-cert-public.crt")) && obj.fileExists(parent.getConfigFilePath("webserver-cert-private.key"))) { - 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")) }; + if (obj.fileExists("webserver-cert-public.crt") && obj.fileExists("webserver-cert-private.key")) { + r.web = { cert: obj.fileLoad("webserver-cert-public.crt", "utf8"), key: obj.fileLoad("webserver-cert-private.key", "utf8") }; rcount++; } } // If the mps certificate already exist, load it - if (obj.fileExists(parent.getConfigFilePath("mpsserver-cert-public.crt")) && obj.fileExists(parent.getConfigFilePath("mpsserver-cert-private.key"))) { - 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")) }; + if (obj.fileExists("mpsserver-cert-public.crt") && obj.fileExists("mpsserver-cert-private.key")) { + r.mps = { cert: obj.fileLoad("mpsserver-cert-public.crt", "utf8"), key: obj.fileLoad("mpsserver-cert-private.key", "utf8") }; rcount++; } // If the agent certificate already exist, load it - if (obj.fileExists(parent.getConfigFilePath("agentserver-cert-public.crt")) && obj.fileExists(parent.getConfigFilePath("agentserver-cert-private.key"))) { - 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")) }; + if (obj.fileExists("agentserver-cert-public.crt") && obj.fileExists("agentserver-cert-private.key")) { + r.agent = { cert: obj.fileLoad("agentserver-cert-public.crt", "utf8"), key: obj.fileLoad("agentserver-cert-private.key", "utf8") }; rcount++; } // If the swarm server certificate exist, load it (This is an optional certificate) - if (obj.fileExists(parent.getConfigFilePath("swarmserver-cert-public.crt")) && obj.fileExists(parent.getConfigFilePath("swarmserver-cert-private.key"))) { - 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")) }; + if (obj.fileExists("swarmserver-cert-public.crt") && obj.fileExists("swarmserver-cert-private.key")) { + r.swarmserver = { cert: obj.fileLoad("swarmserver-cert-public.crt", "utf8"), key: obj.fileLoad("swarmserver-cert-private.key", "utf8") }; } // If the swarm server root certificate exist, load it (This is an optional certificate) - if (obj.fileExists(parent.getConfigFilePath("swarmserverroot-cert-public.crt"))) { - r.swarmserverroot = { cert: fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath("swarmserverroot-cert-public.crt"), "utf8")) }; + if (obj.fileExists("swarmserverroot-cert-public.crt")) { + r.swarmserverroot = { cert: obj.fileLoad("swarmserverroot-cert-public.crt", "utf8") }; } // If CA certificates are present, load them do { caok = false; - if (obj.fileExists(parent.getConfigFilePath("webserver-cert-chain" + caindex + ".crt"))) { - calist.push(fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath("webserver-cert-chain" + caindex + ".crt"), "utf8"))); + if (obj.fileExists("webserver-cert-chain" + caindex + ".crt")) { + calist.push(obj.fileLoad("webserver-cert-chain" + caindex + ".crt", "utf8")); caok = true; } caindex++; @@ -259,24 +275,24 @@ module.exports.CertificateOperations = function () { dnsname = config.domains[i].dns; if (args.tlsoffload) { // If the web certificate already exist, load it. Load just the certificate since we are in TLS offload situation - if (obj.fileExists(parent.getConfigFilePath("webserver-" + i + "-cert-public.crt"))) { - r.dns[i] = { cert: fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath("webserver-" + i + "-cert-public.crt"), "utf8")) }; + if (obj.fileExists("webserver-" + i + "-cert-public.crt")) { + r.dns[i] = { cert: obj.fileLoad("webserver-" + i + "-cert-public.crt", "utf8") }; config.domains[i].certs = r.dns[i]; } else { console.log("WARNING: File \"webserver-" + i + "-cert-public.crt\" missing, domain \"" + i + "\" will not work correctly."); } } else { // If the web certificate already exist, load it. Load both certificate and private key - if (obj.fileExists(parent.getConfigFilePath("webserver-" + i + "-cert-public.crt")) && obj.fileExists(parent.getConfigFilePath("webserver-" + i + "-cert-private.key"))) { - 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")) }; + if (obj.fileExists("webserver-" + i + "-cert-public.crt") && obj.fileExists("webserver-" + i + "-cert-private.key")) { + r.dns[i] = { cert: obj.fileLoad("webserver-" + i + "-cert-public.crt", "utf8"), key: obj.fileLoad("webserver-" + i + "-cert-private.key", "utf8") }; config.domains[i].certs = r.dns[i]; // If CA certificates are present, load them caindex = 1; r.dns[i].ca = []; do { caok = false; - if (obj.fileExists(parent.getConfigFilePath("webserver-" + i + "-cert-chain" + caindex + ".crt"))) { - r.dns[i].ca.push(fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath("webserver-" + i + "-cert-chain" + caindex + ".crt"), "utf8"))); + if (obj.fileExists("webserver-" + i + "-cert-chain" + caindex + ".crt")) { + r.dns[i].ca.push(obj.fileLoad("webserver-" + i + "-cert-chain" + caindex + ".crt", "utf8")); caok = true; } caindex++; @@ -319,6 +335,8 @@ module.exports.CertificateOperations = function () { if (r.AmtMpsName != mpsCommonName) { forceMpsCertGen = 1; } } } + if (parent.configurationFiles != null) { console.log("Error: Database missing some certificates."); process.exit(0); return null; } + console.log("Generating certificates, may take a few minutes..."); parent.updateServerState("state", "generatingcertificates"); @@ -406,7 +424,7 @@ module.exports.CertificateOperations = function () { dnsname = config.domains[i].dns; if (!args.tlsoffload) { // If the web certificate does not exist, create it - if ((obj.fileExists(parent.getConfigFilePath("webserver-" + i + "-cert-public.crt")) === false) || (obj.fileExists(parent.getConfigFilePath("webserver-" + i + "-cert-private.key")) === false)) { + if ((obj.fileExists("webserver-" + i + "-cert-public.crt") === false) || (obj.fileExists("webserver-" + i + "-cert-private.key") === false)) { console.log("Generating HTTPS certificate for " + i + "..."); var xwebCertAndKey = obj.IssueWebServerCertificate(rootCertAndKey, false, dnsname, country, organization, null, strongCertificate); var xwebCertificate = obj.pki.certificateToPem(xwebCertAndKey.cert); @@ -421,7 +439,7 @@ module.exports.CertificateOperations = function () { r.dns[i].ca = []; do { caok = false; - if (obj.fileExists(parent.getConfigFilePath("webserver-" + i + "-cert-chain" + caindex + ".crt"))) { + if (obj.fileExists("webserver-" + i + "-cert-chain" + caindex + ".crt")) { r.dns[i].ca.push(fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath("webserver-" + i + "-cert-chain" + caindex + ".crt"), "utf8"))); caok = true; } @@ -433,12 +451,12 @@ module.exports.CertificateOperations = function () { } // If the swarm server certificate exist, load it (This is an optional certificate) - if (obj.fileExists(parent.getConfigFilePath("swarmserver-cert-public.crt")) && obj.fileExists(parent.getConfigFilePath("swarmserver-cert-private.key"))) { + if (obj.fileExists("swarmserver-cert-public.crt") && obj.fileExists("swarmserver-cert-private.key")) { 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")) }; } // If the swarm server root certificate exist, load it (This is an optional certificate) - if (obj.fileExists(parent.getConfigFilePath("swarmserverroot-cert-public.crt"))) { + if (obj.fileExists("swarmserverroot-cert-public.crt")) { r.swarmserverroot = { cert: fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath("swarmserverroot-cert-public.crt"), "utf8")) }; } @@ -448,7 +466,7 @@ module.exports.CertificateOperations = function () { r.web.ca = []; do { caok = false; - if (obj.fileExists(parent.getConfigFilePath("webserver-cert-chain" + caindex + ".crt"))) { + if (obj.fileExists("webserver-cert-chain" + caindex + ".crt")) { r.web.ca.push(fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath("webserver-cert-chain" + caindex + ".crt"), "utf8"))); caok = true; } diff --git a/db.js b/db.js index 998a78d1..ab715d57 100644 --- a/db.js +++ b/db.js @@ -176,14 +176,60 @@ module.exports.CreateDB = function (parent) { obj.getLocalAmtNodes = function (func) { obj.file.find({ type: 'node', host: { $exists: true, $ne: null }, intelamt: { $exists: true } }, func); }; obj.getAmtUuidNode = function (meshid, uuid, func) { obj.file.find({ type: 'node', meshid: meshid, 'intelamt.uuid': uuid }, func); }; - // Read a file from the database - obj.getFile = function (path, func) { obj.Get('cfile/' + path, func); } + // Read a configuration file from the database + obj.getConfigFile = function (path, func) { obj.Get('cfile/' + path, func); } - // Write a file to the database - obj.setFile = function (path, data, func) { obj.Set({ _id: 'cfile/' + path, type: 'cfile', data: data.toString('base64') }, func); } + // Write a configuration file to the database + obj.setConfigFile = function (path, data, func) { obj.Set({ _id: 'cfile/' + path, type: 'cfile', data: data.toString('base64') }, func); } - // List all files - obj.listFiles = function (func) { obj.file.find({ type: 'cfile' }).sort({ _id: 1 }).exec(func); } + // List all configuration files + obj.listConfigFiles = function (func) { obj.file.find({ type: 'cfile' }).sort({ _id: 1 }).exec(func); } + + // Get all configuration files + obj.getAllConfigFiles = function (password, func) { + obj.file.find({ type: 'cfile' }, function (err, docs) { + if (err != null) { func(null); return; } + var r = null; + for (var i = 0; i < docs.length; i++) { + var name = docs[i]._id.split('/')[1]; + var data = obj.decryptData(password, docs[i].data); + if (data != null) { if (r == null) { r = {}; } r[name] = data; } + } + func(r); + }); + } + + // Get encryption key + obj.getEncryptDataKey = function (password) { + if (typeof password != 'string') return null; + return obj.parent.crypto.createHash('sha384').update(password).digest("raw").slice(0, 32); + } + + // Encrypt data + obj.encryptData = function (password, plaintext) { + var key = obj.getEncryptDataKey(password); + if (key == null) return null; + const iv = obj.parent.crypto.randomBytes(16); + const aes = obj.parent.crypto.createCipheriv('aes-256-cbc', key, iv); + var ciphertext = aes.update(plaintext); + ciphertext = Buffer.concat([iv, ciphertext, aes.final()]); + return ciphertext.toString('base64'); + } + + // Decrypt data + obj.decryptData = function (password, ciphertext) { + try { + var key = obj.getEncryptDataKey(password); + if (key == null) return null; + const ciphertextBytes = Buffer.from(ciphertext, 'base64'); + const iv = ciphertextBytes.slice(0, 16); + const data = ciphertextBytes.slice(16); + const aes = obj.parent.crypto.createDecipheriv('aes-256-cbc', key, iv); + var plaintextBytes = Buffer.from(aes.update(data)); + plaintextBytes = Buffer.concat([plaintextBytes, aes.final()]); + return plaintextBytes; + } catch (ex) { return null; } + } // 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. obj.getStats = function (func) { diff --git a/meshcentral.js b/meshcentral.js index 98158e8e..11f0aa94 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -40,6 +40,7 @@ function CreateMeshCentralServer(config, args) { obj.platform = require('os').platform(); obj.args = args; obj.common = require('./common.js'); + obj.configurationFiles = null; obj.certificates = null; obj.connectivityByNode = {}; // This object keeps a list of all connected CIRA and agents, by nodeid->value (value: 1 = Agent, 2 = CIRA, 4 = AmtDirect) 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) { 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. // Check for invalid arguments - 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']; + 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']; 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; } } 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; } 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,11 +218,200 @@ function CreateMeshCentralServer(config, args) { // Initiate server self-update obj.performServerCertUpdate = function () { console.log('Updating server certificates...'); process.exit(200); }; + // Look for easy command line instructions and do them here. obj.StartEx = function () { var i; //var wincmd = require('node-windows'); //wincmd.list(function (svc) { console.log(svc); }, true); + if (typeof obj.args.userallowedip == 'string') { if (obj.args.userallowedip == '') { obj.args.userallowedip = null; } else { obj.args.userallowedip = obj.args.userallowedip.split(','); } } + if (typeof obj.args.userblockedip == 'string') { if (obj.args.userblockedip == '') { obj.args.userblockedip = null; } else { obj.args.userblockedip = obj.args.userblockedip.split(','); } } + if (typeof obj.args.agentallowedip == 'string') { if (obj.args.agentallowedip == '') { obj.args.agentallowedip = null; } else { obj.args.agentallowedip = obj.args.agentallowedip.split(','); } } + if (typeof obj.args.agentblockedip == 'string') { if (obj.args.agentblockedip == '') { obj.args.agentblockedip = null; } else { obj.args.agentblockedip = obj.args.agentblockedip.split(','); } } + if (typeof obj.args.swarmallowedip == 'string') { if (obj.args.swarmallowedip == '') { obj.args.swarmallowedip = null; } else { obj.args.swarmallowedip = obj.args.swarmallowedip.split(','); } } + if (typeof obj.args.debug == 'number') obj.debugLevel = obj.args.debug; + if (obj.args.debug == true) obj.debugLevel = 1; + obj.db = require('./db.js').CreateDB(obj); + obj.db.SetupDatabase(function (dbversion) { + // See if any database operations needs to be completed + if (obj.args.deletedomain) { obj.db.DeleteDomain(obj.args.deletedomain, function () { console.log('Deleted domain ' + obj.args.deletedomain + '.'); process.exit(); }); return; } + if (obj.args.deletedefaultdomain) { obj.db.DeleteDomain('', function () { console.log('Deleted default domain.'); process.exit(); }); return; } + if (obj.args.showall) { obj.db.GetAll(function (err, docs) { console.log(docs); process.exit(); }); return; } + if (obj.args.showusers) { obj.db.GetAllType('user', function (err, docs) { console.log(docs); process.exit(); }); return; } + if (obj.args.shownodes) { obj.db.GetAllType('node', function (err, docs) { console.log(docs); process.exit(); }); return; } + if (obj.args.showmeshes) { obj.db.GetAllType('mesh', function (err, docs) { console.log(docs); process.exit(); }); return; } + if (obj.args.showevents) { obj.db.GetAllType('event', function (err, docs) { console.log(docs); process.exit(); }); return; } + if (obj.args.showpower) { obj.db.GetAllType('power', function (err, docs) { console.log(docs); process.exit(); }); return; } + if (obj.args.clearpower) { obj.db.RemoveAllOfType('power', function () { process.exit(); }); return; } + if (obj.args.showiplocations) { obj.db.GetAllType('iploc', function (err, docs) { console.log(docs); process.exit(); }); return; } + if (obj.args.logintoken) { obj.getLoginToken(obj.args.logintoken, function (r) { console.log(r); process.exit(); }); return; } + if (obj.args.logintokenkey) { obj.showLoginTokenKey(function (r) { console.log(r); process.exit(); }); return; } + + // Show a list of all configuration files in the database + 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; + } + + // Display the content of a configuration file in the database + if (obj.args.dbshowconfigfile) { + if (typeof obj.args.configkey != 'string') { console.log('Error, --configkey is required.'); process.exit(); return; } + obj.db.getConfigFile(obj.args.dbshowconfigfile, function (err, docs) { + if (err == null) { + if (docs.length == 0) { console.log('File not found.'); } else { + var data = obj.db.decryptData(obj.args.configkey, docs[0].data); + if (data == null) { console.log('Invalid config key.'); } else { console.log(data); } + } + } else { console.log('Unable to read from database.'); } + process.exit(); + }); return; + } + + // Delete all configuration files from database + if (obj.args.dbdeleteconfigfiles) { + console.log('Deleting all configuration files from the database...'); obj.db.RemoveAllOfType('cfile', function () { console.log('Done.'); process.exit(); }); + } + + // Push all relevent files from meshcentral-data into the database + if (obj.args.dbpushconfigfiles) { + if (typeof obj.args.configkey != 'string') { console.log('Error, --configkey is required.'); process.exit(); return; } + if (typeof obj.args.dbpushconfigfiles != 'string') { + console.log('Usage: --dbpulldatafiles (path) This will import files from folder into the database'); + console.log(' --dbpulldatafiles * This will import files from meshcentral-data into the db.'); + process.exit(); + } else { + obj.db.RemoveAllOfType('cfile', function () { + if (obj.args.dbpushconfigfiles == '*') { obj.args.dbpushconfigfiles = obj.datapath; } + obj.fs.readdir(obj.datapath, (err, files) => { + var lockCount = 1 + for (var i in files) { + const file = files[i]; + if ((file == 'config.json') || file.endsWith('.key') || file.endsWith('.crt') || (file == 'terms.txt') || file.endsWith('.jpg') || file.endsWith('.png')) { + const path = obj.path.join(obj.args.dbpushconfigfiles, files[i]), binary = Buffer.from(obj.fs.readFileSync(path, { encoding: 'binary' }), 'binary'); + console.log('Pushing ' + file + ', ' + binary.length + ' bytes.'); + lockCount++; + obj.db.setConfigFile(file, obj.db.encryptData(obj.args.configkey, binary), function () { if ((--lockCount) == 0) { console.log('Done.'); process.exit(); } }); + } + } + if (--lockCount == 0) { process.exit(); } + }); + }); + } + return; + } + + // Pull all database files into meshcentral-data + if (obj.args.dbpullconfigfiles) { + if (typeof obj.args.configkey != 'string') { console.log('Error, --configkey is required.'); process.exit(); return; } + if (typeof obj.args.dbpullconfigfiles != 'string') { + console.log('Usage: --dbpulldatafiles (path)'); + process.exit(); + } else { + obj.db.GetAllType('cfile', function (err, docs) { + if (err == null) { + if (docs.length == 0) { + console.log('File not found.'); + } else { + for (var i in docs) { + const file = docs[i]._id.split('/')[1], binary = obj.db.decryptData(obj.args.configkey, docs[i].data); + if (binary == null) { + console.log('Invalid config key.'); + } else { + obj.fs.writeFileSync(obj.path.join(obj.args.dbpullconfigfiles, file), binary); + console.log('Pulling ' + file + ', ' + binary.length + ' bytes.'); + } + } + } + } else { + console.log('Unable to read from database.'); + } + process.exit(); + }); + } + return; + } + + if (obj.args.dbexport) { + // Export the entire database to a JSON file + if (obj.args.dbexport == true) { obj.args.dbexport = obj.getConfigFilePath('meshcentral.db.json'); } + obj.db.GetAll(function (err, docs) { + obj.fs.writeFileSync(obj.args.dbexport, JSON.stringify(docs)); + console.log('Exported ' + docs.length + ' objects(s) to ' + obj.args.dbexport + '.'); process.exit(); + }); + return; + } + if (obj.args.dbexportmin) { + // Export a minimal database to a JSON file. Export only users, meshes and nodes. + // This is a useful command to look at the database. + if (obj.args.dbexportmin == true) { obj.args.dbexportmin = obj.getConfigFilePath('meshcentral.db.json'); } + obj.db.GetAllType({ $in: ['user', 'node', 'mesh'] }, function (err, docs) { + obj.fs.writeFileSync(obj.args.dbexportmin, JSON.stringify(docs)); + console.log('Exported ' + docs.length + ' objects(s) to ' + obj.args.dbexportmin + '.'); process.exit(); + }); + return; + } + if (obj.args.dbimport) { + // Import the entire database from a JSON file + if (obj.args.dbimport == true) { obj.args.dbimport = obj.getConfigFilePath('meshcentral.db.json'); } + var json = null, json2 = "", badCharCount = 0; + try { json = obj.fs.readFileSync(obj.args.dbimport, { encoding: 'utf8' }); } catch (e) { console.log('Invalid JSON file: ' + obj.args.dbimport + '.'); process.exit(); } + for (i = 0; i < json.length; i++) { if (json.charCodeAt(i) >= 32) { json2 += json[i]; } else { var tt = json.charCodeAt(i); if (tt != 10 && tt != 13) { badCharCount++; } } } // Remove all bad chars + if (badCharCount > 0) { console.log(badCharCount + ' invalid character(s) where removed.'); } + try { json = JSON.parse(json2); } catch (e) { console.log('Invalid JSON format: ' + obj.args.dbimport + ': ' + e); process.exit(); } + if ((json == null) || (typeof json.length != 'number') || (json.length < 1)) { console.log('Invalid JSON format: ' + obj.args.dbimport + '.'); } + for (i in json) { if ((json[i].type == "mesh") && (json[i].links != null)) { for (var j in json[i].links) { var esc = obj.common.escapeFieldName(j); if (esc !== j) { json[i].links[esc] = json[i].links[j]; delete json[i].links[j]; } } } } // Escape MongoDB invalid field chars + //for (i in json) { if ((json[i].type == "node") && (json[i].host != null)) { json[i].rname = json[i].host; delete json[i].host; } } // DEBUG: Change host to rname + obj.db.RemoveAll(function () { obj.db.InsertMany(json, function (err) { if (err != null) { console.log(err); } else { console.log('Imported ' + json.length + ' objects(s) from ' + obj.args.dbimport + '.'); } process.exit(); }); }); + return; + } + + // Load configuration for database if needed + if (obj.args.loadconfigfromdb) { + var key = null; + if (typeof obj.args.configkey == 'string') { key = obj.args.configkey; } + else if (typeof obj.args.loadconfigfromdb == 'string') { key = obj.args.loadconfigfromdb; } + if (key == null) { console.log('Error, --configkey is required.'); process.exit(); return; } + obj.db.getAllConfigFiles(key, function (configFiles) { + if (configFiles == null) { console.log('Error, no configuration files found or invalid configkey.'); process.exit(); return; } + if (!configFiles['config.json']) { console.log('Error, could not file config.json from database.'); process.exit(); return; } + obj.configurationFiles = configFiles; + + // Parse the new configuration file + var config2 = null; + try { config2 = JSON.parse(configFiles['config.json']); } catch (ex) { console.log('Error, unable to parse config.json from database.'); process.exit(); return; } + + // Set the command line arguments to the config file if they are not present + if (!config2.settings) { config2.settings = {}; } + for (i in args) { config2.settings[i] = args[i]; } + + // Lower case all keys in the config file + try { + require('./common.js').objKeysToLower(config2); + } catch (ex) { + console.log('CRITICAL ERROR: Unable to access the file \"./common.js\".\r\nCheck folder & file permissions.'); + process.exit(); + return; + } + + // Grad some of the values from the original config.json file if present. + config2['mongodb'] = config['mongodb']; + config2['mongodbcol'] = config['mongodbcol']; + config2['dbencryptkey'] = config['dbencryptkey']; + + // We got a new config.json from the database, let's use it. + config = obj.config = config2; + obj.StartEx1b(); + }); + } else { + config = obj.config = getConfig(true); + obj.StartEx1b(); + } + }); + }; + + // Time to start the serverf or real. + obj.StartEx1b = function () { + var i; + // If we are targetting a specific version, update now. if (typeof obj.args.selfupdate == 'string') { obj.args.selfupdate = obj.args.selfupdate.toLowerCase(); @@ -269,212 +459,101 @@ function CreateMeshCentralServer(config, args) { if (obj.args.mpsaliasport != null && (typeof obj.args.mpsaliasport != 'number')) obj.args.mpsaliasport = null; if (obj.args.notls == null && obj.args.redirport == null) obj.args.redirport = 80; if (obj.args.minifycore === 0) obj.args.minifycore = false; - if (typeof obj.args.userallowedip == 'string') { if (obj.args.userallowedip == '') { obj.args.userallowedip = null; } else { obj.args.userallowedip = obj.args.userallowedip.split(','); } } - if (typeof obj.args.userblockedip == 'string') { if (obj.args.userblockedip == '') { obj.args.userblockedip = null; } else { obj.args.userblockedip = obj.args.userblockedip.split(','); } } - if (typeof obj.args.agentallowedip == 'string') { if (obj.args.agentallowedip == '') { obj.args.agentallowedip = null; } else { obj.args.agentallowedip = obj.args.agentallowedip.split(','); } } - if (typeof obj.args.agentblockedip == 'string') { if (obj.args.agentblockedip == '') { obj.args.agentblockedip = null; } else { obj.args.agentblockedip = obj.args.agentblockedip.split(','); } } - if (typeof obj.args.swarmallowedip == 'string') { if (obj.args.swarmallowedip == '') { obj.args.swarmallowedip = null; } else { obj.args.swarmallowedip = obj.args.swarmallowedip.split(','); } } - if (typeof obj.args.debug == 'number') obj.debugLevel = obj.args.debug; - if (obj.args.debug == true) obj.debugLevel = 1; - obj.db = require('./db.js').CreateDB(obj); - obj.db.SetupDatabase(function (dbversion) { - // See if any database operations needs to be completed - if (obj.args.deletedomain) { obj.db.DeleteDomain(obj.args.deletedomain, function () { console.log('Deleted domain ' + obj.args.deletedomain + '.'); process.exit(); }); return; } - if (obj.args.deletedefaultdomain) { obj.db.DeleteDomain('', function () { console.log('Deleted default domain.'); process.exit(); }); return; } - if (obj.args.showall) { obj.db.GetAll(function (err, docs) { console.log(docs); process.exit(); }); return; } - if (obj.args.showusers) { obj.db.GetAllType('user', function (err, docs) { console.log(docs); process.exit(); }); return; } - if (obj.args.shownodes) { obj.db.GetAllType('node', function (err, docs) { console.log(docs); process.exit(); }); return; } - if (obj.args.showmeshes) { obj.db.GetAllType('mesh', function (err, docs) { console.log(docs); process.exit(); }); return; } - if (obj.args.showevents) { obj.db.GetAllType('event', function (err, docs) { console.log(docs); process.exit(); }); return; } - if (obj.args.showpower) { obj.db.GetAllType('power', function (err, docs) { console.log(docs); process.exit(); }); return; } - if (obj.args.clearpower) { obj.db.RemoveAllOfType('power', function () { process.exit(); }); return; } - if (obj.args.showiplocations) { obj.db.GetAllType('iploc', function (err, docs) { console.log(docs); process.exit(); }); return; } - if (obj.args.logintoken) { obj.getLoginToken(obj.args.logintoken, function (r) { console.log(r); process.exit(); }); return; } - if (obj.args.logintokenkey) { obj.showLoginTokenKey(function (r) { console.log(r); process.exit(); }); return; } - 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; } - 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; } - 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 - // Push all relevent files from meshcentral-data into the database - if (obj.args.dbpushconfigfiles) { - if (typeof obj.args.dbpushconfigfiles != 'string') { - console.log('Usage: --dbpulldatafiles (path) This will import files from folder into the database'); - console.log(' --dbpulldatafiles * This will import files from meshcentral-data into the db.'); + // Clear old event entries and power entires + obj.db.clearOldEntries('event', 30); // Clear all event entires that are older than 30 days. + 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. + + // Setup a site administrator + if ((obj.args.admin) && (typeof obj.args.admin == 'string')) { + var adminname = obj.args.admin.split('/'); + if (adminname.length == 1) { adminname = 'user//' + adminname[0]; } + else if (adminname.length == 2) { adminname = 'user/' + adminname[0] + '/' + adminname[1]; } + else { console.log('Invalid administrator name.'); process.exit(); return; } + obj.db.Get(adminname, function (err, user) { + if (user.length != 1) { console.log('Invalid user name.'); process.exit(); return; } + user[0].siteadmin = 0xFFFFFFFF; + obj.db.Set(user[0], function () { + 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.'); } process.exit(); - } else { - if (obj.args.dbpushconfigfiles == '*') { obj.args.dbpushconfigfiles = obj.datapath; } - obj.fs.readdir(obj.datapath, (err, files) => { - var lockCount = 1 - for (var i in files) { - const file = files[i]; - if (file.endsWith('.json') || file.endsWith('.key') || file.endsWith('.crt')) { - const path = obj.path.join(obj.args.dbpushconfigfiles, files[i]), binary = Buffer.from(obj.fs.readFileSync(path, { encoding: 'binary' }), 'binary'); - console.log('Pushing ' + file + ', ' + binary.length + ' bytes.'); - lockCount++; - obj.db.setFile(file, binary, function () { if ((--lockCount) == 0) { console.log('Done.'); process.exit(); } }); - } - } - if (--lockCount == 0) { process.exit(); } - }) - } - return; - } - - // Pull all database files into meshcentral-data - if (obj.args.dbpullconfigfiles) { - if (typeof obj.args.dbpullconfigfiles != 'string') { - console.log('Usage: --dbpulldatafiles (path)'); - process.exit(); - } else { - obj.db.GetAllType('cfile', function (err, docs) { - if (err == null) { - if (docs.length == 0) { - console.log('File not found.'); - } else { - for (var i in docs) { - const file = docs[i]._id.split('/')[1], binary = Buffer.from(docs[i].data, 'base64'); - obj.fs.writeFileSync(obj.path.join(obj.args.dbpullconfigfiles, file), binary); - console.log('Pulling ' + file + ', ' + binary.length + ' bytes.'); - } - } - } else { - console.log('Unable to read from database.'); - } - process.exit(); - }); - } - return; - } - - if (obj.args.dbexport) { - // Export the entire database to a JSON file - if (obj.args.dbexport == true) { obj.args.dbexport = obj.getConfigFilePath('meshcentral.db.json'); } - obj.db.GetAll(function (err, docs) { - obj.fs.writeFileSync(obj.args.dbexport, JSON.stringify(docs)); - console.log('Exported ' + docs.length + ' objects(s) to ' + obj.args.dbexport + '.'); process.exit(); - }); - return; - } - if (obj.args.dbexportmin) { - // Export a minimal database to a JSON file. Export only users, meshes and nodes. - // This is a useful command to look at the database. - if (obj.args.dbexportmin == true) { obj.args.dbexportmin = obj.getConfigFilePath('meshcentral.db.json'); } - obj.db.GetAllType({ $in: ['user', 'node', 'mesh'] }, function (err, docs) { - obj.fs.writeFileSync(obj.args.dbexportmin, JSON.stringify(docs)); - console.log('Exported ' + docs.length + ' objects(s) to ' + obj.args.dbexportmin + '.'); process.exit(); - }); - return; - } - if (obj.args.dbimport) { - // Import the entire database from a JSON file - if (obj.args.dbimport == true) { obj.args.dbimport = obj.getConfigFilePath('meshcentral.db.json'); } - var json = null, json2 = "", badCharCount = 0; - try { json = obj.fs.readFileSync(obj.args.dbimport, { encoding: 'utf8' }); } catch (e) { console.log('Invalid JSON file: ' + obj.args.dbimport + '.'); process.exit(); } - for (i = 0; i < json.length; i++) { if (json.charCodeAt(i) >= 32) { json2 += json[i]; } else { var tt = json.charCodeAt(i); if (tt != 10 && tt != 13) { badCharCount++; } } } // Remove all bad chars - if (badCharCount > 0) { console.log(badCharCount + ' invalid character(s) where removed.'); } - try { json = JSON.parse(json2); } catch (e) { console.log('Invalid JSON format: ' + obj.args.dbimport + ': ' + e); process.exit(); } - if ((json == null) || (typeof json.length != 'number') || (json.length < 1)) { console.log('Invalid JSON format: ' + obj.args.dbimport + '.'); } - for (i in json) { if ((json[i].type == "mesh") && (json[i].links != null)) { for (var j in json[i].links) { var esc = obj.common.escapeFieldName(j); if (esc !== j) { json[i].links[esc] = json[i].links[j]; delete json[i].links[j]; } } } } // Escape MongoDB invalid field chars - //for (i in json) { if ((json[i].type == "node") && (json[i].host != null)) { json[i].rname = json[i].host; delete json[i].host; } } // DEBUG: Change host to rname - obj.db.RemoveAll(function () { obj.db.InsertMany(json, function (err) { if (err != null) { console.log(err); } else { console.log('Imported ' + json.length + ' objects(s) from ' + obj.args.dbimport + '.'); } process.exit(); }); }); - return; - } - - // Clear old event entries and power entires - obj.db.clearOldEntries('event', 30); // Clear all event entires that are older than 30 days. - 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. - - // Setup a site administrator - if ((obj.args.admin) && (typeof obj.args.admin == 'string')) { - var adminname = obj.args.admin.split('/'); - if (adminname.length == 1) { adminname = 'user//' + adminname[0]; } - else if (adminname.length == 2) { adminname = 'user/' + adminname[0] + '/' + adminname[1]; } - else { console.log('Invalid administrator name.'); process.exit(); return; } - obj.db.Get(adminname, function (err, user) { - if (user.length != 1) { console.log('Invalid user name.'); process.exit(); return; } - user[0].siteadmin = 0xFFFFFFFF; - obj.db.Set(user[0], function () { - 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.'); } - process.exit(); - return; - }); - }); - return; - } - - // Remove a site administrator - if ((obj.args.unadmin) && (typeof obj.args.unadmin == 'string')) { - var adminname = obj.args.unadmin.split('/'); - if (adminname.length == 1) { adminname = 'user//' + adminname[0]; } - else if (adminname.length == 2) { adminname = 'user/' + adminname[0] + '/' + adminname[1]; } - else { console.log('Invalid administrator name.'); process.exit(); return; } - obj.db.Get(adminname, function (err, user) { - if (user.length != 1) { console.log('Invalid user name.'); process.exit(); return; } - if (user[0].siteadmin) { delete user[0].siteadmin; } - obj.db.Set(user[0], function () { - 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.'); } - process.exit(); - return; - }); - }); - return; - } - - // Perform other database cleanup - obj.db.cleanup(); - - // Set all nodes to power state of unknown (0) - if (obj.multiServer == null) { - obj.db.file.insert({ type: 'power', time: Date.now(), node: '*', power: 0, s: 1 }); - } else { - obj.db.file.insert({ type: 'power', time: Date.now(), node: '*', power: 0, s: 1, server: obj.multiServer.serverid }); - } - - // Read or setup database configuration values - obj.db.Get('dbconfig', function (err, dbconfig) { - if (dbconfig.length == 1) { obj.dbconfig = dbconfig[0]; } else { obj.dbconfig = { _id: 'dbconfig', version: 1 }; } - if (obj.dbconfig.amtWsEventSecret == null) { obj.crypto.randomBytes(32, function (err, buf) { obj.dbconfig.amtWsEventSecret = buf.toString('hex'); obj.db.Set(obj.dbconfig); }); } - - // This is used by the user to create a username/password for a Intel AMT WSMAN event subscription - if (obj.args.getwspass) { - if (obj.args.getwspass.length == 64) { - obj.crypto.randomBytes(6, function (err, buf) { - while (obj.dbconfig.amtWsEventSecret == null) { process.nextTick(); } - var username = buf.toString('hex'); - var nodeid = obj.args.getwspass; - var pass = obj.crypto.createHash('sha384').update(username.toLowerCase() + ":" + nodeid + ":" + obj.dbconfig.amtWsEventSecret).digest("base64").substring(0, 12).split("/").join("x").split("\\").join("x"); - console.log('--- Intel(r) AMT WSMAN eventing credentials ---'); - console.log('Username: ' + username); - console.log('Password: ' + pass); - console.log('Argument: ' + nodeid); - process.exit(); - }); - } else { - console.log('Invalid NodeID.'); - process.exit(); - } return; - } - - // Load the default meshcore and meshcmd - obj.updateMeshCore(); - obj.updateMeshCmd(); - - // Setup and start the redirection server if needed. We must start the redirection server before Let's Encrypt. - if ((obj.args.redirport != null) && (typeof obj.args.redirport == 'number') && (obj.args.redirport != 0)) { - obj.redirserver = require('./redirserver.js').CreateRedirServer(obj, obj.db, obj.args, obj.StartEx2); - } else { - obj.StartEx2(); // If not needed, move on. - } + }); }); + return; + } + + // Remove a site administrator + if ((obj.args.unadmin) && (typeof obj.args.unadmin == 'string')) { + var adminname = obj.args.unadmin.split('/'); + if (adminname.length == 1) { adminname = 'user//' + adminname[0]; } + else if (adminname.length == 2) { adminname = 'user/' + adminname[0] + '/' + adminname[1]; } + else { console.log('Invalid administrator name.'); process.exit(); return; } + obj.db.Get(adminname, function (err, user) { + if (user.length != 1) { console.log('Invalid user name.'); process.exit(); return; } + if (user[0].siteadmin) { delete user[0].siteadmin; } + obj.db.Set(user[0], function () { + 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.'); } + process.exit(); + return; + }); + }); + return; + } + + // Perform other database cleanup + obj.db.cleanup(); + + // Set all nodes to power state of unknown (0) + if (obj.multiServer == null) { + obj.db.file.insert({ type: 'power', time: Date.now(), node: '*', power: 0, s: 1 }); + } else { + obj.db.file.insert({ type: 'power', time: Date.now(), node: '*', power: 0, s: 1, server: obj.multiServer.serverid }); + } + + // Read or setup database configuration values + obj.db.Get('dbconfig', function (err, dbconfig) { + if (dbconfig.length == 1) { obj.dbconfig = dbconfig[0]; } else { obj.dbconfig = { _id: 'dbconfig', version: 1 }; } + if (obj.dbconfig.amtWsEventSecret == null) { obj.crypto.randomBytes(32, function (err, buf) { obj.dbconfig.amtWsEventSecret = buf.toString('hex'); obj.db.Set(obj.dbconfig); }); } + + // This is used by the user to create a username/password for a Intel AMT WSMAN event subscription + if (obj.args.getwspass) { + if (obj.args.getwspass.length == 64) { + obj.crypto.randomBytes(6, function (err, buf) { + while (obj.dbconfig.amtWsEventSecret == null) { process.nextTick(); } + var username = buf.toString('hex'); + var nodeid = obj.args.getwspass; + var pass = obj.crypto.createHash('sha384').update(username.toLowerCase() + ":" + nodeid + ":" + obj.dbconfig.amtWsEventSecret).digest("base64").substring(0, 12).split("/").join("x").split("\\").join("x"); + console.log('--- Intel(r) AMT WSMAN eventing credentials ---'); + console.log('Username: ' + username); + console.log('Password: ' + pass); + console.log('Argument: ' + nodeid); + process.exit(); + }); + } else { + console.log('Invalid NodeID.'); + process.exit(); + } + return; + } + + // Load the default meshcore and meshcmd + obj.updateMeshCore(); + obj.updateMeshCmd(); + + // Setup and start the redirection server if needed. We must start the redirection server before Let's Encrypt. + if ((obj.args.redirport != null) && (typeof obj.args.redirport == 'number') && (obj.args.redirport != 0)) { + obj.redirserver = require('./redirserver.js').CreateRedirServer(obj, obj.db, obj.args, obj.StartEx2); + } else { + obj.StartEx2(); // If not needed, move on. + } }); - }; + } // Done starting the redirection server, go on to load the server certificates obj.StartEx2 = function () { // Load server certificates - obj.certificateOperations = require('./certoperations.js').CertificateOperations(); - obj.certificateOperations.GetMeshServerCertificate(obj, obj.args, obj.config, function (certs) { + obj.certificateOperations = require('./certoperations.js').CertificateOperations(obj); + obj.certificateOperations.GetMeshServerCertificate(obj.args, obj.config, function (certs) { if ((obj.config.letsencrypt == null) || (obj.redirserver == null)) { obj.StartEx3(certs); // Just use the configured certificates } else { @@ -1355,7 +1434,7 @@ function CreateMeshCentralServer(config, args) { } // Return the server configuration -function getConfig() { +function getConfig(createSampleConfig) { // Figure out the datapath location var i; var fs = require('fs'); @@ -1378,9 +1457,11 @@ function getConfig() { if (config.domains == null) { config.domains = {}; } 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; } } } else { - // Copy the "sample-config.json" to give users a starting point - var sampleConfigPath = path.join(__dirname, 'sample-config.json'); - if (fs.existsSync(sampleConfigPath)) { fs.createReadStream(sampleConfigPath).pipe(fs.createWriteStream(configFilePath)); } + if (createSampleConfig === true) { + // Copy the "sample-config.json" to give users a starting point + var sampleConfigPath = path.join(__dirname, 'sample-config.json'); + if (fs.existsSync(sampleConfigPath)) { fs.createReadStream(sampleConfigPath).pipe(fs.createWriteStream(configFilePath)); } + } } // Set the command line arguments to the config file if they are not present @@ -1431,7 +1512,7 @@ function mainStart(args) { // Check for any missing modules. InstallModules(['minimist'], function () { // Get the server configuration - var config = getConfig(); + var config = getConfig(false); if (config == null) { process.exit(); } // Check is Windows SSPI will be used diff --git a/package.json b/package.json index 75d4fedf..15e8e732 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.2.7-g", + "version": "0.2.7-h", "keywords": [ "Remote Management", "Intel AMT", diff --git a/webserver.js b/webserver.js index 8f895605..ca5d2c37 100644 --- a/webserver.js +++ b/webserver.js @@ -873,31 +873,44 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { const domain = checkUserIpAddress(req, res); if (domain == null) return; - // See if there is a terms.txt file in meshcentral-data - var p = obj.path.join(obj.parent.datapath, 'terms.txt'); - if (obj.fs.existsSync(p)) { - obj.fs.readFile(p, 'utf8', function (err, data) { - if (err != null) { res.sendStatus(404); return; } - - // Send the terms - res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' }); - if (req.session && req.session.userid) { - if (req.session.domainid != domain.id) { req.session = null; res.redirect(domain.url); return; } // Check is the session is for the correct domain - var user = obj.users[req.session.userid]; - 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 + '. Logout' }); - } else { - res.render(obj.path.join(obj.parent.webViewsPath, isMobileBrowser(req) ? 'terms-mobile' : 'terms'), { title: domain.title, title2: domain.title2, terms: encodeURIComponent(data) }); - } - }); - } else { - // Send the terms + // See if term.txt was loaded from the database + if ((parent.configurationFiles != null) && (parent.configurationFiles['terms.txt'] != null)) { + // Send the terms from the database res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' }); if (req.session && req.session.userid) { if (req.session.domainid != domain.id) { req.session = null; res.redirect(domain.url); return; } // Check is the session is for the correct domain var user = obj.users[req.session.userid]; - res.render(obj.path.join(obj.parent.webViewsPath, isMobileBrowser(req) ? 'terms-mobile' : 'terms'), { title: domain.title, title2: domain.title2, logoutControl: 'Welcome ' + user.name + '. Logout' }); + 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 + '. Logout' }); } else { - res.render(obj.path.join(obj.parent.webViewsPath, isMobileBrowser(req) ? 'terms-mobile' : 'terms'), { title: domain.title, title2: domain.title2 }); + 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()) }); + } + } else { + // See if there is a terms.txt file in meshcentral-data + var p = obj.path.join(obj.parent.datapath, 'terms.txt'); + if (obj.fs.existsSync(p)) { + obj.fs.readFile(p, 'utf8', function (err, data) { + if (err != null) { res.sendStatus(404); return; } + + // Send the terms from terms.txt + res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' }); + if (req.session && req.session.userid) { + if (req.session.domainid != domain.id) { req.session = null; res.redirect(domain.url); return; } // Check is the session is for the correct domain + var user = obj.users[req.session.userid]; + 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 + '. Logout' }); + } else { + res.render(obj.path.join(obj.parent.webViewsPath, isMobileBrowser(req) ? 'terms-mobile' : 'terms'), { title: domain.title, title2: domain.title2, terms: encodeURIComponent(data) }); + } + }); + } else { + // Send the default terms + res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' }); + if (req.session && req.session.userid) { + if (req.session.domainid != domain.id) { req.session = null; res.redirect(domain.url); return; } // Check is the session is for the correct domain + var user = obj.users[req.session.userid]; + res.render(obj.path.join(obj.parent.webViewsPath, isMobileBrowser(req) ? 'terms-mobile' : 'terms'), { title: domain.title, title2: domain.title2, logoutControl: 'Welcome ' + user.name + '. Logout' }); + } else { + res.render(obj.path.join(obj.parent.webViewsPath, isMobileBrowser(req) ? 'terms-mobile' : 'terms'), { title: domain.title, title2: domain.title2 }); + } } } } @@ -1032,8 +1045,15 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { res.set({ 'Cache-Control': 'max-age=86400' }); // 1 day if ((domain != null) && domain.titlepicture) { - try { res.sendFile(obj.path.join(obj.parent.datapath, domain.titlepicture)); } catch (e) { - try { res.sendFile(obj.path.join(obj.parent.webPublicPath, 'images/logoback.png')); } catch (e) { res.sendStatus(404); } + if ((parent.configurationFiles != null) && (parent.configurationFiles[domain.titlepicture] != null)) { + // Use the logo in the database + res.set({ 'Content-Type': 'image/jpeg' }); + res.send(parent.configurationFiles[domain.titlepicture]); + } else { + // Use the logo on file + try { res.sendFile(obj.path.join(obj.parent.datapath, domain.titlepicture)); } catch (e) { + try { res.sendFile(obj.path.join(obj.parent.webPublicPath, 'images/logoback.png')); } catch (e) { res.sendStatus(404); } + } } } else { 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) { // Server picture obj.app.get(url + 'serverpic.ashx', function (req, res) { - // Check if we have "server.png" in the data folder, if so, use that. - var p = obj.path.join(obj.parent.datapath, 'server.jpg'); - if (obj.fs.existsSync(p)) { - // Use the data folder server picture - try { res.sendFile(p); } catch (e) { res.sendStatus(404); } + // Check if we have "server.jpg" in the data folder, if so, use that. + if ((parent.configurationFiles != null) && (parent.configurationFiles['server.jpg'] != null)) { + res.set({ 'Content-Type': 'image/jpeg' }); + res.send(parent.configurationFiles['server.jpg']); } else { - // Use the default server picture - try { res.sendFile(obj.path.join(obj.parent.webPublicPath, 'images/server-200.jpg')); } catch (e) { res.sendStatus(404); } + // Check if we have "server.jpg" in the data folder, if so, use that. + var p = obj.path.join(obj.parent.datapath, 'server.jpg'); + if (obj.fs.existsSync(p)) { + // Use the data folder server picture + try { res.sendFile(p); } catch (e) { res.sendStatus(404); } + } else { + // Use the default server picture + try { res.sendFile(obj.path.join(obj.parent.webPublicPath, 'images/server-200.jpg')); } catch (e) { res.sendStatus(404); } + } } });