diff --git a/agents/MeshCmd.exe b/agents/MeshCmd.exe index 00b704aa..57c590aa 100644 Binary files a/agents/MeshCmd.exe and b/agents/MeshCmd.exe differ diff --git a/agents/MeshCmd64.exe b/agents/MeshCmd64.exe index e4af314f..fde90d0a 100644 Binary files a/agents/MeshCmd64.exe and b/agents/MeshCmd64.exe differ diff --git a/agents/MeshCmdARM64.exe b/agents/MeshCmdARM64.exe index 83f1079a..6f4f5e6c 100644 Binary files a/agents/MeshCmdARM64.exe and b/agents/MeshCmdARM64.exe differ diff --git a/agents/MeshService.exe b/agents/MeshService.exe index e3aec7ab..b6b4a387 100644 Binary files a/agents/MeshService.exe and b/agents/MeshService.exe differ diff --git a/agents/MeshService64.exe b/agents/MeshService64.exe index 540fed64..bb5ed095 100644 Binary files a/agents/MeshService64.exe and b/agents/MeshService64.exe differ diff --git a/agents/MeshServiceARM64.exe b/agents/MeshServiceARM64.exe index ee191f79..c53d7610 100644 Binary files a/agents/MeshServiceARM64.exe and b/agents/MeshServiceARM64.exe differ diff --git a/agents/meshcore.js b/agents/meshcore.js index 5a7faaf1..e561bb6a 100644 --- a/agents/meshcore.js +++ b/agents/meshcore.js @@ -295,9 +295,8 @@ 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); - 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) { } } + 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) { } } } catch (ex) { } // Check to see if we are the Installed Mesh Agent Service, if we are, make sure we can run in Safe Mode @@ -311,16 +310,6 @@ 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') { @@ -666,39 +655,33 @@ 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(null, false); }); + 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.emit('changed'); - 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); } }); + //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'); }); } catch (ex) { } var meshServerConnectionState = 0; @@ -1175,7 +1158,6 @@ 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; @@ -1590,7 +1572,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) { @@ -2315,59 +2297,6 @@ 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 @@ -2680,101 +2609,6 @@ 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) { @@ -2854,67 +2688,6 @@ 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); } @@ -3028,12 +2801,6 @@ 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)) { @@ -3050,31 +2817,76 @@ function onTunnelData(data) this.end = terminal_end; // Perform User-Consent if needed. - 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 { - terminal_consent_ask(this); + 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); } } - } else { + 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()); + }); + } + 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) @@ -3098,7 +2910,6 @@ 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'){ @@ -3170,33 +2981,119 @@ 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 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); + 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); } } - } else { + 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 + { // User Consent Prompt is not required - kvm_consent_ok(this); + 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); + } } this.removeAllListeners('data'); @@ -3218,12 +3115,6 @@ 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); @@ -3246,31 +3137,71 @@ 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 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 { - // User Consent Prompt is required - files_consent_ask(this); + // 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); } } - } else { + 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 - files_consent_ok(this); + 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) { } + } else { + MeshServerLogEx(43, null, "Started remote files without notification (" + this.httprequest.remoteaddr + ")", this.httprequest); + } + this.resume(); } // Setup files @@ -3758,14 +3689,7 @@ function onTunnelControlData(data, ws) { { // Desktop // Switch the user input from websocket to webrtc at this point. ws.unpipe(ws.httprequest.desktop.kvm); - 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!!! - } + try { ws.webrtc.rtcchannel.pipe(ws.httprequest.desktop.kvm, { dataTypeSkip: 1, end: false }); } catch (ex) { sendConsoleText('EX2'); } // 0 = Binary, 1 = Text. 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. @@ -4366,7 +4290,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 [id]\r\n agentmsg list"; // Display usage + response = "Proper usage:\r\n agentmsg add \"[message]\" [iconIndex]\r\n agentmsg remove [index]\r\n agentmsg list"; // Display usage } else { if ((args['_'][0] == 'add') && (args['_'].length > 1)) { var msgID, iconIndex = 0; @@ -4584,11 +4508,10 @@ 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; - 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; } + try { writtenSize = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MeshCentralAgent', '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\\' + serviceName, 'EstimatedSize', actualSize); } catch (ex) { response = ex; } + try { require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MeshCentralAgent', 'EstimatedSize', actualSize); } catch (ex) { response = ex; } } else { response = "Agent Size: " + actualSize + " kb"; } } else @@ -5668,7 +5591,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 new file mode 100644 index 00000000..00b704aa Binary files /dev/null and b/agents/test_agents/MeshCmd.exe differ diff --git a/agents/test_agents/MeshCmd64.exe b/agents/test_agents/MeshCmd64.exe new file mode 100644 index 00000000..e4af314f Binary files /dev/null and b/agents/test_agents/MeshCmd64.exe differ diff --git a/agents/test_agents/MeshCmdARM64.exe b/agents/test_agents/MeshCmdARM64.exe new file mode 100644 index 00000000..83f1079a Binary files /dev/null and b/agents/test_agents/MeshCmdARM64.exe differ diff --git a/agents/test_agents/MeshService.exe b/agents/test_agents/MeshService.exe new file mode 100644 index 00000000..cedb45eb Binary files /dev/null and b/agents/test_agents/MeshService.exe differ diff --git a/agents/test_agents/MeshService64.exe b/agents/test_agents/MeshService64.exe new file mode 100644 index 00000000..997da544 Binary files /dev/null and b/agents/test_agents/MeshService64.exe differ diff --git a/agents/test_agents/MeshServiceARM64.exe b/agents/test_agents/MeshServiceARM64.exe new file mode 100644 index 00000000..ee191f79 Binary files /dev/null and b/agents/test_agents/MeshServiceARM64.exe differ diff --git a/apprelays.js b/apprelays.js index cd6bc5fa..5a9172ac 100644 --- a/apprelays.js +++ b/apprelays.js @@ -719,8 +719,7 @@ 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 } - // 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 '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 f1fdf105..ab491028 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/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 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 all links, SSH and RDP usernames -// This is required for databases like NeDB that don't accept "." or "," as part of a field name. +// This is required for databases like NeDB that don't accept "." 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 81ea222c..d96ed501 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(path.join(parent.datapath, databaseName + '.sqlite'), sqlite3.OPEN_READWRITE, function (err) { + obj.file = new sqlite3.Database(parent.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(path.join(parent.datapath, databaseName + '.sqlite'), function (err) { + obj.file = new sqlite3.Database(parent.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.", true); + parent.addServerWarning("Current version of MongoDB (" + info.version + ") is too old, please upgrade to MongoDB 3.6 or better."); } } }); @@ -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 }); - fs.unlink(parent.getConfigFilePath('meshcentral-smbios.db'), function () { }); + parent.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,6 +3187,7 @@ 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; } @@ -3196,7 +3197,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 = parent.config.settings.autobackup.backupname + fileSuffix; + obj.newAutoBackupFile = ((typeof parent.config.settings.autobackup.backupname == 'string') ? parent.config.settings.autobackup.backupname : 'meshcentral-autobackup-') + fileSuffix; r += 'DB Name: ' + dbname + '\r\n'; r += 'DB Type: ' + DB_LIST[obj.databaseType] + '\r\n'; @@ -3206,14 +3207,15 @@ 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): ' + parent.config.settings.autobackup.backupintervalhours + '\r\n'; + 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'; } } if (parent.config.settings.autobackup.keeplastdaysbackup != null) { - r += 'Keep Last Backups (Days): ' + parent.config.settings.autobackup.keeplastdaysbackup + '\r\n'; + 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'; } } if (parent.config.settings.autobackup.zippassword != null) { r += 'ZIP Password: '; @@ -3328,70 +3330,48 @@ 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)) { 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; + if ((parent.config.settings.autobackup == null) || (parent.config.settings.autobackup == false)) { func(); return; }; 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) { - 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;} + 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); } }); } 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, 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;} - + 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); } }); } else if (obj.databaseType == DB_POSTGRESQL) { // Check that we have access to pg_dump @@ -3402,14 +3382,17 @@ 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) { - 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;} + 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); } }); } else { - //all ok, enable backup - parent.config.settings.autobackup.backupintervalhours = backupInterval;} + func(); + } } // MongoDB pending bulk read operation, perform fast bulk document reads. @@ -3523,18 +3506,19 @@ module.exports.CreateDB = function (parent, func) { // Perform a server backup obj.performBackup = function (func) { - parent.debug('backup','Entering performBackup'); + parent.debug('db','Entering performBackup'); try { if (obj.performingBackup) return 'Backup alreay in progress.'; - if (parent.config.settings.autobackup.backupintervalhours == -1) { if (func) { func('Backup disabled.'); return 'Backup disabled.' }}; + 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.' }}; 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, parent.config.settings.autobackup.backupname + fileSuffix + '.zip'); - parent.debug('backup','newAutoBackupFile=' + obj.newAutoBackupFile); + obj.newAutoBackupFile = path.join(backupPath, ((typeof parent.config.settings.autobackup.backupname == 'string') ? parent.config.settings.autobackup.backupname : 'meshcentral-autobackup-') + fileSuffix + '.zip'); if ((obj.databaseType == DB_MONGOJS) || (obj.databaseType == DB_MONGODB)) { // Perform a MongoDump @@ -3546,14 +3530,13 @@ 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.error('ERROR: Unable to perform MongoDB backup: ' + error + '\r\n'); obj.createBackupfile(func);}} + (error)=> {if (error) {obj.backupStatus |= BACKUPFAIL_DBDUMP; console.log('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); @@ -3566,16 +3549,15 @@ 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.error('ERROR: Unable to perform MySQL backup: ' + error + '\r\n'); obj.createBackupfile(func);}} + (error)=> {if (error) {obj.backupStatus |= BACKUPFAIL_DBDUMP; console.log('ERROR: Unable to perform MySQL backup: ' + error + '\r\n'); obj.createBackupfile(func);}} ); dumpProcess.on('exit', (code) => { - if (code != 0) {console.error(`MySQLdump child process exited with code ${code}`); obj.backupStatus |= BACKUPFAIL_DBDUMP;} + if (code != 0) {console.log(`MySQLdump child process exited with code ${code}`); obj.backupStatus |= BACKUPFAIL_DBDUMP;} obj.createBackupfile(func); }); @@ -3583,9 +3565,8 @@ 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.error('SQLite backup error: ' + err); obj.backupStatus |=BACKUPFAIL_DBDUMP;}; + if (err) { console.log('SQLite start-backup error: ' + err); obj.backupStatus |=BACKUPFAIL_DBDUMP;}; //always finish/clean up obj.createBackupfile(func); }); @@ -3597,7 +3578,6 @@ 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, @@ -3609,15 +3589,15 @@ module.exports.CreateDB = function (parent, func) { obj.createBackupfile(func); }); } else { - // NeDB/Acebase backup, no db dump needed, just make a file backup + //NeDB backup, no db dump needed, just make a file backup obj.createBackupfile(func); } - } catch (ex) { console.error(ex); parent.addServerWarning( 'Something went wrong during performBackup, check errorlog: ' +ex.message, true); }; + } catch (ex) { console.log(ex); }; return 'Starting auto-backup...'; }; obj.createBackupfile = function(func) { - parent.debug('backup', 'Entering createBackupfile'); + parent.debug('db', 'Entering createFileBackup'); 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); @@ -3631,8 +3611,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.error('Zipencryptionmodule failed, aborting'); + if (func) { func('Zipencryptionmodule failed, aborting'); } + console.log('Zipencryptionmodule failed, aborting'); } } else { if (func) { func('Creating a NON-ENCRYPTED ZIP'); } @@ -3642,36 +3622,51 @@ 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 = fs.createWriteStream(obj.newAutoBackupFile); - - // Archive finalized and closed + let output = parent.fs.createWriteStream(obj.newAutoBackupFile); output.on('close', function () { if (obj.backupStatus == 0) { - 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); - obj.removeExpiredBackupfiles(func); - - } else { - 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) } }) }; + if (obj.databaseType != DB_NEDB) { + try { parent.fs.unlink(obj.newDBDumpFile, function () { }); } catch (ex) {console.log('Failed to clean up dbdump file')}; + }; + 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.'); }; + } 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')}; }; obj.performingBackup = false; obj.backupStatus = 0x0; - } - ); + }); output.on('end', function () { }); output.on('error', function (err) { if ((obj.backupStatus & BACKUPFAIL_ZIPCREATE) == 0) { - console.error('Output error: ' + err.message); - if (func) { func('Output error: ' + err.message); }; + console.log('Output error: ' + err); + if (func) { func('Output error: ' + err); }; obj.backupStatus |= BACKUPFAIL_ZIPCREATE; archive.abort(); }; @@ -3681,16 +3676,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.message); - if (func) { func('Zip warning: ' + err.message); }; + console.log('Zip warning: ' + err); + if (func) { func('Zip warning: ' + err); }; obj.backupStatus |= BACKUPFAIL_ZIPCREATE; archive.abort(); }; }); archive.on('error', function (err) { if ((obj.backupStatus & BACKUPFAIL_ZIPCREATE) == 0) { - console.error('Zip error: ' + err.message); - if (func) { func('Zip error: ' + err.message); }; + console.log('Zip error: ' + err); + if (func) { func('Zip error: ' + err); }; obj.backupStatus |= BACKUPFAIL_ZIPCREATE; archive.abort(); } @@ -3723,67 +3718,22 @@ module.exports.CreateDB = function (parent, func) { archive.finalize(); } else { //failed somewhere before zipping - 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); - } + console.log('Backup failed ('+ (+obj.backupStatus).toString(16).slice(-4) + ')'); + if (func) { func('Backup failed ('+ (+obj.backupStatus).toString(16).slice(-4) + ')') }; //Just in case something's there - if (fs.existsSync(obj.newDBDumpFile)) { fs.unlink(obj.newDBDumpFile, function (err) { if (err) {console.error('Failed to clean up dbdump file: ' + err.message) } }); }; + try { parent.fs.unlink(obj.newDBDumpFile, function () { }); } catch (ex) { }; 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')) { - 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; } @@ -3791,28 +3741,23 @@ 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 = parent.config.settings.autobackup.backupname; + let fileName = (typeof parent.config.settings.autobackup.backupname == 'string') ? parent.config.settings.autobackup.backupname : 'meshcentral-autobackup-'; //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) { - 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); } + client.deleteFile(files.shift().filename).then(function (state) { + if (func) { func('WebDAV file deleted.'); } }).catch(function (err) { - console.error(err); - if (func) { func('WebDAV (deleteFile) error: ' + err.message); } + if (func) { func('WebDAV (deleteFile) error: ' + err); } }); } } ).catch(function (err) { - console.error(err); - if (func) { func('WebDAV (getDirectoryContents) error: ' + err.message); } + if (func) { func('WebDAV (getDirectoryContents) error: ' + err); } }); } } @@ -3821,14 +3766,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 () { 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.on('close', function () { if (func) { func('WebDAV upload completed'); } }) + fileStream.on('error', function (err) { if (func) { func('WebDAV (fileUpload) error: ' + err); } }) fileStream.pipe(client.createWriteStream('/' + webdavfolderName + '/' + require('path').basename(filepath), { headers: { "Content-Length": stat.size } })); - 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('Uploading using WebDAV...'); } }); } + 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, @@ -3842,23 +3787,19 @@ module.exports.CreateDB = function (parent, func) { performWebDavUpload(client, filename); }else{ client.createDirectory(webdavfolderName, {recursive: true}).then(function (a) { - console.log('backup','WebDAV folder created: ' + webdavfolderName); - if (func) { func('WebDAV folder created: ' + webdavfolderName); } + if (func) { func('WebDAV folder created'); } performWebDavUpload(client, filename); }).catch(function (err) { - console.error(err); - if (func) { func('WebDAV (createDirectory) error: ' + err.message); } + if (func) { func('WebDAV (createDirectory) error: ' + err); } }); } }).catch(function (err) { - console.error(err); - if (func) { func('WebDAV (exists) error: ' + err.message); } + if (func) { func('WebDAV (exists) error: ' + err); } }); } // 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...'); } @@ -3937,7 +3878,6 @@ 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 26806e1d..c646a08c 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="true" -ENV MINIFY="false" +ENV LOCALSESSIONRECORDING="false" +ENV MINIFY="true" 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 -RUN cd meshcentral && npm install +# install dependencies from package.json and nedb +RUN cd meshcentral && npm install && npm install nedb # 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 cef4ad33..a6fe3201 100644 --- a/docker/config.json.template +++ b/docker/config.json.template @@ -21,9 +21,9 @@ "": { "_title": "MyServer", "_title2": "Servername", - "minify": false, + "minify": true, "NewAccounts": true, - "localSessionRecording": true, + "localSessionRecording": false, "_userNameIsEmail": true, "_certUrl": "my.reverse.proxy" } diff --git a/docker/startup.sh b/docker/startup.sh index da3f0b34..c198d847 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\": false/\"minify\": $MINIFY/" meshcentral-data/"${CONFIG_FILE}" + sed -i "s/\"minify\": true/\"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 4b85b0c5..6055ab5b 100644 --- a/docs/docs/meshcentral/plugins.md +++ b/docs/docs/meshcentral/plugins.md @@ -123,10 +123,6 @@ 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 43966b88..4e10d8d4 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 Recordings Processor"); + log("MeshCentral Session Recodings 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 7169b9ff..9c1fcb98 100644 --- a/meshagent.js +++ b/meshagent.js @@ -1936,9 +1936,8 @@ 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 91cec3b8..fcff4f68 100644 --- a/meshcentral-config-schema.json +++ b/meshcentral-config-schema.json @@ -886,11 +886,6 @@ "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, @@ -1173,11 +1168,6 @@ "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", @@ -1972,11 +1962,6 @@ "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, @@ -2180,11 +2165,6 @@ "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.", @@ -2915,10 +2895,12 @@ }, "user": { "type": "string", + "format": "string", "description": "SMTP username." }, "pass": { "type": "string", + "format": "string", "description": "SMTP password." }, "tls": { @@ -3271,6 +3253,7 @@ ] } ], + "additionalProperties": false, "properties": { "newAccounts": { "type": "boolean", @@ -3478,7 +3461,8 @@ "required": [ "client_id", "client_secret" - ] + ], + "additionalProperties": false }, "issuer": { "type": [ @@ -3568,7 +3552,8 @@ } } } - } + }, + "additionalProperties": false }, "custom": { "type": "object", @@ -3619,7 +3604,8 @@ "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", @@ -3671,7 +3657,8 @@ "default": "groups", "description": "Custom claim to use." } - } + }, + "additionalProperties": false } } } @@ -3736,7 +3723,8 @@ "description": "EAB HMAC KEY", "default": "" } - } + }, + "additionalProperties": false } }, "required": [ @@ -3831,10 +3819,12 @@ }, "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 a792dda5..f3dc14ea 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -583,11 +583,8 @@ 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. @@ -659,12 +656,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); } - logFromChildProcess(datastr); + console.log(datastr); }); childProcess.stderr.on('data', function (data) { var datastr = data; while (datastr.endsWith('\r') || datastr.endsWith('\n')) { datastr = datastr.substring(0, datastr.length - 1); } - logFromChildProcess('ERR: ' + datastr); + console.log('ERR: ' + datastr); if (data.startsWith('le.challenges[tls-sni-01].loopback')) { return; } // Ignore this error output from GreenLock if (data[data.length - 1] == '\n') { data = data.substring(0, data.length - 1); } obj.logError(data); @@ -1351,7 +1348,7 @@ function CreateMeshCentralServer(config, args) { } // Check if the database is capable of performing a backup - // Moved behind autobackup config init in startex4: obj.db.checkBackupCapability(function (err, msg) { if (msg != null) { obj.addServerWarning(msg, true) } }); + obj.db.checkBackupCapability(function (err, msg) { if (msg != null) { obj.addServerWarning(msg, true) } }); // Load configuration for database if needed if (obj.args.loadconfigfromdb) { @@ -1659,7 +1656,7 @@ function CreateMeshCentralServer(config, args) { } // Setup agent error log - if ((obj.config) && (obj.config.settings) && (obj.config.settings.agentlogdump)) { + if ((obj.config) && (obj.config.settings) && (obj.config.settings.agentlogdump != null)) { obj.fs.open(obj.path.join(obj.datapath, 'agenterrorlogs.txt'), 'a', function (err, fd) { obj.agentErrorLog = fd; }) } @@ -2019,7 +2016,6 @@ 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' }); @@ -2109,19 +2105,18 @@ 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 if the database is capable of performing a backup - obj.db.checkBackupCapability(function (err, msg) { if (msg != null) { obj.addServerWarning(msg, true) } }); + // 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 + } // Load Intel AMT passwords from the "amtactivation.log" file obj.loadAmtActivationLogPasswords(function (amtPasswords) { @@ -2283,19 +2278,14 @@ 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) { console.error("checkAutobackup: Error getting LastBackupTime from DB"); return} + if (err != null) return; var lastBackup = 0; - const currentdate = new Date(); - let currentHour = currentdate.getHours(); - let now = currentdate.getTime(); + const now = new Date().getTime(); if (docs.length == 1) { lastBackup = docs[0].value; } const delta = now - lastBackup; - //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))) { + if (delta > (obj.config.settings.autobackup.backupintervalhours * 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 @@ -3946,7 +3936,6 @@ 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 @@ -4117,7 +4106,6 @@ 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); } } @@ -4224,7 +4212,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.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 (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 ((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 af4f1626..e17e3552 100644 --- a/meshctrl.js +++ b/meshctrl.js @@ -2243,7 +2243,6 @@ 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); } @@ -2666,8 +2665,8 @@ function getDevicesThatMatchFilter(nodes, x) { } else if (tagSearch != null) { // Tag filter for (var d in nodes) { - 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; } } } + 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; } } } } } else if (agentTagSearch != null) { // Agent Tag filter diff --git a/meshdesktopmultiplex.js b/meshdesktopmultiplex.js index 9b6d1e19..3a14540a 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 recording to file: ' + recFullFilename); + parent.parent.debug('relay', 'Relay: Started recoding 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,7 +1347,6 @@ 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 7f91904d..051640f4 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 recording to file: ' + recFullFilename); + parent.parent.debug('relay', 'Relay: Started recoding to file: ' + recFullFilename); var metadata = { magic: 'MeshCentralRelaySession', ver: 1, userid: sessionUser._id, username: sessionUser.name, sessionid: obj.id, - ipaddr1: ((obj.peer == null) || (obj.peer.req == null)) ? null : obj.peer.req.clientIp, - ipaddr2: (obj.req == null) ? null : obj.req.clientIp, + ipaddr1: (obj.req == null) ? null : obj.req.clientIp, + ipaddr2: ((obj.peer == null) || (obj.peer.req == null)) ? null : obj.peer.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,7 +896,6 @@ 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') { @@ -935,7 +934,6 @@ 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') { @@ -954,7 +952,6 @@ 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') { @@ -1007,7 +1004,6 @@ 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 ac237dfd..44e8f0a5 100644 --- a/meshuser.js +++ b/meshuser.js @@ -600,13 +600,7 @@ 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.logoutonidlesessiontimeout == 'boolean') { - serverinfo.logoutonidlesessiontimeout = domain.logoutonidlesessiontimeout; - } else { - // Default - serverinfo.logoutonidlesessiontimeout = true; - } + if ((typeof domain.usersessionidletimeout == 'number') && (domain.usersessionidletimeout > 0)) { serverinfo.timeout = (domain.usersessionidletimeout * 60 * 1000); } 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); } } @@ -928,7 +922,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) || (command.file !== parent.path.basename(command.file))) break; + if (scpath == null) break; const filePath = parent.path.join(scpath, command.file); fs.stat(filePath, function (err, stat) { if ((err != null) || (stat == null) || (stat.size >= 204800)) return; @@ -943,7 +937,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) || (command.file !== parent.path.basename(command.file))) break; + if (scpath == null) break; const filePath = parent.path.join(scpath, command.file); var data = null; try { data = Buffer.from(command.data, 'base64'); } catch (ex) { return; } @@ -1003,7 +997,6 @@ 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') { @@ -3079,16 +3072,7 @@ 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 }; - 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 + 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) { } } @@ -3096,8 +3080,20 @@ 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 { - if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'runcommands', responseid: command.responseid, result: 'Agent not connected' })); } catch (ex) { } } + } 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: 'Invalid command type' })); } catch (ex) { } } @@ -5076,295 +5072,285 @@ 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]; } } - 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; - } + // 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]; } } - // 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,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]; + // 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 += ','; - } - if (typeof n.lastbootuptime == 'number') { output += ',' + n.lastbootuptime; } 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; } - // 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); } - } - } - output += csvClean('' + totalMemory); - } - } 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 += ',,,,,,,,,,,,,,,,,,'; - } + // 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; } } - // 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 += ',,,,'; - } + // 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; } + } + } + + } - // 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 += ',,,'; - } - - // 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 - - // Add the device group name to each device + 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]; - if (nodeinfo.node) { - const mesh = parent.meshes[nodeinfo.node.meshid]; - if (mesh) { results[i].node.groupname = mesh.name; } - } - } - output = JSON.stringify(results, null, 2); + // 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 += ',,,,,,,,,,,,,,,,,,,'; + } + + // 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); } + } + } + output += csvClean('' + totalMemory); + } + } 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 += ',,,,,,,,,,,,,,,,,,'; + } + + // 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 += ',,,'; + } + + // 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 + + // 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; } + } } - try { ws.send(JSON.stringify({ action: 'getDeviceDetails', data: output, type: type })); } catch (ex) { } - }); + + output = JSON.stringify(results, null, 2); + } + try { ws.send(JSON.stringify({ action: 'getDeviceDetails', data: output, type: type })); } catch (ex) { } }); }); + break; } case 'endDesktopMultiplex': { @@ -5613,7 +5599,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. Optionally use info h for human readable form."], + '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."], 'le': [serverUserCommandLe, ""], 'lecheck': [serverUserCommandLeCheck, ""], 'leevents': [serverUserCommandLeEvents, ""], @@ -7559,26 +7545,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use } function serverUserCommandInfo(cmdData) { - 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(); } + var info = {}; 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) { } @@ -7590,24 +7557,9 @@ 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) { } - 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.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 ffb03da7..9fc1e422 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.gaugeMetrics = { // Gauge Metrics always start at 0 and can increase and decrease + obj.guageMetrics = { // Guage 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,7 +42,6 @@ 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'); @@ -52,8 +51,8 @@ module.exports.CreateMonitoring = function (parent, args) { obj.prometheus = require('prom-client'); const collectDefaultMetrics = obj.prometheus.collectDefaultMetrics; collectDefaultMetrics(); - 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.guageMetrics) { + obj.guageMetrics[key].prometheus = new obj.prometheus.Gauge({ name: 'meshcentral_' + String(key).toLowerCase(), help: obj.guageMetrics[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 }); @@ -68,7 +67,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 gauges = { + var guages = { UserAccounts: Object.keys(parent.webserver.users).length, DeviceGroups: activeDeviceGroups, AgentSessions: Object.keys(parent.webserver.wsagents).length, @@ -80,10 +79,10 @@ module.exports.CreateMonitoring = function (parent, args) { }; if (parent.mpsserver != null) { for (var i in parent.mpsserver.ciraConnections) { - gauges.ConnectedIntelAMT += parent.mpsserver.ciraConnections[i].length; + guages.ConnectedIntelAMT += parent.mpsserver.ciraConnections[i].length; } } - for (const key in gauges) { obj.gaugeMetrics[key].prometheus.set(gauges[key]); } + for (const key in guages) { obj.guageMetrics[key].prometheus.set(guages[key]); } // Take a look at agent errors var agentstats = parent.webserver.getAgentStats(); const counters = { @@ -104,7 +103,6 @@ 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); @@ -113,5 +111,4 @@ module.exports.CreateMonitoring = function (parent, args) { }); } } - return obj; } \ No newline at end of file diff --git a/package.json b/package.json index 78cd22bb..84b9ec93 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "1.1.42", + "version": "1.1.36", "keywords": [ "Remote Device Management", "Remote Device Monitoring", diff --git a/pluginHandler.js b/pluginHandler.js index 47c2a42d..139f8015 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 9bfbf813..fb7ec243 100644 --- a/public/scripts/agent-desktop-0.0.2-min.js +++ b/public/scripts/agent-desktop-0.0.2-min.js @@ -1 +1,962 @@ -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
+!function(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]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