diff --git a/agents/MeshCmd.exe b/agents/MeshCmd.exe index 57c590aa..00b704aa 100644 Binary files a/agents/MeshCmd.exe and b/agents/MeshCmd.exe differ diff --git a/agents/MeshCmd64.exe b/agents/MeshCmd64.exe index fde90d0a..e4af314f 100644 Binary files a/agents/MeshCmd64.exe and b/agents/MeshCmd64.exe differ diff --git a/agents/MeshCmdARM64.exe b/agents/MeshCmdARM64.exe index 6f4f5e6c..83f1079a 100644 Binary files a/agents/MeshCmdARM64.exe and b/agents/MeshCmdARM64.exe differ diff --git a/agents/MeshService.exe b/agents/MeshService.exe index b6b4a387..e3aec7ab 100644 Binary files a/agents/MeshService.exe and b/agents/MeshService.exe differ diff --git a/agents/MeshService64.exe b/agents/MeshService64.exe index bb5ed095..540fed64 100644 Binary files a/agents/MeshService64.exe and b/agents/MeshService64.exe differ diff --git a/agents/MeshServiceARM64.exe b/agents/MeshServiceARM64.exe index c53d7610..ee191f79 100644 Binary files a/agents/MeshServiceARM64.exe and b/agents/MeshServiceARM64.exe differ diff --git a/agents/meshcore.js b/agents/meshcore.js index e561bb6a..5a7faaf1 100644 --- a/agents/meshcore.js +++ b/agents/meshcore.js @@ -295,8 +295,9 @@ if (process.platform == 'win32' && require('user-sessions').isRoot()) { // Check the Agent Uninstall MetaData for correctness, as the installer may have written an incorrect value try { var writtenSize = 0, actualSize = Math.floor(require('fs').statSync(process.execPath).size / 1024); - try { writtenSize = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MeshCentralAgent', 'EstimatedSize'); } catch (ex) { } - if (writtenSize != actualSize) { try { require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MeshCentralAgent', 'EstimatedSize', actualSize); } catch (ex) { } } + var serviceName = (_MSH().serviceName ? _MSH().serviceName : (require('_agentNodeId').serviceName() ? require('_agentNodeId').serviceName() : 'Mesh Agent')); + try { writtenSize = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + serviceName, 'EstimatedSize'); } catch (ex) { } + if (writtenSize != actualSize) { try { require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + serviceName, 'EstimatedSize', actualSize); } catch (ex) { } } } catch (ex) { } // Check to see if we are the Installed Mesh Agent Service, if we are, make sure we can run in Safe Mode @@ -310,6 +311,16 @@ if (process.platform == 'win32' && require('user-sessions').isRoot()) { try { meshCheck = require('service-manager').manager.getService(svcname).isMe(); } catch (ex) { } if (meshCheck && require('win-bcd').isSafeModeService && !require('win-bcd').isSafeModeService(svcname)) { require('win-bcd').enableSafeModeService(svcname); } } catch (ex) { } + + // Check the Agent Uninstall MetaData for DisplayVersion and update if not the same and only on windows + if (process.platform == 'win32') { + try { + var writtenDisplayVersion = 0, actualDisplayVersion = process.versions.commitDate.toString(); + var serviceName = (_MSH().serviceName ? _MSH().serviceName : (require('_agentNodeId').serviceName() ? require('_agentNodeId').serviceName() : 'Mesh Agent')); + try { writtenDisplayVersion = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + serviceName, 'DisplayVersion'); } catch (ex) { } + if (writtenDisplayVersion != actualDisplayVersion) { try { require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + serviceName, 'DisplayVersion', actualDisplayVersion); } catch (ex) { } } + } catch (ex) { } + } } if (process.platform != 'win32') { @@ -655,33 +666,39 @@ var meshCoreObj = { action: 'coreinfo', value: (require('MeshAgent').coreHash ? try { require('os').name().then(function (v) { meshCoreObj.osdesc = v; meshCoreObjChanged(); }); } catch (ex) { } // Setup logged in user monitoring (THIS IS BROKEN IN WIN7) +function onUserSessionChanged(user, locked) { + userSession.enumerateUsers().then(function (users) { + if (process.platform == 'linux') { + if (userSession._startTime == null) { + userSession._startTime = Date.now(); + userSession._count = users.length; + } + else if (Date.now() - userSession._startTime < 10000 && users.length == userSession._count) { + userSession.removeAllListeners('changed'); + return; + } + } + + var u = [], a = users.Active; + if(meshCoreObj.lusers == null) { meshCoreObj.lusers = []; } + for (var i = 0; i < a.length; i++) { + var un = a[i].Domain ? (a[i].Domain + '\\' + a[i].Username) : (a[i].Username); + if (user && locked && (JSON.stringify(a[i]) === JSON.stringify(user))) { if (meshCoreObj.lusers.indexOf(un) == -1) { meshCoreObj.lusers.push(un); } } + else if (user && !locked && (JSON.stringify(a[i]) === JSON.stringify(user))) { meshCoreObj.lusers.splice(meshCoreObj.lusers.indexOf(un), 1); } + if (u.indexOf(un) == -1) { u.push(un); } // Only push users in the list once. + } + meshCoreObj.lusers = meshCoreObj.lusers; + meshCoreObj.users = u; + meshCoreObjChanged(); + }); +} + try { var userSession = require('user-sessions'); - userSession.on('changed', function onUserSessionChanged() { - userSession.enumerateUsers().then(function (users) { - if (process.platform == 'linux') { - if (userSession._startTime == null) { - userSession._startTime = Date.now(); - userSession._count = users.length; - } - else if (Date.now() - userSession._startTime < 10000 && users.length == userSession._count) { - userSession.removeAllListeners('changed'); - return; - } - } - - var u = [], a = users.Active; - for (var i = 0; i < a.length; i++) { - var un = a[i].Domain ? (a[i].Domain + '\\' + a[i].Username) : (a[i].Username); - if (u.indexOf(un) == -1) { u.push(un); } // Only push users in the list once. - } - meshCoreObj.users = u; - meshCoreObjChanged(); - }); - }); + userSession.on('changed', function () { onUserSessionChanged(null, false); }); userSession.emit('changed'); - //userSession.on('locked', function (user) { sendConsoleText('[' + (user.Domain ? user.Domain + '\\' : '') + user.Username + '] has LOCKED the desktop'); }); - //userSession.on('unlocked', function (user) { sendConsoleText('[' + (user.Domain ? user.Domain + '\\' : '') + user.Username + '] has UNLOCKED the desktop'); }); + userSession.on('locked', function (user) { if(user != undefined && user != null) { onUserSessionChanged(user, true); } }); + userSession.on('unlocked', function (user) { if(user != undefined && user != null) { onUserSessionChanged(user, false); } }); } catch (ex) { } var meshServerConnectionState = 0; @@ -1158,6 +1175,7 @@ function handleServerCommand(data) { tunnel.soptions = data.soptions; tunnel.consentTimeout = (tunnel.soptions && tunnel.soptions.consentTimeout) ? tunnel.soptions.consentTimeout : 30; tunnel.consentAutoAccept = (tunnel.soptions && (tunnel.soptions.consentAutoAccept === true)); + tunnel.consentAutoAcceptIfNoUser = (tunnel.soptions && (tunnel.soptions.consentAutoAcceptIfNoUser === true)); tunnel.oldStyle = (tunnel.soptions && tunnel.soptions.oldStyle) ? tunnel.soptions.oldStyle : false; tunnel.tcpaddr = data.tcpaddr; tunnel.tcpport = data.tcpport; @@ -1572,7 +1590,7 @@ function handleServerCommand(data) { mesh.cmdchild = require('child_process').execFile('/bin/sh', ['sh'], options); mesh.cmdchild.descriptorMetadata = 'UserCommandsShell'; mesh.cmdchild.stdout.on('data', function (c) { replydata += c.toString(); }); - mesh.cmdchild.stderr.on('data', function (c) { replydata + c.toString(); }); + mesh.cmdchild.stderr.on('data', function (c) { replydata += c.toString(); }); mesh.cmdchild.stdin.write(data.cmds.split('\r').join('') + '\nexit\n'); mesh.cmdchild.on('exit', function () { if (data.reply) { @@ -2297,6 +2315,59 @@ function terminal_end() } +function terminal_consent_ask(ws) { + ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 })); + var consentMessage = currentTranslation['terminalConsent'].replace('{0}', ws.httprequest.realname).replace('{1}', ws.httprequest.username); + var consentTitle = 'MeshCentral'; + if (ws.httprequest.soptions != null) { + if (ws.httprequest.soptions.consentTitle != null) { consentTitle = ws.httprequest.soptions.consentTitle; } + if (ws.httprequest.soptions.consentMsgTerminal != null) { consentMessage = ws.httprequest.soptions.consentMsgTerminal.replace('{0}', ws.httprequest.realname).replace('{1}', ws.httprequest.username); } + } + if (process.platform == 'win32') { + var enhanced = false; + if (ws.httprequest.oldStyle === false) { + try { require('win-userconsent'); enhanced = true; } catch (ex) { } + } + if (enhanced) { + var ipr = server_getUserImage(ws.httprequest.userid); + ipr.consentTitle = consentTitle; + ipr.consentMessage = consentMessage; + ipr.consentTimeout = ws.httprequest.consentTimeout; + ipr.consentAutoAccept = ws.httprequest.consentAutoAccept; + ipr.username = ws.httprequest.realname; + ipr.tsid = ws.tsid; + ipr.translations = { Allow: currentTranslation['allow'], Deny: currentTranslation['deny'], Auto: currentTranslation['autoAllowForFive'], Caption: consentMessage }; + ws.httprequest.tpromise._consent = ipr.then(function (img) { + this.consent = require('win-userconsent').create(this.consentTitle, this.consentMessage, this.username, { b64Image: img.split(',').pop(), uid: this.tsid, timeout: this.consentTimeout * 1000, timeoutAutoAccept: this.consentAutoAccept, translations: this.translations, background: color_options.background, foreground: color_options.foreground }); + this.__childPromise.close = this.consent.close.bind(this.consent); + return (this.consent); + }); + } else { + ws.httprequest.tpromise._consent = require('message-box').create(consentTitle, consentMessage, ws.httprequest.consentTimeout); + } + } else { + ws.httprequest.tpromise._consent = require('message-box').create(consentTitle, consentMessage, ws.httprequest.consentTimeout); + } + ws.httprequest.tpromise._consent.retPromise = ws.httprequest.tpromise; + ws.httprequest.tpromise._consent.then(function (always) { + if (always && process.platform == 'win32') { server_set_consentTimer(this.retPromise.httprequest.userid); } + // Success + MeshServerLogEx(27, null, "Local user accepted remote terminal request (" + this.retPromise.httprequest.remoteaddr + ")", this.retPromise.that.httprequest); + this.retPromise.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null, msgid: 0 })); + this.retPromise._consent = null; + this.retPromise._res(); + }, function (e) { + if (this.retPromise.that) { + if(this.retPromise.that.httprequest){ // User Consent Denied + MeshServerLogEx(28, null, "Local user rejected remote terminal request (" + this.retPromise.that.httprequest.remoteaddr + ")", this.retPromise.that.httprequest); + } else { } // Connection was closed server side, maybe log some messages somewhere? + this.retPromise._consent = null; + this.retPromise.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 })); + } else { } // no websocket, maybe log some messages somewhere? + this.retPromise._rej(e.toString()); + }); +} + function terminal_promise_connection_rejected(e) { // FAILED to connect terminal @@ -2609,6 +2680,101 @@ function kvm_tunnel_consentpromise_closehandler() if (this._consentpromise && this._consentpromise.close) { this._consentpromise.close(); } } +function kvm_consent_ok(ws) { + // User Consent Prompt is not required because no user is present + if (ws.httprequest.consent && (ws.httprequest.consent & 1)){ + // User Notifications is required + MeshServerLogEx(35, null, "Started remote desktop with toast notification (" + ws.httprequest.remoteaddr + ")", ws.httprequest); + var notifyMessage = currentTranslation['desktopNotify'].replace('{0}', ws.httprequest.realname); + var notifyTitle = "MeshCentral"; + if (ws.httprequest.soptions != null) { + if (ws.httprequest.soptions.notifyTitle != null) { notifyTitle = ws.httprequest.soptions.notifyTitle; } + if (ws.httprequest.soptions.notifyMsgDesktop != null) { notifyMessage = ws.httprequest.soptions.notifyMsgDesktop.replace('{0}', ws.httprequest.realname).replace('{1}', ws.httprequest.username); } + } + try { require('toaster').Toast(notifyTitle, notifyMessage, ws.tsid); } catch (ex) { } + } else { + MeshServerLogEx(36, null, "Started remote desktop without notification (" + ws.httprequest.remoteaddr + ")", ws.httprequest); + } + if (ws.httprequest.consent && (ws.httprequest.consent & 0x40)) { + // Connection Bar is required + if (ws.httprequest.desktop.kvm.connectionBar) { + ws.httprequest.desktop.kvm.connectionBar.removeAllListeners('close'); + ws.httprequest.desktop.kvm.connectionBar.close(); + } + try { + ws.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(ws.httprequest.privacybartext.replace('{0}', ws.httprequest.desktop.kvm.rusers.join(', ')).replace('{1}', ws.httprequest.desktop.kvm.users.join(', ')).replace(/'/g, "\\'\\"), require('MeshAgent')._tsid, color_options); + MeshServerLogEx(31, null, "Remote Desktop Connection Bar Activated/Updated (" + ws.httprequest.remoteaddr + ")", ws.httprequest); + } catch (ex) { + MeshServerLogEx(32, null, "Remote Desktop Connection Bar Failed or not Supported (" + ws.httprequest.remoteaddr + ")", ws.httprequest); + } + if (ws.httprequest.desktop.kvm.connectionBar) { + ws.httprequest.desktop.kvm.connectionBar.state = { + userid: ws.httprequest.userid, + xuserid: ws.httprequest.xuserid, + username: ws.httprequest.username, + sessionid: ws.httprequest.sessionid, + remoteaddr: ws.httprequest.remoteaddr, + guestname: ws.httprequest.guestname, + desktop: ws.httprequest.desktop + }; + ws.httprequest.desktop.kvm.connectionBar.on('close', function () { + console.info1('Connection Bar Forcefully closed'); + MeshServerLogEx(29, null, "Remote Desktop Connection forcefully closed by local user (" + this.state.remoteaddr + ")", this.state); + for (var i in this.state.desktop.kvm._pipedStreams) { + this.state.desktop.kvm._pipedStreams[i].end(); + } + this.state.desktop.kvm.end(); + }); + } + } + ws.httprequest.desktop.kvm.pipe(ws, { dataTypeSkip: 1 }); + if (ws.httprequest.autolock) { + destopLockHelper_pipe(ws.httprequest); + } +} + +function kvm_consent_ask(ws){ + // Send a console message back using the console channel, "\n" is supported. + ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 })); + var consentMessage = currentTranslation['desktopConsent'].replace('{0}', ws.httprequest.realname).replace('{1}', ws.httprequest.username); + var consentTitle = 'MeshCentral'; + if (ws.httprequest.soptions != null) { + if (ws.httprequest.soptions.consentTitle != null) { consentTitle = ws.httprequest.soptions.consentTitle; } + if (ws.httprequest.soptions.consentMsgDesktop != null) { consentMessage = ws.httprequest.soptions.consentMsgDesktop.replace('{0}', ws.httprequest.realname).replace('{1}', ws.httprequest.username); } + } + var pr; + if (process.platform == 'win32') { + var enhanced = false; + if (ws.httprequest.oldStyle === false) { + try { require('win-userconsent'); enhanced = true; } catch (ex) { } + } + if (enhanced) { + var ipr = server_getUserImage(ws.httprequest.userid); + ipr.consentTitle = consentTitle; + ipr.consentMessage = consentMessage; + ipr.consentTimeout = ws.httprequest.consentTimeout; + ipr.consentAutoAccept = ws.httprequest.consentAutoAccept; + ipr.tsid = ws.tsid; + ipr.username = ws.httprequest.realname; + ipr.translation = { Allow: currentTranslation['allow'], Deny: currentTranslation['deny'], Auto: currentTranslation['autoAllowForFive'], Caption: consentMessage }; + pr = ipr.then(function (img) { + this.consent = require('win-userconsent').create(this.consentTitle, this.consentMessage, this.username, { b64Image: img.split(',').pop(), uid: this.tsid, timeout: this.consentTimeout * 1000, timeoutAutoAccept: this.consentAutoAccept, translations: this.translation, background: color_options.background, foreground: color_options.foreground }); + this.__childPromise.close = this.consent.close.bind(this.consent); + return (this.consent); + }); + } else { + pr = require('message-box').create(consentTitle, consentMessage, ws.httprequest.consentTimeout, null, ws.tsid); + } + } else { + pr = require('message-box').create(consentTitle, consentMessage, ws.httprequest.consentTimeout, null, ws.tsid); + } + pr.ws = ws; + ws.pause(); + ws._consentpromise = pr; + ws.prependOnceListener('end', kvm_tunnel_consentpromise_closehandler); + pr.then(kvm_consentpromise_resolved, kvm_consentpromise_rejected); +} + function kvm_consentpromise_rejected(e) { if (this.ws) { @@ -2688,6 +2854,67 @@ function kvm_consentpromise_resolved(always) this.ws = null; } +function files_consent_ok(ws){ + // User Consent Prompt is not required + if (ws.httprequest.consent && (ws.httprequest.consent & 4)) { + // User Notifications is required + MeshServerLogEx(42, null, "Started remote files with toast notification (" + ws.httprequest.remoteaddr + ")", ws.httprequest); + var notifyMessage = currentTranslation['fileNotify'].replace('{0}', ws.httprequest.realname); + var notifyTitle = "MeshCentral"; + if (ws.httprequest.soptions != null) { + if (ws.httprequest.soptions.notifyTitle != null) { notifyTitle = ws.httprequest.soptions.notifyTitle; } + if (ws.httprequest.soptions.notifyMsgFiles != null) { notifyMessage = ws.httprequest.soptions.notifyMsgFiles.replace('{0}', ws.httprequest.realname).replace('{1}', ws.httprequest.username); } + } + try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (ex) { } + } else { + MeshServerLogEx(43, null, "Started remote files without notification (" + ws.httprequest.remoteaddr + ")", ws.httprequest); + } + ws.resume(); +} + +function files_consent_ask(ws){ + // Send a console message back using the console channel, "\n" is supported. + ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 })); + var consentMessage = currentTranslation['fileConsent'].replace('{0}', ws.httprequest.realname).replace('{1}', ws.httprequest.username); + var consentTitle = 'MeshCentral'; + + if (ws.httprequest.soptions != null) { + if (ws.httprequest.soptions.consentTitle != null) { consentTitle = ws.httprequest.soptions.consentTitle; } + if (ws.httprequest.soptions.consentMsgFiles != null) { consentMessage = ws.httprequest.soptions.consentMsgFiles.replace('{0}', ws.httprequest.realname).replace('{1}', ws.httprequest.username); } + } + var pr; + if (process.platform == 'win32') { + var enhanced = false; + if (ws.httprequest.oldStyle === false) { + try { require('win-userconsent'); enhanced = true; } catch (ex) { } + } + if (enhanced) { + var ipr = server_getUserImage(ws.httprequest.userid); + ipr.consentTitle = consentTitle; + ipr.consentMessage = consentMessage; + ipr.consentTimeout = ws.httprequest.consentTimeout; + ipr.consentAutoAccept = ws.httprequest.consentAutoAccept; + ipr.username = ws.httprequest.realname; + ipr.tsid = ws.tsid; + ipr.translations = { Allow: currentTranslation['allow'], Deny: currentTranslation['deny'], Auto: currentTranslation['autoAllowForFive'], Caption: consentMessage }; + pr = ipr.then(function (img) { + this.consent = require('win-userconsent').create(this.consentTitle, this.consentMessage, this.username, { b64Image: img.split(',').pop(), uid: this.tsid, timeout: this.consentTimeout * 1000, timeoutAutoAccept: this.consentAutoAccept, translations: this.translations, background: color_options.background, foreground: color_options.foreground }); + this.__childPromise.close = this.consent.close.bind(this.consent); + return (this.consent); + }); + } else { + pr = require('message-box').create(consentTitle, consentMessage, ws.httprequest.consentTimeout, null); + } + } else { + pr = require('message-box').create(consentTitle, consentMessage, ws.httprequest.consentTimeout, null); + } + pr.ws = ws; + ws.pause(); + ws._consentpromise = pr; + ws.prependOnceListener('end', files_tunnel_endhandler); + pr.then(files_consentpromise_resolved, files_consentpromise_rejected); +} + function files_consentpromise_resolved(always) { if (always && process.platform == 'win32') { server_set_consentTimer(this.ws.httprequest.userid); } @@ -2801,6 +3028,12 @@ function onTunnelData(data) this.descriptorMetadata = "Remote Terminal"; + // Look for a TSID + var tsid = null; + if ((this.httprequest.xoptions != null) && (typeof this.httprequest.xoptions.tsid == 'number')) { tsid = this.httprequest.xoptions.tsid; } + require('MeshAgent')._tsid = tsid; + this.tsid = tsid; + if (process.platform == 'win32') { if (!require('win-terminal').PowerShellCapable() && (this.httprequest.protocol == 6 || this.httprequest.protocol == 9)) { @@ -2817,76 +3050,31 @@ function onTunnelData(data) this.end = terminal_end; // Perform User-Consent if needed. - if (this.httprequest.consent && (this.httprequest.consent & 16)) - { - this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 })); - var consentMessage = currentTranslation['terminalConsent'].replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); - var consentTitle = 'MeshCentral'; - - if (this.httprequest.soptions != null) - { - if (this.httprequest.soptions.consentTitle != null) { consentTitle = this.httprequest.soptions.consentTitle; } - if (this.httprequest.soptions.consentMsgTerminal != null) { consentMessage = this.httprequest.soptions.consentMsgTerminal.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); } - } - if (process.platform == 'win32') - { - var enhanced = false; - if (this.httprequest.oldStyle === false) { - try { require('win-userconsent'); enhanced = true; } catch (ex) { } - } - if (enhanced) - { - var ipr = server_getUserImage(this.httprequest.userid); - ipr.consentTitle = consentTitle; - ipr.consentMessage = consentMessage; - ipr.consentTimeout = this.httprequest.consentTimeout; - ipr.consentAutoAccept = this.httprequest.consentAutoAccept; - ipr.username = this.httprequest.realname; - ipr.translations = { Allow: currentTranslation['allow'], Deny: currentTranslation['deny'], Auto: currentTranslation['autoAllowForFive'], Caption: consentMessage }; - this.httprequest.tpromise._consent = ipr.then(function (img) - { - this.consent = require('win-userconsent').create(this.consentTitle, this.consentMessage, this.username, { b64Image: img.split(',').pop(), timeout: this.consentTimeout * 1000, timeoutAutoAccept: this.consentAutoAccept, translations: this.translations, background: color_options.background, foreground: color_options.foreground }); - this.__childPromise.close = this.consent.close.bind(this.consent); - return (this.consent); - }); - } else - { - this.httprequest.tpromise._consent = require('message-box').create(consentTitle, consentMessage, this.httprequest.consentTimeout); - } - } else - { - this.httprequest.tpromise._consent = require('message-box').create(consentTitle, consentMessage, this.httprequest.consentTimeout); - } - this.httprequest.tpromise._consent.retPromise = this.httprequest.tpromise; - this.httprequest.tpromise._consent.then( - function (always) - { - if (always && process.platform == 'win32') { server_set_consentTimer(this.retPromise.httprequest.userid); } - - // Success - MeshServerLogEx(27, null, "Local user accepted remote terminal request (" + this.retPromise.httprequest.remoteaddr + ")", this.retPromise.that.httprequest); - this.retPromise.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null, msgid: 0 })); - this.retPromise._consent = null; - this.retPromise._res(); - }, - function (e) { - if (this.retPromise.that) { - if(this.retPromise.that.httprequest){ // User Consent Denied - MeshServerLogEx(28, null, "Local user rejected remote terminal request (" + this.retPromise.that.httprequest.remoteaddr + ")", this.retPromise.that.httprequest); - } else { } // Connection was closed server side, maybe log some messages somewhere? - this.retPromise._consent = null; - this.retPromise.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 })); - } else { } // no websocket, maybe log some messages somewhere? - this.retPromise._rej(e.toString()); + if (this.httprequest.consent && (this.httprequest.consent & 16)) { + // User asked for consent so now we check if we can auto accept if no user is present/loggedin + if (this.httprequest.consentAutoAcceptIfNoUser) { + var p = require('user-sessions').enumerateUsers(); + p.sessionid = this.httprequest.sessionid; + p.ws = this; + p.then(function (u) { + var v = []; + for (var i in u) { + if (u[i].State == 'Active') { v.push({ tsid: i, type: u[i].StationName, user: u[i].Username, domain: u[i].Domain }); } + } + if (v.length == 0) { // No user is present, auto accept + this.ws.httprequest.tpromise._res(); + } else { + // User is present so we still need consent + terminal_consent_ask(this.ws); + } }); - } - else - { + } else { + terminal_consent_ask(this); + } + } else { // User-Consent is not required, so just resolve this promise this.httprequest.tpromise._res(); } - - this.httprequest.tpromise.then(terminal_promise_consent_resolved, terminal_promise_consent_rejected); } else if (this.httprequest.protocol == 2) @@ -2910,6 +3098,7 @@ function onTunnelData(data) var tsid = null; if ((this.httprequest.xoptions != null) && (typeof this.httprequest.xoptions.tsid == 'number')) { tsid = this.httprequest.xoptions.tsid; } require('MeshAgent')._tsid = tsid; + this.tsid = tsid; // If MacOS, Wake up device with caffeinate if(process.platform == 'darwin'){ @@ -2981,119 +3170,33 @@ function onTunnelData(data) } // Perform notification if needed. Toast messages may not be supported on all platforms. - if (this.httprequest.consent && (this.httprequest.consent & 8)) - { - // User Consent Prompt is required - // Send a console message back using the console channel, "\n" is supported. - this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 })); - var consentMessage = currentTranslation['desktopConsent'].replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); - var consentTitle = 'MeshCentral'; - if (this.httprequest.soptions != null) - { - if (this.httprequest.soptions.consentTitle != null) { consentTitle = this.httprequest.soptions.consentTitle; } - if (this.httprequest.soptions.consentMsgDesktop != null) { consentMessage = this.httprequest.soptions.consentMsgDesktop.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); } + if (this.httprequest.consent && (this.httprequest.consent & 8)) { + + // User asked for consent but now we check if can auto accept if no user is present + if (this.httprequest.consentAutoAcceptIfNoUser) { + // Get list of users to check if we any actual users logged in, and if users logged in, we still need consent + var p = require('user-sessions').enumerateUsers(); + p.sessionid = this.httprequest.sessionid; + p.ws = this; + p.then(function (u) { + var v = []; + for (var i in u) { + if (u[i].State == 'Active') { v.push({ tsid: i, type: u[i].StationName, user: u[i].Username, domain: u[i].Domain }); } + } + if (v.length == 0) { // No user is present, auto accept + kvm_consent_ok(this.ws); + } else { + // User is present so we still need consent + kvm_consent_ask(this.ws); + } + }); + } else { + // User Consent Prompt is required + kvm_consent_ask(this); } - var pr; - if (process.platform == 'win32') - { - var enhanced = false; - if (this.httprequest.oldStyle === false) { - try { require('win-userconsent'); enhanced = true; } catch (ex) { } - } - if (enhanced) - { - var ipr = server_getUserImage(this.httprequest.userid); - ipr.consentTitle = consentTitle; - ipr.consentMessage = consentMessage; - ipr.consentTimeout = this.httprequest.consentTimeout; - ipr.consentAutoAccept = this.httprequest.consentAutoAccept; - ipr.tsid = tsid; - ipr.username = this.httprequest.realname; - ipr.translation = { Allow: currentTranslation['allow'], Deny: currentTranslation['deny'], Auto: currentTranslation['autoAllowForFive'], Caption: consentMessage }; - pr = ipr.then(function (img) - { - this.consent = require('win-userconsent').create(this.consentTitle, this.consentMessage, this.username, { b64Image: img.split(',').pop(), uid: this.tsid, timeout: this.consentTimeout * 1000, timeoutAutoAccept: this.consentAutoAccept, translations: this.translation, background: color_options.background, foreground: color_options.foreground }); - this.__childPromise.close = this.consent.close.bind(this.consent); - return (this.consent); - }); - } - else - { - pr = require('message-box').create(consentTitle, consentMessage, this.httprequest.consentTimeout, null, tsid); - } - } - else - { - pr = require('message-box').create(consentTitle, consentMessage, this.httprequest.consentTimeout, null, tsid); - } - pr.ws = this; - this.pause(); - this._consentpromise = pr; - this.prependOnceListener('end', kvm_tunnel_consentpromise_closehandler); - pr.then(kvm_consentpromise_resolved, kvm_consentpromise_rejected); - } - else - { + } else { // User Consent Prompt is not required - if (this.httprequest.consent && (this.httprequest.consent & 1)) - { - // User Notifications is required - MeshServerLogEx(35, null, "Started remote desktop with toast notification (" + this.httprequest.remoteaddr + ")", this.httprequest); - var notifyMessage = currentTranslation['desktopNotify'].replace('{0}', this.httprequest.realname); - var notifyTitle = "MeshCentral"; - if (this.httprequest.soptions != null) { - if (this.httprequest.soptions.notifyTitle != null) { notifyTitle = this.httprequest.soptions.notifyTitle; } - if (this.httprequest.soptions.notifyMsgDesktop != null) { notifyMessage = this.httprequest.soptions.notifyMsgDesktop.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); } - } - try { require('toaster').Toast(notifyTitle, notifyMessage, tsid); } catch (ex) { } - } else - { - MeshServerLogEx(36, null, "Started remote desktop without notification (" + this.httprequest.remoteaddr + ")", this.httprequest); - } - if (this.httprequest.consent && (this.httprequest.consent & 0x40)) - { - // Connection Bar is required - if (this.httprequest.desktop.kvm.connectionBar) - { - this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close'); - this.httprequest.desktop.kvm.connectionBar.close(); - } - try - { - this.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(this.httprequest.privacybartext.replace('{0}', this.httprequest.desktop.kvm.rusers.join(', ')).replace('{1}', this.httprequest.desktop.kvm.users.join(', ')).replace(/'/g, "\\'\\"), require('MeshAgent')._tsid, color_options); - MeshServerLogEx(31, null, "Remote Desktop Connection Bar Activated/Updated (" + this.httprequest.remoteaddr + ")", this.httprequest); - } catch (ex) { - MeshServerLogEx(32, null, "Remote Desktop Connection Bar Failed or not Supported (" + this.httprequest.remoteaddr + ")", this.httprequest); - } - if (this.httprequest.desktop.kvm.connectionBar) - { - this.httprequest.desktop.kvm.connectionBar.state = - { - userid: this.httprequest.userid, - xuserid: this.httprequest.xuserid, - username: this.httprequest.username, - sessionid: this.httprequest.sessionid, - remoteaddr: this.httprequest.remoteaddr, - guestname: this.httprequest.guestname, - desktop: this.httprequest.desktop - }; - this.httprequest.desktop.kvm.connectionBar.on('close', function () - { - console.info1('Connection Bar Forcefully closed'); - MeshServerLogEx(29, null, "Remote Desktop Connection forcefully closed by local user (" + this.state.remoteaddr + ")", this.state); - for (var i in this.state.desktop.kvm._pipedStreams) - { - this.state.desktop.kvm._pipedStreams[i].end(); - } - this.state.desktop.kvm.end(); - }); - } - } - this.httprequest.desktop.kvm.pipe(this, { dataTypeSkip: 1 }); - if (this.httprequest.autolock) - { - destopLockHelper_pipe(this.httprequest); - } + kvm_consent_ok(this); } this.removeAllListeners('data'); @@ -3115,6 +3218,12 @@ function onTunnelData(data) this.descriptorMetadata = "Remote Files"; + // Look for a TSID + var tsid = null; + if ((this.httprequest.xoptions != null) && (typeof this.httprequest.xoptions.tsid == 'number')) { tsid = this.httprequest.xoptions.tsid; } + require('MeshAgent')._tsid = tsid; + this.tsid = tsid; + // Add the files session to the count to update the server if (this.httprequest.userid != null) { var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest); @@ -3137,71 +3246,31 @@ function onTunnelData(data) // Perform notification if needed. Toast messages may not be supported on all platforms. if (this.httprequest.consent && (this.httprequest.consent & 32)) { - // User Consent Prompt is required - // Send a console message back using the console channel, "\n" is supported. - this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 })); - var consentMessage = currentTranslation['fileConsent'].replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); - var consentTitle = 'MeshCentral'; - - if (this.httprequest.soptions != null) - { - if (this.httprequest.soptions.consentTitle != null) { consentTitle = this.httprequest.soptions.consentTitle; } - if (this.httprequest.soptions.consentMsgFiles != null) { consentMessage = this.httprequest.soptions.consentMsgFiles.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); } - } - var pr; - if (process.platform == 'win32') - { - var enhanced = false; - if (this.httprequest.oldStyle === false) { - try { require('win-userconsent'); enhanced = true; } catch (ex) { } - } - if (enhanced) - { - var ipr = server_getUserImage(this.httprequest.userid); - ipr.consentTitle = consentTitle; - ipr.consentMessage = consentMessage; - ipr.consentTimeout = this.httprequest.consentTimeout; - ipr.consentAutoAccept = this.httprequest.consentAutoAccept; - ipr.username = this.httprequest.realname; - ipr.translations = { Allow: currentTranslation['allow'], Deny: currentTranslation['deny'], Auto: currentTranslation['autoAllowForFive'], Caption: consentMessage }; - pr = ipr.then(function (img) - { - this.consent = require('win-userconsent').create(this.consentTitle, this.consentMessage, this.username, { b64Image: img.split(',').pop(), timeout: this.consentTimeout * 1000, timeoutAutoAccept: this.consentAutoAccept, translations: this.translations, background: color_options.background, foreground: color_options.foreground }); - this.__childPromise.close = this.consent.close.bind(this.consent); - return (this.consent); - }); - } else - { - pr = require('message-box').create(consentTitle, consentMessage, this.httprequest.consentTimeout, null); - } - } - else - { - pr = require('message-box').create(consentTitle, consentMessage, this.httprequest.consentTimeout, null); - } - pr.ws = this; - this.pause(); - this._consentpromise = pr; - this.prependOnceListener('end', files_tunnel_endhandler); - pr.then(files_consentpromise_resolved, files_consentpromise_rejected); - } - else - { - // User Consent Prompt is not required - if (this.httprequest.consent && (this.httprequest.consent & 4)) { - // User Notifications is required - MeshServerLogEx(42, null, "Started remote files with toast notification (" + this.httprequest.remoteaddr + ")", this.httprequest); - var notifyMessage = currentTranslation['fileNotify'].replace('{0}', this.httprequest.realname); - var notifyTitle = "MeshCentral"; - if (this.httprequest.soptions != null) { - if (this.httprequest.soptions.notifyTitle != null) { notifyTitle = this.httprequest.soptions.notifyTitle; } - if (this.httprequest.soptions.notifyMsgFiles != null) { notifyMessage = this.httprequest.soptions.notifyMsgFiles.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); } - } - try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (ex) { } + // User asked for consent so now we check if we can auto accept if no user is present/loggedin + if (this.httprequest.consentAutoAcceptIfNoUser) { + var p = require('user-sessions').enumerateUsers(); + p.sessionid = this.httprequest.sessionid; + p.ws = this; + p.then(function (u) { + var v = []; + for (var i in u) { + if (u[i].State == 'Active') { v.push({ tsid: i, type: u[i].StationName, user: u[i].Username, domain: u[i].Domain }); } + } + if (v.length == 0) { // No user is present, auto accept + // User Consent Prompt is not required + files_consent_ok(this.ws); + } else { + // User is present so we still need consent + files_consent_ask(this.ws); + } + }); } else { - MeshServerLogEx(43, null, "Started remote files without notification (" + this.httprequest.remoteaddr + ")", this.httprequest); + // User Consent Prompt is required + files_consent_ask(this); } - this.resume(); + } else { + // User Consent Prompt is not required + files_consent_ok(this); } // Setup files @@ -3689,7 +3758,14 @@ function onTunnelControlData(data, ws) { { // Desktop // Switch the user input from websocket to webrtc at this point. ws.unpipe(ws.httprequest.desktop.kvm); - try { ws.webrtc.rtcchannel.pipe(ws.httprequest.desktop.kvm, { dataTypeSkip: 1, end: false }); } catch (ex) { sendConsoleText('EX2'); } // 0 = Binary, 1 = Text. + if ((ws.httprequest.desktopviewonly != true) && ((ws.httprequest.rights == 0xFFFFFFFF) || (((ws.httprequest.rights & MESHRIGHT_REMOTECONTROL) != 0) && ((ws.httprequest.rights & MESHRIGHT_REMOTEVIEW) == 0)))) { + // If we have remote control rights, pipe the KVM input + try { ws.webrtc.rtcchannel.pipe(ws.httprequest.desktop.kvm, { dataTypeSkip: 1, end: false }); } catch (ex) { sendConsoleText('EX2'); } // 0 = Binary, 1 = Text. + } else { + // We need to only pipe non-mouse & non-keyboard inputs. + // sendConsoleText('Warning: No Remote Desktop Input Rights.'); + // TODO!!! + } ws.resume(); // Resume the websocket to keep receiving control data } ws.write('{\"ctrlChannel\":\"102938\",\"type\":\"webrtc2\"}'); // Indicates we will no longer get any data on websocket, switching to WebRTC at this point. @@ -4290,7 +4366,7 @@ function processConsoleCommand(cmd, args, rights, sessionid) { } case 'agentmsg': { if (args['_'].length == 0) { - response = "Proper usage:\r\n agentmsg add \"[message]\" [iconIndex]\r\n agentmsg remove [index]\r\n agentmsg list"; // Display usage + response = "Proper usage:\r\n agentmsg add \"[message]\" [iconIndex]\r\n agentmsg remove [id]\r\n agentmsg list"; // Display usage } else { if ((args['_'][0] == 'add') && (args['_'].length > 1)) { var msgID, iconIndex = 0; @@ -4508,10 +4584,11 @@ function processConsoleCommand(cmd, args, rights, sessionid) { if (process.platform == 'win32') { // Check the Agent Uninstall MetaData for correctness, as the installer may have written an incorrect value var writtenSize = 0; - try { writtenSize = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MeshCentralAgent', 'EstimatedSize'); } catch (ex) { response = ex; } + var serviceName = (_MSH().serviceName ? _MSH().serviceName : (require('_agentNodeId').serviceName() ? require('_agentNodeId').serviceName() : 'Mesh Agent')); + try { writtenSize = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + serviceName, 'EstimatedSize'); } catch (ex) { response = ex; } if (writtenSize != actualSize) { response = "Size updated from: " + writtenSize + " to: " + actualSize; - try { require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MeshCentralAgent', 'EstimatedSize', actualSize); } catch (ex) { response = ex; } + try { require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + serviceName, 'EstimatedSize', actualSize); } catch (ex) { response = ex; } } else { response = "Agent Size: " + actualSize + " kb"; } } else @@ -5591,7 +5668,7 @@ function windows_execve(name, agentfilename, sessionid) { sendAgentMessage('Self Update failed because msvcrt.dll is missing', 3); return; } - + var cmd = require('_GenericMarshal').CreateVariable(process.env['windir'] + '\\system32\\cmd.exe', { wide: true }); var args = require('_GenericMarshal').CreateVariable(3 * require('_GenericMarshal').PointerSize); var arg1 = require('_GenericMarshal').CreateVariable('cmd.exe', { wide: true }); diff --git a/agents/test_agents/MeshCmd.exe b/agents/test_agents/MeshCmd.exe deleted file mode 100644 index 00b704aa..00000000 Binary files a/agents/test_agents/MeshCmd.exe and /dev/null differ diff --git a/agents/test_agents/MeshCmd64.exe b/agents/test_agents/MeshCmd64.exe deleted file mode 100644 index e4af314f..00000000 Binary files a/agents/test_agents/MeshCmd64.exe and /dev/null differ diff --git a/agents/test_agents/MeshCmdARM64.exe b/agents/test_agents/MeshCmdARM64.exe deleted file mode 100644 index 83f1079a..00000000 Binary files a/agents/test_agents/MeshCmdARM64.exe and /dev/null differ diff --git a/agents/test_agents/MeshService.exe b/agents/test_agents/MeshService.exe deleted file mode 100644 index cedb45eb..00000000 Binary files a/agents/test_agents/MeshService.exe and /dev/null differ diff --git a/agents/test_agents/MeshService64.exe b/agents/test_agents/MeshService64.exe deleted file mode 100644 index 997da544..00000000 Binary files a/agents/test_agents/MeshService64.exe and /dev/null differ diff --git a/agents/test_agents/MeshServiceARM64.exe b/agents/test_agents/MeshServiceARM64.exe deleted file mode 100644 index ee191f79..00000000 Binary files a/agents/test_agents/MeshServiceARM64.exe and /dev/null differ diff --git a/apprelays.js b/apprelays.js index 5a9172ac..cd6bc5fa 100644 --- a/apprelays.js +++ b/apprelays.js @@ -719,7 +719,8 @@ module.exports.CreateWebRelay = function (parent, db, args, domain, mtype) { } else if (blockHeaders.indexOf(i) == -1) { obj.res.set(i.trim(), header[i]); } // Set the headers if not blocked } - obj.res.set('Content-Security-Policy', "default-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob:;"); // Set an "allow all" policy, see if the can restrict this in the future + // Dont set any Content-Security-Policy at all because some applications like Node-Red, access external websites from there javascript which would be forbidden by the below CSP + //obj.res.set('Content-Security-Policy', "default-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob:;"); // Set an "allow all" policy, see if the can restrict this in the future //obj.res.set('Content-Security-Policy', "default-src * 'unsafe-inline' 'unsafe-eval'; script-src * 'unsafe-inline' 'unsafe-eval'; connect-src * 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src *; style-src * 'unsafe-inline';"); // Set an "allow all" policy, see if the can restrict this in the future obj.res.set('Cache-Control', 'no-store'); // Tell the browser not to cache the responses since since the relay port can be used for many relays } diff --git a/common.js b/common.js index ab491028..f1fdf105 100644 --- a/common.js +++ b/common.js @@ -155,12 +155,12 @@ module.exports.objKeysToLower = function (obj, exceptions, parent) { return obj; }; -// Escape and unescape field names so there are no invalid characters for MongoDB -module.exports.escapeFieldName = function (name) { if ((name.indexOf('%') == -1) && (name.indexOf('.') == -1) && (name.indexOf('$') == -1)) return name; return name.split('%').join('%25').split('.').join('%2E').split('$').join('%24'); }; -module.exports.unEscapeFieldName = function (name) { if (name.indexOf('%') == -1) return name; return name.split('%2E').join('.').split('%24').join('$').split('%25').join('%'); }; +// Escape and unescape field names so there are no invalid characters for MongoDB/NeDB ("$", ",", ".", see https://github.com/seald/nedb/tree/master?tab=readme-ov-file#inserting-documents) +module.exports.escapeFieldName = function (name) { if ((name.indexOf(',') == -1) && (name.indexOf('%') == -1) && (name.indexOf('.') == -1) && (name.indexOf('$') == -1)) return name; return name.split('%').join('%25').split('.').join('%2E').split('$').join('%24').split(',').join('%2C'); }; +module.exports.unEscapeFieldName = function (name) { if (name.indexOf('%') == -1) return name; return name.split('%2C').join(',').split('%2E').join('.').split('%24').join('$').split('%25').join('%'); }; // Escape all links, SSH and RDP usernames -// This is required for databases like NeDB that don't accept "." as part of a field name. +// This is required for databases like NeDB that don't accept "." or "," as part of a field name. module.exports.escapeLinksFieldNameEx = function (docx) { if ((docx.links == null) && (docx.ssh == null) && (docx.rdp == null)) { return docx; } return module.exports.escapeLinksFieldName(docx); }; module.exports.escapeLinksFieldName = function (docx) { var doc = Object.assign({}, docx); diff --git a/db.js b/db.js index d96ed501..81ea222c 100644 --- a/db.js +++ b/db.js @@ -781,10 +781,10 @@ module.exports.CreateDB = function (parent, func) { parent.debug('db', 'SQlite config options: ' + JSON.stringify(obj.sqliteConfig, null, 4)); if (obj.sqliteConfig.journalMode == 'memory') { console.log('[WARNING] journal_mode=memory: this can lead to database corruption if there is a crash during a transaction. See https://www.sqlite.org/pragma.html#pragma_journal_mode') }; //.cached not usefull - obj.file = new sqlite3.Database(parent.path.join(parent.datapath, databaseName + '.sqlite'), sqlite3.OPEN_READWRITE, function (err) { + obj.file = new sqlite3.Database(path.join(parent.datapath, databaseName + '.sqlite'), sqlite3.OPEN_READWRITE, function (err) { if (err && (err.code == 'SQLITE_CANTOPEN')) { // Database needs to be created - obj.file = new sqlite3.Database(parent.path.join(parent.datapath, databaseName + '.sqlite'), function (err) { + obj.file = new sqlite3.Database(path.join(parent.datapath, databaseName + '.sqlite'), function (err) { if (err) { console.log("SQLite Error: " + err); process.exit(1); } obj.file.exec(` CREATE TABLE main (id VARCHAR(256) PRIMARY KEY NOT NULL, type CHAR(32), domain CHAR(64), extra CHAR(255), extraex CHAR(255), doc JSON); @@ -975,7 +975,7 @@ module.exports.CreateDB = function (parent, func) { } else { if ((info.versionArray[0] < 3) || ((info.versionArray[0] == 3) && (info.versionArray[1] < 6))) { // We are running with mongoDB older than 3.6, this is not good. - parent.addServerWarning("Current version of MongoDB (" + info.version + ") is too old, please upgrade to MongoDB 3.6 or better."); + parent.addServerWarning("Current version of MongoDB (" + info.version + ") is too old, please upgrade to MongoDB 3.6 or better.", true); } } }); @@ -1294,7 +1294,7 @@ module.exports.CreateDB = function (parent, func) { // Setup the SMBIOS collection, for NeDB we don't setup SMBIOS since NeDB will corrupt the database. Remove any existing ones. //obj.smbiosfile = new Datastore({ filename: parent.getConfigFilePath('meshcentral-smbios.db'), autoload: true, corruptAlertThreshold: 1 }); - parent.fs.unlink(parent.getConfigFilePath('meshcentral-smbios.db'), function () { }); + fs.unlink(parent.getConfigFilePath('meshcentral-smbios.db'), function () { }); // Setup the server stats collection and setup indexes obj.serverstatsfile = new Datastore({ filename: parent.getConfigFilePath('meshcentral-stats.db'), autoload: true, corruptAlertThreshold: 1 }); @@ -3187,7 +3187,6 @@ module.exports.CreateDB = function (parent, func) { // Return a human readable string with current backup configuration obj.getBackupConfig = function () { var r = '', backupPath = parent.backuppath; - if (parent.config.settings.autobackup && parent.config.settings.autobackup.backuppath) { backupPath = parent.config.settings.autobackup.backuppath; } let dbname = 'meshcentral'; if (parent.args.mongodbname) { dbname = parent.args.mongodbname; } @@ -3197,7 +3196,7 @@ module.exports.CreateDB = function (parent, func) { const currentDate = new Date(); const fileSuffix = currentDate.getFullYear() + '-' + padNumber(currentDate.getMonth() + 1, 2) + '-' + padNumber(currentDate.getDate(), 2) + '-' + padNumber(currentDate.getHours(), 2) + '-' + padNumber(currentDate.getMinutes(), 2); - obj.newAutoBackupFile = ((typeof parent.config.settings.autobackup.backupname == 'string') ? parent.config.settings.autobackup.backupname : 'meshcentral-autobackup-') + fileSuffix; + obj.newAutoBackupFile = parent.config.settings.autobackup.backupname + fileSuffix; r += 'DB Name: ' + dbname + '\r\n'; r += 'DB Type: ' + DB_LIST[obj.databaseType] + '\r\n'; @@ -3207,15 +3206,14 @@ module.exports.CreateDB = function (parent, func) { if (parent.config.settings.autobackup == null) { r += 'No Settings/AutoBackup\r\n'; } else { + if (parent.config.settings.autobackup.backuphour != null && parent.config.settings.autobackup.backuphour != -1) { + r += 'Backup between: ' + parent.config.settings.autobackup.backuphour + 'H-' + (parent.config.settings.autobackup.backuphour + 1) + 'H\r\n'; + } if (parent.config.settings.autobackup.backupintervalhours != null) { - r += 'Backup Interval (Hours): '; - if (typeof parent.config.settings.autobackup.backupintervalhours != 'number') { r += 'Bad backupintervalhours type\r\n'; } - else { r += parent.config.settings.autobackup.backupintervalhours + '\r\n'; } + r += 'Backup Interval (Hours): ' + parent.config.settings.autobackup.backupintervalhours + '\r\n'; } if (parent.config.settings.autobackup.keeplastdaysbackup != null) { - r += 'Keep Last Backups (Days): '; - if (typeof parent.config.settings.autobackup.keeplastdaysbackup != 'number') { r += 'Bad keeplastdaysbackup type\r\n'; } - else { r += parent.config.settings.autobackup.keeplastdaysbackup + '\r\n'; } + r += 'Keep Last Backups (Days): ' + parent.config.settings.autobackup.keeplastdaysbackup + '\r\n'; } if (parent.config.settings.autobackup.zippassword != null) { r += 'ZIP Password: '; @@ -3330,48 +3328,70 @@ module.exports.CreateDB = function (parent, func) { } // Check that the server is capable of performing a backup + // Tries configured custom location with fallback to default location + // Now runs after autobackup config init in meshcentral.js so config options are checked obj.checkBackupCapability = function (func) { - if ((parent.config.settings.autobackup == null) || (parent.config.settings.autobackup == false)) { func(); return; }; + if ((parent.config.settings.autobackup == null) || (parent.config.settings.autobackup == false)) { return; }; + //block backup until validated. Gets put back if all checks are ok. + let backupInterval = parent.config.settings.autobackup.backupintervalhours; + parent.config.settings.autobackup.backupintervalhours = -1; let backupPath = parent.backuppath; - if (parent.config.settings.autobackup && parent.config.settings.autobackup.backuppath) { backupPath = parent.config.settings.autobackup.backuppath; } - try { parent.fs.mkdirSync(backupPath); } catch (e) { } - if (parent.fs.existsSync(backupPath) == false) { func(1, "Backup folder \"" + backupPath + "\" does not exist, auto-backup will not be performed."); return; } + if (backupPath.startsWith(parent.datapath)) { + func(1, "Backup path can't be set within meshcentral-data folder. No backups will be made."); + return; + } + // Check create/write backupdir + try { fs.mkdirSync(backupPath); } + catch (e) { + // EEXIST error = dir already exists + if (e.code != 'EEXIST' ) { + //Unable to create backuppath + console.error(e.message); + func(1, 'Unable to create ' + backupPath + '. No backups will be made. Error: ' + e.message); + return; + } + } + const testFile = path.join(backupPath, (parent.config.settings.autobackup.backupname + ".test")); + + try { fs.writeFileSync( testFile, "DeleteMe"); } + catch (e) { + //Unable to create file + console.error (e.message); + func(1, "Backuppath (" + backupPath + ") can't be written to. No backups will be made. Error: " + e.message); + return; + } + try { fs.unlinkSync(testFile); parent.debug('backup', 'Backuppath ' + backupPath + ' accesscheck successful');} + catch (e) { + console.error (e.message); + func(1, "Backuppathtestfile (" + testFile + ") can't be deleted, check filerights. Error: " + e.message); + // Assume write rights, no delete rights. Continue with warning. + //return; + } + + // Check database dumptools if ((obj.databaseType == DB_MONGOJS) || (obj.databaseType == DB_MONGODB)) { // Check that we have access to MongoDump var cmd = buildMongoDumpCommand(); cmd += (parent.platform == 'win32') ? ' --archive=\"nul\"' : ' --archive=\"/dev/null\"'; const child_process = require('child_process'); child_process.exec(cmd, { cwd: backupPath }, function (error, stdout, stderr) { - try { - if ((error != null) && (error != '')) { - if (parent.platform == 'win32') { - func(1, "Unable to find mongodump.exe, MongoDB database auto-backup will not be performed."); - } else { - func(1, "Unable to find mongodump, MongoDB database auto-backup will not be performed."); - } - } else { - func(); - } - } catch (ex) { console.log(ex); } + if ((error != null) && (error != '')) { + func(1, "Unable to find mongodump tool, backup will not be performed. Command tried: " + cmd); + return; + } else {parent.config.settings.autobackup.backupintervalhours = backupInterval;} }); } else if ((obj.databaseType == DB_MARIADB) || (obj.databaseType == DB_MYSQL)) { // Check that we have access to mysqldump var cmd = buildSqlDumpCommand(); cmd += ' > ' + ((parent.platform == 'win32') ? '\"nul\"' : '\"/dev/null\"'); const child_process = require('child_process'); - child_process.exec(cmd, { cwd: backupPath }, function(error, stdout, stdin) { - try { - if ((error != null) && (error != '')) { - if (parent.platform == 'win32') { - func(1, "Unable to find mysqldump.exe, MySQL/MariaDB database auto-backup will not be performed."); - } else { - func(1, "Unable to find mysqldump, MySQL/MariaDB database auto-backup will not be performed."); - } - } else { - func(); - } - } catch (ex) { console.log(ex); } + child_process.exec(cmd, { cwd: backupPath, timeout: 1000*30 }, function(error, stdout, stdin) { + if ((error != null) && (error != '')) { + func(1, "Unable to find mysqldump tool, backup will not be performed. Command tried: " + cmd); + return; + } else {parent.config.settings.autobackup.backupintervalhours = backupInterval;} + }); } else if (obj.databaseType == DB_POSTGRESQL) { // Check that we have access to pg_dump @@ -3382,17 +3402,14 @@ module.exports.CreateDB = function (parent, func) { + ' > ' + ((parent.platform == 'win32') ? '\"nul\"' : '\"/dev/null\"'); const child_process = require('child_process'); child_process.exec(cmd, { cwd: backupPath }, function(error, stdout, stdin) { - try { - if ((error != null) && (error != '')) { - func(1, "Unable to find pg_dump, PostgreSQL database auto-backup will not be performed."); - } else { - func(); - } - } catch (ex) { console.log(ex); } + if ((error != null) && (error != '')) { + func(1, "Unable to find pg_dump tool, backup will not be performed. Command tried: " + cmd); + return; + } else {parent.config.settings.autobackup.backupintervalhours = backupInterval;} }); } else { - func(); - } + //all ok, enable backup + parent.config.settings.autobackup.backupintervalhours = backupInterval;} } // MongoDB pending bulk read operation, perform fast bulk document reads. @@ -3506,19 +3523,18 @@ module.exports.CreateDB = function (parent, func) { // Perform a server backup obj.performBackup = function (func) { - parent.debug('db','Entering performBackup'); + parent.debug('backup','Entering performBackup'); try { if (obj.performingBackup) return 'Backup alreay in progress.'; - if (parent.config.settings.autobackup.backupintervalhours == -1) { if (func) { func('Unable to create backup if backuppath is set to the data folder.'); return 'Backup aborted.' }}; + if (parent.config.settings.autobackup.backupintervalhours == -1) { if (func) { func('Backup disabled.'); return 'Backup disabled.' }}; obj.performingBackup = true; let backupPath = parent.backuppath; let dataPath = parent.datapath; - if (parent.config.settings.autobackup && parent.config.settings.autobackup.backuppath) { backupPath = parent.config.settings.autobackup.backuppath; } - try { parent.fs.mkdirSync(backupPath); } catch (e) { } const currentDate = new Date(); const fileSuffix = currentDate.getFullYear() + '-' + padNumber(currentDate.getMonth() + 1, 2) + '-' + padNumber(currentDate.getDate(), 2) + '-' + padNumber(currentDate.getHours(), 2) + '-' + padNumber(currentDate.getMinutes(), 2); - obj.newAutoBackupFile = path.join(backupPath, ((typeof parent.config.settings.autobackup.backupname == 'string') ? parent.config.settings.autobackup.backupname : 'meshcentral-autobackup-') + fileSuffix + '.zip'); + obj.newAutoBackupFile = path.join(backupPath, parent.config.settings.autobackup.backupname + fileSuffix + '.zip'); + parent.debug('backup','newAutoBackupFile=' + obj.newAutoBackupFile); if ((obj.databaseType == DB_MONGOJS) || (obj.databaseType == DB_MONGODB)) { // Perform a MongoDump @@ -3530,13 +3546,14 @@ module.exports.CreateDB = function (parent, func) { var cmd = buildMongoDumpCommand(); cmd += (dburl) ? ' --archive=\"' + obj.newDBDumpFile + '\"' : ' --db=\"' + dbname + '\" --archive=\"' + obj.newDBDumpFile + '\"'; - + parent.debug('backup','Mongodump cmd: ' + cmd); const child_process = require('child_process'); const dumpProcess = child_process.exec( cmd, { cwd: parent.parentpath }, - (error)=> {if (error) {obj.backupStatus |= BACKUPFAIL_DBDUMP; console.log('ERROR: Unable to perform MongoDB backup: ' + error + '\r\n'); obj.createBackupfile(func);}} + (error)=> {if (error) {obj.backupStatus |= BACKUPFAIL_DBDUMP; console.error('ERROR: Unable to perform MongoDB backup: ' + error + '\r\n'); obj.createBackupfile(func);}} ); + dumpProcess.on('exit', (code) => { if (code != 0) {console.log(`Mongodump child process exited with code ${code}`); obj.backupStatus |= BACKUPFAIL_DBDUMP;} obj.createBackupfile(func); @@ -3549,15 +3566,16 @@ module.exports.CreateDB = function (parent, func) { var cmd = buildSqlDumpCommand(); cmd += ' --result-file=\"' + obj.newDBDumpFile + '\"'; + parent.debug('backup','Maria/MySQLdump cmd: ' + cmd); const child_process = require('child_process'); const dumpProcess = child_process.exec( cmd, { cwd: parent.parentpath }, - (error)=> {if (error) {obj.backupStatus |= BACKUPFAIL_DBDUMP; console.log('ERROR: Unable to perform MySQL backup: ' + error + '\r\n'); obj.createBackupfile(func);}} + (error)=> {if (error) {obj.backupStatus |= BACKUPFAIL_DBDUMP; console.error('ERROR: Unable to perform MySQL backup: ' + error + '\r\n'); obj.createBackupfile(func);}} ); dumpProcess.on('exit', (code) => { - if (code != 0) {console.log(`MySQLdump child process exited with code ${code}`); obj.backupStatus |= BACKUPFAIL_DBDUMP;} + if (code != 0) {console.error(`MySQLdump child process exited with code ${code}`); obj.backupStatus |= BACKUPFAIL_DBDUMP;} obj.createBackupfile(func); }); @@ -3565,8 +3583,9 @@ module.exports.CreateDB = function (parent, func) { //.db3 suffix to escape escape backupfile glob to exclude the sqlite db files obj.newDBDumpFile = path.join(backupPath, databaseName + '-sqlitedump-' + fileSuffix + '.db3'); // do a VACUUM INTO in favor of the backup API to compress the export, see https://www.sqlite.org/backup.html + parent.debug('backup','SQLitedump: VACUUM INTO ' + obj.newDBDumpFile); obj.file.exec('VACUUM INTO \'' + obj.newDBDumpFile + '\'', function (err) { - if (err) { console.log('SQLite start-backup error: ' + err); obj.backupStatus |=BACKUPFAIL_DBDUMP;}; + if (err) { console.error('SQLite backup error: ' + err); obj.backupStatus |=BACKUPFAIL_DBDUMP;}; //always finish/clean up obj.createBackupfile(func); }); @@ -3578,6 +3597,7 @@ module.exports.CreateDB = function (parent, func) { + ' --dbname=postgresql://' + parent.config.settings.postgres.user + ":" +parent.config.settings.postgres.password + "@" + parent.config.settings.postgres.host + ":" + parent.config.settings.postgres.port + "/" + databaseName + " --file=" + obj.newDBDumpFile; + parent.debug('backup','Postgresqldump cmd: ' + cmd); const child_process = require('child_process'); const dumpProcess = child_process.exec( cmd, @@ -3589,15 +3609,15 @@ module.exports.CreateDB = function (parent, func) { obj.createBackupfile(func); }); } else { - //NeDB backup, no db dump needed, just make a file backup + // NeDB/Acebase backup, no db dump needed, just make a file backup obj.createBackupfile(func); } - } catch (ex) { console.log(ex); }; + } catch (ex) { console.error(ex); parent.addServerWarning( 'Something went wrong during performBackup, check errorlog: ' +ex.message, true); }; return 'Starting auto-backup...'; }; obj.createBackupfile = function(func) { - parent.debug('db', 'Entering createFileBackup'); + parent.debug('backup', 'Entering createBackupfile'); let archiver = require('archiver'); let archive = null; let zipLevel = Math.min(Math.max(Number(parent.config.settings.autobackup.zipcompression ? parent.config.settings.autobackup.zipcompression : 5),1),9); @@ -3611,8 +3631,8 @@ module.exports.CreateDB = function (parent, func) { if (func) { func('Creating encrypted ZIP'); } } catch (ex) { // registering encryption failed, do not fall back to non-encrypted, fail backup and skip old backup removal as a precaution to not lose any backups obj.backupStatus |= BACKUPFAIL_ZIPMODULE; - if (func) { func('Zipencryptionmodule failed, aborting'); } - console.log('Zipencryptionmodule failed, aborting'); + if (func) { func('Zipencryptionmodule failed, aborting');} + console.error('Zipencryptionmodule failed, aborting'); } } else { if (func) { func('Creating a NON-ENCRYPTED ZIP'); } @@ -3622,51 +3642,36 @@ module.exports.CreateDB = function (parent, func) { //original behavior, just a filebackup if dbdump fails : (obj.backupStatus == 0 || obj.backupStatus == BACKUPFAIL_DBDUMP) if (obj.backupStatus == 0) { // Zip the data directory with the dbdump|NeDB files - let output = parent.fs.createWriteStream(obj.newAutoBackupFile); + let output = fs.createWriteStream(obj.newAutoBackupFile); + + // Archive finalized and closed output.on('close', function () { if (obj.backupStatus == 0) { - //remove dump archive file, because zipped and otherwise fills up - if (obj.databaseType != DB_NEDB) { - try { parent.fs.unlink(obj.newDBDumpFile, function () { }); } catch (ex) {console.log('Failed to clean up dbdump file')}; - }; + let mesg = 'Auto-backup completed: ' + obj.newAutoBackupFile + ', backup-size: ' + ((archive.pointer() / 1048576).toFixed(2)) + "Mb"; + console.log(mesg); + if (func) { func(mesg); }; obj.performCloudBackup(obj.newAutoBackupFile, func); - // Remove old backups - if (parent.config.settings.autobackup && (typeof parent.config.settings.autobackup.keeplastdaysbackup == 'number')) { - let cutoffDate = new Date(); - cutoffDate.setDate(cutoffDate.getDate() - parent.config.settings.autobackup.keeplastdaysbackup); - parent.fs.readdir(parent.backuppath, function (err, dir) { - try { - if ((err == null) && (dir.length > 0)) { - let fileName = (typeof parent.config.settings.autobackup.backupname == 'string') ? parent.config.settings.autobackup.backupname : 'meshcentral-autobackup-'; - for (var i in dir) { - var name = dir[i]; - if (name.startsWith(fileName) && name.endsWith('.zip')) { - var timex = name.substring(23, name.length - 4).split('-'); - if (timex.length == 5) { - var fileDate = new Date(parseInt(timex[0]), parseInt(timex[1]) - 1, parseInt(timex[2]), parseInt(timex[3]), parseInt(timex[4])); - if (fileDate && (cutoffDate > fileDate)) { try { parent.fs.unlink(parent.path.join(parent.backuppath, name), function () { }); } catch (ex) { } } - } - } - } - } - } catch (ex) { console.log(ex); } - }); - } - console.log('Auto-backup completed.'); - if (func) { func('Auto-backup completed.'); }; + obj.removeExpiredBackupfiles(func); + } else { - console.log('Zipbackup failed ('+ (+obj.backupStatus).toString(16).slice(-4) + '), deleting incomplete backup: ' + obj.newAutoBackupFile ); - if (func) { func('Zipbackup failed ('+ (+obj.backupStatus).toString(16).slice(-4) + '), deleting incomplete backup: ' + obj.newAutoBackupFile) }; - try { parent.fs.unlink(obj.newAutoBackupFile, function () { }); parent.fs.unlink(obj.newDBDumpFile, function () { }); } catch (ex) {console.log('Failed to delete incomplete backup files')}; + let mesg = 'Zipbackup failed (' + obj.backupStatus.toString(2).slice(-8) + '), deleting incomplete backup: ' + obj.newAutoBackupFile; + if (func) { func(mesg) } + else { parent.addServerWarning(mesg, true ) }; + if (fs.existsSync(obj.newAutoBackupFile)) { fs.unlink(obj.newAutoBackupFile, function (err) { console.error('Failed to clean up backupfile: ' + err.message) }) }; + }; + if (obj.databaseType != DB_NEDB) { + //remove dump archive file, because zipped and otherwise fills up + if (fs.existsSync(obj.newDBDumpFile)) { fs.unlink(obj.newDBDumpFile, function (err) { if (err) {console.error('Failed to clean up dbdump file: ' + err.message) } }) }; }; obj.performingBackup = false; obj.backupStatus = 0x0; - }); + } + ); output.on('end', function () { }); output.on('error', function (err) { if ((obj.backupStatus & BACKUPFAIL_ZIPCREATE) == 0) { - console.log('Output error: ' + err); - if (func) { func('Output error: ' + err); }; + console.error('Output error: ' + err.message); + if (func) { func('Output error: ' + err.message); }; obj.backupStatus |= BACKUPFAIL_ZIPCREATE; archive.abort(); }; @@ -3676,16 +3681,16 @@ module.exports.CreateDB = function (parent, func) { //an ENOENT warning is given, but the archiver module has no option to/does not skip/resume //so the backup needs te be aborted as it otherwise leaves an incomplete zip and never 'ends' if ((obj.backupStatus & BACKUPFAIL_ZIPCREATE) == 0) { - console.log('Zip warning: ' + err); - if (func) { func('Zip warning: ' + err); }; + console.log('Zip warning: ' + err.message); + if (func) { func('Zip warning: ' + err.message); }; obj.backupStatus |= BACKUPFAIL_ZIPCREATE; archive.abort(); }; }); archive.on('error', function (err) { if ((obj.backupStatus & BACKUPFAIL_ZIPCREATE) == 0) { - console.log('Zip error: ' + err); - if (func) { func('Zip error: ' + err); }; + console.error('Zip error: ' + err.message); + if (func) { func('Zip error: ' + err.message); }; obj.backupStatus |= BACKUPFAIL_ZIPCREATE; archive.abort(); } @@ -3718,22 +3723,67 @@ module.exports.CreateDB = function (parent, func) { archive.finalize(); } else { //failed somewhere before zipping - console.log('Backup failed ('+ (+obj.backupStatus).toString(16).slice(-4) + ')'); - if (func) { func('Backup failed ('+ (+obj.backupStatus).toString(16).slice(-4) + ')') }; + console.error('Backup failed ('+ obj.backupStatus.toString(2).slice(-8) + ')'); + if (func) { func('Backup failed ('+ obj.backupStatus.toString(2).slice(-8) + ')') } + else { + parent.addServerWarning('Backup failed ('+ obj.backupStatus.toString(2).slice(-8) + ')', true); + } //Just in case something's there - try { parent.fs.unlink(obj.newDBDumpFile, function () { }); } catch (ex) { }; + if (fs.existsSync(obj.newDBDumpFile)) { fs.unlink(obj.newDBDumpFile, function (err) { if (err) {console.error('Failed to clean up dbdump file: ' + err.message) } }); }; obj.backupStatus = 0x0; obj.performingBackup = false; }; }; + // Remove expired backupfiles by filenamedate + obj.removeExpiredBackupfiles = function (func) { + if (parent.config.settings.autobackup && (typeof parent.config.settings.autobackup.keeplastdaysbackup == 'number')) { + let cutoffDate = new Date(); + cutoffDate.setDate(cutoffDate.getDate() - parent.config.settings.autobackup.keeplastdaysbackup); + fs.readdir(parent.backuppath, function (err, dir) { + try { + if (err == null) { + if (dir.length > 0) { + let fileName = parent.config.settings.autobackup.backupname; + let checked = 0; + let removed = 0; + for (var i in dir) { + var name = dir[i]; + parent.debug('backup', "checking file: ", path.join(parent.backuppath, name)); + if (name.startsWith(fileName) && name.endsWith('.zip')) { + var timex = name.substring(fileName.length, name.length - 4).split('-'); + if (timex.length == 5) { + checked++; + var fileDate = new Date(parseInt(timex[0]), parseInt(timex[1]) - 1, parseInt(timex[2]), parseInt(timex[3]), parseInt(timex[4])); + if (fileDate && (cutoffDate > fileDate)) { + console.log("Removing expired backup file: ", path.join(parent.backuppath, name)); + fs.unlink(path.join(parent.backuppath, name), function (err) { if (err) { console.error(err.message); if (func) {func('Error removing: ' + err.message); } } }); + removed++; + } + } + else { parent.debug('backup', "file: " + name + " timestamp failure: ", timex); } + } + } + let mesg= 'Checked ' + checked + ' candidates in ' + parent.backuppath + '. Removed ' + removed + ' expired backupfiles using cutoffDate: '+ cutoffDate.toLocaleString('default', { dateStyle: 'short', timeStyle: 'short' }); + parent.debug (mesg); + if (func) { func(mesg); } + } else { console.error('No files found in ' + parent.backuppath + '. There should be at least one.')} + } + else + { console.error(err); parent.addServerWarning( 'Reading files in backup directory ' + parent.backuppath + ' failed, check errorlog: ' + err.message, true); } + } catch (ex) { console.error(ex); parent.addServerWarning( 'Something went wrong during removeExpiredBackupfiles, check errorlog: ' +ex.message, true); } + }); + } + } + // Perform cloud backup obj.performCloudBackup = function (filename, func) { - // WebDAV Backup if ((typeof parent.config.settings.autobackup == 'object') && (typeof parent.config.settings.autobackup.webdav == 'object')) { - const xdateTimeSort = function (a, b) { if (a.xdate > b.xdate) return 1; if (a.xdate < b.xdate) return -1; return 0; } + parent.debug( 'backup', 'Entering WebDAV backup'); + if (func) { func('Entering WebDAV backup.'); } + const xdateTimeSort = function (a, b) { if (a.xdate > b.xdate) return 1; if (a.xdate < b.xdate) return -1; return 0; } // Fetch the folder name var webdavfolderName = 'MeshCentral-Backups'; if (typeof parent.config.settings.autobackup.webdav.foldername == 'string') { webdavfolderName = parent.config.settings.autobackup.webdav.foldername; } @@ -3741,23 +3791,28 @@ module.exports.CreateDB = function (parent, func) { // Clean up our WebDAV folder function performWebDavCleanup(client) { if ((typeof parent.config.settings.autobackup.webdav.maxfiles == 'number') && (parent.config.settings.autobackup.webdav.maxfiles > 1)) { - let fileName = (typeof parent.config.settings.autobackup.backupname == 'string') ? parent.config.settings.autobackup.backupname : 'meshcentral-autobackup-'; + let fileName = parent.config.settings.autobackup.backupname; //only files matching our backupfilename let directoryItems = client.getDirectoryContents(webdavfolderName, { deep: false, glob: "/**/" + fileName + "*.zip" }); directoryItems.then( function (files) { for (var i in files) { files[i].xdate = new Date(files[i].lastmod); } files.sort(xdateTimeSort); + parent.debug('backup','WebDAV filtered directory contents: ' + JSON.stringify(files, null, 4)); while (files.length >= parent.config.settings.autobackup.webdav.maxfiles) { - client.deleteFile(files.shift().filename).then(function (state) { - if (func) { func('WebDAV file deleted.'); } + let delFile = files.shift().filename; + client.deleteFile(delFile).then(function (state) { + parent.debug('backup','WebDAV file deleted: ' + delFile); + if (func) { func('WebDAV file deleted: ' + delFile); } }).catch(function (err) { - if (func) { func('WebDAV (deleteFile) error: ' + err); } + console.error(err); + if (func) { func('WebDAV (deleteFile) error: ' + err.message); } }); } } ).catch(function (err) { - if (func) { func('WebDAV (getDirectoryContents) error: ' + err); } + console.error(err); + if (func) { func('WebDAV (getDirectoryContents) error: ' + err.message); } }); } } @@ -3766,14 +3821,14 @@ module.exports.CreateDB = function (parent, func) { function performWebDavUpload(client, filepath) { require('fs').stat(filepath, function(err,stat){ var fileStream = require('fs').createReadStream(filepath); - fileStream.on('close', function () { if (func) { func('WebDAV upload completed'); } }) - fileStream.on('error', function (err) { if (func) { func('WebDAV (fileUpload) error: ' + err); } }) + fileStream.on('close', function () { console.log('WebDAV upload completed: ' + webdavfolderName + '/' + require('path').basename(filepath)); if (func) { func('WebDAV upload completed: ' + webdavfolderName + '/' + require('path').basename(filepath)); } }) + fileStream.on('error', function (err) { console.error(err); if (func) { func('WebDAV (fileUpload) error: ' + err.message); } }) fileStream.pipe(client.createWriteStream('/' + webdavfolderName + '/' + require('path').basename(filepath), { headers: { "Content-Length": stat.size } })); - if (func) { func('Uploading using WebDAV...'); } + parent.debug('backup', 'Uploading using WebDAV to: ' + parent.config.settings.autobackup.webdav.url); + if (func) { func('Uploading using WebDAV to: ' + parent.config.settings.autobackup.webdav.url); } }); } - if (func) { func('Attempting WebDAV upload...'); } const { createClient } = require('webdav'); const client = createClient(parent.config.settings.autobackup.webdav.url, { username: parent.config.settings.autobackup.webdav.username, @@ -3787,19 +3842,23 @@ module.exports.CreateDB = function (parent, func) { performWebDavUpload(client, filename); }else{ client.createDirectory(webdavfolderName, {recursive: true}).then(function (a) { - if (func) { func('WebDAV folder created'); } + console.log('backup','WebDAV folder created: ' + webdavfolderName); + if (func) { func('WebDAV folder created: ' + webdavfolderName); } performWebDavUpload(client, filename); }).catch(function (err) { - if (func) { func('WebDAV (createDirectory) error: ' + err); } + console.error(err); + if (func) { func('WebDAV (createDirectory) error: ' + err.message); } }); } }).catch(function (err) { - if (func) { func('WebDAV (exists) error: ' + err); } + console.error(err); + if (func) { func('WebDAV (exists) error: ' + err.message); } }); } // Google Drive Backup if ((typeof parent.config.settings.autobackup == 'object') && (typeof parent.config.settings.autobackup.googledrive == 'object')) { + parent.debug( 'backup', 'Entering Google Drive backup'); obj.Get('GoogleDriveBackup', function (err, docs) { if ((err != null) || (docs.length != 1) || (docs[0].state != 3)) return; if (func) { func('Attempting Google Drive upload...'); } @@ -3878,6 +3937,7 @@ module.exports.CreateDB = function (parent, func) { // S3 Backup if ((typeof parent.config.settings.autobackup == 'object') && (typeof parent.config.settings.autobackup.s3 == 'object')) { + parent.debug( 'backup', 'Entering S3 backup'); var s3folderName = 'MeshCentral-Backups'; if (typeof parent.config.settings.autobackup.s3.foldername == 'string') { s3folderName = parent.config.settings.autobackup.s3.foldername; } // Construct the config object diff --git a/docker/Dockerfile b/docker/Dockerfile index c646a08c..26806e1d 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -62,8 +62,8 @@ ENV MONGO_URL="" ENV HOSTNAME="localhost" ENV ALLOW_NEW_ACCOUNTS="true" ENV ALLOWPLUGINS="false" -ENV LOCALSESSIONRECORDING="false" -ENV MINIFY="true" +ENV LOCALSESSIONRECORDING="true" +ENV MINIFY="false" ENV WEBRTC="false" ENV IFRAME="false" ENV SESSION_KEY="" @@ -83,8 +83,8 @@ COPY --from=builder /opt/meshcentral/meshcentral /opt/meshcentral/meshcentral COPY ./docker/startup.sh ./startup.sh COPY ./docker/config.json.template /opt/meshcentral/config.json.template -# install dependencies from package.json and nedb -RUN cd meshcentral && npm install && npm install nedb +# install dependencies from package.json +RUN cd meshcentral && npm install # NOTE: ALL MODULES MUST HAVE A VERSION NUMBER AND THE VERSION MUST MATCH THAT USED IN meshcentral.js mainStart() RUN if ! [ -z "$INCLUDE_MONGODBTOOLS" ]; then cd meshcentral && npm install mongodb@4.13.0 saslprep@1.0.3; fi diff --git a/docker/config.json.template b/docker/config.json.template index a6fe3201..cef4ad33 100644 --- a/docker/config.json.template +++ b/docker/config.json.template @@ -21,9 +21,9 @@ "": { "_title": "MyServer", "_title2": "Servername", - "minify": true, + "minify": false, "NewAccounts": true, - "localSessionRecording": false, + "localSessionRecording": true, "_userNameIsEmail": true, "_certUrl": "my.reverse.proxy" } diff --git a/docker/startup.sh b/docker/startup.sh index c198d847..da3f0b34 100644 --- a/docker/startup.sh +++ b/docker/startup.sh @@ -18,7 +18,7 @@ else sed -i "s/\"NewAccounts\": true/\"NewAccounts\": $ALLOW_NEW_ACCOUNTS/" meshcentral-data/"${CONFIG_FILE}" sed -i "s/\"enabled\": false/\"enabled\": $ALLOWPLUGINS/" meshcentral-data/"${CONFIG_FILE}" sed -i "s/\"localSessionRecording\": false/\"localSessionRecording\": $LOCALSESSIONRECORDING/" meshcentral-data/"${CONFIG_FILE}" - sed -i "s/\"minify\": true/\"minify\": $MINIFY/" meshcentral-data/"${CONFIG_FILE}" + sed -i "s/\"minify\": false/\"minify\": $MINIFY/" meshcentral-data/"${CONFIG_FILE}" sed -i "s/\"WebRTC\": false/\"WebRTC\": $WEBRTC/" meshcentral-data/"${CONFIG_FILE}" sed -i "s/\"AllowFraming\": false/\"AllowFraming\": $IFRAME/" meshcentral-data/"${CONFIG_FILE}" if [ -z "$SESSION_KEY" ]; then diff --git a/docs/docs/meshcentral/plugins.md b/docs/docs/meshcentral/plugins.md index 6055ab5b..4b85b0c5 100644 --- a/docs/docs/meshcentral/plugins.md +++ b/docs/docs/meshcentral/plugins.md @@ -123,6 +123,10 @@ Use of the optional file `plugin_name.js` in the optional folder `modules_meshco Much of MeshCentral revolves around returning objects for your structures, and plugins are no different. Within your plugin you can traverse all the way up to the web server and MeshCentral Server classes to access all the functionality those layers provide. This is done by passing the current object to newly created objects, and assigning that reference to a `parent` variable within that object. +## Ping-Pong + +If you build a plugin which makes use of `meshrelay.ashx`, keep in mind to either handle ping-pong messages (`serverPing`, `serverPong`) on the control channel or to request MeshCentral to not send such messages through sending the `noping=1` parameter in the connection URL. For a deeper sight search for "PING/PONG" in `meshrelay.js`. + ## Versioning Versioning your plugin correctly and consistently is essential to ensure users of your plugin are prompted to upgrade when it is available. Semantic versioning is recommended. diff --git a/mcrec.js b/mcrec.js index 4e10d8d4..43966b88 100644 --- a/mcrec.js +++ b/mcrec.js @@ -321,7 +321,7 @@ function setup() { InstallModules(['image-size'], start); } function start() { startEx(process.argv); } function startEx(argv) { if (argv.length > 2) { indexFile(argv[2]); } else { - log("MeshCentral Session Recodings Processor"); + log("MeshCentral Session Recordings Processor"); log("This tool will index a .mcrec file so that the player can seek thru the file."); log(""); log(" Usage: node mcrec [file]"); diff --git a/meshagent.js b/meshagent.js index 9c1fcb98..7169b9ff 100644 --- a/meshagent.js +++ b/meshagent.js @@ -1936,8 +1936,9 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { change = 1; // Don't save this change as an event to the db, so no log=1. parent.removePmtFromAllOtherNodes(device); // We need to make sure to remove this push messaging token from any other device on this server, all domains included. } - + if ((command.users != null) && (Array.isArray(command.users)) && (device.users != command.users)) { device.users = command.users; change = 1; } // Don't save this to the db. + if ((command.lusers != null) && (Array.isArray(command.lusers)) && (device.lusers != command.lusers)) { device.lusers = command.lusers; change = 1; } // Don't save this to the db. if ((mesh.mtype == 2) && (!args.wanonly)) { // In WAN mode, the hostname of a computer is not important. Don't log hostname changes. if (device.host != obj.remoteaddr) { device.host = obj.remoteaddr; change = 1; changes.push('host'); } diff --git a/meshcentral-config-schema.json b/meshcentral-config-schema.json index fcff4f68..91cec3b8 100644 --- a/meshcentral-config-schema.json +++ b/meshcentral-config-schema.json @@ -886,6 +886,11 @@ "default": 24, "description": "How often should the autobackup run in hours from the second meshcentral starts up? Default is every 24 hours" }, + "backupHour": { + "type": "integer", + "default": 0, + "description": "At which hour the autobackup should run. This forces a daily backup, overrules a custom 'backupIntervalHours'." + }, "keepLastDaysBackup": { "type": "integer", "default": 10, @@ -1168,6 +1173,11 @@ "default": 2, "description": "Valid numbers are 1 and 2, changes the style of the login page and some secondary pages." }, + "showModernUIToggle": { + "type": "boolean", + "default": false, + "description": "When set to true, the user will be able to toggle between the modern and classic UI." + }, "title": { "type": "string", "default": "MeshCentral", @@ -1962,6 +1972,11 @@ "default": false, "description": "If true, user consent is accepted after the timeout." }, + "autoAcceptIfNoUser": { + "type": "boolean", + "default": false, + "description": "If true, user consent is accepted if no user is logged in." + }, "oldStyle": { "type": "boolean", "default": false, @@ -2165,6 +2180,11 @@ "default": null, "description": "When set, idle users will be disconnected after a set amounts of minutes." }, + "logoutOnIdleSessionTimeout": { + "type": "boolean", + "default": true, + "description": "Determines whether MeshCentral should logout after the session idle timeout elapsed or should just disconnect remote desktop, terminal and files." + }, "userConsentFlags": { "type": "object", "description": "Use this section to require user consent for this domain.", @@ -2895,12 +2915,10 @@ }, "user": { "type": "string", - "format": "string", "description": "SMTP username." }, "pass": { "type": "string", - "format": "string", "description": "SMTP password." }, "tls": { @@ -3253,7 +3271,6 @@ ] } ], - "additionalProperties": false, "properties": { "newAccounts": { "type": "boolean", @@ -3461,8 +3478,7 @@ "required": [ "client_id", "client_secret" - ], - "additionalProperties": false + ] }, "issuer": { "type": [ @@ -3552,8 +3568,7 @@ } } } - }, - "additionalProperties": false + } }, "custom": { "type": "object", @@ -3604,8 +3619,7 @@ "type": "string", "description": "REQUIRED IF USING GROUPS: Customer ID from Google Workspace Admin Console (https://admin.google.com/ac/accountsettings/profile)" } - }, - "additionalProperties": false + } }, "groups": { "type": "object", @@ -3657,8 +3671,7 @@ "default": "groups", "description": "Custom claim to use." } - }, - "additionalProperties": false + } } } } @@ -3723,8 +3736,7 @@ "description": "EAB HMAC KEY", "default": "" } - }, - "additionalProperties": false + } } }, "required": [ @@ -3819,12 +3831,10 @@ }, "user": { "type": "string", - "format": "string", "description": "SMTP username." }, "pass": { "type": "string", - "format": "string", "description": "SMTP password." }, "tls": { diff --git a/meshcentral.js b/meshcentral.js index f3dc14ea..a792dda5 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -583,8 +583,11 @@ function CreateMeshCentralServer(config, args) { // Launch MeshCentral as a child server and monitor it. obj.launchChildServer = function (startArgs) { const child_process = require('child_process'); + const isInspectorAttached = (()=> { try { return require('node:inspector').url() !== undefined; } catch (_) { return false; } }).call(); + const logFromChildProcess = isInspectorAttached ? () => {} : console.log.bind(console); try { if (process.traceDeprecation === true) { startArgs.unshift('--trace-deprecation'); } } catch (ex) { } try { if (process.traceProcessWarnings === true) { startArgs.unshift('--trace-warnings'); } } catch (ex) { } + if (startArgs[0] != "--disable-proto=delete") startArgs.unshift("--disable-proto=delete") 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. @@ -656,12 +659,12 @@ function CreateMeshCentralServer(config, args) { 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); + logFromChildProcess(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); + logFromChildProcess('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); @@ -1348,7 +1351,7 @@ function CreateMeshCentralServer(config, args) { } // Check if the database is capable of performing a backup - obj.db.checkBackupCapability(function (err, msg) { if (msg != null) { obj.addServerWarning(msg, true) } }); + // Moved behind autobackup config init in startex4: obj.db.checkBackupCapability(function (err, msg) { if (msg != null) { obj.addServerWarning(msg, true) } }); // Load configuration for database if needed if (obj.args.loadconfigfromdb) { @@ -1656,7 +1659,7 @@ function CreateMeshCentralServer(config, args) { } // Setup agent error log - if ((obj.config) && (obj.config.settings) && (obj.config.settings.agentlogdump != null)) { + if ((obj.config) && (obj.config.settings) && (obj.config.settings.agentlogdump)) { obj.fs.open(obj.path.join(obj.datapath, 'agenterrorlogs.txt'), 'a', function (err, fd) { obj.agentErrorLog = fd; }) } @@ -2016,6 +2019,7 @@ function CreateMeshCentralServer(config, args) { // Start periodic maintenance obj.maintenanceTimer = setInterval(obj.maintenanceActions, 1000 * 60 * 60); // Run this every hour + //obj.maintenanceTimer = setInterval(obj.maintenanceActions, 1000 * 10 * 1); // DEBUG: Run this more often // Dispatch an event that the server is now running obj.DispatchEvent(['*'], obj, { etype: 'server', action: 'started', msg: 'Server started' }); @@ -2105,18 +2109,19 @@ function CreateMeshCentralServer(config, args) { if (obj.config.settings.autobackup == null || obj.config.settings.autobackup === true) { obj.config.settings.autobackup = {backupintervalhours: 24, keeplastdaysbackup: 10}; }; if (typeof obj.config.settings.autobackup.backupintervalhours != 'number') { obj.config.settings.autobackup.backupintervalhours = 24; }; if (typeof obj.config.settings.autobackup.keeplastdaysbackup != 'number') { obj.config.settings.autobackup.keeplastdaysbackup = 10; }; + if (obj.config.settings.autobackup.backuphour != null ) { obj.config.settings.autobackup.backupintervalhours = 24; if ((typeof obj.config.settings.autobackup.backuphour != 'number') || (obj.config.settings.autobackup.backuphour > 23 || obj.config.settings.autobackup.backuphour < 0 )) { obj.config.settings.autobackup.backuphour = 0; }} + else {obj.config.settings.autobackup.backuphour = -1 }; //arrayfi in case of string and remove possible ', ' space. !! If a string instead of an array is passed, it will be split by ',' so *{.txt,.log} won't work in that case !! if (!obj.config.settings.autobackup.backupignorefilesglob) {obj.config.settings.autobackup.backupignorefilesglob = []} else if (typeof obj.config.settings.autobackup.backupignorefilesglob == 'string') { obj.config.settings.autobackup.backupignorefilesglob = obj.config.settings.autobackup.backupignorefilesglob.replaceAll(', ', ',').split(','); }; if (!obj.config.settings.autobackup.backupskipfoldersglob) {obj.config.settings.autobackup.backupskipfoldersglob = []} else if (typeof obj.config.settings.autobackup.backupskipfoldersglob == 'string') { obj.config.settings.autobackup.backupskipfoldersglob = obj.config.settings.autobackup.backupskipfoldersglob.replaceAll(', ', ',').split(','); }; + if (typeof obj.config.settings.autobackup.backuppath == 'string') { obj.backuppath = (obj.config.settings.autobackup.backuppath = (obj.path.resolve(obj.config.settings.autobackup.backuppath))) } else { obj.config.settings.autobackup.backuppath = obj.backuppath }; + if (typeof obj.config.settings.autobackup.backupname != 'string') { obj.config.settings.autobackup.backupname = 'meshcentral-autobackup-'}; } - // Check that autobackup path is not within the "meshcentral-data" folder. - if ((typeof obj.config.settings.autobackup == 'object') && (typeof obj.config.settings.autobackup.backuppath == 'string') && (obj.path.normalize(obj.config.settings.autobackup.backuppath).startsWith(obj.path.normalize(obj.datapath)))) { - addServerWarning("Backup path can't be set within meshcentral-data folder, backup settings ignored.", 21); - obj.config.settings.autobackup = {backupintervalhours: -1}; //block console autobackup - } + // Check if the database is capable of performing a backup + obj.db.checkBackupCapability(function (err, msg) { if (msg != null) { obj.addServerWarning(msg, true) } }); // Load Intel AMT passwords from the "amtactivation.log" file obj.loadAmtActivationLogPasswords(function (amtPasswords) { @@ -2278,14 +2283,19 @@ function CreateMeshCentralServer(config, args) { // Check if we need to perform an automatic backup function checkAutobackup() { - if (obj.config.settings.autobackup.backupintervalhours >= 1) { + if (obj.config.settings.autobackup.backupintervalhours >= 1 ) { obj.db.Get('LastAutoBackupTime', function (err, docs) { - if (err != null) return; + if (err != null) { console.error("checkAutobackup: Error getting LastBackupTime from DB"); return} var lastBackup = 0; - const now = new Date().getTime(); + const currentdate = new Date(); + let currentHour = currentdate.getHours(); + let now = currentdate.getTime(); if (docs.length == 1) { lastBackup = docs[0].value; } const delta = now - lastBackup; - if (delta > (obj.config.settings.autobackup.backupintervalhours * 60 * 60 * 1000)) { + //const delta = 9999999999; // DEBUG: backup always + obj.debug ('backup', 'Entering checkAutobackup, lastAutoBackupTime: ' + new Date(lastBackup).toLocaleString('default', { dateStyle: 'medium', timeStyle: 'short' }) + ', delta: ' + (delta/(1000*60*60)).toFixed(2) + ' hours'); + //start autobackup if interval has passed or at configured hour, whichever comes first. When an hour schedule is missed, it will make a backup immediately. + if ((delta > (obj.config.settings.autobackup.backupintervalhours * 60 * 60 * 1000)) || ((currentHour == obj.config.settings.autobackup.backuphour) && (delta >= 2 * 60 * 60 * 1000))) { // A new auto-backup is required. obj.db.Set({ _id: 'LastAutoBackupTime', value: now }); // Save the current time in the database obj.db.performBackup(); // Perform the backup @@ -3936,6 +3946,7 @@ function CreateMeshCentralServer(config, args) { function logWarnEvent(msg) { if (obj.servicelog != null) { obj.servicelog.warn(msg); } console.log(msg); } function logErrorEvent(msg) { if (obj.servicelog != null) { obj.servicelog.error(msg); } console.error(msg); } obj.getServerWarnings = function () { return serverWarnings; } + // TODO: migrate from other addServerWarning function and add timestamp obj.addServerWarning = function (msg, id, args, print) { serverWarnings.push({ msg: msg, id: id, args: args }); if (print !== false) { console.log("WARNING: " + msg); } } // auth.log functions @@ -4106,6 +4117,7 @@ function InstallModuleEx(modulenames, args, func) { process.on('SIGINT', function () { if (meshserver != null) { meshserver.Stop(); meshserver = null; } console.log('Server Ctrl-C exit...'); process.exit(); }); // Add a server warning, warnings will be shown to the administrator on the web application +// TODO: migrate to obj.addServerWarning? const serverWarnings = []; function addServerWarning(msg, id, args, print) { serverWarnings.push({ msg: msg, id: id, args: args }); if (print !== false) { console.log("WARNING: " + msg); } } @@ -4212,7 +4224,7 @@ function mainStart() { if (mstsc == false) { config.domains[i].mstsc = false; } if (config.domains[i].ssh == true) { ssh = true; } if ((typeof config.domains[i].authstrategies == 'object')) { - if (passport.length == 0) { passport = ['passport','connect-flash']; } // Passport v0.6.0 requires a patch, see https://github.com/jaredhanson/passport/issues/904 and include connect-flash here to display errors + if (passport.indexOf('passport') == -1) { passport.push('passport','connect-flash'); } // Passport v0.6.0 requires a patch, see https://github.com/jaredhanson/passport/issues/904 and include connect-flash here to display errors if ((typeof config.domains[i].authstrategies.twitter == 'object') && (typeof config.domains[i].authstrategies.twitter.clientid == 'string') && (typeof config.domains[i].authstrategies.twitter.clientsecret == 'string') && (passport.indexOf('passport-twitter') == -1)) { passport.push('passport-twitter'); } if ((typeof config.domains[i].authstrategies.google == 'object') && (typeof config.domains[i].authstrategies.google.clientid == 'string') && (typeof config.domains[i].authstrategies.google.clientsecret == 'string') && (passport.indexOf('passport-google-oauth20') == -1)) { passport.push('passport-google-oauth20'); } if ((typeof config.domains[i].authstrategies.github == 'object') && (typeof config.domains[i].authstrategies.github.clientid == 'string') && (typeof config.domains[i].authstrategies.github.clientsecret == 'string') && (passport.indexOf('passport-github2') == -1)) { passport.push('passport-github2'); } diff --git a/meshctrl.js b/meshctrl.js index e17e3552..af4f1626 100644 --- a/meshctrl.js +++ b/meshctrl.js @@ -2243,6 +2243,7 @@ function serverConnect() { case 'removeDeviceShare': case 'userbroadcast': { // BROADCAST if ((settings.cmd == 'shell') || (settings.cmd == 'upload') || (settings.cmd == 'download')) return; + if ((data.type == 'runcommands') && (settings.cmd != 'runcommand')) return; if ((settings.multiresponse != null) && (settings.multiresponse > 1)) { settings.multiresponse--; break; } if (data.responseid == 'meshctrl') { if (data.meshid) { console.log(data.result, data.meshid); } @@ -2665,8 +2666,8 @@ function getDevicesThatMatchFilter(nodes, x) { } else if (tagSearch != null) { // Tag filter for (var d in nodes) { - if ((nodes[d].tags == null) && (tagSearch == '')) { r.push(d); } - else if (nodes[d].tags != null) { for (var j in nodes[d].tags) { if (nodes[d].tags[j].toLowerCase() == tagSearch) { r.push(d); break; } } } + if ((nodes[d].tags == null) && (tagSearch == '')) { r.push(nodes[d]); } + else if (nodes[d].tags != null) { for (var j in nodes[d].tags) { if (nodes[d].tags[j].toLowerCase() == tagSearch) { r.push(nodes[d]); break; } } } } } else if (agentTagSearch != null) { // Agent Tag filter diff --git a/meshdesktopmultiplex.js b/meshdesktopmultiplex.js index 3a14540a..9b6d1e19 100644 --- a/meshdesktopmultiplex.js +++ b/meshdesktopmultiplex.js @@ -847,7 +847,7 @@ function CreateDesktopMultiplexor(parent, domain, nodeid, id, func) { return; } // Write the recording file header - parent.parent.debug('relay', 'Relay: Started recoding to file: ' + recFullFilename); + parent.parent.debug('relay', 'Relay: Started recording to file: ' + recFullFilename); var metadata = { magic: 'MeshCentralRelaySession', ver: 1, nodeid: obj.nodeid, meshid: obj.meshid, time: new Date().toLocaleString(), protocol: 2, devicename: obj.name, devicegroup: obj.meshname }; var firstBlock = JSON.stringify(metadata); recordingEntry(fd, 1, 0, firstBlock, function () { @@ -1347,6 +1347,7 @@ function CreateMeshRelayEx2(parent, ws, req, domain, user, cookie) { if (typeof domain.consentmessages.files == 'string') { command.soptions.consentMsgFiles = domain.consentmessages.files; } if ((typeof domain.consentmessages.consenttimeout == 'number') && (domain.consentmessages.consenttimeout > 0)) { command.soptions.consentTimeout = domain.consentmessages.consenttimeout; } if (domain.consentmessages.autoacceptontimeout === true) { command.soptions.consentAutoAccept = true; } + if (domain.consentmessages.autoacceptifnouser === true) { command.soptions.consentAutoAcceptIfNoUser = true; } if (domain.consentmessages.oldstyle === true) { command.soptions.oldStyle = true; } } if (typeof domain.notificationmessages == 'object') { diff --git a/meshrelay.js b/meshrelay.js index 051640f4..7f91904d 100644 --- a/meshrelay.js +++ b/meshrelay.js @@ -445,15 +445,15 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) { relayinfo.peer1.sendPeerImage(); } else { // Write the recording file header - parent.parent.debug('relay', 'Relay: Started recoding to file: ' + recFullFilename); + parent.parent.debug('relay', 'Relay: Started recording to file: ' + recFullFilename); var metadata = { magic: 'MeshCentralRelaySession', ver: 1, userid: sessionUser._id, username: sessionUser.name, sessionid: obj.id, - ipaddr1: (obj.req == null) ? null : obj.req.clientIp, - ipaddr2: ((obj.peer == null) || (obj.peer.req == null)) ? null : obj.peer.req.clientIp, + ipaddr1: ((obj.peer == null) || (obj.peer.req == null)) ? null : obj.peer.req.clientIp, + ipaddr2: (obj.req == null) ? null : obj.req.clientIp, time: new Date().toLocaleString(), protocol: (((obj.req == null) || (obj.req.query == null)) ? null : obj.req.query.p), nodeid: (((obj.req == null) || (obj.req.query == null)) ? null : obj.req.query.nodeid) @@ -896,6 +896,7 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) { if (typeof domain.consentmessages.files == 'string') { command.soptions.consentMsgFiles = domain.consentmessages.files; } if ((typeof domain.consentmessages.consenttimeout == 'number') && (domain.consentmessages.consenttimeout > 0)) { command.soptions.consentTimeout = domain.consentmessages.consenttimeout; } if (domain.consentmessages.autoacceptontimeout === true) { command.soptions.consentAutoAccept = true; } + if (domain.consentmessages.autoacceptifnouser === true) { command.soptions.consentAutoAcceptIfNoUser = true; } if (domain.consentmessages.oldstyle === true) { command.soptions.oldStyle = true; } } if (typeof domain.notificationmessages == 'object') { @@ -934,6 +935,7 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) { if (typeof domain.consentmessages.files == 'string') { command.soptions.consentMsgFiles = domain.consentmessages.files; } if ((typeof domain.consentmessages.consenttimeout == 'number') && (domain.consentmessages.consenttimeout > 0)) { command.soptions.consentTimeout = domain.consentmessages.consenttimeout; } if (domain.consentmessages.autoacceptontimeout === true) { command.soptions.consentAutoAccept = true; } + if (domain.consentmessages.autoacceptifnouser === true) { command.soptions.consentAutoAcceptIfNoUser = true; } if (domain.consentmessages.oldstyle === true) { command.soptions.oldStyle = true; } } if (typeof domain.notificationmessages == 'object') { @@ -952,6 +954,7 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) { if (typeof domain.consentmessages.files == 'string') { command.soptions.consentMsgFiles = domain.consentmessages.files; } if ((typeof domain.consentmessages.consenttimeout == 'number') && (domain.consentmessages.consenttimeout > 0)) { command.soptions.consentTimeout = domain.consentmessages.consenttimeout; } if (domain.consentmessages.autoacceptontimeout === true) { command.soptions.consentAutoAccept = true; } + if (domain.consentmessages.autoacceptifnouser === true) { command.soptions.consentAutoAcceptIfNoUser = true; } if (domain.consentmessages.oldstyle === true) { command.soptions.oldStyle = true; } } if (typeof domain.notificationmessages == 'object') { @@ -1004,6 +1007,7 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) { if (typeof domain.consentmessages.files == 'string') { command.soptions.consentMsgFiles = domain.consentmessages.files; } if ((typeof domain.consentmessages.consenttimeout == 'number') && (domain.consentmessages.consenttimeout > 0)) { command.soptions.consentTimeout = domain.consentmessages.consenttimeout; } if (domain.consentmessages.autoacceptontimeout === true) { command.soptions.consentAutoAccept = true; } + if (domain.consentmessages.autoacceptifnouser === true) { command.soptions.consentAutoAcceptIfNoUser = true; } if (domain.consentmessages.oldstyle === true) { command.soptions.oldStyle = true; } } if (typeof domain.notificationmessages == 'object') { diff --git a/meshuser.js b/meshuser.js index 44e8f0a5..ac237dfd 100644 --- a/meshuser.js +++ b/meshuser.js @@ -600,7 +600,13 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use } } if (typeof domain.userconsentflags == 'number') { serverinfo.consent = domain.userconsentflags; } - if ((typeof domain.usersessionidletimeout == 'number') && (domain.usersessionidletimeout > 0)) { serverinfo.timeout = (domain.usersessionidletimeout * 60 * 1000); } + if ((typeof domain.usersessionidletimeout == 'number') && (domain.usersessionidletimeout > 0)) {serverinfo.timeout = (domain.usersessionidletimeout * 60 * 1000); } + if (typeof domain.logoutonidlesessiontimeout == 'boolean') { + serverinfo.logoutonidlesessiontimeout = domain.logoutonidlesessiontimeout; + } else { + // Default + serverinfo.logoutonidlesessiontimeout = true; + } if (user.siteadmin === SITERIGHT_ADMIN) { if (parent.parent.config.settings.managealldevicegroups.indexOf(user._id) >= 0) { serverinfo.manageAllDeviceGroups = true; } if (obj.crossDomain === true) { serverinfo.crossDomain = []; for (var i in parent.parent.config.domains) { serverinfo.crossDomain.push(i); } } @@ -922,7 +928,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use // Get a short file and send it back on the web socket if (common.validateString(command.file, 1, 4096) == false) return; const scpath = meshPathToRealPath(command.path, user); // This will also check access rights - if (scpath == null) break; + if ((scpath == null) || (command.file !== parent.path.basename(command.file))) break; const filePath = parent.path.join(scpath, command.file); fs.stat(filePath, function (err, stat) { if ((err != null) || (stat == null) || (stat.size >= 204800)) return; @@ -937,7 +943,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use if (common.validateString(command.file, 1, 4096) == false) return; if (typeof command.data != 'string') return; const scpath = meshPathToRealPath(command.path, user); // This will also check access rights - if (scpath == null) break; + if ((scpath == null) || (command.file !== parent.path.basename(command.file))) break; const filePath = parent.path.join(scpath, command.file); var data = null; try { data = Buffer.from(command.data, 'base64'); } catch (ex) { return; } @@ -997,6 +1003,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use if (typeof domain.consentmessages.files == 'string') { command.soptions.consentMsgFiles = domain.consentmessages.files; } if ((typeof domain.consentmessages.consenttimeout == 'number') && (domain.consentmessages.consenttimeout > 0)) { command.soptions.consentTimeout = domain.consentmessages.consenttimeout; } if (domain.consentmessages.autoacceptontimeout === true) { command.soptions.consentAutoAccept = true; } + if (domain.consentmessages.autoacceptifnouser === true) { command.soptions.consentAutoAcceptIfNoUser = true; } if (domain.consentmessages.oldstyle === true) { command.soptions.oldStyle = true; } } if (typeof domain.notificationmessages == 'object') { @@ -3072,7 +3079,16 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use } if (commandsOk == true) { var theCommand = { action: 'runcommands', type: command.type, cmds: command.cmds, runAsUser: command.runAsUser, reply: command.reply, responseid: command.responseid }; - if (parent.parent.multiServer != null) { // peering setup + var agent = parent.wsagents[node._id]; + if ((agent != null) && (agent.authenticated == 2) && (agent.agentInfo != null)) { + // Send the commands to the agent + try { agent.send(JSON.stringify(theCommand)); } catch (ex) { } + if (command.responseid != null && command.reply == false) { try { ws.send(JSON.stringify({ action: 'runcommands', responseid: command.responseid, result: 'OK' })); } catch (ex) { } } + // Send out an event that these commands where run on this device + var targets = parent.CreateNodeDispatchTargets(node.meshid, node._id, ['server-users', user._id]); + var event = { etype: 'node', userid: user._id, username: user.name, nodeid: node._id, action: 'runcommands', msg: 'Running commands', msgid: msgid, cmds: command.cmds, cmdType: command.type, runAsUser: command.runAsUser, domain: domain.id }; + parent.parent.DispatchEvent(targets, obj, event); + } else if (parent.parent.multiServer != null) { // peering setup // Send the commands to the agent parent.parent.multiServer.DispatchMessage({ action: 'agentCommand', nodeid: node._id, command: theCommand}); if (command.responseid != null && command.reply == false) { try { ws.send(JSON.stringify({ action: 'runcommands', responseid: command.responseid, result: 'OK' })); } catch (ex) { } } @@ -3080,20 +3096,8 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use var targets = parent.CreateNodeDispatchTargets(node.meshid, node._id, ['server-users', user._id]); var event = { etype: 'node', userid: user._id, username: user.name, nodeid: node._id, action: 'runcommands', msg: 'Running commands', msgid: msgid, cmds: command.cmds, cmdType: command.type, runAsUser: command.runAsUser, domain: domain.id }; parent.parent.multiServer.DispatchEvent(targets, obj, event); - } else { // normal setup - // Get the agent and run the commands - var agent = parent.wsagents[node._id]; - if ((agent != null) && (agent.authenticated == 2) && (agent.agentInfo != null)) { - // Send the commands to the agent - try { agent.send(JSON.stringify(theCommand)); } catch (ex) { } - if (command.responseid != null && command.reply == false) { try { ws.send(JSON.stringify({ action: 'runcommands', responseid: command.responseid, result: 'OK' })); } catch (ex) { } } - // Send out an event that these commands where run on this device - var targets = parent.CreateNodeDispatchTargets(node.meshid, node._id, ['server-users', user._id]); - var event = { etype: 'node', userid: user._id, username: user.name, nodeid: node._id, action: 'runcommands', msg: 'Running commands', msgid: msgid, cmds: command.cmds, cmdType: command.type, runAsUser: command.runAsUser, domain: domain.id }; - parent.parent.DispatchEvent(targets, obj, event); - } else { - if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'runcommands', responseid: command.responseid, result: 'Agent not connected' })); } catch (ex) { } } - } + } else { + if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'runcommands', responseid: command.responseid, result: 'Agent not connected' })); } catch (ex) { } } } } else { if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'runcommands', responseid: command.responseid, result: 'Invalid command type' })); } catch (ex) { } } @@ -5072,285 +5076,295 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use case 'getDeviceDetails': { if ((common.validateStrArray(command.nodeids, 1) == false) && (command.nodeids != null)) break; // Check nodeids if (common.validateString(command.type, 3, 4) == false) break; // Check type + + const links = parent.GetAllMeshIdWithRights(user); + const extraids = getUserExtraIds(); + db.GetAllTypeNoTypeFieldMeshFiltered(links, extraids, domain.id, 'node', null, obj.deviceSkip, obj.deviceLimit, function (err, docs) { + if (docs == null) return; + const ids = []; + if (command.nodeids != null) { + // Create a list of node ids and query them for last device connection time + for (var i in command.nodeids) { ids.push('lc' + command.nodeids[i]); } + } else { + // Create a list of node ids for this user and query them for last device connection time + for (var i in docs) { ids.push('lc' + docs[i]._id); } + } + db.GetAllIdsOfType(ids, domain.id, 'lastconnect', function (err, docs) { + const lastConnects = {}; + if (docs != null) { for (var i in docs) { lastConnects[docs[i]._id] = docs[i]; } } - // Create a list of node ids and query them for last device connection time - const ids = [] - for (var i in command.nodeids) { ids.push('lc' + command.nodeids[i]); } - db.GetAllIdsOfType(ids, domain.id, 'lastconnect', function (err, docs) { - const lastConnects = {}; - if (docs != null) { for (var i in docs) { lastConnects[docs[i]._id] = docs[i]; } } - - getDeviceDetailedInfo(command.nodeids, command.type, function (results, type) { - for (var i = 0; i < results.length; i++) { - // Remove any device system and network information is we do not have details rights to this device - if ((parent.GetNodeRights(user, results[i].node.meshid, results[i].node._id) & MESHRIGHT_DEVICEDETAILS) == 0) { - delete results[i].sys; delete results[i].net; - } - - // Merge any last connection information - const lc = lastConnects['lc' + results[i].node._id]; - if (lc != null) { delete lc._id; delete lc.type; delete lc.meshid; delete lc.domain; results[i].lastConnect = lc; } - - // Remove any connectivity and power state information, that should not be in the database anyway. - // TODO: Find why these are sometimes saved in the db. - if (results[i].node.conn != null) { delete results[i].node.conn; } - if (results[i].node.pwr != null) { delete results[i].node.pwr; } - if (results[i].node.agct != null) { delete results[i].node.agct; } - if (results[i].node.cict != null) { delete results[i].node.cict; } - - // Add the connection state - var state = parent.parent.GetConnectivityState(results[i].node._id); - if (state) { - results[i].node.conn = state.connectivity; - results[i].node.pwr = state.powerState; - if ((state.connectivity & 1) != 0) { var agent = parent.wsagents[results[i].node._id]; if (agent != null) { results[i].node.agct = agent.connectTime; } } - - // Use the connection time of the CIRA/Relay connection - if ((state.connectivity & 2) != 0) { - var ciraConnection = parent.parent.mpsserver.GetConnectionToNode(results[i].node._id, null, true); - if ((ciraConnection != null) && (ciraConnection.tag != null)) { results[i].node.cict = ciraConnection.tag.connectTime; } + getDeviceDetailedInfo(command.nodeids, command.type, function (results, type) { + for (var i = 0; i < results.length; i++) { + // Remove any device system and network information is we do not have details rights to this device + if ((parent.GetNodeRights(user, results[i].node.meshid, results[i].node._id) & MESHRIGHT_DEVICEDETAILS) == 0) { + delete results[i].sys; delete results[i].net; } - } - - } - var output = null; - if (type == 'csv') { - try { - // Create the CSV file - output = 'id,name,rname,host,icon,ip,osdesc,groupname,av,update,firewall,bitlocker,avdetails,tags,cpu,osbuild,biosDate,biosVendor,biosVersion,biosSerial,biosMode,boardName,boardVendor,boardVersion,productUuid,tpmversion,tpmmanufacturer,tpmmanufacturerversion,tpmisactivated,tpmisenabled,tpmisowned,totalMemory,agentOpenSSL,agentCommitDate,agentCommitHash,agentCompileTime,netIfCount,macs,addresses,lastConnectTime,lastConnectAddr\r\n'; - for (var i = 0; i < results.length; i++) { - const nodeinfo = results[i]; + // Merge any last connection information + const lc = lastConnects['lc' + results[i].node._id]; + if (lc != null) { delete lc._id; delete lc.type; delete lc.meshid; delete lc.domain; results[i].lastConnect = lc; } - // Node information - if (nodeinfo.node != null) { - const n = nodeinfo.node; - output += csvClean(n._id) + ',' + csvClean(n.name) + ',' + csvClean(n.rname ? n.rname : '') + ',' + csvClean(n.host ? n.host : '') + ',' + (n.icon ? n.icon : 1) + ',' + (n.ip ? n.ip : '') + ',' + (n.osdesc ? csvClean(n.osdesc) : '') + ',' + csvClean(parent.meshes[n.meshid].name); - if (typeof n.wsc == 'object') { - output += ',' + csvClean(n.wsc.antiVirus ? n.wsc.antiVirus : '') + ',' + csvClean(n.wsc.autoUpdate ? n.wsc.autoUpdate : '') + ',' + csvClean(n.wsc.firewall ? n.wsc.firewall : '') - } else { output += ',,,'; } - if (typeof n.volumes == 'object') { - var bitlockerdetails = '', firstbitlocker = true; - for (var a in n.volumes) { if (typeof n.volumes[a].protectionStatus !== 'undefined') { if (firstbitlocker) { firstbitlocker = false; } else { bitlockerdetails += '|'; } bitlockerdetails += a + '/' + n.volumes[a].volumeStatus; } } - output += ',' + csvClean(bitlockerdetails); - } else { - output += ','; - } - if (typeof n.av == 'object') { - var avdetails = '', firstav = true; - for (var a in n.av) { if (typeof n.av[a].product == 'string') { if (firstav) { firstav = false; } else { avdetails += '|'; } avdetails += (n.av[a].product + '/' + ((n.av[a].enabled) ? 'enabled' : 'disabled') + '/' + ((n.av[a].updated) ? 'updated' : 'notupdated')); } } - output += ',' + csvClean(avdetails); - } else { - output += ','; - } - if (typeof n.tags == 'object') { - var tagsdetails = '', firsttags = true; - for (var a in n.tags) { if (firsttags) { firsttags = false; } else { tagsdetails += '|'; } tagsdetails += n.tags[a]; } - output += ',' + csvClean(tagsdetails); - } else { - output += ','; - } - } else { - output += ',,,,,,,,,,,,,,,,,,,'; + // Remove any connectivity and power state information, that should not be in the database anyway. + // TODO: Find why these are sometimes saved in the db. + if (results[i].node.conn != null) { delete results[i].node.conn; } + if (results[i].node.pwr != null) { delete results[i].node.pwr; } + if (results[i].node.agct != null) { delete results[i].node.agct; } + if (results[i].node.cict != null) { delete results[i].node.cict; } + + // Add the connection state + var state = parent.parent.GetConnectivityState(results[i].node._id); + if (state) { + results[i].node.conn = state.connectivity; + results[i].node.pwr = state.powerState; + if ((state.connectivity & 1) != 0) { var agent = parent.wsagents[results[i].node._id]; if (agent != null) { results[i].node.agct = agent.connectTime; } } + + // Use the connection time of the CIRA/Relay connection + if ((state.connectivity & 2) != 0) { + var ciraConnection = parent.parent.mpsserver.GetConnectionToNode(results[i].node._id, null, true); + if ((ciraConnection != null) && (ciraConnection.tag != null)) { results[i].node.cict = ciraConnection.tag.connectTime; } } + } + + } - // System infomation - if ((nodeinfo.sys) && (nodeinfo.sys.hardware) && (nodeinfo.sys.hardware.windows)) { - // Windows - output += ','; - if (nodeinfo.sys.hardware.windows.cpu && (nodeinfo.sys.hardware.windows.cpu.length > 0) && (typeof nodeinfo.sys.hardware.windows.cpu[0].Name == 'string')) { output += csvClean(nodeinfo.sys.hardware.windows.cpu[0].Name); } - output += ','; - if (nodeinfo.sys.hardware.windows.osinfo && (nodeinfo.sys.hardware.windows.osinfo.BuildNumber)) { output += csvClean(nodeinfo.sys.hardware.windows.osinfo.BuildNumber); } - output += ','; - if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.bios_date)) { output += csvClean(nodeinfo.sys.hardware.identifiers.bios_date); } - output += ','; - if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.bios_vendor)) { output += csvClean(nodeinfo.sys.hardware.identifiers.bios_vendor); } - output += ','; - if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.bios_version)) { output += csvClean(nodeinfo.sys.hardware.identifiers.bios_version); } - output += ','; - if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.bios_serial)) { output += csvClean(nodeinfo.sys.hardware.identifiers.bios_serial); } - output += ','; - if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.bios_mode)) { output += csvClean(nodeinfo.sys.hardware.identifiers.bios_mode); } - output += ','; - if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.board_name)) { output += csvClean(nodeinfo.sys.hardware.identifiers.board_name); } - output += ','; - if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.board_vendor)) { output += csvClean(nodeinfo.sys.hardware.identifiers.board_vendor); } - output += ','; - if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.board_version)) { output += csvClean(nodeinfo.sys.hardware.identifiers.board_version); } - output += ','; - if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.product_uuid)) { output += csvClean(nodeinfo.sys.hardware.identifiers.product_uuid); } - output += ','; - if (nodeinfo.sys.hardware.tpm && nodeinfo.sys.hardware.tpm.SpecVersion) { output += csvClean(nodeinfo.sys.hardware.tpm.SpecVersion); } - output += ','; - if (nodeinfo.sys.hardware.tpm && nodeinfo.sys.hardware.tpm.ManufacturerId) { output += csvClean(nodeinfo.sys.hardware.tpm.ManufacturerId); } - output += ','; - if (nodeinfo.sys.hardware.tpm && nodeinfo.sys.hardware.tpm.ManufacturerVersion) { output += csvClean(nodeinfo.sys.hardware.tpm.ManufacturerVersion); } - output += ','; - if (nodeinfo.sys.hardware.tpm && nodeinfo.sys.hardware.tpm.IsActivated) { output += csvClean(nodeinfo.sys.hardware.tpm.IsActivated ? 'true' : 'false'); } - output += ','; - if (nodeinfo.sys.hardware.tpm && nodeinfo.sys.hardware.tpm.IsEnabled) { output += csvClean(nodeinfo.sys.hardware.tpm.IsEnabled ? 'true' : 'false'); } - output += ','; - if (nodeinfo.sys.hardware.tpm && nodeinfo.sys.hardware.tpm.IsOwned) { output += csvClean(nodeinfo.sys.hardware.tpm.IsOwned ? 'true' : 'false'); } - output += ','; - if (nodeinfo.sys.hardware.windows.memory) { - var totalMemory = 0; - for (var j in nodeinfo.sys.hardware.windows.memory) { - if (nodeinfo.sys.hardware.windows.memory[j].Capacity) { - if (typeof nodeinfo.sys.hardware.windows.memory[j].Capacity == 'number') { totalMemory += nodeinfo.sys.hardware.windows.memory[j].Capacity; } - if (typeof nodeinfo.sys.hardware.windows.memory[j].Capacity == 'string') { totalMemory += parseInt(nodeinfo.sys.hardware.windows.memory[j].Capacity); } - } + var output = null; + if (type == 'csv') { + try { + // Create the CSV file + output = 'id,name,rname,host,icon,ip,osdesc,groupname,av,update,firewall,bitlocker,avdetails,tags,lastbootuptime,cpu,osbuild,biosDate,biosVendor,biosVersion,biosSerial,biosMode,boardName,boardVendor,boardVersion,productUuid,tpmversion,tpmmanufacturer,tpmmanufacturerversion,tpmisactivated,tpmisenabled,tpmisowned,totalMemory,agentOpenSSL,agentCommitDate,agentCommitHash,agentCompileTime,netIfCount,macs,addresses,lastConnectTime,lastConnectAddr\r\n'; + for (var i = 0; i < results.length; i++) { + const nodeinfo = results[i]; + + // Node information + if (nodeinfo.node != null) { + const n = nodeinfo.node; + output += csvClean(n._id) + ',' + csvClean(n.name) + ',' + csvClean(n.rname ? n.rname : '') + ',' + csvClean(n.host ? n.host : '') + ',' + (n.icon ? n.icon : 1) + ',' + (n.ip ? n.ip : '') + ',' + (n.osdesc ? csvClean(n.osdesc) : '') + ',' + csvClean(parent.meshes[n.meshid].name); + if (typeof n.wsc == 'object') { + output += ',' + csvClean(n.wsc.antiVirus ? n.wsc.antiVirus : '') + ',' + csvClean(n.wsc.autoUpdate ? n.wsc.autoUpdate : '') + ',' + csvClean(n.wsc.firewall ? n.wsc.firewall : '') + } else { output += ',,,'; } + if (typeof n.volumes == 'object') { + var bitlockerdetails = '', firstbitlocker = true; + for (var a in n.volumes) { if (typeof n.volumes[a].protectionStatus !== 'undefined') { if (firstbitlocker) { firstbitlocker = false; } else { bitlockerdetails += '|'; } bitlockerdetails += a + '/' + n.volumes[a].volumeStatus; } } + output += ',' + csvClean(bitlockerdetails); + } else { + output += ','; } - output += csvClean('' + totalMemory); + if (typeof n.av == 'object') { + var avdetails = '', firstav = true; + for (var a in n.av) { if (typeof n.av[a].product == 'string') { if (firstav) { firstav = false; } else { avdetails += '|'; } avdetails += (n.av[a].product + '/' + ((n.av[a].enabled) ? 'enabled' : 'disabled') + '/' + ((n.av[a].updated) ? 'updated' : 'notupdated')); } } + output += ',' + csvClean(avdetails); + } else { + output += ','; + } + if (typeof n.tags == 'object') { + var tagsdetails = '', firsttags = true; + for (var a in n.tags) { if (firsttags) { firsttags = false; } else { tagsdetails += '|'; } tagsdetails += n.tags[a]; } + output += ',' + csvClean(tagsdetails); + } else { + output += ','; + } + if (typeof n.lastbootuptime == 'number') { output += ',' + n.lastbootuptime; } else { output += ','; } + } else { + output += ',,,,,,,,,,,,,,,,,,,,'; } - } else if ((nodeinfo.sys) && (nodeinfo.sys.hardware) && (nodeinfo.sys.hardware.mobile)) { - // Mobile - output += ','; - output += ','; - output += ','; - output += ','; - output += ','; - if (nodeinfo.sys.hardware.mobile && (nodeinfo.sys.hardware.mobile.bootloader)) { output += csvClean(nodeinfo.sys.hardware.mobile.bootloader); } - output += ','; - output += ','; - output += ','; - if (nodeinfo.sys.hardware.mobile && (nodeinfo.sys.hardware.mobile.model)) { output += csvClean(nodeinfo.sys.hardware.mobile.model); } - output += ','; - if (nodeinfo.sys.hardware.mobile && (nodeinfo.sys.hardware.mobile.brand)) { output += csvClean(nodeinfo.sys.hardware.mobile.brand); } - output += ','; - output += ','; - if (nodeinfo.sys.hardware.mobile && (nodeinfo.sys.hardware.mobile.id)) { output += csvClean(nodeinfo.sys.hardware.mobile.id); } - output += ','; - output += ','; - output += ','; - output += ','; - output += ','; - output += ','; - output += ','; - } else if ((nodeinfo.sys) && (nodeinfo.sys.hardware) && (nodeinfo.sys.hardware.linux)) { - // Linux - output += ','; - if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.cpu_name)) { output += csvClean(nodeinfo.sys.hardware.identifiers.cpu_name); } - output += ',,'; - if (nodeinfo.sys.hardware.linux && (nodeinfo.sys.hardware.linux.bios_date)) { output += csvClean(nodeinfo.sys.hardware.linux.bios_date); } - output += ','; - if (nodeinfo.sys.hardware.linux && (nodeinfo.sys.hardware.linux.bios_vendor)) { output += csvClean(nodeinfo.sys.hardware.linux.bios_vendor); } - output += ','; - if (nodeinfo.sys.hardware.linux && (nodeinfo.sys.hardware.linux.bios_version)) { output += csvClean(nodeinfo.sys.hardware.linux.bios_version); } - output += ','; - if (nodeinfo.sys.hardware.linux && (nodeinfo.sys.hardware.linux.product_serial)) { output += csvClean(nodeinfo.sys.hardware.linux.product_serial); } - else if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.bios_serial)) { output += csvClean(nodeinfo.sys.hardware.identifiers.bios_serial); } - output += ','; - if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.bios_mode)) { output += csvClean(nodeinfo.sys.hardware.identifiers.bios_mode); } - output += ','; - if (nodeinfo.sys.hardware.linux && (nodeinfo.sys.hardware.linux.board_name)) { output += csvClean(nodeinfo.sys.hardware.linux.board_name); } - output += ','; - if (nodeinfo.sys.hardware.linux && (nodeinfo.sys.hardware.linux.board_vendor)) { output += csvClean(nodeinfo.sys.hardware.linux.board_vendor); } - output += ','; - if (nodeinfo.sys.hardware.linux && (nodeinfo.sys.hardware.linux.board_version)) { output += csvClean(nodeinfo.sys.hardware.linux.board_version); } - output += ','; - if (nodeinfo.sys.hardware.linux && (nodeinfo.sys.hardware.linux.product_uuid)) { output += csvClean(nodeinfo.sys.hardware.linux.product_uuid); } - output += ','; - if (nodeinfo.sys.hardware.tpm && nodeinfo.sys.hardware.tpm.SpecVersion) { output += csvClean(nodeinfo.sys.hardware.tpm.SpecVersion); } - output += ','; - if (nodeinfo.sys.hardware.tpm && nodeinfo.sys.hardware.tpm.ManufacturerId) { output += csvClean(nodeinfo.sys.hardware.tpm.ManufacturerId); } - output += ','; - if (nodeinfo.sys.hardware.tpm && nodeinfo.sys.hardware.tpm.ManufacturerVersion) { output += csvClean(nodeinfo.sys.hardware.tpm.ManufacturerVersion); } - output += ','; - if (nodeinfo.sys.hardware.tpm && nodeinfo.sys.hardware.tpm.IsActivated) { output += csvClean(nodeinfo.sys.hardware.tpm.IsActivated ? 'true' : 'false'); } - output += ','; - if (nodeinfo.sys.hardware.tpm && nodeinfo.sys.hardware.tpm.IsEnabled) { output += csvClean(nodeinfo.sys.hardware.tpm.IsEnabled ? 'true' : 'false'); } - output += ','; - if (nodeinfo.sys.hardware.tpm && nodeinfo.sys.hardware.tpm.IsOwned) { output += csvClean(nodeinfo.sys.hardware.tpm.IsOwned ? 'true' : 'false'); } - output += ','; - if (nodeinfo.sys.hardware.linux.memory) { - if (nodeinfo.sys.hardware.linux.memory.Memory_Device) { + + // System infomation + if ((nodeinfo.sys) && (nodeinfo.sys.hardware) && (nodeinfo.sys.hardware.windows)) { + // Windows + output += ','; + if (nodeinfo.sys.hardware.windows.cpu && (nodeinfo.sys.hardware.windows.cpu.length > 0) && (typeof nodeinfo.sys.hardware.windows.cpu[0].Name == 'string')) { output += csvClean(nodeinfo.sys.hardware.windows.cpu[0].Name); } + output += ','; + if (nodeinfo.sys.hardware.windows.osinfo && (nodeinfo.sys.hardware.windows.osinfo.BuildNumber)) { output += csvClean(nodeinfo.sys.hardware.windows.osinfo.BuildNumber); } + output += ','; + if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.bios_date)) { output += csvClean(nodeinfo.sys.hardware.identifiers.bios_date); } + output += ','; + if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.bios_vendor)) { output += csvClean(nodeinfo.sys.hardware.identifiers.bios_vendor); } + output += ','; + if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.bios_version)) { output += csvClean(nodeinfo.sys.hardware.identifiers.bios_version); } + output += ','; + if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.bios_serial)) { output += csvClean(nodeinfo.sys.hardware.identifiers.bios_serial); } + output += ','; + if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.bios_mode)) { output += csvClean(nodeinfo.sys.hardware.identifiers.bios_mode); } + output += ','; + if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.board_name)) { output += csvClean(nodeinfo.sys.hardware.identifiers.board_name); } + output += ','; + if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.board_vendor)) { output += csvClean(nodeinfo.sys.hardware.identifiers.board_vendor); } + output += ','; + if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.board_version)) { output += csvClean(nodeinfo.sys.hardware.identifiers.board_version); } + output += ','; + if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.product_uuid)) { output += csvClean(nodeinfo.sys.hardware.identifiers.product_uuid); } + output += ','; + if (nodeinfo.sys.hardware.tpm && nodeinfo.sys.hardware.tpm.SpecVersion) { output += csvClean(nodeinfo.sys.hardware.tpm.SpecVersion); } + output += ','; + if (nodeinfo.sys.hardware.tpm && nodeinfo.sys.hardware.tpm.ManufacturerId) { output += csvClean(nodeinfo.sys.hardware.tpm.ManufacturerId); } + output += ','; + if (nodeinfo.sys.hardware.tpm && nodeinfo.sys.hardware.tpm.ManufacturerVersion) { output += csvClean(nodeinfo.sys.hardware.tpm.ManufacturerVersion); } + output += ','; + if (nodeinfo.sys.hardware.tpm && nodeinfo.sys.hardware.tpm.IsActivated) { output += csvClean(nodeinfo.sys.hardware.tpm.IsActivated ? 'true' : 'false'); } + output += ','; + if (nodeinfo.sys.hardware.tpm && nodeinfo.sys.hardware.tpm.IsEnabled) { output += csvClean(nodeinfo.sys.hardware.tpm.IsEnabled ? 'true' : 'false'); } + output += ','; + if (nodeinfo.sys.hardware.tpm && nodeinfo.sys.hardware.tpm.IsOwned) { output += csvClean(nodeinfo.sys.hardware.tpm.IsOwned ? 'true' : 'false'); } + output += ','; + if (nodeinfo.sys.hardware.windows.memory) { var totalMemory = 0; - for (var j in nodeinfo.sys.hardware.linux.memory.Memory_Device) { - if (nodeinfo.sys.hardware.linux.memory.Memory_Device[j].Size) { - if (typeof nodeinfo.sys.hardware.linux.memory.Memory_Device[j].Size == 'number') { totalMemory += nodeinfo.sys.hardware.linux.memory.Memory_Device[j].Size; } - if (typeof nodeinfo.sys.hardware.linux.memory.Memory_Device[j].Size == 'string') { totalMemory += parseInt(nodeinfo.sys.hardware.linux.memory.Memory_Device[j].Size); } + for (var j in nodeinfo.sys.hardware.windows.memory) { + if (nodeinfo.sys.hardware.windows.memory[j].Capacity) { + if (typeof nodeinfo.sys.hardware.windows.memory[j].Capacity == 'number') { totalMemory += nodeinfo.sys.hardware.windows.memory[j].Capacity; } + if (typeof nodeinfo.sys.hardware.windows.memory[j].Capacity == 'string') { totalMemory += parseInt(nodeinfo.sys.hardware.windows.memory[j].Capacity); } } } - output += csvClean('' + (totalMemory * Math.pow(1024, 3))); + output += csvClean('' + totalMemory); } - } - } else { - output += ',,,,,,,,,,,,,,,,,,'; - } - - // Agent information - if ((nodeinfo.sys) && (nodeinfo.sys.hardware) && (nodeinfo.sys.hardware.agentvers)) { - output += ','; - if (nodeinfo.sys.hardware.agentvers.openssl) { output += csvClean(nodeinfo.sys.hardware.agentvers.openssl); } - output += ','; - if (nodeinfo.sys.hardware.agentvers.commitDate) { output += csvClean(nodeinfo.sys.hardware.agentvers.commitDate); } - output += ','; - if (nodeinfo.sys.hardware.agentvers.commitHash) { output += csvClean(nodeinfo.sys.hardware.agentvers.commitHash); } - output += ','; - if (nodeinfo.sys.hardware.agentvers.compileTime) { output += csvClean(nodeinfo.sys.hardware.agentvers.compileTime); } - } else { - output += ',,,,'; - } - - // Network interfaces - if ((nodeinfo.net) && (nodeinfo.net.netif2)) { - output += ','; - output += Object.keys(nodeinfo.net.netif2).length; // Interface count - var macs = [], addresses = []; - for (var j in nodeinfo.net.netif2) { - if (Array.isArray(nodeinfo.net.netif2[j])) { - for (var k = 0; k < nodeinfo.net.netif2[j].length; k++) { - if (typeof nodeinfo.net.netif2[j][k].mac == 'string') { macs.push(nodeinfo.net.netif2[j][k].mac); } - if (typeof nodeinfo.net.netif2[j][k].address == 'string') { addresses.push(nodeinfo.net.netif2[j][k].address); } + } else if ((nodeinfo.sys) && (nodeinfo.sys.hardware) && (nodeinfo.sys.hardware.mobile)) { + // Mobile + output += ','; + output += ','; + output += ','; + output += ','; + output += ','; + if (nodeinfo.sys.hardware.mobile && (nodeinfo.sys.hardware.mobile.bootloader)) { output += csvClean(nodeinfo.sys.hardware.mobile.bootloader); } + output += ','; + output += ','; + output += ','; + if (nodeinfo.sys.hardware.mobile && (nodeinfo.sys.hardware.mobile.model)) { output += csvClean(nodeinfo.sys.hardware.mobile.model); } + output += ','; + if (nodeinfo.sys.hardware.mobile && (nodeinfo.sys.hardware.mobile.brand)) { output += csvClean(nodeinfo.sys.hardware.mobile.brand); } + output += ','; + output += ','; + if (nodeinfo.sys.hardware.mobile && (nodeinfo.sys.hardware.mobile.id)) { output += csvClean(nodeinfo.sys.hardware.mobile.id); } + output += ','; + output += ','; + output += ','; + output += ','; + output += ','; + output += ','; + output += ','; + } else if ((nodeinfo.sys) && (nodeinfo.sys.hardware) && (nodeinfo.sys.hardware.linux)) { + // Linux + output += ','; + if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.cpu_name)) { output += csvClean(nodeinfo.sys.hardware.identifiers.cpu_name); } + output += ',,'; + if (nodeinfo.sys.hardware.linux && (nodeinfo.sys.hardware.linux.bios_date)) { output += csvClean(nodeinfo.sys.hardware.linux.bios_date); } + output += ','; + if (nodeinfo.sys.hardware.linux && (nodeinfo.sys.hardware.linux.bios_vendor)) { output += csvClean(nodeinfo.sys.hardware.linux.bios_vendor); } + output += ','; + if (nodeinfo.sys.hardware.linux && (nodeinfo.sys.hardware.linux.bios_version)) { output += csvClean(nodeinfo.sys.hardware.linux.bios_version); } + output += ','; + if (nodeinfo.sys.hardware.linux && (nodeinfo.sys.hardware.linux.product_serial)) { output += csvClean(nodeinfo.sys.hardware.linux.product_serial); } + else if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.bios_serial)) { output += csvClean(nodeinfo.sys.hardware.identifiers.bios_serial); } + output += ','; + if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.bios_mode)) { output += csvClean(nodeinfo.sys.hardware.identifiers.bios_mode); } + output += ','; + if (nodeinfo.sys.hardware.linux && (nodeinfo.sys.hardware.linux.board_name)) { output += csvClean(nodeinfo.sys.hardware.linux.board_name); } + output += ','; + if (nodeinfo.sys.hardware.linux && (nodeinfo.sys.hardware.linux.board_vendor)) { output += csvClean(nodeinfo.sys.hardware.linux.board_vendor); } + output += ','; + if (nodeinfo.sys.hardware.linux && (nodeinfo.sys.hardware.linux.board_version)) { output += csvClean(nodeinfo.sys.hardware.linux.board_version); } + output += ','; + if (nodeinfo.sys.hardware.linux && (nodeinfo.sys.hardware.linux.product_uuid)) { output += csvClean(nodeinfo.sys.hardware.linux.product_uuid); } + output += ','; + if (nodeinfo.sys.hardware.tpm && nodeinfo.sys.hardware.tpm.SpecVersion) { output += csvClean(nodeinfo.sys.hardware.tpm.SpecVersion); } + output += ','; + if (nodeinfo.sys.hardware.tpm && nodeinfo.sys.hardware.tpm.ManufacturerId) { output += csvClean(nodeinfo.sys.hardware.tpm.ManufacturerId); } + output += ','; + if (nodeinfo.sys.hardware.tpm && nodeinfo.sys.hardware.tpm.ManufacturerVersion) { output += csvClean(nodeinfo.sys.hardware.tpm.ManufacturerVersion); } + output += ','; + if (nodeinfo.sys.hardware.tpm && nodeinfo.sys.hardware.tpm.IsActivated) { output += csvClean(nodeinfo.sys.hardware.tpm.IsActivated ? 'true' : 'false'); } + output += ','; + if (nodeinfo.sys.hardware.tpm && nodeinfo.sys.hardware.tpm.IsEnabled) { output += csvClean(nodeinfo.sys.hardware.tpm.IsEnabled ? 'true' : 'false'); } + output += ','; + if (nodeinfo.sys.hardware.tpm && nodeinfo.sys.hardware.tpm.IsOwned) { output += csvClean(nodeinfo.sys.hardware.tpm.IsOwned ? 'true' : 'false'); } + output += ','; + if (nodeinfo.sys.hardware.linux.memory) { + if (nodeinfo.sys.hardware.linux.memory.Memory_Device) { + var totalMemory = 0; + for (var j in nodeinfo.sys.hardware.linux.memory.Memory_Device) { + if (nodeinfo.sys.hardware.linux.memory.Memory_Device[j].Size) { + if (typeof nodeinfo.sys.hardware.linux.memory.Memory_Device[j].Size == 'number') { totalMemory += nodeinfo.sys.hardware.linux.memory.Memory_Device[j].Size; } + if (typeof nodeinfo.sys.hardware.linux.memory.Memory_Device[j].Size == 'string') { totalMemory += parseInt(nodeinfo.sys.hardware.linux.memory.Memory_Device[j].Size); } + } + } + output += csvClean('' + (totalMemory * Math.pow(1024, 3))); } } + } else { + output += ',,,,,,,,,,,,,,,,,,'; } - output += ','; - output += csvClean(macs.join(' ')); // MACS - output += ','; - output += csvClean(addresses.join(' ')); // Addresses - } else { - output += ',,,'; - } - // Last connection information - if (nodeinfo.lastConnect) { - output += ','; - if (nodeinfo.lastConnect.time) { - // Last connection time - if ((typeof command.l == 'string') && (typeof command.tz == 'string')) { - output += csvClean(new Date(nodeinfo.lastConnect.time).toLocaleString(command.l, { timeZone: command.tz })) - } else { - output += nodeinfo.lastConnect.time; + // Agent information + if ((nodeinfo.sys) && (nodeinfo.sys.hardware) && (nodeinfo.sys.hardware.agentvers)) { + output += ','; + if (nodeinfo.sys.hardware.agentvers.openssl) { output += csvClean(nodeinfo.sys.hardware.agentvers.openssl); } + output += ','; + if (nodeinfo.sys.hardware.agentvers.commitDate) { output += csvClean(nodeinfo.sys.hardware.agentvers.commitDate); } + output += ','; + if (nodeinfo.sys.hardware.agentvers.commitHash) { output += csvClean(nodeinfo.sys.hardware.agentvers.commitHash); } + output += ','; + if (nodeinfo.sys.hardware.agentvers.compileTime) { output += csvClean(nodeinfo.sys.hardware.agentvers.compileTime); } + } else { + output += ',,,,'; + } + + // Network interfaces + if ((nodeinfo.net) && (nodeinfo.net.netif2)) { + output += ','; + output += Object.keys(nodeinfo.net.netif2).length; // Interface count + var macs = [], addresses = []; + for (var j in nodeinfo.net.netif2) { + if (Array.isArray(nodeinfo.net.netif2[j])) { + for (var k = 0; k < nodeinfo.net.netif2[j].length; k++) { + if (typeof nodeinfo.net.netif2[j][k].mac == 'string') { macs.push(nodeinfo.net.netif2[j][k].mac); } + if (typeof nodeinfo.net.netif2[j][k].address == 'string') { addresses.push(nodeinfo.net.netif2[j][k].address); } + } + } } + output += ','; + output += csvClean(macs.join(' ')); // MACS + output += ','; + output += csvClean(addresses.join(' ')); // Addresses + } else { + output += ',,,'; } - output += ','; - if (typeof nodeinfo.lastConnect.addr == 'string') { output += csvClean(nodeinfo.lastConnect.addr); } // Last connection address and port - } else { - output += ',,'; + + // Last connection information + if (nodeinfo.lastConnect) { + output += ','; + if (nodeinfo.lastConnect.time) { + // Last connection time + if ((typeof command.l == 'string') && (typeof command.tz == 'string')) { + output += csvClean(new Date(nodeinfo.lastConnect.time).toLocaleString(command.l, { timeZone: command.tz })) + } else { + output += nodeinfo.lastConnect.time; + } + } + output += ','; + if (typeof nodeinfo.lastConnect.addr == 'string') { output += csvClean(nodeinfo.lastConnect.addr); } // Last connection address and port + } else { + output += ',,'; + } + + output += '\r\n'; } + } catch (ex) { console.log(ex); } + } else { + // Create the JSON file - output += '\r\n'; + // Add the device group name to each device + for (var i = 0; i < results.length; i++) { + const nodeinfo = results[i]; + if (nodeinfo.node) { + const mesh = parent.meshes[nodeinfo.node.meshid]; + if (mesh) { results[i].node.groupname = mesh.name; } + } } - } catch (ex) { console.log(ex); } - } else { - // Create the JSON file - // Add the device group name to each device - for (var i = 0; i < results.length; i++) { - const nodeinfo = results[i]; - if (nodeinfo.node) { - const mesh = parent.meshes[nodeinfo.node.meshid]; - if (mesh) { results[i].node.groupname = mesh.name; } - } + output = JSON.stringify(results, null, 2); } - - output = JSON.stringify(results, null, 2); - } - try { ws.send(JSON.stringify({ action: 'getDeviceDetails', data: output, type: type })); } catch (ex) { } + try { ws.send(JSON.stringify({ action: 'getDeviceDetails', data: output, type: type })); } catch (ex) { } + }); }); }); - break; } case 'endDesktopMultiplex': { @@ -5599,7 +5613,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use 'heapdump': [serverUserCommandHeapDump, ""], 'heapdump2': [serverUserCommandHeapDump2, ""], 'help': [serverUserCommandHelp, ""], - 'info': [serverUserCommandInfo, "Returns the most immidiatly useful information about this server, including MeshCentral and NodeJS versions. This is often information required to file a bug."], + 'info': [serverUserCommandInfo, "Returns the most immidiatly useful information about this server, including MeshCentral and NodeJS versions. This is often information required to file a bug. Optionally use info h for human readable form."], 'le': [serverUserCommandLe, ""], 'lecheck': [serverUserCommandLeCheck, ""], 'leevents': [serverUserCommandLeEvents, ""], @@ -7545,7 +7559,26 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use } function serverUserCommandInfo(cmdData) { - var info = {}; + function convertSeconds (s, form) { + if (!['long', 'shortprecise'].includes(form)) { + form = 'shortprecise'; + } + let t = {}, r = ''; + t.d = Math.floor(s / (24 * 3600)); + s %= 24 * 3600; + t.h= Math.floor(s / 3600); + s %= 3600; + t.m = Math.floor(s / 60); + t.s =(s%60).toFixed(0); + if ( form == 'long') { + r = t.d + ((t.d == 1) ? ' day, ' : ' days, ') + t.h + ((t.h == 1) ? ' hour, ' : ' hours, ') + t.m + ((t.m == 1) ? ' minute, ' : ' minutes, ') + t.s+ ((t.s == 1) ? ' second' : ' seconds'); + } else if (form == 'shortprecise') { + r = String(t.d).padStart(2, '0') + ':' + String(t.h).padStart(2, '0') + ':' + String(t.m).padStart(2, '0') + ':' + String((s%60).toFixed(2)).padStart(5, '0') + 's'; + } + return r; + } + var info = {}, arg = null, t = {}, r = ''; + if ((cmdData.cmdargs['_'] != null) && (cmdData.cmdargs['_'][0] != null)) { arg = cmdData.cmdargs['_'][0].toLowerCase(); } try { info.meshVersion = 'v' + parent.parent.currentVer; } catch (ex) { } try { info.nodeVersion = process.version; } catch (ex) { } try { info.runMode = (["Hybrid (LAN + WAN) mode", "WAN mode", "LAN mode"][(args.lanonly ? 2 : (args.wanonly ? 1 : 0))]); } catch (ex) { } @@ -7557,9 +7590,24 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use try { info.platform = process.platform; } catch (ex) { } try { info.arch = process.arch; } catch (ex) { } try { info.pid = process.pid; } catch (ex) { } - try { info.uptime = process.uptime(); } catch (ex) { } - try { info.cpuUsage = process.cpuUsage(); } catch (ex) { } - try { info.memoryUsage = process.memoryUsage(); } catch (ex) { } + if (arg == 'h') { + try { + info.uptime = convertSeconds(process.uptime(), 'long'); + info.cpuUsage = { + system: (convertSeconds(process.cpuUsage().system /1000000)), + user: (convertSeconds(process.cpuUsage().user /1000000)) + } + info.memoryUsage = {}; + for (const [key,value] of Object.entries(process.memoryUsage())){ + info.memoryUsage[key] = ([value]/1048576).toFixed(2) + 'Mb'; + } + } catch (ex) { } + } + else { + try { info.uptime = process.uptime(); } catch (ex) { } + try { info.cpuUsage = process.cpuUsage(); } catch (ex) { } + try { info.memoryUsage = process.memoryUsage(); } catch (ex) { } + } try { info.warnings = parent.parent.getServerWarnings(); } catch (ex) { console.log(ex); } try { info.allDevGroupManagers = parent.parent.config.settings.managealldevicegroups; } catch (ex) { } try { if (process.traceDeprecation == true) { info.traceDeprecation = true; } } catch (ex) { } diff --git a/monitoring.js b/monitoring.js index 9fc1e422..ffb03da7 100644 --- a/monitoring.js +++ b/monitoring.js @@ -32,7 +32,7 @@ module.exports.CreateMonitoring = function (parent, args) { blockedUsers: { description: "Blocked Users" }, // blockedUsers blockedAgents: { description: "Blocked Agents" }, // blockedAgents }; - obj.guageMetrics = { // Guage Metrics always start at 0 and can increase and decrease + obj.gaugeMetrics = { // Gauge Metrics always start at 0 and can increase and decrease ConnectedIntelAMT: { description: "Connected Intel AMT" }, // parent.mpsserver.ciraConnections[i].length UserAccounts: { description: "User Accounts" }, // Object.keys(parent.webserver.users).length DeviceGroups: { description: "Device Groups" }, // parent.webserver.meshes (ONLY WHERE deleted=null) @@ -42,6 +42,7 @@ module.exports.CreateMonitoring = function (parent, args) { RelaySessions: { description: "Relay Sessions" }, // parent.webserver.relaySessionCount RelayCount: { description: "Relay Count" } // Object.keys(parent.webserver.wsrelays).length30bb4fb74dfb758d36be52a7 } + obj.collectors = []; if (parent.config.settings.prometheus != null) { // Create Prometheus Monitoring Endpoint if ((typeof parent.config.settings.prometheus == 'number') && ((parent.config.settings.prometheus < 1) || (parent.config.settings.prometheus > 65535))) { console.log('Promethus port number is invalid, Prometheus metrics endpoint has be disabled'); @@ -51,8 +52,8 @@ module.exports.CreateMonitoring = function (parent, args) { obj.prometheus = require('prom-client'); const collectDefaultMetrics = obj.prometheus.collectDefaultMetrics; collectDefaultMetrics(); - for (const key in obj.guageMetrics) { - obj.guageMetrics[key].prometheus = new obj.prometheus.Gauge({ name: 'meshcentral_' + String(key).toLowerCase(), help: obj.guageMetrics[key].description }); + for (const key in obj.gaugeMetrics) { + obj.gaugeMetrics[key].prometheus = new obj.prometheus.Gauge({ name: 'meshcentral_' + String(key).toLowerCase(), help: obj.gaugeMetrics[key].description }); } for (const key in obj.counterMetrics) { obj.counterMetrics[key].prometheus = new obj.prometheus.Counter({ name: 'meshcentral_' + String(key).toLowerCase(), help: obj.counterMetrics[key].description }); @@ -67,7 +68,7 @@ module.exports.CreateMonitoring = function (parent, args) { // Count the number of device groups that are not deleted var activeDeviceGroups = 0; for (var i in parent.webserver.meshes) { if (parent.webserver.meshes[i].deleted == null) { activeDeviceGroups++; } } // This is not ideal for performance, we want to dome something better. - var guages = { + var gauges = { UserAccounts: Object.keys(parent.webserver.users).length, DeviceGroups: activeDeviceGroups, AgentSessions: Object.keys(parent.webserver.wsagents).length, @@ -79,10 +80,10 @@ module.exports.CreateMonitoring = function (parent, args) { }; if (parent.mpsserver != null) { for (var i in parent.mpsserver.ciraConnections) { - guages.ConnectedIntelAMT += parent.mpsserver.ciraConnections[i].length; + gauges.ConnectedIntelAMT += parent.mpsserver.ciraConnections[i].length; } } - for (const key in guages) { obj.guageMetrics[key].prometheus.set(guages[key]); } + for (const key in gauges) { obj.gaugeMetrics[key].prometheus.set(gauges[key]); } // Take a look at agent errors var agentstats = parent.webserver.getAgentStats(); const counters = { @@ -103,6 +104,7 @@ module.exports.CreateMonitoring = function (parent, args) { }; for (const key in counters) { obj.counterMetrics[key].prometheus.reset(); obj.counterMetrics[key].prometheus.inc(counters[key]); } res.set('Content-Type', obj.prometheus.register.contentType); + await Promise.all(obj.collectors.map((collector) => (collector(req, res)))); res.end(await obj.prometheus.register.metrics()); } catch (ex) { console.log(ex); @@ -111,4 +113,5 @@ module.exports.CreateMonitoring = function (parent, args) { }); } } + return obj; } \ No newline at end of file diff --git a/package.json b/package.json index 84b9ec93..78cd22bb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "1.1.36", + "version": "1.1.42", "keywords": [ "Remote Device Management", "Remote Device Monitoring", diff --git a/pluginHandler.js b/pluginHandler.js index 139f8015..47c2a42d 100644 --- a/pluginHandler.js +++ b/pluginHandler.js @@ -139,7 +139,7 @@ module.exports.pluginHandler = function (parent) { try { obj.plugins[p][hookName](...args); } catch (e) { - console.log("Error occurred while running plugin hook" + p + ':' + hookName + ' (' + e + ')'); + console.log("Error occurred while running plugin hook " + p + ':' + hookName, e); } } } diff --git a/public/scripts/agent-desktop-0.0.2-min.js b/public/scripts/agent-desktop-0.0.2-min.js index fb7ec243..9bfbf813 100644 --- a/public/scripts/agent-desktop-0.0.2-min.js +++ b/public/scripts/agent-desktop-0.0.2-min.js @@ -1,962 +1 @@ -/** -* @description Remote Desktop -* @author Ylian Saint-Hilaire -* @version v0.0.2g -*/ - -// Polyfill Uint8Array.slice() for IE -if (!Uint8Array.prototype.slice) { Object.defineProperty(Uint8Array.prototype, 'slice', { value: function (begin, end) { return new Uint8Array(Array.prototype.slice.call(this, begin, end)); } }); } - -function isWindowsBrowser() { - return navigator && !!(/win/i).exec(navigator.platform); -} - -// Construct a MeshServer object -var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) { - var obj = {} - obj.CanvasId = canvasid; - if (typeof canvasid === 'string') obj.CanvasId = Q(canvasid); - obj.Canvas = obj.CanvasId.getContext('2d'); - obj.scrolldiv = scrolldiv; - obj.State = 0; - obj.PendingOperations = []; - obj.tilesReceived = 0; - obj.TilesDrawn = 0; - obj.KillDraw = 0; - obj.ipad = false; - obj.tabletKeyboardVisible = false; - obj.LastX = 0; - obj.LastY = 0; - obj.touchenabled = 0; - obj.submenuoffset = 0; - obj.touchtimer = null; - obj.TouchArray = {}; - obj.connectmode = 0; // 0 = HTTP, 1 = WebSocket, 2 = WebRTC - obj.connectioncount = 0; - obj.rotation = 0; - obj.protocol = 2; // KVM - obj.debugmode = 0; - obj.firstUpKeys = []; - obj.stopInput = false; - obj.localKeyMap = true; - obj.remoteKeyMap = false; // If false, the remote keyboard mapping is not used. - obj.pressedKeys = []; - obj._altGrArmed = false; // Windows AltGr detection - obj._altGrTimeout = 0; - obj.isWindowsBrowser = isWindowsBrowser(); - - obj.sessionid = 0; - obj.username; - obj.oldie = false; - obj.ImageType = 1; // 1 = JPEG, 2 = PNG, 3 = TIFF, 4 = WebP - obj.CompressionLevel = 50; - obj.ScalingLevel = 1024; - obj.FrameRateTimer = 100; - obj.SwapMouse = false; - obj.UseExtendedKeyFlag = true; - obj.FirstDraw = false; - - // Remote user mouse and keyboard lock - obj.onRemoteInputLockChanged = null; - obj.RemoteInputLock = null; - - // Remote keyboard state - obj.onKeyboardStateChanged = null; - obj.KeyboardState = 0; // 1 = NumLock, 2 = ScrollLock, 4 = CapsLock - - obj.ScreenWidth = 960; - obj.ScreenHeight = 701; - obj.width = 960; - obj.height = 960; - - obj.displays = null; - obj.selectedDisplay = null; - - obj.onScreenSizeChange = null; - obj.onMessage = null; - obj.onConnectCountChanged = null; - obj.onDebugMessage = null; - obj.onTouchEnabledChanged = null; - obj.onDisplayinfo = null; - obj.accumulator = null; - - var xMouseCursorActive = true; - var xMouseCursorCurrent = 'default'; - obj.mouseCursorActive = function (x) { if (xMouseCursorActive == x) return; xMouseCursorActive = x; obj.CanvasId.style.cursor = ((x == true) ? xMouseCursorCurrent : 'default'); } - var mouseCursors = ['default', 'progress', 'crosshair', 'pointer', 'help', 'text', 'no-drop', 'move', 'nesw-resize', 'ns-resize', 'nwse-resize', 'w-resize', 'alias', 'wait', 'none', 'not-allowed', 'col-resize', 'row-resize', 'copy', 'zoom-in', 'zoom-out']; - - obj.Start = function () { - obj.State = 0; - obj.accumulator = null; - } - - obj.Stop = function () { - obj.setRotation(0); - obj.UnGrabKeyInput(); - obj.UnGrabMouseInput(); - obj.touchenabled = 0; - if (obj.onScreenSizeChange != null) { obj.onScreenSizeChange(obj, obj.ScreenWidth, obj.ScreenHeight, obj.CanvasId); } - obj.Canvas.clearRect(0, 0, obj.CanvasId.width, obj.CanvasId.height); - } - - obj.xxStateChange = function (newstate) { - if (obj.State == newstate) return; - obj.State = newstate; - obj.CanvasId.style.cursor = 'default'; - //console.log('xxStateChange', newstate); - switch (newstate) { - case 0: { - // Disconnect - obj.Stop(); - break; - } - case 3: { - // Websocket connected - - break; - } - } - } - - obj.send = function (x) { - if (obj.debugmode > 2) { console.log('KSend(' + x.length + '): ' + rstr2hex(x)); } - if (obj.parent != null) { obj.parent.send(x); } - } - - // KVM Control. - // Routines for processing incoming packets from the AJAX server, and handling individual messages. - obj.ProcessPictureMsg = function (data, X, Y) { - //if (obj.targetnode != null) obj.Debug("ProcessPictureMsg " + X + "," + Y + " - " + obj.targetnode.substring(0, 8)); - var tile = new Image(); - tile.xcount = obj.tilesReceived++; - var r = obj.tilesReceived, tdata = data.slice(4), ptr = 0, strs = []; - // String.fromCharCode.apply() can't handle very large argument count, so we have to split like this. - while ((tdata.byteLength - ptr) > 50000) { strs.push(String.fromCharCode.apply(null, tdata.slice(ptr, ptr + 50000))); ptr += 50000; } - if (ptr > 0) { strs.push(String.fromCharCode.apply(null, tdata.slice(ptr))); } else { strs.push(String.fromCharCode.apply(null, tdata)); } - tile.src = 'data:image/jpeg;base64,' + btoa(strs.join('')); - tile.onload = function () { - //console.log('DecodeTile #' + this.xcount); - if ((obj.Canvas != null) && (obj.KillDraw < r) && (obj.State != 0)) { - obj.PendingOperations.push([r, 2, tile, X, Y]); - while (obj.DoPendingOperations()) { } - } else { - obj.PendingOperations.push([r, 0]); - } - } - tile.error = function () { console.log('DecodeTileError'); } - } - - obj.DoPendingOperations = function () { - if (obj.PendingOperations.length == 0) return false; - for (var i = 0; i < obj.PendingOperations.length; i++) { // && KillDraw < tilesDrawn - var Msg = obj.PendingOperations[i]; - if (Msg[0] == (obj.TilesDrawn + 1)) { - if (obj.onPreDrawImage != null) obj.onPreDrawImage(); // Notify that we are about to draw on the canvas. - if (Msg[1] == 1) { obj.ProcessCopyRectMsg(Msg[2]); } - else if (Msg[1] == 2) { obj.Canvas.drawImage(Msg[2], obj.rotX(Msg[3], Msg[4]), obj.rotY(Msg[3], Msg[4])); delete Msg[2]; } - obj.PendingOperations.splice(i, 1); - delete Msg; - obj.TilesDrawn++; - if ((obj.TilesDrawn == obj.tilesReceived) && (obj.KillDraw < obj.TilesDrawn)) { obj.KillDraw = obj.TilesDrawn = obj.tilesReceived = 0; } - return true; - } - } - if (obj.oldie && obj.PendingOperations.length > 0) { obj.TilesDrawn++; } - return false; - } - - obj.ProcessCopyRectMsg = function (str) { - var SX = ((str.charCodeAt(0) & 0xFF) << 8) + (str.charCodeAt(1) & 0xFF); - var SY = ((str.charCodeAt(2) & 0xFF) << 8) + (str.charCodeAt(3) & 0xFF); - var DX = ((str.charCodeAt(4) & 0xFF) << 8) + (str.charCodeAt(5) & 0xFF); - var DY = ((str.charCodeAt(6) & 0xFF) << 8) + (str.charCodeAt(7) & 0xFF); - var WIDTH = ((str.charCodeAt(8) & 0xFF) << 8) + (str.charCodeAt(9) & 0xFF); - var HEIGHT = ((str.charCodeAt(10) & 0xFF) << 8) + (str.charCodeAt(11) & 0xFF); - obj.Canvas.drawImage(Canvas.canvas, SX, SY, WIDTH, HEIGHT, DX, DY, WIDTH, HEIGHT); - } - - obj.SendUnPause = function () { - if (obj.debugmode > 1) { console.log('SendUnPause'); } - //obj.xxStateChange(3); - obj.send(String.fromCharCode(0x00, 0x08, 0x00, 0x05, 0x00)); - } - - obj.SendPause = function () { - if (obj.debugmode > 1) { console.log('SendPause'); } - //obj.xxStateChange(2); - obj.send(String.fromCharCode(0x00, 0x08, 0x00, 0x05, 0x01)); - } - - obj.SendCompressionLevel = function (type, level, scaling, frametimer) { // Type: 1 = JPEG, 2 = PNG, 3 = TIFF, 4 = WebP - obj.ImageType = type; - if (level) { obj.CompressionLevel = level; } - if (scaling) { obj.ScalingLevel = scaling; } - if (frametimer) { obj.FrameRateTimer = frametimer; } - obj.send(String.fromCharCode(0x00, 0x05, 0x00, 0x0A, type, obj.CompressionLevel) + obj.shortToStr(obj.ScalingLevel) + obj.shortToStr(obj.FrameRateTimer)); - } - - obj.SendRefresh = function () { - obj.send(String.fromCharCode(0x00, 0x06, 0x00, 0x04)); - } - - obj.ProcessScreenMsg = function (width, height) { - if (obj.debugmode > 0) { console.log('ScreenSize: ' + width + ' x ' + height); } - if ((obj.ScreenWidth == width) && (obj.ScreenHeight == height)) return; // Ignore change if screen is same size. - obj.Canvas.setTransform(1, 0, 0, 1, 0, 0); - obj.rotation = 0; - obj.FirstDraw = true; - obj.ScreenWidth = obj.width = width; - obj.ScreenHeight = obj.height = height; - obj.KillDraw = obj.tilesReceived; - while (obj.PendingOperations.length > 0) { obj.PendingOperations.shift(); } - obj.SendCompressionLevel(obj.ImageType); - obj.SendUnPause(); - obj.SendRemoteInputLock(2); // Query input lock state - // No need to event the display size change now, it will be evented on first draw. - if (obj.onScreenSizeChange != null) { obj.onScreenSizeChange(obj, obj.ScreenWidth, obj.ScreenHeight, obj.CanvasId); } - } - - obj.ProcessBinaryCommand = function (cmd, cmdsize, view) { - var X, Y; - if ((cmd == 3) || (cmd == 4) || (cmd == 7)) { X = (view[4] << 8) + view[5]; Y = (view[6] << 8) + view[7]; } - if (obj.debugmode > 2) { console.log('CMD', cmd, cmdsize, X, Y); } - - // Record the command if needed - if (obj.recordedData != null) { - if (cmdsize > 65000) { - obj.recordedData.push(recordingEntry(2, 1, obj.shortToStr(27) + obj.shortToStr(8) + obj.intToStr(cmdsize) + obj.shortToStr(cmd) + obj.shortToStr(0) + obj.shortToStr(0) + obj.shortToStr(0) + String.fromCharCode.apply(null, view))); - } else { - obj.recordedData.push(recordingEntry(2, 1, String.fromCharCode.apply(null, view))); - } - } - - switch (cmd) { - case 3: // Tile - if (obj.FirstDraw) obj.onResize(); - //console.log('TILE', X, Y, cmdsize); - obj.ProcessPictureMsg(view.slice(4), X, Y); - break; - case 7: // Screen size - obj.ProcessScreenMsg(X, Y); - obj.SendKeyMsgKC(obj.KeyAction.UP, 16); // Shift - obj.SendKeyMsgKC(obj.KeyAction.UP, 17); // Ctrl - 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 (TODO) - var selectedDisplay = 0, displays = {}, dcount = (view[4] << 8) + view[5]; - if (dcount > 0) { - // Many displays present - selectedDisplay = (view[6 + (dcount * 2)] << 8) + view[7 + (dcount * 2)]; - for (var i = 0; i < dcount; i++) { - var disp = (view[6 + (i * 2)] << 8) + view[7 + (i * 2)]; - if (disp == 65535) { displays[disp] = 'All Displays'; } else { displays[disp] = 'Display ' + disp; } - } - } - //console.log('Get Displays', displays, selectedDisplay, rstr2hex(str)); - obj.displays = displays; obj.selectedDisplay = selectedDisplay; - if (obj.onDisplayinfo != null) { obj.onDisplayinfo(obj, displays, selectedDisplay); } - break; - case 12: // SetDisplay - //console.log('SetDisplayConfirmed'); - break; - case 14: // KVM_INIT_TOUCH - obj.touchenabled = 1; - obj.TouchArray = {}; - if (obj.onTouchEnabledChanged != null) obj.onTouchEnabledChanged(obj.touchenabled); - break; - case 15: // KVM_TOUCH - obj.TouchArray = {}; - break; - case 17: // MNG_KVM_MESSAGE - var str = String.fromCharCode.apply(null, view.slice(4)); - console.log('Got KVM Message: ' + str); - if (obj.onMessage != null) obj.onMessage(str, obj); - break; - case 18: // MNG_KVM_KEYSTATE - if ((cmdsize != 5) || (obj.KeyboardState == view[4])) break; - obj.KeyboardState = view[4]; // 1 = NumLock, 2 = ScrollLock, 4 = CapsLock - if (obj.onKeyboardStateChanged) { obj.onKeyboardStateChanged(obj, obj.KeyboardState); } - console.log('MNG_KVM_KEYSTATE:' + ((obj.KeyboardState & 1) ? ' NumLock' : '') + ((obj.KeyboardState & 2) ? ' ScrollLock' : '') + ((obj.KeyboardState & 4) ? ' CapsLock' : '')); - break; - case 65: // Alert - var str = String.fromCharCode.apply(null, view.slice(4)); - if (str[0] != '.') { - console.log(str); //alert('KVM: ' + str); - if (obj.parent && obj.parent.setConsoleMessage) { obj.parent.setConsoleMessage(str); } - } else { - console.log('KVM: ' + str.substring(1)); - } - break; - case 82: // DISPLAY LOCATION & SIZE - if ((cmdsize < 4) || (((cmdsize - 4) % 10) != 0)) break; - var screenCount = ((cmdsize - 4) / 10), screenInfo = {}, ptr = 4; - for (var i = 0; i < screenCount; i++) { screenInfo[(view[ptr + 0] << 8) + view[ptr + 1]] = { x: ((view[ptr + 2] << 8) + view[ptr + 3]), y: ((view[ptr + 4] << 8) + view[ptr + 5]), w: ((view[ptr + 6] << 8) + view[ptr + 7]), h: ((view[ptr + 8] << 8) + view[ptr + 9]) }; ptr += 10; } - //console.log('ScreenInfo', JSON.stringify(screenInfo, null, 2)); - break; - case 87: // MNG_KVM_INPUT_LOCK - if (cmdsize != 5) break; - if ((obj.RemoteInputLock == null) || (obj.RemoteInputLock !== (view[4] != 0))) { - obj.RemoteInputLock = (view[4] != 0); - if (obj.onRemoteInputLockChanged) { obj.onRemoteInputLockChanged(obj, obj.RemoteInputLock); } - } - break; - case 88: // MNG_KVM_MOUSE_CURSOR - if ((cmdsize != 5) || (obj.stopInput)) break; - var cursorNum = view[4]; - if (cursorNum > mouseCursors.length) { cursorNum = 0; } - xMouseCursorCurrent = mouseCursors[cursorNum]; - if (xMouseCursorActive) { obj.CanvasId.style.cursor = xMouseCursorCurrent; } - break; - default: - console.log('Unknown command', cmd, cmdsize); - break; - } - - } - - // Keyboard and Mouse I/O. - obj.MouseButton = { "NONE": 0x00, "LEFT": 0x02, "RIGHT": 0x08, "MIDDLE": 0x20 }; - obj.KeyAction = { "NONE": 0, "DOWN": 1, "UP": 2, "SCROLL": 3, "EXUP": 4, "EXDOWN": 5, "DBLCLICK": 6 }; - obj.InputType = { "KEY": 1, "MOUSE": 2, "CTRLALTDEL": 10, "TOUCH": 15, "KEYUNICODE": 85 }; - obj.Alternate = 0; - - var convertKeyCodeTable = { - "Pause": 19, - "CapsLock": 20, - "Space": 32, - "Quote": 222, - "Minus": 189, - "NumpadMultiply": 106, - "NumpadAdd": 107, - "PrintScreen": 44, - "Comma": 188, - "NumpadSubtract": 109, - "NumpadDecimal": 110, - "Period": 190, - "Slash": 191, - "NumpadDivide": 111, - "Semicolon": 186, - "Equal": 187, - "OSLeft": 91, - "BracketLeft": 219, - "OSRight": 91, - "Backslash": 220, - "BracketRight": 221, - "ContextMenu": 93, - "Backquote": 192, - "NumLock": 144, - "ScrollLock": 145, - "Backspace": 8, - "Tab": 9, - "Enter": 13, - "NumpadEnter": 13, - "Escape": 27, - "Delete": 46, - "Home": 36, - "PageUp": 33, - "PageDown": 34, - "ArrowLeft": 37, - "ArrowUp": 38, - "ArrowRight": 39, - "ArrowDown": 40, - "End": 35, - "Insert": 45, - "F1": 112, - "F2": 113, - "F3": 114, - "F4": 115, - "F5": 116, - "F6": 117, - "F7": 118, - "F8": 119, - "F9": 120, - "F10": 121, - "F11": 122, - "F12": 123, - "ShiftLeft": 16, - "ShiftRight": 16, - "ControlLeft": 17, - "ControlRight": 17, - "AltLeft": 18, - "AltRight": 18, - "MetaLeft": 91, - "MetaRight": 92, - "VolumeMute": 181 - //"LaunchMail": - //"LaunchApp1": - //"LaunchApp2": - //"BrowserStop": - //"MediaStop": - //"MediaTrackPrevious": - //"MediaTrackNext": - //"MediaPlayPause": - //"MediaSelect": - } - - function convertKeyCode(e) { - if (e.code.startsWith('Key') && e.code.length == 4) { return e.code.charCodeAt(3); } - if (e.code.startsWith('Digit') && e.code.length == 6) { return e.code.charCodeAt(5); } - if (e.code.startsWith('Numpad') && e.code.length == 7) { return e.code.charCodeAt(6) + 48; } - return convertKeyCodeTable[e.code]; - } - - var extendedKeyTable = ['ShiftRight', 'AltRight', 'ControlRight', 'Home', 'End', 'Insert', 'Delete', 'PageUp', 'PageDown', 'NumpadDivide', 'NumpadEnter', 'NumLock', 'Pause']; - obj.SendKeyMsg = function (action, event) { - if (action == null) return; - if (!event) { event = window.event; } - - var extendedKey = false; // Test feature, add ?extkeys=1 to url to use. - - if ((obj.UseExtendedKeyFlag || (urlargs.extkeys == 1)) && (typeof event.code == 'string') && (event.code.startsWith('Arrow') || (extendedKeyTable.indexOf(event.code) >= 0))) { - extendedKey = true; - } - - if (obj.isWindowsBrowser) { - if( obj.checkAltGr(obj, event, action) ) { - return; - }; - } - - if ((extendedKey == false) && event.code && (event.code.startsWith('NumPad') == false) && (obj.localKeyMap == false)) { - // Convert "event.code" into a scancode. This works the same regardless of the keyboard language. - // Older browsers will not support this. - var kc = convertKeyCode(event); - if (kc != null) { obj.SendKeyMsgKC(action, kc, extendedKey); } - } else { - // Use this keycode, this works best with "US-EN" keyboards. - // Older browser support this. - var kc = event.keyCode; - if (kc == 0x3B) { kc = 0xBA; } // Fix the ';' key - else if (kc == 173) { kc = 189; } // Fix the '-' key for Firefox - else if (kc == 61) { kc = 187; } // Fix the '=' key for Firefox - obj.SendKeyMsgKC(action, kc, extendedKey); - } - } - - const ControlLeftKc = 17; - const AltGrKc = 225; - //return true: Key is alredy handled. - obj.checkAltGr = function (obj, event, action) { - // Windows doesn't have a proper AltGr, but handles it using - // fake Ctrl+Alt. However the remote end might not be Windows, - // so we need to merge those into a single AltGr event. We - // detect this case by seeing the two key events directly after - // each other with a very short time between them (<50ms). - if (obj._altGrArmed) { - obj._altGrArmed = false; - clearTimeout(obj._altGrTimeout); - - if ((event.code === "AltRight") && ((event.timeStamp - obj._altGrCtrlTime) < 50)) { - //AltGr detected. - obj.SendKeyMsgKC( action, AltGrKc, false); - return true; - } - } - - // Possible start of AltGr sequence? - if ((event.code === "ControlLeft") && !(ControlLeftKc in obj.pressedKeys)) { - obj._altGrArmed = true; - obj._altGrCtrlTime = event.timeStamp; - if( action == 1 ) { - obj._altGrTimeout = setTimeout(obj._handleAltGrTimeout.bind(obj), 100); - return true; - } - } - return false; - } - - obj._handleAltGrTimeout = function () { //Windows and no Ctrl+Alt -> send only Ctrl. - obj._altGrArmed = false; - clearTimeout(obj._altGrTimeout); - obj.SendKeyMsgKC( 1, ControlLeftKc, false); // (KeyDown, "ControlLeft", false) - } - - // Send remote input lock. 0 = Unlock, 1 = Lock, 2 = Query - obj.SendRemoteInputLock = function (code) { obj.send(String.fromCharCode(0x00, 87, 0x00, 0x05, code)); } - - obj.SendMessage = function (msg) { - if (obj.State == 3) obj.send(String.fromCharCode(0x00, 0x11) + obj.shortToStr(4 + msg.length) + msg); // 0x11 = 17 MNG_KVM_MESSAGE - } - - obj.SendKeyMsgKC = function (action, kc, extendedKey) { - if (obj.State != 3) return; - if (typeof action == 'object') { for (var i in action) { obj.SendKeyMsgKC(action[i][0], action[i][1], action[i][2]); } } - else { - if (action == 1) { // Key Down - if (obj.pressedKeys.indexOf(kc) == -1) { obj.pressedKeys.unshift(kc); } // Add key press to start of array - } else if (action == 2) { // Key Up - var i = obj.pressedKeys.indexOf(kc); - if (i != -1) { obj.pressedKeys.splice(i, 1); } // Remove the key press from the pressed array - } - if (obj.debugmode > 0) { console.log('Sending Key ' + kc + ', action ' + action); } - - var up = (action - 1); - if (extendedKey) { if (up == 1) { up = 3; } else { up = 4; } } - obj.send(String.fromCharCode(0x00, obj.InputType.KEY, 0x00, 0x06, up, kc)); - } - } - - obj.SendStringUnicode = function (str) { - if (obj.State != 3) return; - for (var i = 0; i < str.length; i++) { - obj.send(String.fromCharCode(0x00, obj.InputType.KEYUNICODE, 0x00, 0x07, 0) + ShortToStr(str.charCodeAt(i))); - obj.send(String.fromCharCode(0x00, obj.InputType.KEYUNICODE, 0x00, 0x07, 1) + ShortToStr(str.charCodeAt(i))); - } - } - - obj.SendKeyUnicode = function (action, val) { - if (obj.State != 3) return; - if (obj.debugmode > 0) { console.log('Sending UnicodeKey ' + val + ', action ' + action); } - obj.send(String.fromCharCode(0x00, obj.InputType.KEYUNICODE, 0x00, 0x07, (action - 1)) + ShortToStr(val)); - } - - obj.sendcad = function() { obj.SendCtrlAltDelMsg(); } - - obj.SendCtrlAltDelMsg = function () { - if (obj.State == 3) { obj.send(String.fromCharCode(0x00, obj.InputType.CTRLALTDEL, 0x00, 0x04)); } - } - - obj.SendEscKey = function () { - if (obj.State == 3) obj.send(String.fromCharCode(0x00, obj.InputType.KEY, 0x00, 0x06, 0x00, 0x1B, 0x00, obj.InputType.KEY, 0x00, 0x06, 0x01, 0x1B)); - } - - obj.SendStartMsg = function () { - obj.SendKeyMsgKC(obj.KeyAction.EXDOWN, 0x5B); // L-Windows - obj.SendKeyMsgKC(obj.KeyAction.EXUP, 0x5B); // L-Windows - } - - obj.SendCharmsMsg = function () { - obj.SendKeyMsgKC(obj.KeyAction.EXDOWN, 0x5B); // L-Windows - obj.SendKeyMsgKC(obj.KeyAction.DOWN, 67); // C - obj.SendKeyMsgKC(obj.KeyAction.UP, 67); // C - obj.SendKeyMsgKC(obj.KeyAction.EXUP, 0x5B); // L-Windows - } - - obj.SendTouchMsg1 = function (id, flags, x, y) { - if (obj.State == 3) obj.send(String.fromCharCode(0x00, obj.InputType.TOUCH) + obj.shortToStr(14) + String.fromCharCode(0x01, id) + obj.intToStr(flags) + obj.shortToStr(x) + obj.shortToStr(y)); - } - - obj.SendTouchMsg2 = function (id, flags) { - var msg = ''; - var flags2; - var str = "TOUCHSEND: "; - for (var k in obj.TouchArray) { - if (k == id) { flags2 = flags; } else { - if (obj.TouchArray[k].f == 1) { flags2 = 0x00010000 | 0x00000002 | 0x00000004; obj.TouchArray[k].f = 3; str += "START" + k; } // POINTER_FLAG_DOWN - else if (obj.TouchArray[k].f == 2) { flags2 = 0x00040000; str += "STOP" + k; } // POINTER_FLAG_UP - else flags2 = 0x00000002 | 0x00000004 | 0x00020000; // POINTER_FLAG_UPDATE - } - msg += String.fromCharCode(k) + obj.intToStr(flags2) + obj.shortToStr(obj.TouchArray[k].x) + obj.shortToStr(obj.TouchArray[k].y); - if (obj.TouchArray[k].f == 2) delete obj.TouchArray[k]; - } - if (obj.State == 3) obj.send(String.fromCharCode(0x00, obj.InputType.TOUCH) + obj.shortToStr(5 + msg.length) + String.fromCharCode(0x02) + msg); - if (Object.keys(obj.TouchArray).length == 0 && obj.touchtimer != null) { clearInterval(obj.touchtimer); obj.touchtimer = null; } - } - - obj.SendMouseMsg = function (Action, event) { - if (obj.State != 3) return; - if (Action != null && obj.Canvas != null) { - if (!event) { var event = window.event; } - - var ScaleFactorHeight = (obj.Canvas.canvas.height / obj.CanvasId.clientHeight); - var ScaleFactorWidth = (obj.Canvas.canvas.width / obj.CanvasId.clientWidth); - var Offsets = obj.GetPositionOfControl(obj.Canvas.canvas); - var X = ((event.pageX - Offsets[0]) * ScaleFactorWidth); - var Y = ((event.pageY - Offsets[1]) * ScaleFactorHeight); - if (event.addx) { X += event.addx; } - if (event.addy) { Y += event.addy; } - - if (X >= 0 && X <= obj.Canvas.canvas.width && Y >= 0 && Y <= obj.Canvas.canvas.height) { - var Button = 0; - var Delta = 0; - if (Action == obj.KeyAction.UP || Action == obj.KeyAction.DOWN) { - if (event.which) { ((event.which == 1) ? (Button = obj.MouseButton.LEFT) : ((event.which == 2) ? (Button = obj.MouseButton.MIDDLE) : (Button = obj.MouseButton.RIGHT))); } - else if (typeof event.button == 'number') { ((event.button == 0) ? (Button = obj.MouseButton.LEFT) : ((event.button == 1) ? (Button = obj.MouseButton.MIDDLE) : (Button = obj.MouseButton.RIGHT))); } - } - else if (Action == obj.KeyAction.SCROLL) { - if (event.detail) { Delta = (-1 * (event.detail * 120)); } else if (event.wheelDelta) { Delta = (event.wheelDelta * 3); } - } - - // Swap mouse buttons if needed - if (obj.SwapMouse === true) { - if (Button == obj.MouseButton.LEFT) { Button = obj.MouseButton.RIGHT; } - else if (Button == obj.MouseButton.RIGHT) { Button = obj.MouseButton.LEFT; } - } - - // Reverse mouse wheel if needed - if (obj.ReverseMouseWheel) { Delta = -1 * Delta; } - - var MouseMsg = ""; - if (Action == obj.KeyAction.DBLCLICK) { - MouseMsg = String.fromCharCode(0x00, obj.InputType.MOUSE, 0x00, 0x0A, 0x00, 0x88, ((X / 256) & 0xFF), (X & 0xFF), ((Y / 256) & 0xFF), (Y & 0xFF)); - } else if (Action == obj.KeyAction.SCROLL) { - var deltaHigh = 0, deltaLow = 0; - if (Delta < 0) { deltaHigh = (255 - (Math.abs(Delta) >> 8)); deltaLow = (255 - (Math.abs(Delta) & 0xFF)); } else { deltaHigh = (Delta >> 8); deltaLow = (Delta & 0xFF); } - MouseMsg = String.fromCharCode(0x00, obj.InputType.MOUSE, 0x00, 0x0C, 0x00, 0x00, ((X / 256) & 0xFF), (X & 0xFF), ((Y / 256) & 0xFF), (Y & 0xFF), deltaHigh, deltaLow); - } else { - MouseMsg = String.fromCharCode(0x00, obj.InputType.MOUSE, 0x00, 0x0A, 0x00, ((Action == obj.KeyAction.DOWN) ? Button : ((Button * 2) & 0xFF)), ((X / 256) & 0xFF), (X & 0xFF), ((Y / 256) & 0xFF), (Y & 0xFF)); - } - - if (obj.Action == obj.KeyAction.NONE) { - if (obj.Alternate == 0 || obj.ipad) { obj.send(MouseMsg); obj.Alternate = 1; } else { obj.Alternate = 0; } - } else { - obj.send(MouseMsg); - } - } - } - } - - obj.GetDisplayNumbers = function () { obj.send(String.fromCharCode(0x00, 0x0B, 0x00, 0x04)); } // Get Terminal display - obj.SetDisplay = function (number) { /*console.log('Set display', 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); } - - obj.onResize = function () { - if (obj.ScreenWidth == 0 || obj.ScreenHeight == 0) return; - if ((obj.Canvas.canvas.width == obj.ScreenWidth) && (obj.Canvas.canvas.height == obj.ScreenHeight)) return; - if (obj.FirstDraw) { - obj.Canvas.canvas.width = obj.ScreenWidth; - obj.Canvas.canvas.height = obj.ScreenHeight; - obj.Canvas.fillRect(0, 0, obj.ScreenWidth, obj.ScreenHeight); - if (obj.onScreenSizeChange != null) { obj.onScreenSizeChange(obj, obj.ScreenWidth, obj.ScreenHeight, obj.CanvasId); } - } - obj.FirstDraw = false; - if (obj.debugmode > 1) { console.log("onResize: " + obj.ScreenWidth + " x " + obj.ScreenHeight); } - } - - obj.xxMouseInputGrab = false; - obj.xxKeyInputGrab = false; - obj.xxMouseMove = function (e) { if (obj.State == 3) obj.SendMouseMsg(obj.KeyAction.NONE, e); if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return false; } - obj.xxMouseUp = function (e) { if (obj.State == 3) obj.SendMouseMsg(obj.KeyAction.UP, e); if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return false; } - obj.xxMouseDown = function (e) { if (obj.State == 3) obj.SendMouseMsg(obj.KeyAction.DOWN, e); if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return false; } - obj.xxMouseDblClick = function (e) { if (obj.State == 3) obj.SendMouseMsg(obj.KeyAction.DBLCLICK, e); if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return false; } - obj.xxDOMMouseScroll = function (e) { if (obj.State == 3) { obj.SendMouseMsg(obj.KeyAction.SCROLL, e); return false; } return true; } - obj.xxMouseWheel = function (e) { if (obj.State == 3) { obj.SendMouseMsg(obj.KeyAction.SCROLL, e); return false; } return true; } - obj.xxKeyUp = function (e) { - if ((e.key != 'Dead') && (obj.State == 3)) { - if ((typeof e.key == 'string') && (e.key.length == 1) && (e.ctrlKey != true) && (e.altKey != true) && (obj.remoteKeyMap == false)) { - obj.SendKeyUnicode(obj.KeyAction.UP, e.key.charCodeAt(0)); - } else { - obj.SendKeyMsg(obj.KeyAction.UP, e); - } - } - if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return false; - } - obj.xxKeyDown = function (e) { - if ((e.key != 'Dead') && (obj.State == 3)) { - if (!((typeof e.key == 'string') && (e.key.length == 1) && (e.ctrlKey != true) && (e.altKey != true) && (obj.remoteKeyMap == false))) { - obj.SendKeyMsg(obj.KeyAction.DOWN, e); - if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return false; - } - } - } - obj.xxKeyPress = function (e) { - if ((e.key != 'Dead') && (obj.State == 3)) { - if ((typeof e.key == 'string') && (e.key.length == 1) && (e.ctrlKey != true) && (e.altKey != true) && (obj.remoteKeyMap == false)) { - obj.SendKeyUnicode(obj.KeyAction.DOWN, e.key.charCodeAt(0)); - } // else { obj.SendKeyMsg(obj.KeyAction.DOWN, e); } - } - if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return false; - } - - // Key handlers - obj.handleKeys = function (e) { - //console.log('keypress', e.code, e.key, e.keyCode, (e.key.length == 1) ? e.key.charCodeAt(0) : 0); - if (obj.stopInput == true || desktop.State != 3) return false; - return obj.xxKeyPress(e); - } - obj.handleKeyUp = function (e) { - //console.log('keyup', e.code, e.key, e.keyCode, (e.key.length == 1)?e.key.charCodeAt(0):0); - 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) { - //console.log('keydown', e.code, e.key, e.keyCode, (e.key.length == 1) ? e.key.charCodeAt(0) : 0); - if (obj.stopInput == true || desktop.State != 3) return false; - return obj.xxKeyDown(e); - } - - // Release the CTRL, ALT, SHIFT keys if they are pressed. - obj.handleReleaseKeys = function () { - var p = JSON.parse(JSON.stringify(obj.pressedKeys)); // Clone the pressed array - for (var i in p) { obj.SendKeyMsgKC(obj.KeyAction.UP, p[i]); } // Release all keys - } - - // Mouse handlers - obj.mousedblclick = function (e) { if (obj.stopInput == true) return false; return obj.xxMouseDblClick(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. - if (evt.preventDefault) evt.preventDefault(); - if (evt.stopPropagation) evt.stopPropagation(); - if (evt.type == 'MSPointerDown' || evt.type == 'MSPointerMove' || evt.type == 'MSPointerUp') { - var flags = 0; - var id = evt.originalEvent.pointerId % 256; - var X = evt.offsetX * (Canvas.canvas.width / obj.CanvasId.clientWidth); - var Y = evt.offsetY * (Canvas.canvas.height / obj.CanvasId.clientHeight); - - if (evt.type == 'MSPointerDown') flags = 0x00010000 | 0x00000002 | 0x00000004; // POINTER_FLAG_DOWN - else if (evt.type == 'MSPointerMove') { - //if (obj.TouchArray[id] && MuchTheSame(obj.TouchArray[id].x, X) && MuchTheSame(obj.TouchArray[id].y, Y)) return; - flags = 0x00020000 | 0x00000002 | 0x00000004; // POINTER_FLAG_UPDATE - } - else if (evt.type == 'MSPointerUp') flags = 0x00040000; // POINTER_FLAG_UP - - if (!obj.TouchArray[id]) obj.TouchArray[id] = { x: X, y : Y }; - obj.SendTouchMsg2(id, flags) - if (evt.type == 'MSPointerUp') delete obj.TouchArray[id]; - } else { - alert(evt.type); - } - return true; - } - - obj.xxTouchStart = function (e) { - if (obj.State != 3) return; - if (e.preventDefault) e.preventDefault(); - if (obj.touchenabled == 0 || obj.touchenabled == 1) { - if (e.originalEvent.touches.length > 1) return; - var t = e.originalEvent.touches[0]; - e.which = 1; - obj.LastX = e.pageX = t.pageX; - obj.LastY = e.pageY = t.pageY; - obj.SendMouseMsg(KeyAction.DOWN, e); - } else { - var Offsets = obj.GetPositionOfControl(Canvas.canvas); - for (var i in e.originalEvent.changedTouches) { - if (!e.originalEvent.changedTouches[i].identifier) continue; - var id = e.originalEvent.changedTouches[i].identifier % 256; - if (!obj.TouchArray[id]) { obj.TouchArray[id] = { x: (e.originalEvent.touches[i].pageX - Offsets[0]) * (Canvas.canvas.width / obj.CanvasId.clientWidth), y: (e.originalEvent.touches[i].pageY - Offsets[1]) * (Canvas.canvas.height / obj.CanvasId.clientHeight), f: 1 }; } - } - if (Object.keys(obj.TouchArray).length > 0 && touchtimer == null) { obj.touchtimer = setInterval(function () { obj.SendTouchMsg2(256, 0); }, 50); } - } - } - - obj.xxTouchMove = function (e) { - if (obj.State != 3) return; - if (e.preventDefault) e.preventDefault(); - if (obj.touchenabled == 0 || obj.touchenabled == 1) { - if (e.originalEvent.touches.length > 1) return; - var t = e.originalEvent.touches[0]; - e.which = 1; - obj.LastX = e.pageX = t.pageX; - obj.LastY = e.pageY = t.pageY; - obj.SendMouseMsg(obj.KeyAction.NONE, e); - } else { - var Offsets = obj.GetPositionOfControl(Canvas.canvas); - for (var i in e.originalEvent.changedTouches) { - if (!e.originalEvent.changedTouches[i].identifier) continue; - var id = e.originalEvent.changedTouches[i].identifier % 256; - if (obj.TouchArray[id]) { - obj.TouchArray[id].x = (e.originalEvent.touches[i].pageX - Offsets[0]) * (obj.Canvas.canvas.width / obj.CanvasId.clientWidth); - obj.TouchArray[id].y = (e.originalEvent.touches[i].pageY - Offsets[1]) * (obj.Canvas.canvas.height / obj.CanvasId.clientHeight); - } - } - } - } - - obj.xxTouchEnd = function (e) { - if (obj.State != 3) return; - if (e.preventDefault) e.preventDefault(); - if (obj.touchenabled == 0 || obj.touchenabled == 1) { - if (e.originalEvent.touches.length > 1) return; - e.which = 1; - e.pageX = LastX; - e.pageY = LastY; - obj.SendMouseMsg(KeyAction.UP, e); - } else { - for (var i in e.originalEvent.changedTouches) { - if (!e.originalEvent.changedTouches[i].identifier) continue; - var id = e.originalEvent.changedTouches[i].identifier % 256; - if (obj.TouchArray[id]) obj.TouchArray[id].f = 2; - } - } - } - - obj.GrabMouseInput = function () { - if (obj.xxMouseInputGrab == true) return; - var c = obj.CanvasId; - c.onmousemove = obj.xxMouseMove; - c.onmouseup = obj.xxMouseUp; - c.onmousedown = obj.xxMouseDown; - c.touchstart = obj.xxTouchStart; - c.touchmove = obj.xxTouchMove; - c.touchend = obj.xxTouchEnd; - c.MSPointerDown = obj.xxMsTouchEvent; - c.MSPointerMove = obj.xxMsTouchEvent; - c.MSPointerUp = obj.xxMsTouchEvent; - if (navigator.userAgent.match(/mozilla/i)) c.DOMMouseScroll = obj.xxDOMMouseScroll; else c.onmousewheel = obj.xxMouseWheel; - obj.xxMouseInputGrab = true; - } - - obj.UnGrabMouseInput = function () { - if (obj.xxMouseInputGrab == false) return; - var c = obj.CanvasId; - c.onmousemove = null; - c.onmouseup = null; - c.onmousedown = null; - c.touchstart = null; - c.touchmove = null; - c.touchend = null; - c.MSPointerDown = null; - c.MSPointerMove = null; - c.MSPointerUp = null; - if (navigator.userAgent.match(/mozilla/i)) c.DOMMouseScroll = null; else c.onmousewheel = null; - obj.xxMouseInputGrab = false; - } - - obj.GrabKeyInput = function () { - if (obj.xxKeyInputGrab == true) return; - document.onkeyup = obj.xxKeyUp; - document.onkeydown = obj.xxKeyDown; - document.onkeypress = obj.xxKeyPress;c - obj.xxKeyInputGrab = true; - } - - obj.UnGrabKeyInput = function () { - if (obj.xxKeyInputGrab == false) return; - document.onkeyup = null; - document.onkeydown = null; - document.onkeypress = null; - obj.xxKeyInputGrab = false; - } - - obj.GetPositionOfControl = function (Control) { - var Position = Array(2); - Position[0] = Position[1] = 0; - while (Control) { Position[0] += Control.offsetLeft; Position[1] += Control.offsetTop; Control = Control.offsetParent; } - return Position; - } - - obj.crotX = function (x, y) { - if (obj.rotation == 0) return x; - if (obj.rotation == 1) return y; - if (obj.rotation == 2) return obj.Canvas.canvas.width - x; - if (obj.rotation == 3) return obj.Canvas.canvas.height - y; - } - - obj.crotY = function (x, y) { - if (obj.rotation == 0) return y; - if (obj.rotation == 1) return obj.Canvas.canvas.width - x; - if (obj.rotation == 2) return obj.Canvas.canvas.height - y; - if (obj.rotation == 3) return x; - } - - obj.rotX = function (x, y) { - if (obj.rotation == 0 || obj.rotation == 1) return x; - if (obj.rotation == 2) return x - obj.Canvas.canvas.width; - if (obj.rotation == 3) return x - obj.Canvas.canvas.height; - } - - obj.rotY = function (x, y) { - if (obj.rotation == 0 || obj.rotation == 3) return y; - if (obj.rotation == 1) return y - obj.Canvas.canvas.width; - if (obj.rotation == 2) return y - obj.Canvas.canvas.height; - } - - obj.tcanvas = null; - obj.setRotation = function (x) { - while (x < 0) { x += 4; } - var newrotation = x % 4; - if (newrotation == obj.rotation) return true; - var rw = obj.Canvas.canvas.width; - var rh = obj.Canvas.canvas.height; - if (obj.rotation == 1 || obj.rotation == 3) { rw = obj.Canvas.canvas.height; rh = obj.Canvas.canvas.width; } - - // Copy the canvas, put it back in the correct direction - if (obj.tcanvas == null) obj.tcanvas = document.createElement('canvas'); - var tcanvasctx = obj.tcanvas.getContext('2d'); - tcanvasctx.setTransform(1, 0, 0, 1, 0, 0); - tcanvasctx.canvas.width = rw; - tcanvasctx.canvas.height = rh; - tcanvasctx.rotate((obj.rotation * -90) * Math.PI / 180); - if (obj.rotation == 0) tcanvasctx.drawImage(obj.Canvas.canvas, 0, 0); - if (obj.rotation == 1) tcanvasctx.drawImage(obj.Canvas.canvas, -obj.Canvas.canvas.width, 0); - if (obj.rotation == 2) tcanvasctx.drawImage(obj.Canvas.canvas, -obj.Canvas.canvas.width, -obj.Canvas.canvas.height); - if (obj.rotation == 3) tcanvasctx.drawImage(obj.Canvas.canvas, 0, -obj.Canvas.canvas.height); - - // Change the size and orientation and copy the canvas back into the rotation - if (obj.rotation == 0 || obj.rotation == 2) { obj.Canvas.canvas.height = rw; obj.Canvas.canvas.width = rh; } - if (obj.rotation == 1 || obj.rotation == 3) { obj.Canvas.canvas.height = rh; obj.Canvas.canvas.width = rw; } - obj.Canvas.setTransform(1, 0, 0, 1, 0, 0); - obj.Canvas.rotate((newrotation * 90) * Math.PI / 180); - obj.rotation = newrotation; - obj.Canvas.drawImage(obj.tcanvas, obj.rotX(0, 0), obj.rotY(0, 0)); - - obj.ScreenWidth = obj.Canvas.canvas.width; - obj.ScreenHeight = obj.Canvas.canvas.height; - if (obj.onScreenSizeChange != null) { console.log('s4', obj.ScreenWidth, obj.ScreenHeight); obj.onScreenSizeChange(obj, obj.ScreenWidth, obj.ScreenHeight, obj.CanvasId); } - return true; - } - - obj.StartRecording = function () { - if (obj.recordedData != null) return; - // Take a screen shot and save it to file - obj.CanvasId['toBlob'](function (blob) { - var fileReader = new FileReader(); - fileReader.readAsArrayBuffer(blob); - fileReader.onload = function (event) { - // This is an ArrayBuffer, convert it to a string array - var binary = '', bytes = new Uint8Array(fileReader.result), length = bytes.byteLength; - for (var i = 0; i < length; i++) { binary += String.fromCharCode(bytes[i]); } - obj.recordedData = []; - obj.recordedStart = Date.now(); - obj.recordedSize = 0; - obj.recordedData.push(recordingEntry(1, 0, JSON.stringify({ magic: 'MeshCentralRelaySession', ver: 1, time: new Date().toLocaleString(), protocol: 2 }))); // Metadata (nodeid: obj.nodeid) - obj.recordedData.push(recordingEntry(2, 1, obj.shortToStr(7) + obj.shortToStr(8) + obj.shortToStr(obj.ScreenWidth) + obj.shortToStr(obj.ScreenHeight))); // Screen width and height - // Save a screenshot - var cmdlen = (8 + binary.length); - if (cmdlen > 65000) { - // Jumbo Packet - obj.recordedData.push(recordingEntry(2, 1, obj.shortToStr(27) + obj.shortToStr(8) + obj.intToStr(cmdlen) + obj.shortToStr(3) + obj.shortToStr(0) + obj.shortToStr(0) + obj.shortToStr(0) + binary)); - } else { - // Normal packet - obj.recordedData.push(recordingEntry(2, 1, obj.shortToStr(3) + obj.shortToStr(cmdlen) + obj.shortToStr(0) + obj.shortToStr(0) + binary)); - } - }; - }); - } - - obj.StopRecording = function () { - if (obj.recordedData == null) return; - var r = obj.recordedData; - r.push(recordingEntry(3, 0, 'MeshCentralMCREC')); - delete obj.recordedData; - delete obj.recordedStart; - delete obj.recordedSize; - return r; - } - - function recordingEntry(type, flags, data) { - // Header: Type (2) + Flags (2) + Size(4) + Time(8) - // Type (1 = Header, 2 = Network Data), Flags (1 = Binary, 2 = User), Size (4 bytes), Time (8 bytes) - var now = Date.now(); - if (typeof data == 'number') { - obj.recordedSize += data; - return obj.shortToStr(type) + obj.shortToStr(flags) + obj.intToStr(data) + obj.intToStr(now >> 32) + obj.intToStr(now & 32); - } else { - obj.recordedSize += data.length; - return obj.shortToStr(type) + obj.shortToStr(flags) + obj.intToStr(data.length) + obj.intToStr(now >> 32) + obj.intToStr(now & 32) + data; - } - } - - // Private method - obj.MuchTheSame = function (a, b) { return (Math.abs(a - b) < 4); } - obj.Debug = function (msg) { console.log(msg); } - obj.getIEVersion = function () { var r = -1; if (navigator.appName == 'Microsoft Internet Explorer') { var ua = navigator.userAgent; var re = new RegExp("MSIE ([0-9]{1,}[.0-9]{0,})"); if (re.exec(ua) != null) r = parseFloat(RegExp.$1); } return r; } - obj.haltEvent = function (e) { if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return false; } - - return obj; -} \ No newline at end of file +function isWindowsBrowser(){return navigator&&!!/win/i.exec(navigator.platform)}Uint8Array.prototype.slice||Object.defineProperty(Uint8Array.prototype,"slice",{value:function(e,t){return new Uint8Array(Array.prototype.slice.call(this,e,t))}});var CreateAgentRemoteDesktop=function(e,t){var p={},S=("string"==typeof(p.CanvasId=e)&&(p.CanvasId=Q(e)),p.Canvas=p.CanvasId.getContext("2d"),p.scrolldiv=t,p.State=0,p.PendingOperations=[],p.tilesReceived=0,p.TilesDrawn=0,p.KillDraw=0,p.ipad=!1,p.tabletKeyboardVisible=!1,p.LastX=0,p.LastY=0,p.touchenabled=0,p.submenuoffset=0,p.touchtimer=null,p.TouchArray={},p.connectmode=0,p.connectioncount=0,p.rotation=0,p.protocol=2,p.debugmode=0,p.firstUpKeys=[],p.stopInput=!1,p.localKeyMap=!0,p.remoteKeyMap=!1,p.pressedKeys=[],p._altGrArmed=!1,p._altGrTimeout=0,p.isWindowsBrowser=isWindowsBrowser(),p.sessionid=0,p.oldie=!1,p.ImageType=1,p.CompressionLevel=50,p.ScalingLevel=1024,p.FrameRateTimer=100,p.SwapMouse=!1,p.UseExtendedKeyFlag=!0,p.FirstDraw=!1,p.onRemoteInputLockChanged=null,p.RemoteInputLock=null,p.onKeyboardStateChanged=null,p.KeyboardState=0,p.ScreenWidth=960,p.ScreenHeight=701,p.width=960,p.height=960,p.displays=null,p.selectedDisplay=null,p.onScreenSizeChange=null,p.onMessage=null,p.onConnectCountChanged=null,p.onDebugMessage=null,p.onTouchEnabledChanged=null,p.onDisplayinfo=null,!(p.accumulator=null)),v="default",C=(p.mouseCursorActive=function(e){S!=e&&(S=e,p.CanvasId.style.cursor=1==e?v:"default")},["default","progress","crosshair","pointer","help","text","no-drop","move","nesw-resize","ns-resize","nwse-resize","w-resize","alias","wait","none","not-allowed","col-resize","row-resize","copy","zoom-in","zoom-out"]),a=(p.Start=function(){p.State=0,p.accumulator=null},p.Stop=function(){p.setRotation(0),p.UnGrabKeyInput(),p.UnGrabMouseInput(),p.touchenabled=0,null!=p.onScreenSizeChange&&p.onScreenSizeChange(p,p.ScreenWidth,p.ScreenHeight,p.CanvasId),p.Canvas.clearRect(0,0,p.CanvasId.width,p.CanvasId.height)},p.xxStateChange=function(e){p.State!=e&&(p.State=e,p.CanvasId.style.cursor="default",0===e)&&p.Stop()},p.send=function(e){2
=this._cols?this._cols-1:e<0?0:e}nextStop(e){for(null==e&&(e=this.x);!this.tabs[++e]&&eh&&(n-=h,o++),2===i[o].getWidth(n-1)),h=(h&&n--,h?e-1:e);t.push(h),a+=h}return t},t.getWrappedLineTrimmedLength=u},5295:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.BufferSet=void 0;let r=i(9092),s=i(8460),n=i(844);class o extends n.Disposable{constructor(e,t){super(),this._optionsService=e,this._bufferService=t,this._onBufferActivate=this.register(new s.EventEmitter),this.reset()}get onBufferActivate(){return this._onBufferActivate.event}reset(){this._normal=new r.Buffer(!0,this._optionsService,this._bufferService),this._normal.fillViewportRows(),this._alt=new r.Buffer(!1,this._optionsService,this._bufferService),this._activeBuffer=this._normal,this._onBufferActivate.fire({activeBuffer:this._normal,inactiveBuffer:this._alt}),this.setupTabStops()}get alt(){return this._alt}get active(){return this._activeBuffer}get normal(){return this._normal}activateNormalBuffer(){this._activeBuffer!==this._normal&&(this._normal.x=this._alt.x,this._normal.y=this._alt.y,this._alt.clearAllMarkers(),this._alt.clear(),this._activeBuffer=this._normal,this._onBufferActivate.fire({activeBuffer:this._normal,inactiveBuffer:this._alt}))}activateAltBuffer(e){this._activeBuffer!==this._alt&&(this._alt.fillViewportRows(e),this._alt.x=this._normal.x,this._alt.y=this._normal.y,this._activeBuffer=this._alt,this._onBufferActivate.fire({activeBuffer:this._alt,inactiveBuffer:this._normal}))}resize(e,t){this._normal.resize(e,t),this._alt.resize(e,t)}setupTabStops(e){this._normal.setupTabStops(e),this._alt.setupTabStops(e)}}t.BufferSet=o},511:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.CellData=void 0;let r=i(482),s=i(643),n=i(3734);class o extends n.AttributeData{constructor(){super(...arguments),this.content=0,this.fg=0,this.bg=0,this.extended=new n.ExtendedAttrs,this.combinedData=""}static fromCharData(e){var t=new o;return t.setFromCharData(e),t}isCombined(){return 2097152&this.content}getWidth(){return this.content>>22}getChars(){return 2097152&this.content?this.combinedData:2097151&this.content?(0,r.stringFromCodePoint)(2097151&this.content):""}getCode(){return this.isCombined()?this.combinedData.charCodeAt(this.combinedData.length-1):2097151&this.content}setFromCharData(e){this.fg=e[s.CHAR_DATA_ATTR_INDEX],this.bg=0;let t=!1;var i,r;2=s)return this._interim=e,n;var a=i.charCodeAt(t);56320<=a&&a<=57343?r[n++]=1024*(e-55296)+a-56320+65536:(r[n++]=e,r[n++]=a)}else 65279!==e&&(r[n++]=e)}return n}},t.Utf8ToUtf32=class{constructor(){this.interim=new Uint8Array(3)}clear(){this.interim.fill(0)}decode(o,a){var h=o.length;if(!h)return 0;let e,t,i,r,l=0,s=0,c=0;if(this.interim[0]){let e=!1,t=this.interim[0];t&=192==(224&t)?31:224==(240&t)?15:7;let i,r=0;for(;(i=63&this.interim[++r])&&r<4;)t=(t<<=6)|i;let s=192==(224&this.interim[0])?2:224==(240&this.interim[0])?3:4,n=s-r;for(;c{Object.defineProperty(t,"__esModule",{value:!0}),t.UnicodeV6=void 0;let r=i(8273),s=[[768,879],[1155,1158],[1160,1161],[1425,1469],[1471,1471],[1473,1474],[1476,1477],[1479,1479],[1536,1539],[1552,1557],[1611,1630],[1648,1648],[1750,1764],[1767,1768],[1770,1773],[1807,1807],[1809,1809],[1840,1866],[1958,1968],[2027,2035],[2305,2306],[2364,2364],[2369,2376],[2381,2381],[2385,2388],[2402,2403],[2433,2433],[2492,2492],[2497,2500],[2509,2509],[2530,2531],[2561,2562],[2620,2620],[2625,2626],[2631,2632],[2635,2637],[2672,2673],[2689,2690],[2748,2748],[2753,2757],[2759,2760],[2765,2765],[2786,2787],[2817,2817],[2876,2876],[2879,2879],[2881,2883],[2893,2893],[2902,2902],[2946,2946],[3008,3008],[3021,3021],[3134,3136],[3142,3144],[3146,3149],[3157,3158],[3260,3260],[3263,3263],[3270,3270],[3276,3277],[3298,3299],[3393,3395],[3405,3405],[3530,3530],[3538,3540],[3542,3542],[3633,3633],[3636,3642],[3655,3662],[3761,3761],[3764,3769],[3771,3772],[3784,3789],[3864,3865],[3893,3893],[3895,3895],[3897,3897],[3953,3966],[3968,3972],[3974,3975],[3984,3991],[3993,4028],[4038,4038],[4141,4144],[4146,4146],[4150,4151],[4153,4153],[4184,4185],[4448,4607],[4959,4959],[5906,5908],[5938,5940],[5970,5971],[6002,6003],[6068,6069],[6071,6077],[6086,6086],[6089,6099],[6109,6109],[6155,6157],[6313,6313],[6432,6434],[6439,6440],[6450,6450],[6457,6459],[6679,6680],[6912,6915],[6964,6964],[6966,6970],[6972,6972],[6978,6978],[7019,7027],[7616,7626],[7678,7679],[8203,8207],[8234,8238],[8288,8291],[8298,8303],[8400,8431],[12330,12335],[12441,12442],[43014,43014],[43019,43019],[43045,43046],[64286,64286],[65024,65039],[65056,65059],[65279,65279],[65529,65531]],n=[[68097,68099],[68101,68102],[68108,68111],[68152,68154],[68159,68159],[119143,119145],[119155,119170],[119173,119179],[119210,119213],[119362,119364],[917505,917505],[917536,917631],[917760,917999]],o;t.UnicodeV6=class{constructor(){if(this.version="6",!o){o=new Uint8Array(65536),(0,r.fill)(o,1),(o[0]=0,r.fill)(o,0,1,32),(0,r.fill)(o,0,127,160),(0,r.fill)(o,2,4352,4448),o[9001]=2,o[9002]=2,(0,r.fill)(o,2,11904,42192),o[12351]=1,(0,r.fill)(o,2,44032,55204),(0,r.fill)(o,2,63744,64256),(0,r.fill)(o,2,65040,65050),(0,r.fill)(o,2,65072,65136),(0,r.fill)(o,2,65280,65377),(0,r.fill)(o,2,65504,65511);for(let e=0;e{var t;a.xmin=null!=(t=e.options.x)?t:0,a.xmax=a.xmin+(null!=(t=e.options.width)?t:1),i>=a.xmin&&i=s)return r+this.wcwidth(e);var n=i.charCodeAt(t);56320<=n&&n<=57343?e=1024*(e-55296)+n-56320+65536:r+=this.wcwidth(n)}r+=this.wcwidth(e)}return r}}}},r={};function a(e){var t=r[e];return void 0!==t||(t=r[e]={exports:{}},i[e].call(t.exports,t,t.exports,a)),t.exports}var h={};{var l=h;Object.defineProperty(l,"__esModule",{value:!0}),l.Terminal=void 0;let t=a(3236),e=a(9042),i=a(7975),r=a(7090),s=a(5741),n=a(8285),o=["cols","rows"];l.Terminal=class{constructor(e){this._core=new t.Terminal(e),this._addonManager=new s.AddonManager,this._publicOptions=Object.assign({},this._core.options);var i=e=>this._core.options[e],r=(e,t)=>{this._checkReadonlyOptions(e),this._core.options[e]=t};for(let t in this._core.options){let e={get:i.bind(this,t),set:r.bind(this,t)};Object.defineProperty(this._publicOptions,t,e)}}_checkReadonlyOptions(e){if(o.includes(e))throw new Error(`Option "${e}" can only be set in the constructor`)}_checkProposedApi(){if(!this._core.optionsService.rawOptions.allowProposedApi)throw new Error("You must set the allowProposedApi option to true to use proposed API")}get onBell(){return this._core.onBell}get onBinary(){return this._core.onBinary}get onCursorMove(){return this._core.onCursorMove}get onData(){return this._core.onData}get onKey(){return this._core.onKey}get onLineFeed(){return this._core.onLineFeed}get onRender(){return this._core.onRender}get onResize(){return this._core.onResize}get onScroll(){return this._core.onScroll}get onSelectionChange(){return this._core.onSelectionChange}get onTitleChange(){return this._core.onTitleChange}get onWriteParsed(){return this._core.onWriteParsed}get element(){return this._core.element}get parser(){return this._checkProposedApi(),this._parser||(this._parser=new i.ParserApi(this._core)),this._parser}get unicode(){return this._checkProposedApi(),new r.UnicodeApi(this._core)}get textarea(){return this._core.textarea}get rows(){return this._core.rows}get cols(){return this._core.cols}get buffer(){return this._checkProposedApi(),this._buffer||(this._buffer=new n.BufferNamespaceApi(this._core)),this._buffer}get markers(){return this._checkProposedApi(),this._core.markers}get modes(){var e=this._core.coreService.decPrivateModes;let t="none";switch(this._core.coreMouseService.activeProtocol){case"X10":t="x10";break;case"VT200":t="vt200";break;case"DRAG":t="drag";break;case"ANY":t="any"}return{applicationCursorKeysMode:e.applicationCursorKeys,applicationKeypadMode:e.applicationKeypad,bracketedPasteMode:e.bracketedPasteMode,insertMode:this._core.coreService.modes.insertMode,mouseTrackingMode:t,originMode:e.origin,reverseWraparoundMode:e.reverseWraparound,sendFocusMode:e.sendFocus,wraparoundMode:e.wraparound}}get options(){return this._publicOptions}set options(e){for(var t in e)this._publicOptions[t]=e[t]}blur(){this._core.blur()}focus(){this._core.focus()}resize(e,t){this._verifyIntegers(e,t),this._core.resize(e,t)}open(e){this._core.open(e)}attachCustomKeyEventHandler(e){this._core.attachCustomKeyEventHandler(e)}registerLinkProvider(e){return this._checkProposedApi(),this._core.registerLinkProvider(e)}registerCharacterJoiner(e){return this._checkProposedApi(),this._core.registerCharacterJoiner(e)}deregisterCharacterJoiner(e){this._checkProposedApi(),this._core.deregisterCharacterJoiner(e)}registerMarker(e=0){return this._verifyIntegers(e),this._core.addMarker(e)}registerDecoration(e){var t;return this._checkProposedApi(),this._verifyPositiveIntegers(null!=(t=e.x)?t:0,null!=(t=e.width)?t:0,null!=(t=e.height)?t:0),this._core.registerDecoration(e)}hasSelection(){return this._core.hasSelection()}select(e,t,i){this._verifyIntegers(e,t,i),this._core.select(e,t,i)}getSelection(){return this._core.getSelection()}getSelectionPosition(){return this._core.getSelectionPosition()}clearSelection(){this._core.clearSelection()}selectAll(){this._core.selectAll()}selectLines(e,t){this._verifyIntegers(e,t),this._core.selectLines(e,t)}dispose(){this._addonManager.dispose(),this._core.dispose()}scrollLines(e){this._verifyIntegers(e),this._core.scrollLines(e)}scrollPages(e){this._verifyIntegers(e),this._core.scrollPages(e)}scrollToTop(){this._core.scrollToTop()}scrollToBottom(){this._core.scrollToBottom()}scrollToLine(e){this._verifyIntegers(e),this._core.scrollToLine(e)}clear(){this._core.clear()}write(e,t){this._core.write(e,t)}writeln(e,t){this._core.write(e),this._core.write("\r\n",t)}paste(e){this._core.paste(e)}refresh(e,t){this._verifyIntegers(e,t),this._core.refresh(e,t)}reset(){this._core.reset()}clearTextureAtlas(){this._core.clearTextureAtlas()}loadAddon(e){return this._addonManager.loadAddon(this,e)}static get strings(){return e}_verifyIntegers(...e){for(var t of e)if(t===1/0||isNaN(t)||t%1!=0)throw new Error("This API only accepts integers")}_verifyPositiveIntegers(...e){for(var t of e)if(t&&(t===1/0||isNaN(t)||t%1!=0||t<0))throw new Error("This API only accepts positive integers")}}}return h})
\ No newline at end of file
+((e,t)=>{if("object"==typeof exports&&"object"==typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var i,r=t();for(i in r)("object"==typeof exports?exports:e)[i]=r[i]}})(self,function(){var i={4567:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.AccessibilityManager=void 0;let r=i(9042),s=i(6114),n=i(9924),o=i(3656),a=i(844),h=i(5596),l=i(9631);class c extends a.Disposable{constructor(e,t){super(),this._terminal=e,this._renderService=t,this._liveRegionLineCount=0,this._charsToConsume=[],this._charsToAnnounce="",this._accessibilityTreeRoot=document.createElement("div"),this._accessibilityTreeRoot.classList.add("xterm-accessibility"),this._accessibilityTreeRoot.tabIndex=0,this._rowContainer=document.createElement("div"),this._rowContainer.setAttribute("role","list"),this._rowContainer.classList.add("xterm-accessibility-tree"),this._rowElements=[];for(let e=0;ee.replace(g," ")).join(a.isWindows?"\r\n":"\n")}clearSelection(){this._model.clearSelection(),this._removeMouseDownListeners(),this.refresh(),this._onSelectionChange.fire()}refresh(e){this._refreshAnimationFrame||(this._refreshAnimationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>this._refresh())),a.isLinux&&e&&this.selectionText.length&&this._onLinuxMouseSelection.fire(this.selectionText)}_refresh(){this._refreshAnimationFrame=void 0,this._onRedrawRequest.fire({start:this._model.finalSelectionStart,end:this._model.finalSelectionEnd,columnSelectMode:3===this._activeSelectionMode})}_isClickInSelection(e){var e=this._getMouseBufferCoords(e),t=this._model.finalSelectionStart,i=this._model.finalSelectionEnd;return!!(t&&i&&e)&&this._areCoordsInSelection(e,t,i)}isCellInSelection(e,t){var i=this._model.finalSelectionStart,r=this._model.finalSelectionEnd;return!(!i||!r)&&this._areCoordsInSelection([e,t],i,r)}_areCoordsInSelection(e,t,i){return e[1]>t[1]&&e[1]=t[0]&&e[0]=t[0]}_selectWordAtCursor(e,t){var i=null==(i=null==(i=this._linkifier.currentLink)?void 0:i.link)?void 0:i.range;return i?(this._model.selectionStart=[i.start.x-1,i.start.y-1],this._model.selectionStartLength=(0,f.getRangeLength)(i,this._bufferService.cols),!(this._model.selectionEnd=void 0)):!!(i=this._getMouseBufferCoords(e))&&(this._selectWordAt(i,t),!(this._model.selectionEnd=void 0))}selectAll(){this._model.isSelectAllActive=!0,this.refresh(),this._onSelectionChange.fire()}selectLines(e,t){this._model.clearSelection(),e=Math.max(e,0),t=Math.min(t,this._bufferService.buffer.lines.length-1),this._model.selectionStart=[0,e],this._model.selectionEnd=[this._bufferService.cols,t],this.refresh(),this._onSelectionChange.fire()}_onTrim(e){this._model.onTrim(e)&&this.refresh()}_getMouseBufferCoords(e){e=this._mouseService.getCoords(e,this._screenElement,this._bufferService.cols,this._bufferService.rows,!0);if(e)return e[0]--,e[1]--,e[1]+=this._bufferService.buffer.ydisp,e}_getMouseEventScrollAmount(e){let t=(0,h.getCoordsRelativeToElement)(this._coreBrowserService.window,e,this._screenElement)[1];e=this._renderService.dimensions.canvasHeight;return 0<=t&&t<=e?0:(t>e&&(t-=e),t=Math.min(Math.max(t,-50),50),(t/=50)/Math.abs(t)+Math.round(14*t))}shouldForceSelection(e){return a.isMac?e.altKey&&this._optionsService.rawOptions.macOptionClickForcesSelection:e.shiftKey}onMouseDown(e){if(this._mouseDownTimeStamp=e.timeStamp,(2!==e.button||!this.hasSelection)&&0===e.button){if(!this._enabled){if(!this.shouldForceSelection(e))return;e.stopPropagation()}e.preventDefault(),this._dragScrollAmount=0,this._enabled&&e.shiftKey?this._onIncrementalClick(e):1===e.detail?this._onSingleClick(e):2===e.detail?this._onDoubleClick(e):3===e.detail&&this._onTripleClick(e),this._addMouseDownListeners(),this.refresh(!0)}}_addMouseDownListeners(){this._screenElement.ownerDocument&&(this._screenElement.ownerDocument.addEventListener("mousemove",this._mouseMoveListener),this._screenElement.ownerDocument.addEventListener("mouseup",this._mouseUpListener)),this._dragScrollIntervalTimer=this._coreBrowserService.window.setInterval(()=>this._dragScroll(),50)}_removeMouseDownListeners(){this._screenElement.ownerDocument&&(this._screenElement.ownerDocument.removeEventListener("mousemove",this._mouseMoveListener),this._screenElement.ownerDocument.removeEventListener("mouseup",this._mouseUpListener)),this._coreBrowserService.window.clearInterval(this._dragScrollIntervalTimer),this._dragScrollIntervalTimer=void 0}_onIncrementalClick(e){this._model.selectionStart&&(this._model.selectionEnd=this._getMouseBufferCoords(e))}_onSingleClick(e){this._model.selectionStartLength=0,this._model.isSelectAllActive=!1,this._activeSelectionMode=this.shouldColumnSelect(e)?3:0,this._model.selectionStart=this._getMouseBufferCoords(e),this._model.selectionStart&&(this._model.selectionEnd=void 0,e=this._bufferService.buffer.lines.get(this._model.selectionStart[1]))&&e.length!==this._model.selectionStart[0]&&0===e.hasWidth(this._model.selectionStart[0])&&this._model.selectionStart[0]++}_onDoubleClick(e){this._selectWordAtCursor(e,!0)&&(this._activeSelectionMode=1)}_onTripleClick(e){e=this._getMouseBufferCoords(e);e&&(this._activeSelectionMode=2,this._selectLineAt(e[1]))}shouldColumnSelect(e){return e.altKey&&!(a.isMac&&this._optionsService.rawOptions.macOptionClickForcesSelection)}_onMouseMove(e){if(e.stopImmediatePropagation(),this._model.selectionStart){var t=this._model.selectionEnd?[this._model.selectionEnd[0],this._model.selectionEnd[1]]:null;if(this._model.selectionEnd=this._getMouseBufferCoords(e),this._model.selectionEnd){2===this._activeSelectionMode?this._model.selectionEnd[1]=this._bufferService.cols)){var d=this._bufferService.buffer,u=d.lines.get(c[1]);if(u){var f=d.translateBufferLineToString(c[1],!1);let r=this._convertViewportColToCharacterIndex(u,c),s=r;var v=c[0]-r;let n=0,o=0,a=0,h=0;if(" "===f.charAt(r)){for(;0