diff --git a/MeshCentralServer.njsproj b/MeshCentralServer.njsproj index f3f74002..aaa47303 100644 --- a/MeshCentralServer.njsproj +++ b/MeshCentralServer.njsproj @@ -28,7 +28,7 @@ - + diff --git a/certoperations.js b/certoperations.js index 0fd71d71..8ded6fc0 100644 --- a/certoperations.js +++ b/certoperations.js @@ -213,7 +213,7 @@ module.exports.CertificateOperations = function () { } caindex++; } while (caok == true); - r.ca = calist; + r.web.ca = calist; // Decode certificate arguments var commonName = 'un-configured', country, organization, forceWebCertGen = 0; diff --git a/letsEncrypt.js b/letsEncrypt.js index 5d0145ab..403ca775 100644 --- a/letsEncrypt.js +++ b/letsEncrypt.js @@ -1,81 +1,118 @@ /** -* @description MeshCentral letsEncrypt module +* @description MeshCentral letsEncrypt module, uses GreenLock to do all the work. * @author Ylian Saint-Hilaire * @copyright Intel Corporation 2018 * @license Apache-2.0 -* @version v0.0.1 +* @version v0.0.2 */ module.exports.CreateLetsEncrypt = function (parent) { - var obj = {}; - obj.parent = parent; - obj.webrootPath = obj.parent.path.join(obj.parent.datapath, 'acme-challenges'); - obj.workPath = obj.parent.path.join(obj.parent.datapath, 'acme-challenges', 'work'); - obj.logsPath = obj.parent.path.join(obj.parent.datapath, 'acme-challenges', 'logs'); + try { + const greenlock = require('greenlock');; + const path = require('path'); - try { obj.parent.fs.mkdirSync(obj.webrootPath); } catch (e) { } - try { obj.parent.fs.mkdirSync(obj.workPath); } catch (e) { } - try { obj.parent.fs.mkdirSync(obj.logsPath); } catch (e) { } + var obj = {}; + obj.parent = parent; + obj.redirWebServerHooked = false; + obj.leDomains = null; + obj.leResults = null; - console.log('CreateLetsEncrypt-1', obj.webrootPath); - console.log('CreateLetsEncrypt-1', obj.workPath); - console.log('CreateLetsEncrypt-1', obj.logsPath); + // Setup the certificate storage paths + obj.configPath = obj.parent.path.join(obj.parent.datapath, 'letsencrypt'); + obj.webrootPath = obj.parent.path.join(obj.parent.datapath, 'letsencrypt', 'webroot'); + try { obj.parent.fs.mkdirSync(obj.configPath); } catch (e) { } + try { obj.parent.fs.mkdirSync(obj.webrootPath); } catch (e) { } - obj.lex = require('greenlock-express').create({ - // Set to https://acme-v01.api.letsencrypt.org/directory in production - server: 'staging' + // Storage Backend, store data in the "meshcentral-data/letencrypt" folder. + var leStore = require('le-store-certbot').create({ configDir: obj.configPath, webrootPath: obj.webrootPath, debug: obj.parent.args.debug > 0 }); - // If you wish to replace the default plugins, you may do so here - , challenges: { - 'http-01': require('le-challenge-fs').create({ webrootPath: obj.webrootPath }) + // ACME Challenge Handlers + var leHttpChallenge = require('le-challenge-fs').create({ webrootPath: obj.webrootPath, debug: obj.parent.args.debug > 0 }); + + // Function to agree to terms of service + function leAgree(opts, agreeCb) { agreeCb(null, opts.tosUrl); } + + // Create the main GreenLock code module. + var greenlockargs = { + server: (obj.parent.config.letsencrypt.production === true) ? greenlock.productionServerUrl : greenlock.stagingServerUrl, + store: leStore, + challenges: { 'http-01': leHttpChallenge }, + challengeType: 'http-01', + agreeToTerms: leAgree, + debug: obj.parent.args.debug > 0 } - , store: require('le-store-certbot').create({ - //configDir: '/etc/letsencrypt', - //privkeyPath: ':configDir/live/:hostname/privkey.pem', - //fullchainPath: ':configDir/live/:hostname/fullchain.pem', - //certPath: ':configDir/live/:hostname/cert.pem', - //chainPath: ':configDir/live/:hostname/chain.pem', - workDir: obj.workPath, - logsDir: obj.logsPath, - webrootPath: obj.webrootPath, - debug: false - }) - , approveDomains: approveDomains - }); + if (obj.parent.args.debug == null) { greenlockargs.log = function (debug) { } } // If not in debug mode, ignore all console output from greenlock (makes things clean). + obj.le = greenlock.create(greenlockargs); - console.log('CreateLetsEncrypt-2'); - function approveDomains(opts, certs, func) { - console.log('approveDomains', opts, certs); + // Hook up GreenLock to the redirection server + if (obj.parent.redirserver.port == 80) { obj.parent.redirserver.app.use('/', obj.le.middleware()); obj.redirWebServerHooked = true; } - // This is where you check your database and associated - // email addresses with domains and agreements and such + obj.getCertificate = function (certs, func) { + if (certs.CommonName == 'un-configured') { console.log("ERROR: Use --cert to setup the default server name before using Let's Encrypt."); func(certs); return; } + if (obj.parent.config.letsencrypt == null) { func(certs); return; } + if (obj.parent.config.letsencrypt.email == null) { console.log("ERROR: Let's Encrypt email address not specified."); func(certs); return; } + if ((obj.parent.redirserver == null) || (obj.parent.redirserver.port !== 80)) { console.log("ERROR: Redirection web server must be active on port 80 for Let's Encrypt to work."); func(certs); return; } + if (obj.redirWebServerHooked !== true) { console.log("ERROR: Redirection web server not setup for Let's Encrypt to work."); func(certs); return; } + if ((obj.parent.config.letsencrypt.rsakeysize != null) && (obj.parent.config.letsencrypt.rsakeysize !== 2048) && (obj.parent.config.letsencrypt.rsakeysize !== 3072)) { console.log("ERROR: Invalid Let's Encrypt certificate key size, must be 2048 or 3072."); func(certs); return; } + // Get the list of domains + obj.leDomains = [certs.CommonName]; + if (obj.parent.config.letsencrypt.names != null) { + if (typeof obj.parent.config.letsencrypt.names == 'string') { obj.parent.config.letsencrypt.names = obj.parent.config.letsencrypt.names.split(','); } + obj.parent.config.letsencrypt.names.map(function (s) { return s.trim() }); // Trim each name + if ((typeof obj.parent.config.letsencrypt.names != 'object') || (obj.parent.config.letsencrypt.names.length == null)) { console.log("ERROR: Let's Encrypt names must be an array in config.json."); func(certs); return; } + obj.leDomains = obj.parent.config.letsencrypt.names; + obj.leDomains.sort(); // Sort the array so it's always going to be in the same order. + } - // The domains being approved for the first time are listed in opts.domains - // Certs being renewed are listed in certs.altnames - if (certs) { - opts.domains = ['example.com', 'yourdomain.com'] - } else { - opts.email = 'john.doe@example.com'; - opts.agreeTos = true; + obj.le.check({ domains: obj.leDomains }).then(function (results) { + if (results) { + obj.leResults = results; + + // If we already have real certificates, use them. + if (results.altnames.indexOf(certs.CommonName) >= 0) { certs.web.cert = results.cert; certs.web.key = results.privkey; certs.web.ca = [results.chain]; } + for (var i in obj.parent.config.domains) { if ((obj.parent.config.domains[i].dns != null) && (results.altnames.indexOf(obj.parent.config.domains[i].dns) >= 0)) { certs.dns[i].cert = results.cert; certs.dns[i].key = results.privkey; certs.dns[i].ca = [results.chain]; } } + func(certs); + + // Check if the Let's Encrypt certificate needs to be renewed. + setTimeout(obj.checkRenewCertificate, 300000); // Check in 5 minutes. + setInterval(obj.checkRenewCertificate, 86400000); // Check again in 24 hours and every 24 hours. + return; + } else { + // Otherwise return default certificates and try to get a real one + func(certs); + } + console.log("Attempting to get Let's Encrypt certificate, may take a few minutes..."); + + // Figure out the RSA key size + var rsaKeySize = (obj.parent.config.letsencrypt.rsakeysize === 2048) ? 2048 : 3072; + + // TODO: Only register on one of the peers if multi-peers are active. + // Register Certificate manually + obj.le.register({ + domains: obj.leDomains, + email: obj.parent.config.letsencrypt.email, + agreeTos: true, + rsaKeySize: rsaKeySize, + challengeType: 'http-01' + }).then(function (xresults) { + obj.parent.performServerCertUpdate(); // Reset the server, TODO: Reset all peers + }, function (err) { + console.error("ERROR: Let's encrypt error: ", err); + }); + }); } - // NOTE: you can also change other options such as `challengeType` and `challenge` - // opts.challengeType = 'http-01'; - // opts.challenge = require('le-challenge-fs').create({}); + // Check if we need to renew the certificate, call this every day. + obj.checkRenewCertificate = function () { + if (obj.leResults == null) { return; } + // TODO: Only renew on one of the peers if multi-peers are active. + // Check if we need to renew the certificate + obj.le.renew({ duplicate: false }, obj.leResults).then(function (xresults) { + obj.parent.performServerCertUpdate(); // Reset the server, TODO: Reset all peers + }, function (err) { }); // If we can't renew, ignore. + } - func(null, { options: opts, certs: certs }); - } - - // Handles acme-challenge and redirects to https - require('http').createServer(obj.lex.middleware(require('redirect-https')())).listen(81, function () { console.log("Listening for ACME http-01 challenges on", this.address()); }); - - var app = require('express')(); - app.use('/', function (req, res) { res.end('Hello, World!'); }); - - // Handles your app - require('https').createServer(obj.lex.httpsOptions, obj.lex.middleware(app)).listen(443, function () { console.log("Listening for ACME tls-sni-01 challenges and serve app on", this.address()); }); - - console.log('CreateLetsEncrypt-3'); + } catch (e) { console.error(e); return null; } // Unable to start Let's Encrypt return obj; } \ No newline at end of file diff --git a/meshcentral.js b/meshcentral.js index a536bb4d..6cf5d910 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -20,6 +20,7 @@ function CreateMeshCentralServer() { obj.amtEventHandler; obj.amtScanner; obj.meshScanner; + obj.letsencrypt; obj.eventsDispatch = {}; obj.fs = require('fs'); obj.path = require('path'); @@ -163,8 +164,11 @@ function CreateMeshCentralServer() { } } }); - xprocess.stdout.on('data', function (data) { if (data[data.length - 1] == '\n') { data = data.substring(0, data.length - 1); } if (data.indexOf('Updating settings folder...') >= 0) { xprocess.xrestart = 1; } else if (data.indexOf('Server Ctrl-C exit...') >= 0) { xprocess.xrestart = 2; } else if (data.indexOf('Starting self upgrade...') >= 0) { xprocess.xrestart = 3; } console.log(data); }); - xprocess.stderr.on('data', function (data) { if (data[data.length - 1] == '\n') { data = data.substring(0, data.length - 1); } obj.fs.appendFileSync(obj.path.join(obj.datapath, 'mesherrors.txt'), '-------- ' + new Date().toLocaleString() + ' --------\r\n\r\n' + data + '\r\n\r\n\r\n'); }); + xprocess.stdout.on('data', function (data) { if (data[data.length - 1] == '\n') { data = data.substring(0, data.length - 1); } if (data.indexOf('Updating settings folder...') >= 0) { xprocess.xrestart = 1; } else if (data.indexOf('Updating server certificates...') >= 0) { xprocess.xrestart = 1; } else if (data.indexOf('Server Ctrl-C exit...') >= 0) { xprocess.xrestart = 2; } else if (data.indexOf('Starting self upgrade...') >= 0) { xprocess.xrestart = 3; } console.log(data); }); + xprocess.stderr.on('data', function (data) { + 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.fs.appendFileSync(obj.path.join(obj.datapath, 'mesherrors.txt'), '-------- ' + new Date().toLocaleString() + ' --------\r\n\r\n' + data + '\r\n\r\n\r\n'); + }); xprocess.on('close', function (code) { if ((code != 0) && (code != 123)) { /* console.log("Exited with code " + code); */ } }); } @@ -186,6 +190,9 @@ function CreateMeshCentralServer() { // Initiate server self-update obj.performServerUpdate = function () { console.log('Starting self upgrade...'); process.exit(200); } + // Initiate server self-update + obj.performServerCertUpdate = function () { console.log('Updating server certificates...'); process.exit(200); } + obj.StartEx = function () { // Look to see if data and/or file path is specified if (obj.args.datapath) { obj.datapath = obj.args.datapath; } @@ -310,100 +317,122 @@ function CreateMeshCentralServer() { obj.updateMeshCore(); obj.updateMeshCmd(); - // Load server certificates - obj.certificateOperations = require('./certoperations.js').CertificateOperations() - obj.certificateOperations.GetMeshServerCertificate(obj.datapath, obj.args, obj.config, function (certs) { - obj.certificates = certs; - obj.certificateOperations.acceleratorStart(certs); // Set the state of the accelerators + // Setup and start the redirection server if needed + 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. + } + }); + }); + } - // If the certificate is un-configured, force LAN-only mode - if (obj.certificates.CommonName == 'un-configured') { console.log('Server name not configured, running in LAN-only mode.'); obj.args.lanonly = true; } + // 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.datapath, obj.args, obj.config, function (certs) { + if (obj.config.letsencrypt == null) { + obj.StartEx3(certs); // Just use the configured certificates + } else { + var le = require('./letsencrypt.js'); + obj.letsencrypt = le.CreateLetsEncrypt(obj); + if (obj.letsencrypt != null) { + obj.letsencrypt.getCertificate(certs, obj.StartEx3); // Use Let's Encrypt certificate + } else { + console.log('ERROR: Unable to setup GreenLock module.'); + obj.StartEx3(certs); // Let's Encrypt did not load, just use the configured certificates + } + } + }); + } - // Check that no sub-domains have the same DNS as the parent - for (var i in obj.config.domains) { - if ((obj.config.domains[i].dns != null) && (obj.certificates.CommonName.toLowerCase() === obj.config.domains[i].dns.toLowerCase())) { - console.log("ERROR: Server sub-domain can't have same DNS name as the parent."); process.exit(0); return; + // Start the server with the given certificates + obj.StartEx3 = function (certs) { + obj.certificates = certs; + obj.certificateOperations.acceleratorStart(certs); // Set the state of the accelerators + + // If the certificate is un-configured, force LAN-only mode + if (obj.certificates.CommonName == 'un-configured') { console.log('Server name not configured, running in LAN-only mode.'); obj.args.lanonly = true; } + + // Check that no sub-domains have the same DNS as the parent + for (var i in obj.config.domains) { + if ((obj.config.domains[i].dns != null) && (obj.certificates.CommonName.toLowerCase() === obj.config.domains[i].dns.toLowerCase())) { + console.log("ERROR: Server sub-domain can't have same DNS name as the parent."); process.exit(0); return; + } + } + + // Load the list of mesh agents and install scripts + if (obj.args.noagentupdate == 1) { for (var i in obj.meshAgentsArchitectureNumbers) { obj.meshAgentsArchitectureNumbers[i].update = false; } } + obj.updateMeshAgentsTable(function () { + obj.updateMeshAgentInstallScripts(); + + // Setup and start the web server + require('crypto').randomBytes(48, function (err, buf) { + // Setup Mesh Multi-Server if needed + obj.multiServer = require('./multiserver.js').CreateMultiServer(obj, obj.args); + if (obj.multiServer != null) { + obj.serverId = obj.multiServer.serverid; + for (var serverid in obj.config.peers.servers) { obj.peerConnectivityByNode[serverid] = {}; } + } + + // If the server is set to "nousers", allow only loopback unless IP filter is set + if ((obj.args.nousers == true) && (obj.args.userallowedip == null)) { obj.args.userallowedip = "::1,127.0.0.1"; } + + if (obj.args.secret) { + // This secret is used to encrypt HTTP session information, if specified, user it. + obj.webserver = require('./webserver.js').CreateWebServer(obj, obj.db, obj.args, obj.args.secret, obj.certificates); + } else { + // If the secret is not specified, generate a random number. + obj.webserver = require('./webserver.js').CreateWebServer(obj, obj.db, obj.args, buf.toString('hex').toUpperCase(), obj.certificates); + } + if (obj.redirserver != null) { obj.redirserver.hookMainWebServer(obj.certificates); } + + // Setup the Intel AMT event handler + obj.amtEventHandler = require('./amtevents.js').CreateAmtEventsHandler(obj); + + // Setup the Intel AMT local network scanner + if (obj.args.wanonly != true) { + obj.amtScanner = require('./amtscanner.js').CreateAmtScanner(obj).start(); + obj.meshScanner = require('./meshscanner.js').CreateMeshScanner(obj).start(); + } + + // Setup and start the MPS server + if (obj.args.lanonly != true) { + obj.mpsserver = require('./mpsserver.js').CreateMpsServer(obj, obj.db, obj.args, obj.certificates); + } + + // Setup and start the legacy swarm server + if (obj.certificates.swarmserver != null) { + if (obj.args.swarmport == null) { obj.args.swarmport = 8080; } + obj.swarmserver = require('./swarmserver.js').CreateSwarmServer(obj, obj.db, obj.args, obj.certificates); + } + + // Setup email server + if ((obj.config.smtp != null) && (obj.config.smtp.host != null) && (obj.config.smtp.from != null)) { + obj.mailserver = require('./meshmail.js').CreateMeshMain(obj); + obj.mailserver.verify(); + //obj.mailserver.sendMail('ylian.saint-hilaire@intel.com', 'Test Subject', 'This is a sample test', 'This is a sample html test'); + } + + // Start periodic maintenance + obj.maintenanceTimer = setInterval(obj.maintenanceActions, 1000 * 60 * 60); // Run this every hour + + // Dispatch an event that the server is now running + obj.DispatchEvent(['*'], obj, { etype: 'server', action: 'started', msg: 'Server started' }) + + // Load the login cookie encryption key from the database if allowed + if ((obj.config) && (obj.config.settings) && (obj.config.settings.allowlogintoken == true)) { + obj.db.Get('LoginCookieEncryptionKey', function (err, docs) { + if ((docs.length > 0) && (docs[0].key != null) && (obj.args.logintokengen == null)) { + obj.loginCookieEncryptionKey = Buffer.from(docs[0].key, 'hex'); + } else { + obj.loginCookieEncryptionKey = obj.generateCookieKey(); obj.db.Set({ _id: 'LoginCookieEncryptionKey', key: obj.loginCookieEncryptionKey.toString('hex'), time: Date.now() }); } - } - - // Load the list of mesh agents and install scripts - if (obj.args.noagentupdate == 1) { for (var i in obj.meshAgentsArchitectureNumbers) { obj.meshAgentsArchitectureNumbers[i].update = false; } } - obj.updateMeshAgentsTable(function () { - obj.updateMeshAgentInstallScripts(); - - // Setup and start the web server - require('crypto').randomBytes(48, function (err, buf) { - // Setup Mesh Multi-Server if needed - obj.multiServer = require('./multiserver.js').CreateMultiServer(obj, obj.args); - if (obj.multiServer != null) { - obj.serverId = obj.multiServer.serverid; - for (var serverid in obj.config.peers.servers) { obj.peerConnectivityByNode[serverid] = {}; } - } - - // If the server is set to "nousers", allow only loopback unless IP filter is set - if ((obj.args.nousers == true) && (obj.args.userallowedip == null)) { obj.args.userallowedip = "::1,127.0.0.1"; } - - if (obj.args.secret) { - // This secret is used to encrypt HTTP session information, if specified, user it. - obj.webserver = require('./webserver.js').CreateWebServer(obj, obj.db, obj.args, obj.args.secret, obj.certificates); - } else { - // If the secret is not specified, generate a random number. - obj.webserver = require('./webserver.js').CreateWebServer(obj, obj.db, obj.args, buf.toString('hex').toUpperCase(), obj.certificates); - } - - // Setup and start the redirection server if needed - 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.certificates); - } - - // Setup the Intel AMT event handler - obj.amtEventHandler = require('./amtevents.js').CreateAmtEventsHandler(obj); - - // Setup the Intel AMT local network scanner - if (obj.args.wanonly != true) { - obj.amtScanner = require('./amtscanner.js').CreateAmtScanner(obj).start(); - obj.meshScanner = require('./meshscanner.js').CreateMeshScanner(obj).start(); - } - - // Setup and start the MPS server - if (obj.args.lanonly != true) { - obj.mpsserver = require('./mpsserver.js').CreateMpsServer(obj, obj.db, obj.args, obj.certificates); - } - - // Setup and start the legacy swarm server - if (obj.certificates.swarmserver != null) { - if (obj.args.swarmport == null) { obj.args.swarmport = 8080; } - obj.swarmserver = require('./swarmserver.js').CreateSwarmServer(obj, obj.db, obj.args, obj.certificates); - } - - // Setup email server - if ((obj.config.smtp != null) && (obj.config.smtp.host != null) && (obj.config.smtp.from != null)) { - obj.mailserver = require('./meshmail.js').CreateMeshMain(obj); - obj.mailserver.verify(); - //obj.mailserver.sendMail('ylian.saint-hilaire@intel.com', 'Test Subject', 'This is a sample test', 'This is a sample html test'); - } - - // Start periodic maintenance - obj.maintenanceTimer = setInterval(obj.maintenanceActions, 1000 * 60 * 60); // Run this every hour - - // Dispatch an event that the server is now running - obj.DispatchEvent(['*'], obj, { etype: 'server', action: 'started', msg: 'Server started' }) - - // Load the login cookie encryption key from the database if allowed - if ((obj.config) && (obj.config.settings) && (obj.config.settings.allowlogintoken == true)) { - obj.db.Get('LoginCookieEncryptionKey', function (err, docs) { - if ((docs.length > 0) && (docs[0].key != null) && (obj.args.logintokengen == null)) { - obj.loginCookieEncryptionKey = Buffer.from(docs[0].key, 'hex'); - } else { - obj.loginCookieEncryptionKey = obj.generateCookieKey(); obj.db.Set({ _id: 'LoginCookieEncryptionKey', key: obj.loginCookieEncryptionKey.toString('hex'), time: Date.now() }); - } - }); - } - - obj.debug(1, 'Server started'); - }); }); - }); + } + + obj.debug(1, 'Server started'); }); }); } diff --git a/package.json b/package.json index 87cbffd0..9d12f037 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.1.2-h", + "version": "0.1.2-s", "keywords": [ "Remote Management", "Intel AMT", @@ -48,7 +48,11 @@ "optionalDependencies": { "node-sspi": "^0.2.2", "node-windows": "^0.1.14", - "mongojs": "^2.4.0" + "mongojs": "^2.4.0", + "greenlock": "^2.1.18", + "le-store-certbot": "^2.0.5", + "le-challenge-fs": "^2.0.8", + "le-acme-core": "^2.1.1" }, "devDependencies": {}, "readme": "readme.txt" diff --git a/public/index.html b/public/index.html index 7fab174b..6dc064a6 100644 --- a/public/index.html +++ b/public/index.html @@ -1693,6 +1693,7 @@ } function deskAdjust() { + console.log('deskAdjust'); var x = (Math.max(document.documentElement.clientHeight, window.innerHeight || 0) - (Q('deskarea1').clientHeight + Q('deskarea2').clientHeight + Q('Desk').clientHeight + Q('deskarea4').clientHeight + 2)) / 2; if (fullscreen) { document.documentElement.style.overflow = 'hidden'; diff --git a/public/scripts/agent-desktop-0.0.2.js b/public/scripts/agent-desktop-0.0.2.js index 916caacf..dc94d6e7 100644 --- a/public/scripts/agent-desktop-0.0.2.js +++ b/public/scripts/agent-desktop-0.0.2.js @@ -29,6 +29,8 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) { obj.rotation = 0; obj.protocol = 2; // KVM obj.debugmode = 0; + obj.firstUpKeys = []; + obj.stopInput = false; obj.sessionid = 0; obj.username; @@ -43,7 +45,7 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) { obj.width = 960; obj.height = 960; - obj.onScreenResize = null; + obj.onScreenSizeChange = null; obj.onMessage = null; obj.onConnectCountChanged = null; obj.onDebugMessage = null; @@ -59,7 +61,7 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) { obj.UnGrabKeyInput(); obj.UnGrabMouseInput(); obj.touchenabled = 0; - if (obj.onScreenResize != null) obj.onScreenResize(obj, obj.ScreenWidth, obj.ScreenHeight, obj.CanvasId); + if (obj.onScreenSizeChange != null) obj.onScreenSizeChange(obj, obj.ScreenWidth, obj.ScreenHeight, obj.CanvasId); obj.Canvas.clearRect(0, 0, obj.CanvasId.width, obj.CanvasId.height); } @@ -164,6 +166,7 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) { while (obj.PendingOperations.length > 0) { obj.PendingOperations.shift(); } obj.SendCompressionLevel(1); obj.SendUnPause(); + if (obj.onScreenSizeChange != null) { obj.onScreenSizeChange(obj, obj.ScreenWidth, obj.ScreenHeight, obj.CanvasId); } } obj.ProcessData = function (str) { @@ -201,6 +204,7 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) { obj.SendKeyMsgKC(obj.KeyAction.UP, 18); // Alt obj.SendKeyMsgKC(obj.KeyAction.UP, 91); // Left-Windows obj.SendKeyMsgKC(obj.KeyAction.UP, 92); // Right-Windows + obj.SendKeyMsgKC(obj.KeyAction.UP, 16); // Shift obj.Send(String.fromCharCode(0x00, 0x0E, 0x00, 0x04)); break; case 11: // GetDisplays @@ -334,7 +338,7 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) { } obj.GetDisplayNumbers = function () { obj.Send(String.fromCharCode(0x00, 0x0B, 0x00, 0x04)); } // Get Terminal display - obj.SetDisplay = function (number) { console.log('SetDisplay', number); obj.Send(String.fromCharCode(0x00, 0x0C, 0x00, 0x06, number >> 8, number & 0xFF)); } // Set Terminal display + obj.SetDisplay = function (number) { obj.Send(String.fromCharCode(0x00, 0x0C, 0x00, 0x06, number >> 8, number & 0xFF)); } // Set Terminal display obj.intToStr = function (x) { return String.fromCharCode((x >> 24) & 0xFF, (x >> 16) & 0xFF, (x >> 8) & 0xFF, x & 0xFF); } obj.shortToStr = function (x) { return String.fromCharCode((x >> 8) & 0xFF, x & 0xFF); } @@ -345,7 +349,7 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) { obj.Canvas.canvas.width = obj.ScreenWidth; obj.Canvas.canvas.height = obj.ScreenHeight; obj.Canvas.fillRect(0, 0, obj.ScreenWidth, obj.ScreenHeight); - if (obj.onScreenResize != null) obj.onScreenResize(obj, obj.ScreenWidth, obj.ScreenHeight, obj.CanvasId); + if (obj.onScreenSizeChange != null) obj.onScreenSizeChange(obj, obj.ScreenWidth, obj.ScreenHeight, obj.CanvasId); } obj.FirstDraw = false; //obj.Debug("onResize: " + obj.ScreenWidth + " x " + obj.ScreenHeight); @@ -363,15 +367,21 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) { obj.xxKeyPress = function (e) { if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return false; } // Key handlers - obj.handleKeys = function (e) { return obj.xxKeyPress(e); } - obj.handleKeyUp = function (e) { return obj.xxKeyUp(e); } - obj.handleKeyDown = function (e) { return obj.xxKeyDown(e); } + obj.handleKeys = function (e) { if (obj.stopInput == true || desktop.State != 3) return false; return obj.xxKeyPress(e); } + obj.handleKeyUp = function (e) { + if (obj.stopInput == true || desktop.State != 3) return false; + if (obj.firstUpKeys.length < 5) { + obj.firstUpKeys.push(e.keyCode); + if ((obj.firstUpKeys.length == 5)) { var j = obj.firstUpKeys.join(','); if ((j == '16,17,91,91,16') || (j == '16,17,18,91,92')) { obj.stopInput = true; } } + } return obj.xxKeyUp(e); + } + obj.handleKeyDown = function (e) { if (obj.stopInput == true || desktop.State != 3) return false; return obj.xxKeyDown(e); } // Mouse handlers - obj.mousedown = function (e) { return obj.xxMouseDown(e); } - obj.mouseup = function (e) { return obj.xxMouseUp(e); } - obj.mousemove = function (e) { return obj.xxMouseMove(e); } - obj.mousewheel = function (e) { return obj.xxMouseWheel(e); } + obj.mousedown = function (e) { if (obj.stopInput == true) return false; return obj.xxMouseDown(e); } + obj.mouseup = function (e) { if (obj.stopInput == true) return false; return obj.xxMouseUp(e); } + obj.mousemove = function (e) { if (obj.stopInput == true) return false; return obj.xxMouseMove(e); } + obj.mousewheel = function (e) { if (obj.stopInput == true) return false; return obj.xxMouseWheel(e); } obj.xxMsTouchEvent = function (evt) { if (evt.originalEvent.pointerType == 4) return; // If this is a mouse pointer, ignore this event. Touch & pen are ok. @@ -573,7 +583,7 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) { obj.ScreenWidth = obj.Canvas.canvas.width; obj.ScreenHeight = obj.Canvas.canvas.height; - if (obj.onScreenResize != null) obj.onScreenResize(obj, obj.ScreenWidth, obj.ScreenHeight, obj.CanvasId); + if (obj.onScreenSizeChange != null) obj.onScreenSizeChange(obj, obj.ScreenWidth, obj.ScreenHeight, obj.CanvasId); return true; } diff --git a/redirserver.js b/redirserver.js index 391f25a7..251ae9a2 100644 --- a/redirserver.js +++ b/redirserver.js @@ -10,21 +10,25 @@ // https://github.com/expressjs/express/blob/master/examples/auth/index.js // Construct a HTTP redirection web server object -module.exports.CreateRedirServer = function (parent, db, args, certificates) { +module.exports.CreateRedirServer = function (parent, db, args, func) { var obj = {}; obj.parent = parent; obj.db = db; obj.args = args; - obj.certificates = certificates; + obj.certificates = null; obj.express = require('express'); obj.net = require('net'); obj.app = obj.express(); obj.tcpServer; + obj.port = null; // Perform an HTTP to HTTPS redirection function performRedirection(req, res) { - var host = certificates.CommonName; - if ((certificates.CommonName == 'sample.org') || (certificates.CommonName == 'un-configured')) { host = req.headers.host; } + var host = req.headers.host; + if (obj.certificates != null) { + host = obj.certificates.CommonName; + if ((obj.certificates.CommonName == 'sample.org') || (obj.certificates.CommonName == 'un-configured')) { host = req.headers.host; } + } if (req.headers && req.headers.host && (req.headers.host.split(':')[0].toLowerCase() == 'localhost')) { res.redirect('https://localhost:' + args.port + req.url); } else { res.redirect('https://' + host + ':' + args.port + req.url); } } @@ -54,17 +58,25 @@ module.exports.CreateRedirServer = function (parent, db, args, certificates) { return next(); }); + // Once the main web server is started, call this to hookup additional handlers + obj.hookMainWebServer = function (certs) { + obj.certificates = certs; + for (var i in parent.config.domains) { + if (parent.config.domains[i].dns != null) { continue; } + var url = parent.config.domains[i].url; + obj.app.post(url + 'amtevents.ashx', obj.parent.webserver.handleAmtEventRequest); + obj.app.get(url + 'meshsettings', obj.parent.webserver.handleMeshSettingsRequest); + obj.app.get(url + 'meshagents', obj.parent.webserver.handleMeshAgentRequest); + } + } + // Setup all HTTP redirection handlers //obj.app.set('etag', false); for (var i in parent.config.domains) { + if (parent.config.domains[i].dns != null) { continue; } var url = parent.config.domains[i].url; obj.app.get(url, performRedirection); - obj.app.post(url + 'amtevents.ashx', obj.parent.webserver.handleAmtEventRequest); - obj.app.get(url + 'meshsettings', obj.parent.webserver.handleMeshSettingsRequest); - obj.app.get(url + 'meshagents', obj.parent.webserver.handleMeshAgentRequest); - - // Indicates the clickonce folder is public - obj.app.use(url + 'clickonce', obj.express.static(obj.parent.path.join(__dirname, 'public/clickonce'))); + obj.app.use(url + 'clickonce', obj.express.static(obj.parent.path.join(__dirname, 'public/clickonce'))); // Indicates the clickonce folder is public } // Find a free port starting with the specified one and going up. @@ -79,8 +91,13 @@ module.exports.CreateRedirServer = function (parent, db, args, certificates) { // Start the ExpressJS web server, if the port is busy try the next one. function StartRedirServer(port) { if (port == 0 || port == 65535) return; - obj.args.redirport = port; - obj.tcpServer = obj.app.listen(port, function () { console.log('MeshCentral HTTP redirection web server running on port ' + port + '.'); }).on('error', function (err) { if ((err.code == 'EACCES') && (port < 65535)) { StartRedirServer(port + 1); } else { console.log(err); } }); + obj.tcpServer = obj.app.listen(port, function () { + obj.port = port; + console.log('MeshCentral HTTP redirection web server running on port ' + port + '.'); + func(obj.port); + }).on('error', function (err) { + if ((err.code == 'EACCES') && (port < 65535)) { StartRedirServer(port + 1); } else { console.log(err); func(obj.port); } + }); } CheckListenPort(args.redirport, StartRedirServer); diff --git a/views/default.handlebars b/views/default.handlebars index 054f9040..1629cdc7 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -1242,7 +1242,7 @@ } function ondockeypress(e) { - if (!xxdialogMode && xxcurrentView == 11 && desktop && desktop.State == 3) return desktop.m.handleKeys(e); + if (!xxdialogMode && xxcurrentView == 11 && desktop) return desktop.m.handleKeys(e); if (!xxdialogMode && xxcurrentView == 12 && terminal && terminal.State == 3) return terminal.m.TermHandleKeys(e); if (!xxdialogMode && xxcurrentView == 15) return agentConsoleHandleKeys(e); if (xxdialogMode || xxcurrentView != 1) return; @@ -1278,7 +1278,7 @@ } function ondockeydown(e) { - if (!xxdialogMode && xxcurrentView == 11 && desktop && desktop.State == 3) return desktop.m.handleKeyDown(e); + if (!xxdialogMode && xxcurrentView == 11 && desktop) return desktop.m.handleKeyDown(e); if (!xxdialogMode && xxcurrentView == 12 && terminal && terminal.State == 3) return terminal.m.TermHandleKeyDown(e); if (!xxdialogMode && xxcurrentView == 13 && e.keyCode == 116 && p13filetree != null) { haltEvent(e); return false; } // F5 Refresh on files if (xxdialogMode || xxcurrentView != 1 || e.ctrlKey == true || e.altKey == true || e.metaKey == true) return; @@ -1295,7 +1295,7 @@ } function ondockeyup(e) { - if (!xxdialogMode && xxcurrentView == 11 && desktop && desktop.State == 3) return desktop.m.handleKeyUp(e); + if (!xxdialogMode && xxcurrentView == 11 && desktop) return desktop.m.handleKeyUp(e); if (!xxdialogMode && xxcurrentView == 12 && terminal && terminal.State == 3) return terminal.m.TermHandleKeyUp(e); if (!xxdialogMode && xxcurrentView == 13 && e.keyCode == 116 && p13filetree != null) { p13folderup(9999); haltEvent(e); return false; } // F5 Refresh on files if (xxdialogMode && e.keyCode == 27) { dialogclose(0); } @@ -2939,6 +2939,7 @@ desktop.m.CompressionLevel = desktopsettings.quality; // Number from 1 to 100. 50 or less is best. desktop.m.ScalingLevel = desktopsettings.scaling; desktop.m.onDisplayinfo = deskDisplayInfo; + desktop.m.onScreenSizeChange = deskAdjust; desktop.Start(desktopNode._id); desktop.contype = 1; } diff --git a/webserver.js b/webserver.js index 78fd8536..9ec1b48c 100644 --- a/webserver.js +++ b/webserver.js @@ -129,7 +129,7 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate var dnscount = 0; obj.tlsSniCredentials = {}; for (var i in obj.certificates.dns) { if (obj.parent.config.domains[i].dns != null) { obj.dnsDomains[obj.parent.config.domains[i].dns.toLowerCase()] = obj.parent.config.domains[i]; obj.tlsSniCredentials[obj.parent.config.domains[i].dns] = obj.tls.createSecureContext(obj.certificates.dns[i]).context; dnscount++; } } - if (dnscount > 0) { obj.tlsSniCredentials[''] = obj.tls.createSecureContext({ cert: obj.certificates.web.cert, key: obj.certificates.web.key, ca: obj.certificates.ca }).context; } else { obj.tlsSniCredentials = null; } + if (dnscount > 0) { obj.tlsSniCredentials[''] = obj.tls.createSecureContext({ cert: obj.certificates.web.cert, key: obj.certificates.web.key, ca: obj.certificates.web.ca }).context; } else { obj.tlsSniCredentials = null; } } function TlsSniCallback(name, cb) { var c = obj.tlsSniCredentials[name]; if (c != null) { cb(null, c); } else { cb(null, obj.tlsSniCredentials['']); } } @@ -143,10 +143,10 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate // Setup the HTTP server with TLS if (obj.tlsSniCredentials != null) { // We have multiple web server certificate used depending on the domain name - obj.tlsServer = require('https').createServer({ SNICallback: TlsSniCallback, cert: obj.certificates.web.cert, key: obj.certificates.web.key, ca: obj.certificates.ca, rejectUnauthorized: true }, obj.app); + obj.tlsServer = require('https').createServer({ SNICallback: TlsSniCallback, cert: obj.certificates.web.cert, key: obj.certificates.web.key, ca: obj.certificates.web.ca, rejectUnauthorized: true }, obj.app); } else { // We have a single web server certificate - obj.tlsServer = require('https').createServer({ cert: obj.certificates.web.cert, key: obj.certificates.web.key, ca: obj.certificates.ca, rejectUnauthorized: true }, obj.app); + obj.tlsServer = require('https').createServer({ cert: obj.certificates.web.cert, key: obj.certificates.web.key, ca: obj.certificates.web.ca, rejectUnauthorized: true }, obj.app); } obj.expressWs = require('express-ws')(obj.app, obj.tlsServer); }