UNPKG

meshcentral

Version:

Web based remote computer management server

677 lines (630 loc) • 256 kB
/** * @description MeshCentral main module * @author Ylian Saint-Hilaire * @copyright Intel Corporation 2018-2021 * @license Apache-2.0 * @version v0.0.1 */ /*xjslint node: true */ /*xjslint plusplus: true */ /*xjslint maxlen: 256 */ /*jshint node: true */ /*jshint strict: false */ /*jshint esversion: 6 */ "use strict"; const common = require('./common.js'); // If app metrics is available if (process.argv[2] == '--launch') { try { require('appmetrics-dash').monitor({ url: '/', title: 'MeshCentral', port: 88, host: '127.0.0.1' }); } catch (e) { } } function CreateMeshCentralServer(config, args) { var obj = {}; obj.db = null; obj.webserver = null; obj.redirserver = null; obj.mpsserver = null; obj.mqttbroker = null; obj.swarmserver = null; obj.smsserver = null; obj.amtEventHandler = null; obj.pluginHandler = null; obj.amtScanner = null; obj.amtManager = null; obj.meshScanner = null; obj.letsencrypt = null; obj.eventsDispatch = {}; obj.fs = require('fs'); obj.path = require('path'); obj.crypto = require('crypto'); obj.exeHandler = require('./exeHandler.js'); obj.platform = require('os').platform(); obj.args = args; obj.common = common; 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) obj.debugSources = []; obj.debugRemoteSources = null; obj.config = config; // Configuration file obj.dbconfig = {}; // Persistance values, loaded from database obj.certificateOperations = null; obj.defaultMeshCmd = null; obj.defaultMeshCores = {}; obj.defaultMeshCoresDeflate = {}; obj.defaultMeshCoresHash = {}; obj.meshToolsBinaries = {}; // Mesh Tools Binaries, ToolName --> { hash:(sha384 hash), size:(binary size), path:(binary path) } obj.meshAgentBinaries = {}; // Mesh Agent Binaries, Architecture type --> { hash:(sha384 hash), size:(binary size), path:(binary path) } obj.meshAgentInstallScripts = {}; // Mesh Install Scripts, Script ID -- { hash:(sha384 hash), size:(binary size), path:(binary path) } obj.multiServer = null; obj.maintenanceTimer = null; obj.serverId = null; obj.serverKey = Buffer.from(obj.crypto.randomBytes(48), 'binary'); obj.loginCookieEncryptionKey = null; obj.invitationLinkEncryptionKey = null; obj.serverSelfWriteAllowed = true; obj.serverStatsCounter = Math.floor(Math.random() * 1000); obj.taskLimiter = obj.common.createTaskLimiterQueue(50, 20, 60); // (maxTasks, maxTaskTime, cleaningInterval) This is a task limiter queue to smooth out server work. obj.agentUpdateBlockSize = 65531; // MeshAgent update block size obj.serverWarnings = []; // List of warnings that should be shown to administrators obj.cookieUseOnceTable = {}; // List of cookies that are already expired obj.cookieUseOnceTableCleanCounter = 0; // Clean the cookieUseOnceTable each 20 additions obj.firstStats = true; // True until this server saves it's not stats to the database // Server version obj.currentVer = null; function getCurrentVersion() { try { obj.currentVer = JSON.parse(obj.fs.readFileSync(obj.path.join(__dirname, 'package.json'), 'utf8')).version; } catch (e) { } return obj.currentVer; } // Fetch server version getCurrentVersion(); // Setup the default configuration and files paths if ((__dirname.endsWith('/node_modules/meshcentral')) || (__dirname.endsWith('\\node_modules\\meshcentral')) || (__dirname.endsWith('/node_modules/meshcentral/')) || (__dirname.endsWith('\\node_modules\\meshcentral\\'))) { obj.parentpath = obj.path.join(__dirname, '../..'); obj.datapath = obj.path.join(__dirname, '../../meshcentral-data'); obj.filespath = obj.path.join(__dirname, '../../meshcentral-files'); obj.backuppath = obj.path.join(__dirname, '../../meshcentral-backup'); obj.recordpath = obj.path.join(__dirname, '../../meshcentral-recordings'); obj.webViewsPath = obj.path.join(__dirname, 'views'); obj.webPublicPath = obj.path.join(__dirname, 'public'); obj.webEmailsPath = obj.path.join(__dirname, 'emails'); if (obj.fs.existsSync(obj.path.join(__dirname, '../../meshcentral-web/views'))) { obj.webViewsOverridePath = obj.path.join(__dirname, '../../meshcentral-web/views'); } if (obj.fs.existsSync(obj.path.join(__dirname, '../../meshcentral-web/public'))) { obj.webPublicOverridePath = obj.path.join(__dirname, '../../meshcentral-web/public'); } if (obj.fs.existsSync(obj.path.join(__dirname, '../../meshcentral-web/emails'))) { obj.webEmailsOverridePath = obj.path.join(__dirname, '../../meshcentral-web/emails'); } } else { obj.parentpath = __dirname; obj.datapath = obj.path.join(__dirname, '../meshcentral-data'); obj.filespath = obj.path.join(__dirname, '../meshcentral-files'); obj.backuppath = obj.path.join(__dirname, '../meshcentral-backups'); obj.recordpath = obj.path.join(__dirname, '../meshcentral-recordings'); obj.webViewsPath = obj.path.join(__dirname, 'views'); obj.webPublicPath = obj.path.join(__dirname, 'public'); obj.webEmailsPath = obj.path.join(__dirname, 'emails'); if (obj.fs.existsSync(obj.path.join(__dirname, '../meshcentral-web/views'))) { obj.webViewsOverridePath = obj.path.join(__dirname, '../meshcentral-web/views'); } if (obj.fs.existsSync(obj.path.join(__dirname, '../meshcentral-web/public'))) { obj.webPublicOverridePath = obj.path.join(__dirname, '../meshcentral-web/public'); } if (obj.fs.existsSync(obj.path.join(__dirname, '../meshcentral-web/emails'))) { obj.webEmailsOverridePath = obj.path.join(__dirname, '../meshcentral-web/emails'); } } // Clean up any temporary files var removeTime = new Date(Date.now()).getTime() - (30 * 60 * 1000); // 30 minutes var dir = obj.fs.readdir(obj.path.join(obj.filespath, 'tmp'), function (err, files) { if (err != null) return; for (var i in files) { try { const filepath = obj.path.join(obj.filespath, 'tmp', files[i]); if (obj.fs.statSync(filepath).mtime.getTime() < removeTime) { obj.fs.unlink(filepath, function () { }); } } catch (ex) { } } }); // Look to see if data and/or file path is specified if (obj.config.settings && (typeof obj.config.settings.datapath == 'string')) { obj.datapath = obj.config.settings.datapath; } if (obj.config.settings && (typeof obj.config.settings.filespath == 'string')) { obj.filespath = obj.config.settings.filespath; } // Create data and files folders if needed try { obj.fs.mkdirSync(obj.datapath); } catch (e) { } try { obj.fs.mkdirSync(obj.filespath); } catch (e) { } // Windows Specific Code, setup service and event log obj.service = null; obj.servicelog = null; if (obj.platform == 'win32') { var nodewindows = require('node-windows'); obj.service = nodewindows.Service; var eventlogger = nodewindows.EventLogger; obj.servicelog = new eventlogger('MeshCentral'); } // Start the Meshcentral server obj.Start = function () { var i; try { require('./pass').hash('test', function () { }, 0); } 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 = ['_', 'user', 'port', 'aliasport', 'mpsport', 'mpsaliasport', 'redirport', 'rediraliasport', 'cert', 'mpscert', 'deletedomain', 'deletedefaultdomain', 'showall', 'showusers', 'showitem', 'listuserids', 'showusergroups', 'shownodes', 'showallmeshes', 'showmeshes', 'showevents', 'showsmbios', 'showpower', 'clearpower', 'showiplocations', 'help', 'exactports', 'xinstall', 'xuninstall', 'install', 'uninstall', 'start', 'stop', 'restart', 'debug', 'filespath', 'datapath', 'noagentupdate', 'launch', 'noserverbackup', 'mongodb', 'mongodbcol', 'wanonly', 'lanonly', 'nousers', 'mpspass', 'ciralocalfqdn', 'dbexport', 'dbexportmin', 'dbimport', 'dbmerge', 'dbfix', 'dbencryptkey', 'selfupdate', 'tlsoffload', 'userallowedip', 'userblockedip', 'swarmallowedip', 'agentallowedip', 'agentblockedip', 'fastcert', 'swarmport', 'logintoken', 'logintokenkey', 'logintokengen', 'mailtokengen', 'admin', 'unadmin', 'sessionkey', 'sessiontime', 'minify', 'minifycore', 'dblistconfigfiles', 'dbshowconfigfile', 'dbpushconfigfiles', 'dbpullconfigfiles', 'dbdeleteconfigfiles', 'vaultpushconfigfiles', 'vaultpullconfigfiles', 'vaultdeleteconfigfiles', 'configkey', 'loadconfigfromdb', 'npmpath', 'serverid', 'recordencryptionrecode', 'vault', 'token', 'unsealkey', 'name', 'log', 'dbstats', 'translate', 'createaccount', 'resetaccount', 'pass', 'adminaccount', 'domain', 'email', 'configfile', 'maintenancemode', 'nedbtodb', 'removetestagents', 'agentupdatetest', 'hashpassword', 'hashpass', 'indexmcrec', 'mpsdebug']; 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. if ((obj.args.help == true) || (obj.args['?'] == true)) { console.log('MeshCentral v' + getCurrentVersion() + ', remote computer management web portal.'); console.log('This software is open source under Apache 2.0 license.'); console.log('Details at: https://www.meshcommander.com/meshcentral2\r\n'); if ((obj.platform == 'win32') || (obj.platform == 'linux')) { console.log('Run as a background service'); console.log(' --install/uninstall Install MeshCentral as a background service.'); console.log(' --start/stop/restart Control MeshCentral background service.'); console.log(''); console.log('Run standalone, console application'); } console.log(' --user [username] Always login as [username] if account exists.'); console.log(' --port [number] Web server port number.'); console.log(' --redirport [number] Creates an additional HTTP server to redirect users to the HTTPS server.'); console.log(' --exactports Server must run with correct ports or exit.'); console.log(' --noagentupdate Server will not update mesh agent native binaries.'); console.log(' --nedbtodb Transfer all NeDB records into current database.'); console.log(' --listuserids Show a list of a user identifiers in the database.'); console.log(' --cert [name], (country), (org) Create a web server certificate with [name] server name.'); console.log(' country and organization can optionally be set.'); console.log(''); console.log('Server recovery commands, use only when MeshCentral is offline.'); console.log(' --createaccount [userid] Create a new user account.'); console.log(' --resetaccount [userid] Unlock an account, disable 2FA and set a new account password.'); console.log(' --adminaccount [userid] Promote account to site administrator.'); return; } // Fix a NeDB database if (obj.args.dbfix) { var lines = null, badJsonCount = 0, feildNames = [], fixedDb = []; try { lines = obj.fs.readFileSync(obj.getConfigFilePath(obj.args.dbfix), { encoding: 'utf8' }).split('\n'); } catch (e) { console.log('Invalid file: ' + obj.args.dbfix + ': ' + e); process.exit(); } for (var i = 0; i < lines.length; i++) { var x = null; try { x = JSON.parse(lines[i]); } catch (ex) { badJsonCount++; } if (x != null) { fixedDb.push(lines[i]); for (var j in x) { if (feildNames.indexOf(j) == -1) { feildNames.push(j); } } } } console.log('Lines: ' + lines.length + ', badJSON: ' + badJsonCount + ', Feilds: ' + feildNames); obj.fs.writeFileSync(obj.getConfigFilePath(obj.args.dbfix) + '-fixed', fixedDb.join('\n'), { encoding: 'utf8' }); return; } // Check for invalid cert name if ((obj.args.cert != null) && ((typeof obj.args.cert != "string") || (obj.args.cert.indexOf('@') >= 0) || (obj.args.cert.indexOf('/') >= 0) || (obj.args.cert.indexOf(':') >= 0))) { console.log("Invalid certificate name"); process.exit(); return; } // Perform a password hash if (obj.args.hashpassword) { require('./pass').hash(obj.args.hashpassword, function (err, salt, hash, tag) { console.log(salt + ',' + hash); process.exit(); }); return; } // Perform web site translations into different languages if (obj.args.translate) { // Check NodeJS version const NodeJSVer = Number(process.version.match(/^v(\d+\.\d+)/)[1]); if (NodeJSVer < 8) { console.log("Translation feature requires Node v8 or above, current version is " + process.version + "."); process.exit(); return; } // Check if translate.json is in the "meshcentral-data" folder, if so use that and translate default pages. var translationFile = null, customTranslation = false; if (require('fs').existsSync(obj.path.join(obj.datapath, 'translate.json'))) { translationFile = obj.path.join(obj.datapath, 'translate.json'); console.log("Using translate.json in meshentral-data."); customTranslation = true; } if (translationFile == null) { if (require('fs').existsSync(obj.path.join(__dirname, 'translate', 'translate.json'))) { translationFile = obj.path.join(__dirname, 'translate', 'translate.json'); console.log("Using default translate.json."); } } if (translationFile == null) { console.log("Unable to find translate.json."); process.exit(); return; } // Perform translation operations var didSomething = false; process.chdir(obj.path.join(__dirname, 'translate')); var translateEngine = require('./translate/translate.js') if (customTranslation == true) { // Translate all of the default files using custom translation file translateEngine.startEx(['', '', 'minifyall']); translateEngine.startEx(['', '', 'translateall', translationFile]); translateEngine.startEx(['', '', 'extractall', translationFile]); didSomething = true; } // Check if "meshcentral-web" exists, if so, translate all pages in that folder. if (obj.webViewsOverridePath != null) { didSomething = true; var files = obj.fs.readdirSync(obj.webViewsOverridePath); for (var i in files) { var file = obj.path.join(obj.webViewsOverridePath, files[i]); if (file.endsWith('.handlebars') && !file.endsWith('-min.handlebars')) { translateEngine.startEx(['', '', 'minify', file]); } } files = obj.fs.readdirSync(obj.webViewsOverridePath); for (var i in files) { var file = obj.path.join(obj.webViewsOverridePath, files[i]); if (file.endsWith('.handlebars') || file.endsWith('-min.handlebars')) { translateEngine.startEx(['', '', 'translate', '*', translationFile, file, '--subdir:translations']); } } } /* if (obj.webPublicOverridePath != null) { didSomething = true; var files = obj.fs.readdirSync(obj.webPublicOverridePath); for (var i in files) { var file = obj.path.join(obj.webPublicOverridePath, files[i]); if (file.endsWith('.htm') && !file.endsWith('-min.htm')) { translateEngine.startEx(['', '', 'translate', '*', translationFile, file, '--subdir:translations']); } } } */ if (didSomething == false) { console.log("Nothing to do."); } process.exit(); return; } // Setup the Node+NPM path if possible, this makes it possible to update the server even if NodeJS and NPM are not in default paths. if (obj.args.npmpath == null) { try { var nodepath = process.argv[0]; var npmpath = obj.path.join(obj.path.dirname(process.argv[0]), 'npm'); if (obj.fs.existsSync(nodepath) && obj.fs.existsSync(npmpath)) { if (nodepath.indexOf(' ') >= 0) { nodepath = '"' + nodepath + '"'; } if (npmpath.indexOf(' ') >= 0) { npmpath = '"' + npmpath + '"'; } if (obj.platform == 'win32') { obj.args.npmpath = npmpath; } else { obj.args.npmpath = (nodepath + ' ' + npmpath); } } } catch (ex) { } } // Linux background service systemd handling if (obj.platform == 'linux') { if (obj.args.install == true) { // Install MeshCentral in Systemd console.log('Installing MeshCentral as background Service...'); var userinfo = require('os').userInfo(), systemdConf = null; if (require('fs').existsSync('/etc/systemd/system')) { systemdConf = '/etc/systemd/system/meshcentral.service'; } else if (require('fs').existsSync('/lib/systemd/system')) { systemdConf = '/lib/systemd/system/meshcentral.service'; } else if (require('fs').existsSync('/usr/lib/systemd/system')) { systemdConf = '/usr/lib/systemd/system/meshcentral.service'; } else { console.log('Unable to find systemd configuration folder.'); process.exit(); return; } console.log('Writing config file...'); require('child_process').exec('which node', {}, function (error, stdout, stderr) { if ((error != null) || (stdout.indexOf('\n') == -1)) { console.log('ERROR: Unable to get node location: ' + error); process.exit(); return; } var nodePath = stdout.substring(0, stdout.indexOf('\n')); var config = '[Unit]\nDescription=MeshCentral Server\n\n[Service]\nType=simple\nLimitNOFILE=1000000\nExecStart=' + nodePath + ' ' + __dirname + '/meshcentral\nWorkingDirectory=' + userinfo.homedir + '\nEnvironment=NODE_ENV=production\nUser=' + userinfo.username + '\nGroup=' + userinfo.username + '\nRestart=always\n# Restart service after 10 seconds if node service crashes\nRestartSec=10\n# Set port permissions capability\nAmbientCapabilities=cap_net_bind_service\n\n[Install]\nWantedBy=multi-user.target\n'; require('child_process').exec('echo \"' + config + '\" | sudo tee ' + systemdConf, {}, function (error, stdout, stderr) { if ((error != null) && (error != '')) { console.log('ERROR: Unable to write config file: ' + error); process.exit(); return; } console.log('Enabling service...'); require('child_process').exec('sudo systemctl enable meshcentral.service', {}, function (error, stdout, stderr) { if ((error != null) && (error != '')) { console.log('ERROR: Unable to enable MeshCentral as a service: ' + error); process.exit(); return; } if (stdout.length > 0) { console.log(stdout); } console.log('Starting service...'); require('child_process').exec('sudo systemctl start meshcentral.service', {}, function (error, stdout, stderr) { if ((error != null) && (error != '')) { console.log('ERROR: Unable to start MeshCentral as a service: ' + error); process.exit(); return; } if (stdout.length > 0) { console.log(stdout); } console.log('Done.'); }); }); }); }); return; } else if (obj.args.uninstall == true) { // Uninstall MeshCentral in Systemd console.log('Uninstalling MeshCentral background service...'); var systemdConf = null; if (require('fs').existsSync('/etc/systemd/system')) { systemdConf = '/etc/systemd/system/meshcentral.service'; } else if (require('fs').existsSync('/lib/systemd/system')) { systemdConf = '/lib/systemd/system/meshcentral.service'; } else if (require('fs').existsSync('/usr/lib/systemd/system')) { systemdConf = '/usr/lib/systemd/system/meshcentral.service'; } else { console.log('Unable to find systemd configuration folder.'); process.exit(); return; } console.log('Stopping service...'); require('child_process').exec('sudo systemctl stop meshcentral.service', {}, function (err, stdout, stderr) { if ((err != null) && (err != '')) { console.log('ERROR: Unable to stop MeshCentral as a service: ' + err); } if (stdout.length > 0) { console.log(stdout); } console.log('Disabling service...'); require('child_process').exec('sudo systemctl disable meshcentral.service', {}, function (err, stdout, stderr) { if ((err != null) && (err != '')) { console.log('ERROR: Unable to disable MeshCentral as a service: ' + err); } if (stdout.length > 0) { console.log(stdout); } console.log('Removing config file...'); require('child_process').exec('sudo rm ' + systemdConf, {}, function (err, stdout, stderr) { if ((err != null) && (err != '')) { console.log('ERROR: Unable to delete MeshCentral config file: ' + err); } console.log('Done.'); }); }); }); return; } else if (obj.args.start == true) { // Start MeshCentral in Systemd require('child_process').exec('sudo systemctl start meshcentral.service', {}, function (err, stdout, stderr) { if ((err != null) && (err != '')) { console.log('ERROR: Unable to start MeshCentral: ' + err); process.exit(); return; } console.log('Done.'); }); return; } else if (obj.args.stop == true) { // Stop MeshCentral in Systemd require('child_process').exec('sudo systemctl stop meshcentral.service', {}, function (err, stdout, stderr) { if ((err != null) && (err != '')) { console.log('ERROR: Unable to stop MeshCentral: ' + err); process.exit(); return; } console.log('Done.'); }); return; } else if (obj.args.restart == true) { // Restart MeshCentral in Systemd require('child_process').exec('sudo systemctl restart meshcentral.service', {}, function (err, stdout, stderr) { if ((err != null) && (err != '')) { console.log('ERROR: Unable to restart MeshCentral: ' + err); process.exit(); return; } console.log('Done.'); }); return; } } // Index a recorded file if (obj.args.indexmcrec != null) { if (typeof obj.args.indexmcrec != 'string') { console.log('Usage: --indexmrec [filename.mcrec]'); } else if (obj.fs.existsSync(obj.args.indexmcrec)) { console.log('Indexing file: ' + obj.args.indexmcrec); require(require('path').join(__dirname, 'mcrec.js')).indexFile(obj.args.indexmcrec); } else { console.log('Unable to find file: ' + obj.args.indexmcrec); } return; } // Windows background service handling if ((obj.platform == 'win32') && (obj.service != null)) { // Build MeshCentral parent path and Windows Service path var mcpath = __dirname; if (mcpath.endsWith('\\node_modules\\meshcentral') || mcpath.endsWith('/node_modules/meshcentral')) { mcpath = require('path').join(mcpath, '..', '..'); } var servicepath = obj.path.join(mcpath, 'WinService'); // Check if we need to install, start, stop, remove ourself as a background service if (((obj.args.xinstall == true) || (obj.args.xuninstall == true) || (obj.args.start == true) || (obj.args.stop == true) || (obj.args.restart == true))) { var env = [], xenv = ['user', 'port', 'aliasport', 'mpsport', 'mpsaliasport', 'redirport', 'exactport', 'rediraliasport', 'debug']; for (i in xenv) { if (obj.args[xenv[i]] != null) { env.push({ name: 'mesh' + xenv[i], value: obj.args[xenv[i]] }); } } // Set some args as service environement variables. var serviceFilePath = null; if (obj.fs.existsSync(obj.path.join(servicepath, 'winservice.js'))) { serviceFilePath = obj.path.join(servicepath, 'winservice.js'); } else if (obj.fs.existsSync(obj.path.join(__dirname, '../WinService/winservice.js'))) { serviceFilePath = obj.path.join(__dirname, '../WinService/winservice.js'); } else if (obj.fs.existsSync(obj.path.join(__dirname, 'winservice.js'))) { serviceFilePath = obj.path.join(__dirname, 'winservice.js'); } if (serviceFilePath == null) { console.log('Unable to find winservice.js'); return; } var svc = new obj.service({ name: 'MeshCentral', description: 'MeshCentral Remote Management Server', script: servicepath, env: env, wait: 2, grow: 0.5 }); svc.on('install', function () { console.log('MeshCentral service installed.'); svc.start(); }); svc.on('uninstall', function () { console.log('MeshCentral service uninstalled.'); process.exit(); }); svc.on('start', function () { console.log('MeshCentral service started.'); process.exit(); }); svc.on('stop', function () { console.log('MeshCentral service stopped.'); if (obj.args.stop) { process.exit(); } if (obj.args.restart) { console.log('Holding 5 seconds...'); setTimeout(function () { svc.start(); }, 5000); } }); svc.on('alreadyinstalled', function () { console.log('MeshCentral service already installed.'); process.exit(); }); svc.on('invalidinstallation', function () { console.log('Invalid MeshCentral service installation.'); process.exit(); }); if (obj.args.xinstall == true) { try { svc.install(); } catch (e) { logException(e); } } if (obj.args.stop == true || obj.args.restart == true) { try { svc.stop(); } catch (e) { logException(e); } } if (obj.args.start == true) { try { svc.start(); } catch (e) { logException(e); } } if (obj.args.xuninstall == true) { try { svc.uninstall(); } catch (e) { logException(e); } } return; } // Windows service install using the external winservice.js if (obj.args.install == true) { console.log('Installing MeshCentral as Windows Service...'); if (obj.fs.existsSync(servicepath) == false) { try { obj.fs.mkdirSync(servicepath); } catch (ex) { console.log('ERROR: Unable to create WinService folder: ' + ex); process.exit(); return; } } try { obj.fs.createReadStream(obj.path.join(__dirname, 'winservice.js')).pipe(obj.fs.createWriteStream(obj.path.join(servicepath, 'winservice.js'))); } catch (ex) { console.log('ERROR: Unable to copy winservice.js: ' + ex); process.exit(); return; } require('child_process').exec('node winservice.js --install', { maxBuffer: 512000, timeout: 120000, cwd: servicepath }, function (error, stdout, stderr) { if ((error != null) && (error != '')) { console.log('ERROR: Unable to install MeshCentral as a service: ' + error); process.exit(); return; } console.log(stdout); }); return; } else if (obj.args.uninstall == true) { console.log('Uninstalling MeshCentral Windows Service...'); if (obj.fs.existsSync(servicepath) == true) { require('child_process').exec('node winservice.js --uninstall', { maxBuffer: 512000, timeout: 120000, cwd: servicepath }, function (error, stdout, stderr) { if ((error != null) && (error != '')) { console.log('ERROR: Unable to uninstall MeshCentral service: ' + error); process.exit(); return; } console.log(stdout); try { obj.fs.unlinkSync(obj.path.join(servicepath, 'winservice.js')); } catch (ex) { } try { obj.fs.rmdirSync(servicepath); } catch (ex) { } }); } else if (obj.fs.existsSync(obj.path.join(__dirname, '../WinService')) == true) { require('child_process').exec('node winservice.js --uninstall', { maxBuffer: 512000, timeout: 120000, cwd: obj.path.join(__dirname, '../WinService') }, function (error, stdout, stderr) { if ((error != null) && (error != '')) { console.log('ERROR: Unable to uninstall MeshCentral service: ' + error); process.exit(); return; } console.log(stdout); try { obj.fs.unlinkSync(obj.path.join(__dirname, '../WinService/winservice.js')); } catch (ex) { } try { obj.fs.rmdirSync(obj.path.join(__dirname, '../WinService')); } catch (ex) { } }); } else { require('child_process').exec('node winservice.js --uninstall', { maxBuffer: 512000, timeout: 120000, cwd: __dirname }, function (error, stdout, stderr) { if ((error != null) && (error != '')) { console.log('ERROR: Unable to uninstall MeshCentral service: ' + error); process.exit(); return; } console.log(stdout); }); } return; } } // If "--launch" is in the arguments, launch now if (obj.args.launch) { if (obj.args.vault) { obj.StartVault(); } else { obj.StartEx(); } } else { // if "--launch" is not specified, launch the server as a child process. var startArgs = []; for (i in process.argv) { if (i > 0) { var arg = process.argv[i]; if ((arg.length > 0) && ((arg.indexOf(' ') >= 0) || (arg.indexOf('&') >= 0))) { startArgs.push(arg); } else { startArgs.push(arg); } } } startArgs.push('--launch', process.pid); obj.launchChildServer(startArgs); } }; // Launch MeshCentral as a child server and monitor it. obj.launchChildServer = function (startArgs) { var child_process = require('child_process'); childProcess = child_process.execFile(process.argv[0], startArgs, { maxBuffer: Infinity, cwd: obj.parentpath }, function (error, stdout, stderr) { if (childProcess.xrestart == 1) { setTimeout(function () { obj.launchChildServer(startArgs); }, 500); // This is an expected restart. } else if (childProcess.xrestart == 2) { console.log('Expected exit...'); process.exit(); // User CTRL-C exit. } else if (childProcess.xrestart == 3) { // Server self-update exit var version = ''; if (typeof obj.args.selfupdate == 'string') { version = '@' + obj.args.selfupdate; } else if (typeof obj.args.specificupdate == 'string') { version = '@' + obj.args.specificupdate; delete obj.args.specificupdate; } var child_process = require('child_process'); var npmpath = ((typeof obj.args.npmpath == 'string') ? obj.args.npmpath : 'npm'); var npmproxy = ((typeof obj.args.npmproxy == 'string') ? (' --proxy ' + obj.args.npmproxy) : ''); var env = Object.assign({}, process.env); // Shallow clone if (typeof obj.args.npmproxy == 'string') { env['HTTP_PROXY'] = env['HTTPS_PROXY'] = env['http_proxy'] = env['https_proxy'] = obj.args.npmproxy; } var xxprocess = child_process.exec(npmpath + ' install meshcentral' + version + npmproxy, { maxBuffer: Infinity, cwd: obj.parentpath, env: env }, function (error, stdout, stderr) { if ((error != null) && (error != '')) { console.log('Update failed: ' + error); } }); xxprocess.data = ''; xxprocess.stdout.on('data', function (data) { xxprocess.data += data; }); xxprocess.stderr.on('data', function (data) { xxprocess.data += data; }); xxprocess.on('close', function (code) { if (code == 0) { console.log('Update completed...'); } if (obj.args.cleannpmcacheonupdate === true) { // Perform NPM cache clean console.log('Cleaning NPM cache...'); var xxxprocess = child_process.exec(npmpath + ' cache clean --force', { maxBuffer: Infinity, cwd: obj.parentpath, env: env }, function (error, stdout, stderr) { }); xxxprocess.on('close', function (code) { setTimeout(function () { obj.launchChildServer(startArgs); }, 1000); }); } else { // Run the updated server setTimeout(function () { obj.launchChildServer(startArgs); }, 1000); } }); } else { if (error != null) { // This is an un-expected restart console.log(error); console.log('ERROR: MeshCentral failed with critical error, check mesherrors.txt. Restarting in 5 seconds...'); setTimeout(function () { obj.launchChildServer(startArgs); }, 5000); } } }); childProcess.stdout.on('data', function (data) { if (data[data.length - 1] == '\n') { data = data.substring(0, data.length - 1); } if (data.indexOf('Updating settings folder...') >= 0) { childProcess.xrestart = 1; } else if (data.indexOf('Updating server certificates...') >= 0) { childProcess.xrestart = 1; } else if (data.indexOf('Server Ctrl-C exit...') >= 0) { childProcess.xrestart = 2; } else if (data.indexOf('Starting self upgrade...') >= 0) { childProcess.xrestart = 3; } else if (data.indexOf('Server restart...') >= 0) { childProcess.xrestart = 1; } else if (data.indexOf('Starting self upgrade to: ') >= 0) { obj.args.specificupdate = data.substring(26).split('\r')[0].split('\n')[0]; childProcess.xrestart = 3; } var datastr = data; while (datastr.endsWith('\r') || datastr.endsWith('\n')) { datastr = datastr.substring(0, datastr.length - 1); } console.log(datastr); }); childProcess.stderr.on('data', function (data) { var datastr = data; while (datastr.endsWith('\r') || datastr.endsWith('\n')) { datastr = datastr.substring(0, datastr.length - 1); } console.log('ERR: ' + datastr); if (data.startsWith('le.challenges[tls-sni-01].loopback')) { return; } // Ignore this error output from GreenLock if (data[data.length - 1] == '\n') { data = data.substring(0, data.length - 1); } obj.logError(data); }); childProcess.on('close', function (code) { if ((code != 0) && (code != 123)) { /* console.log("Exited with code " + code); */ } }); }; obj.logError = function (err) { try { var errlogpath = null; if (typeof obj.args.mesherrorlogpath == 'string') { errlogpath = obj.path.join(obj.args.mesherrorlogpath, 'mesherrors.txt'); } else { errlogpath = obj.getConfigFilePath('mesherrors.txt'); } obj.fs.appendFileSync(errlogpath, '-------- ' + new Date().toLocaleString() + ' ---- ' + getCurrentVersion() + ' --------\r\n\r\n' + err + '\r\n\r\n\r\n'); } catch (ex) { console.log('ERROR: Unable to write to mesherrors.txt.'); } }; // Get current and latest MeshCentral server versions using NPM obj.getLatestServerVersion = function (callback) { if (callback == null) return; try { if (typeof obj.args.selfupdate == 'string') { callback(getCurrentVersion(), obj.args.selfupdate); return; } // If we are targetting a specific version, return that one as current. var child_process = require('child_process'); var npmpath = ((typeof obj.args.npmpath == 'string') ? obj.args.npmpath : 'npm'); var npmproxy = ((typeof obj.args.npmproxy == 'string') ? (' --proxy ' + obj.args.npmproxy) : ''); var env = Object.assign({}, process.env); // Shallow clone if (typeof obj.args.npmproxy == 'string') { env['HTTP_PROXY'] = env['HTTPS_PROXY'] = env['http_proxy'] = env['https_proxy'] = obj.args.npmproxy; } var xxprocess = child_process.exec(npmpath + npmproxy + ' view meshcentral dist-tags.latest', { maxBuffer: 512000, cwd: obj.parentpath, env: env }, function (error, stdout, stderr) { }); xxprocess.data = ''; xxprocess.stdout.on('data', function (data) { xxprocess.data += data; }); xxprocess.stderr.on('data', function (data) { }); xxprocess.on('close', function (code) { var latestVer = null; if (code == 0) { try { latestVer = xxprocess.data.split(' ').join('').split('\r').join('').split('\n').join(''); } catch (e) { } } callback(getCurrentVersion(), latestVer); }); } catch (ex) { callback(getCurrentVersion(), null, ex); } // If the system is running out of memory, an exception here can easily happen. }; // Get current version and all MeshCentral server tags using NPM obj.getServerTags = function (callback) { if (callback == null) return; try { if (typeof obj.args.selfupdate == 'string') { callback({ current: getCurrentVersion(), latest: obj.args.selfupdate }); return; } // If we are targetting a specific version, return that one as current. var child_process = require('child_process'); var npmpath = ((typeof obj.args.npmpath == 'string') ? obj.args.npmpath : 'npm'); var npmproxy = ((typeof obj.args.npmproxy == 'string') ? (' --proxy ' + obj.args.npmproxy) : ''); var env = Object.assign({}, process.env); // Shallow clone if (typeof obj.args.npmproxy == 'string') { env['HTTP_PROXY'] = env['HTTPS_PROXY'] = env['http_proxy'] = env['https_proxy'] = obj.args.npmproxy; } var xxprocess = child_process.exec(npmpath + npmproxy + ' dist-tag ls meshcentral', { maxBuffer: 512000, cwd: obj.parentpath, env: env }, function (error, stdout, stderr) { }); xxprocess.data = ''; xxprocess.stdout.on('data', function (data) { xxprocess.data += data; }); xxprocess.stderr.on('data', function (data) { }); xxprocess.on('close', function (code) { var tags = { current: getCurrentVersion() }; if (code == 0) { try { var lines = xxprocess.data.split('\r\n').join('\n').split('\n'); for (var i in lines) { var s = lines[i].split(': '); if ((s.length == 2) && (obj.args.npmtag == null) || (obj.args.npmtag == s[0])) { tags[s[0]] = s[1]; } } } catch (e) { } } callback(tags); }); } catch (ex) { callback({ current: getCurrentVersion() }, ex); } // If the system is running out of memory, an exception here can easily happen. }; // Use NPM to get list of versions obj.getServerVersions = function (callback) { try { var child_process = require('child_process'); var npmpath = ((typeof obj.args.npmpath == 'string') ? obj.args.npmpath : 'npm'); var npmproxy = ((typeof obj.args.npmproxy == 'string') ? (' --proxy ' + obj.args.npmproxy) : ''); var env = Object.assign({}, process.env); // Shallow clone if (typeof obj.args.npmproxy == 'string') { env['HTTP_PROXY'] = env['HTTPS_PROXY'] = env['http_proxy'] = env['https_proxy'] = obj.args.npmproxy; } var xxprocess = child_process.exec(npmpath + npmproxy + ' view meshcentral versions --json', { maxBuffer: 512000, cwd: obj.parentpath, env: env }, function (error, stdout, stderr) { }); xxprocess.data = ''; xxprocess.stdout.on('data', function (data) { xxprocess.data += data; }); xxprocess.stderr.on('data', function (data) { }); xxprocess.on('close', function (code) { (code == 0) ? callback(xxprocess.data) : callback('{}'); }); } catch (ex) { callback('{}'); } }; // Initiate server self-update obj.performServerUpdate = function (version) { if (obj.serverSelfWriteAllowed != true) return false; if ((version == null) || (version == '') || (typeof version != 'string')) { console.log('Starting self upgrade...'); } else { console.log('Starting self upgrade to: ' + version); } process.exit(200); return true; }; // Initiate server self-update obj.performServerCertUpdate = function () { console.log('Updating server certificates...'); process.exit(200); }; // Start by loading configuration from Vault obj.StartVault = function () { // Check that the configuration can only be loaded from one place if ((obj.args.vault != null) && (obj.args.loadconfigfromdb != null)) { console.log("Can't load configuration from both database and Vault."); process.exit(); return; } // Fix arguments if needed if (typeof obj.args.vault == 'string') { obj.args.vault = { endpoint: obj.args.vault }; if (typeof obj.args.token == 'string') { obj.args.vault.token = obj.args.token; } if (typeof obj.args.unsealkey == 'string') { obj.args.vault.unsealkey = obj.args.unsealkey; } if (typeof obj.args.name == 'string') { obj.args.vault.name = obj.args.name; } } // Load configuration for HashiCorp's Vault if needed if (obj.args.vault) { if (obj.args.vault.endpoint == null) { console.log('Missing Vault endpoint.'); process.exit(); return; } if (obj.args.vault.token == null) { console.log('Missing Vault token.'); process.exit(); return; } if (obj.args.vault.unsealkey == null) { console.log('Missing Vault unsealkey.'); process.exit(); return; } if (obj.args.vault.name == null) { obj.args.vault.name = 'meshcentral'; } // Get new instance of the client var vault = require("node-vault")({ endpoint: obj.args.vault.endpoint, token: obj.args.vault.token }); vault.unseal({ key: obj.args.vault.unsealkey }) .then(() => { if (obj.args.vaultdeleteconfigfiles) { vault.delete('secret/data/' + obj.args.vault.name) .then(function (r) { console.log('Done.'); process.exit(); }) .catch(function (x) { console.log(x); process.exit(); }); } else if (obj.args.vaultpushconfigfiles) { // Push configuration files into Vault if ((obj.args.vaultpushconfigfiles == '*') || (obj.args.vaultpushconfigfiles === true)) { obj.args.vaultpushconfigfiles = obj.datapath; } obj.fs.readdir(obj.args.vaultpushconfigfiles, function (err, files) { if (err != null) { console.log('ERROR: Unable to read from folder ' + obj.args.vaultpushconfigfiles); process.exit(); return; } var configFound = false; for (var i in files) { if (files[i] == 'config.json') { configFound = true; } } if (configFound == false) { console.log('ERROR: No config.json in folder ' + obj.args.vaultpushconfigfiles); process.exit(); return; } var configFiles = {}; 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.vaultpushconfigfiles, files[i]), binary = Buffer.from(obj.fs.readFileSync(path, { encoding: 'binary' }), 'binary'); console.log('Pushing ' + file + ', ' + binary.length + ' bytes.'); if (file.endsWith('.json') || file.endsWith('.key') || file.endsWith('.crt')) { configFiles[file] = binary.toString(); } else { configFiles[file] = binary.toString('base64'); } } } vault.write('secret/data/' + obj.args.vault.name, { "data": configFiles }) .then(function (r) { console.log('Done.'); process.exit(); }) .catch(function (x) { console.log(x); process.exit(); }); }); } else { // Read configuration files from Vault vault.read('secret/data/' + obj.args.vault.name) .then(function (r) { if ((r == null) || (r.data == null) || (r.data.data == null)) { console.log('Unable to read configuration from Vault.'); process.exit(); return; } var configFiles = obj.configurationFiles = r.data.data; // Decode Base64 when needed for (var file in configFiles) { if (!file.endsWith('.json') && !file.endsWith('.key') && !file.endsWith('.crt')) { configFiles[file] = Buffer.from(configFiles[file], 'base64'); } } // Save all of the files if (obj.args.vaultpullconfigfiles) { for (var i in configFiles) { var fullFileName = obj.path.join(obj.args.vaultpullconfigfiles, i); try { obj.fs.writeFileSync(fullFileName, configFiles[i]); } catch (ex) { console.log('Unable to write to ' + fullFileName); process.exit(); return; } console.log('Pulling ' + i + ', ' + configFiles[i].length + ' bytes.'); } console.log('Done.'); process.exit(); } // 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 Vault.'); process.exit(); return; } // Set the command line arguments to the config file if they are not present if (!config2.settings) { config2.settings = {}; } for (var i in args) { config2.settings[i] = args[i]; } obj.args = args = config2.settings; // Lower case all keys