diff --git a/agents/meshcore.js b/agents/meshcore.js index 8f360db7..32aae0c6 100644 --- a/agents/meshcore.js +++ b/agents/meshcore.js @@ -627,6 +627,7 @@ function createMeshCore(agent) { tunnel.sessionid = data.sessionid; tunnel.rights = data.rights; tunnel.consent = data.consent; + tunnel.privacybartext = data.privacybartext ? data.privacybartext : 'Sharing desktop with: {0}'; tunnel.username = data.username; tunnel.userid = data.userid; tunnel.remoteaddr = data.remoteaddr; @@ -1300,8 +1301,7 @@ function createMeshCore(agent) { this.httprequest.desktop.kvm.users.splice(i, 1); this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close'); this.httprequest.desktop.kvm.connectionBar.close(); - - this.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')('Sharing desktop with: ' + this.httprequest.desktop.kvm.users.sort().join(', '), require('MeshAgent')._tsid); + this.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(this.httprequest.privacybartext.replace('{0}', this.httprequest.desktop.kvm.users.sort().join(', ')), require('MeshAgent')._tsid); this.httprequest.desktop.kvm.connectionBar.httprequest = this.httprequest; this.httprequest.desktop.kvm.connectionBar.on('close', function () { @@ -1366,7 +1366,7 @@ function createMeshCore(agent) { } try { - this.ws.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')('Sharing desktop with: ' + this.ws.httprequest.desktop.kvm.users.sort().join(', '), require('MeshAgent')._tsid); + this.ws.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(this.ws.httprequest.privacybartext.replace('{0}', this.ws.httprequest.desktop.kvm.users.sort().join(', ')), require('MeshAgent')._tsid); MeshServerLog('Remote Desktop Connection Bar Activated/Updated (' + this.ws.httprequest.remoteaddr + ')', this.ws.httprequest); } catch(xx) @@ -1420,7 +1420,7 @@ function createMeshCore(agent) { } try { - this.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')('Sharing desktop with: ' + this.httprequest.desktop.kvm.users.sort().join(', '), require('MeshAgent')._tsid); + this.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(this.httprequest.privacybartext.replace('{0}', this.httprequest.desktop.kvm.users.sort().join(', ')), require('MeshAgent')._tsid); MeshServerLog('Remote Desktop Connection Bar Activated/Updated (' + this.httprequest.remoteaddr + ')', this.httprequest); } catch(xx) diff --git a/agents/meshcore.min.js b/agents/meshcore.min.js index 378f6317..32aae0c6 100644 --- a/agents/meshcore.min.js +++ b/agents/meshcore.min.js @@ -627,6 +627,7 @@ function createMeshCore(agent) { tunnel.sessionid = data.sessionid; tunnel.rights = data.rights; tunnel.consent = data.consent; + tunnel.privacybartext = data.privacybartext ? data.privacybartext : 'Sharing desktop with: {0}'; tunnel.username = data.username; tunnel.userid = data.userid; tunnel.remoteaddr = data.remoteaddr; @@ -696,18 +697,30 @@ function createMeshCore(agent) { } catch (e) { } break; } - case 'deskBackground': { + case 'deskBackground': + { // Toggle desktop background try { - if (process.platform == 'win32') { + if (process.platform == 'win32') + { + var stype = require('user-sessions').getProcessOwnerName(process.pid).tsid == 0 ? 1 : 0; + var sid = undefined; + if (stype == 1) + { + if(require('MeshAgent')._tsid != null) + { + stype = 5; + sid = require('MeshAgent')._tsid; + } + } var id = require('user-sessions').getProcessOwnerName(process.pid).tsid == 0 ? 1 : 0; - var child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0='], { type: id }); + var child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0='], { type: stype, uid: sid }); child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); child.stderr.on('data', function () { }); child.waitExit(); var current = child.stdout.str.trim(); if (current != '') { require('MeshAgent')._wallpaper = current; } - child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0=', current != '' ? '""' : require('MeshAgent')._wallpaper], { type: id }); + child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-b64exec', 'dmFyIFNQSV9HRVRERVNLV0FMTFBBUEVSID0gMHgwMDczOwp2YXIgU1BJX1NFVERFU0tXQUxMUEFQRVIgPSAweDAwMTQ7CnZhciBHTSA9IHJlcXVpcmUoJ19HZW5lcmljTWFyc2hhbCcpOwp2YXIgdXNlcjMyID0gR00uQ3JlYXRlTmF0aXZlUHJveHkoJ3VzZXIzMi5kbGwnKTsKdXNlcjMyLkNyZWF0ZU1ldGhvZCgnU3lzdGVtUGFyYW1ldGVyc0luZm9BJyk7CgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA9PSAzKQp7CiAgICB2YXIgdiA9IEdNLkNyZWF0ZVZhcmlhYmxlKDEwMjQpOwogICAgdXNlcjMyLlN5c3RlbVBhcmFtZXRlcnNJbmZvQShTUElfR0VUREVTS1dBTExQQVBFUiwgdi5fc2l6ZSwgdiwgMCk7CiAgICBjb25zb2xlLmxvZyh2LlN0cmluZyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQplbHNlCnsKICAgIHZhciBuYiA9IEdNLkNyZWF0ZVZhcmlhYmxlKHByb2Nlc3MuYXJndlszXSk7CiAgICB1c2VyMzIuU3lzdGVtUGFyYW1ldGVyc0luZm9BKFNQSV9TRVRERVNLV0FMTFBBUEVSLCBuYi5fc2l6ZSwgbmIsIDApOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0=', current != '' ? '""' : require('MeshAgent')._wallpaper], { type: stype, uid: sid }); child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); child.stderr.on('data', function () { }); child.waitExit(); @@ -717,7 +730,10 @@ function createMeshCore(agent) { if (current != '/dev/null') { require('MeshAgent')._wallpaper = current; } require('linux-gnome-helpers').setDesktopWallpaper(id, current != '/dev/null' ? undefined : require('MeshAgent')._wallpaper); } - } catch (e) { } + } catch (e) + { + sendConsoleText(e); + } break; } case 'openUrl': { @@ -1285,8 +1301,7 @@ function createMeshCore(agent) { this.httprequest.desktop.kvm.users.splice(i, 1); this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close'); this.httprequest.desktop.kvm.connectionBar.close(); - - this.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')('Sharing desktop with: ' + this.httprequest.desktop.kvm.users.sort().join(', '), require('MeshAgent')._tsid); + this.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(this.httprequest.privacybartext.replace('{0}', this.httprequest.desktop.kvm.users.sort().join(', ')), require('MeshAgent')._tsid); this.httprequest.desktop.kvm.connectionBar.httprequest = this.httprequest; this.httprequest.desktop.kvm.connectionBar.on('close', function () { @@ -1351,7 +1366,7 @@ function createMeshCore(agent) { } try { - this.ws.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')('Sharing desktop with: ' + this.ws.httprequest.desktop.kvm.users.sort().join(', '), require('MeshAgent')._tsid); + this.ws.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(this.ws.httprequest.privacybartext.replace('{0}', this.ws.httprequest.desktop.kvm.users.sort().join(', ')), require('MeshAgent')._tsid); MeshServerLog('Remote Desktop Connection Bar Activated/Updated (' + this.ws.httprequest.remoteaddr + ')', this.ws.httprequest); } catch(xx) @@ -1405,7 +1420,7 @@ function createMeshCore(agent) { } try { - this.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')('Sharing desktop with: ' + this.httprequest.desktop.kvm.users.sort().join(', '), require('MeshAgent')._tsid); + this.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(this.httprequest.privacybartext.replace('{0}', this.httprequest.desktop.kvm.users.sort().join(', ')), require('MeshAgent')._tsid); MeshServerLog('Remote Desktop Connection Bar Activated/Updated (' + this.httprequest.remoteaddr + ')', this.httprequest); } catch(xx) @@ -1856,7 +1871,11 @@ function createMeshCore(agent) { var response = null; switch (cmd) { case 'help': { // Displays available commands - var fin = '', f = '', availcommands = 'help,info,osinfo,args,print,type,dbkeys,dbget,dbset,dbcompact,eval,parseuri,httpget,nwslist,plugin,wsconnect,wssend,wsclose,notify,ls,ps,kill,amt,netinfo,location,power,wakeonlan,setdebug,smbios,rawsmbios,toast,lock,users,sendcaps,openurl,amtreset,amtccm,amtacm,amtdeactivate,amtpolicy,getscript,getclip,setclip,log,av,cpuinfo,sysinfo,apf,scanwifi,scanamt,safemode,wallpaper'; + var fin = '', f = '', availcommands = 'help,info,osinfo,args,print,type,dbkeys,dbget,dbset,dbcompact,eval,parseuri,httpget,nwslist,plugin,wsconnect,wssend,wsclose,notify,ls,ps,kill,amt,netinfo,location,power,wakeonlan,setdebug,smbios,rawsmbios,toast,lock,users,sendcaps,openurl,amtreset,amtccm,amtacm,amtdeactivate,amtpolicy,getscript,getclip,setclip,log,av,cpuinfo,sysinfo,apf,scanwifi,scanamt,wallpaper'; + if (process.platform == 'win32') + { + availcommands += ',safemode,wpfhwacceleration'; + } availcommands = availcommands.split(',').sort(); while (availcommands.length > 0) { if (f.length > 100) { fin += (f + ',\r\n'); f = ''; } @@ -1866,6 +1885,71 @@ function createMeshCore(agent) { response = 'Available commands: \r\n' + fin + '.'; break; } + case 'wpfhwacceleration': + if (process.platform != 'win32') { throw ('wpfhwacceleration setting is only supported on Windows'); } + if (args['_'].length != 1) + { + response = 'Proper usage: wpfhwacceleration (ON|OFF|STATUS)'; // Display usage + } + else + { + var reg = require('win-registry'); + var uname = require('user-sessions').getUsername(require('user-sessions').consoleUid()); + var key = reg.usernameToUserKey(uname); + + switch(args['_'][0].toUpperCase()) + { + default: + response = 'Proper usage: wpfhwacceleration (ON|OFF|STATUS|DEFAULT)'; // Display usage + break; + case 'ON': + try + { + reg.WriteKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration', 0); + response = 'OK'; + } + catch (ee) + { + response = 'FAILED'; + } + break; + case 'OFF': + try + { + reg.WriteKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration', 1); + response = 'OK'; + } + catch (ee) + { + response = 'FAILED'; + } + break; + break; + case 'STATUS': + var s; + try + { + s = reg.QueryKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration')==1?'DISABLED':'ENABLED'; + } + catch (ee) + { + s = 'DEFAULT'; + } + response = 'WPF Hardware Acceleration: ' + s; + break; + case 'DEFAULT': + try + { + reg.DeleteKey(reg.HKEY.Users, key + '\\SOFTWARE\\Microsoft\\Avalon.Graphics', 'DisableHWAcceleration'); + } + catch (ee) + { + } + response = 'OK'; + break; + } + } + break; case 'tsid': if (process.platform == 'win32') { @@ -1880,6 +1964,10 @@ function createMeshCore(agent) { response = 'TSID set to: ' + (require('MeshAgent')._tsid == null ? 'console' : require('MeshAgent')._tsid); } } + else + { + response = 'TSID command only supported on Windows'; + } break; case 'activeusers': if (process.platform == 'win32') @@ -1899,6 +1987,10 @@ function createMeshCore(agent) { sendConsoleText(JSON.stringify(v, null, 1), this.sessionid); }); } + else + { + response = 'activeusers command only supported on Windows'; + } break; case 'wallpaper': if (process.platform != 'win32' && !(process.platform == 'linux' && require('linux-gnome-helpers').available)) @@ -2106,8 +2198,16 @@ function createMeshCore(agent) { break; } case 'toast': { - if (args['_'].length < 1) { response = 'Proper usage: toast "message"'; } else { - require('toaster').Toast('MeshCentral', args['_'][0]).then(sendConsoleText, sendConsoleText); + if (args['_'].length < 1) { response = 'Proper usage: toast "message"'; } else + { + if (require('MeshAgent')._tsid == null) + { + require('toaster').Toast('MeshCentral', args['_'][0]).then(sendConsoleText, sendConsoleText); + } + else + { + require('toaster').Toast('MeshCentral', args['_'][0], require('MeshAgent')._tsid).then(sendConsoleText, sendConsoleText); + } } break; } @@ -2243,10 +2343,14 @@ function createMeshCore(agent) { case 'osinfo': { // Return the operating system information var i = 1; if (args['_'].length > 0) { i = parseInt(args['_'][0]); if (i > 8) { i = 8; } response = 'Calling ' + i + ' times.'; } - for (var j = 0; j < i; j++) { + for (var j = 0; j < i; j++) + { var pr = require('os').name(); pr.sessionid = sessionid; - pr.then(function (v) { sendConsoleText("OS: " + v, this.sessionid); }); + pr.then(function (v) + { + sendConsoleText("OS: " + v + (process.platform=='win32'?(require('win-virtual-terminal').supported?' [ConPTY: YES]':' [ConPTY: NO]'):''), this.sessionid); + }); } break; } diff --git a/meshcentral.js b/meshcentral.js index ca3a85c3..aea81d70 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -69,6 +69,8 @@ function CreateMeshCentralServer(config, args) { obj.taskLimiter = obj.common.createTaskLimiterQueue(50, 20, 60); // (maxTasks, maxTaskTime, cleaningInterval) This is a task limiter queue to smooth out server work. obj.agentUpdateBlockSize = 65531; // MeshAgent update block size obj.serverWarnings = []; // List of warnings that should be shown to administrators + obj.cookieUseOnceTable = {}; // List of cookies that are already expired + obj.cookieUseOnceTableCleanCounter = 0; // Clean the cookieUseOnceTable each 20 additions try { obj.currentVer = JSON.parse(obj.fs.readFileSync(obj.path.join(__dirname, 'package.json'), 'utf8')).version; } catch (e) { } // Fetch server version // Setup the default configuration and files paths @@ -1857,7 +1859,30 @@ function CreateMeshCentralServer(config, args) { // Decode a cookie back into an object using a key using AES256-GCM or AES128-CBC/HMAC-SHA386. Return null if it's not a valid cookie. (key must be 32 bytes or more) obj.decodeCookie = function (cookie, key, timeout) { const r = obj.decodeCookieAESGCM(cookie, key, timeout); - if (r == null) { return obj.decodeCookieAESSHA(cookie, key, timeout); } + if (r == null) { r = obj.decodeCookieAESSHA(cookie, key, timeout); } + if ((r != null) && (typeof r.once == 'string') && (r.once.length > 0)) { + // This cookie must only be used once. + if (timeout == null) { timeout = 2; } + if (obj.cookieUseOnceTable[r.once] == null) { + const ctimeout = (((r.expire) == null || (typeof r.expire != 'number')) ? (r.time + ((timeout + 3) * 60000)) : (r.time + ((r.expire + 3) * 60000))); + + // Store the used cookie in RAM + obj.cookieUseOnceTable[r.once] = ctimeout; + + // Store the used cookie in the database + // TODO + + // Send the used cookie to peer servers + // TODO + + // Clean up the used table + if (++obj.cookieUseOnceTableCleanCounter > 20) { + const now = Date.now(); + for (var i in obj.cookieUseOnceTable) { if (obj.cookieUseOnceTable[i] < now) { delete obj.cookieUseOnceTable[i]; } } + obj.cookieUseOnceTableCleanCounter = 0; + } + } else { return null; } + } return r; } diff --git a/meshrelay.js b/meshrelay.js index d7eace99..37356362 100644 --- a/meshrelay.js +++ b/meshrelay.js @@ -88,6 +88,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie command.consent = mesh.consent; // Add user consent if (typeof domain.userconsentflags == 'number') { command.consent |= domain.userconsentflags; } // Add server required consent flags command.username = user.name; // Add user name + if (typeof domain.desktopprivacybartext == 'string') { command.privacybartext = domain.desktopprivacybartext; } // Privacy bar text delete command.nodeid; // Remove the nodeid since it's implyed. agent.send(JSON.stringify(command)); return true; @@ -105,6 +106,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie command.consent = mesh.consent; // Add user consent if (typeof domain.userconsentflags == 'number') { command.consent |= domain.userconsentflags; } // Add server required consent flags command.username = user.name; // Add user name + if (typeof domain.desktopprivacybartext == 'string') { command.privacybartext = domain.desktopprivacybartext; } // Privacy bar text parent.parent.multiServer.DispatchMessageSingleServer(command, routing.serverid); return true; } diff --git a/meshuser.js b/meshuser.js index 3d105576..c91f85fc 100644 --- a/meshuser.js +++ b/meshuser.js @@ -165,6 +165,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use command.username = user.name; // Add user name command.userid = user._id; // Add user id command.remoteaddr = cleanRemoteAddr(req.ip); // User's IP address + if (typeof domain.desktopprivacybartext == 'string') { command.privacybartext = domain.desktopprivacybartext; } // Privacy bar text delete command.nodeid; // Remove the nodeid since it's implied try { agent.send(JSON.stringify(command)); } catch (ex) { } } @@ -183,6 +184,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use command.username = user.name; // Add user name command.userid = user._id; // Add user id command.remoteaddr = cleanRemoteAddr(req.ip); // User's IP address + if (typeof domain.desktopprivacybartext == 'string') { command.privacybartext = domain.desktopprivacybartext; } // Privacy bar text parent.parent.multiServer.DispatchMessageSingleServer(command, routing.serverid); } } diff --git a/package.json b/package.json index f964c236..ac0f6074 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.4.5-q", + "version": "0.4.5-r", "keywords": [ "Remote Management", "Intel AMT",