diff --git a/apprelays.js b/apprelays.js index 7bf95e58..0a3cdddc 100644 --- a/apprelays.js +++ b/apprelays.js @@ -168,6 +168,7 @@ module.exports.CreateMstscRelay = function (parent, db, ws, req, args, domain) { // Save SSH credentials into device function saveRdpCredentials() { + if (domain.allowsavingdevicecredentials == false) return; parent.parent.db.Get(obj.nodeid, function (err, nodes) { if ((err != null) || (nodes == null) || (nodes.length != 1)) return; const node = nodes[0]; @@ -214,7 +215,7 @@ module.exports.CreateMstscRelay = function (parent, db, ws, req, args, domain) { // Check if we need to load server stored credentials if ((typeof obj.infos.options == 'object') && (obj.infos.options.useServerCreds == true)) { // Check if RDP credentials exist - if ((typeof node.rdp == 'object') && (typeof node.rdp.d == 'string') && (typeof node.rdp.u == 'string') && (typeof node.rdp.p == 'string')) { + if ((domain.allowsavingdevicecredentials === false) && (typeof node.rdp == 'object') && (typeof node.rdp.d == 'string') && (typeof node.rdp.u == 'string') && (typeof node.rdp.p == 'string')) { obj.infos.domain = node.rdp.d; obj.infos.username = node.rdp.u; obj.infos.password = node.rdp.p; @@ -340,6 +341,7 @@ module.exports.CreateSshRelay = function (parent, db, ws, req, args, domain) { // Save SSH credentials into device function saveSshCredentials() { + if (domain.allowsavingdevicecredentials == false) return; parent.parent.db.Get(obj.cookie.nodeid, function (err, nodes) { if ((err != null) || (nodes == null) || (nodes.length != 1)) return; const node = nodes[0]; @@ -471,7 +473,7 @@ module.exports.CreateSshRelay = function (parent, db, ws, req, args, domain) { parent.parent.db.Get(obj.cookie.nodeid, function (err, nodes) { if ((err != null) || (nodes == null) || (nodes.length != 1)) return; const node = nodes[0]; - if ((node.ssh == null) || (typeof node.ssh != 'object') || (typeof node.ssh.u != 'string') || ((typeof node.ssh.p != 'string') && (typeof node.ssh.k != 'string'))) { + if ((domain.allowsavingdevicecredentials === false) || (node.ssh == null) || (typeof node.ssh != 'object') || (typeof node.ssh.u != 'string') || ((typeof node.ssh.p != 'string') && (typeof node.ssh.k != 'string'))) { // Send a request for SSH authentication try { ws.send(JSON.stringify({ action: 'sshauth' })) } catch (ex) { } } else { @@ -611,6 +613,7 @@ module.exports.CreateSshTerminalRelay = function (parent, db, ws, req, domain, u // Save SSH credentials into device function saveSshCredentials() { + if (domain.allowsavingdevicecredentials == false) return; parent.parent.db.Get(obj.nodeid, function (err, nodes) { if ((err != null) || (nodes == null) || (nodes.length != 1)) return; const node = nodes[0]; @@ -811,7 +814,7 @@ module.exports.CreateSshTerminalRelay = function (parent, db, ws, req, domain, u if ((err != null) || (nodes == null) || (nodes.length != 1)) return; const node = nodes[0]; - if ((node.ssh == null) || (typeof node.ssh != 'object') || (typeof node.ssh.u != 'string') || ((typeof node.ssh.p != 'string') && (typeof node.ssh.k != 'string'))) { + if ((domain.allowsavingdevicecredentials === false) || (node.ssh == null) || (typeof node.ssh != 'object') || (typeof node.ssh.u != 'string') || ((typeof node.ssh.p != 'string') && (typeof node.ssh.k != 'string'))) { // Send a request for SSH authentication try { ws.send(JSON.stringify({ action: 'sshauth' })) } catch (ex) { } } else { @@ -903,6 +906,7 @@ module.exports.CreateSshFilesRelay = function (parent, db, ws, req, domain, user // Save SSH credentials into device function saveSshCredentials() { + if (domain.allowsavingdevicecredentials == false) return; parent.parent.db.Get(obj.nodeid, function (err, nodes) { if ((err != null) || (nodes == null) || (nodes.length != 1)) return; const node = nodes[0]; @@ -1283,7 +1287,7 @@ module.exports.CreateSshFilesRelay = function (parent, db, ws, req, domain, user if ((err != null) || (nodes == null) || (nodes.length != 1)) return; const node = nodes[0]; - if ((node.ssh == null) || (typeof node.ssh != 'object') || (typeof node.ssh.u != 'string') || ((typeof node.ssh.p != 'string') && (typeof node.ssh.k != 'string'))) { + if ((domain.allowsavingdevicecredentials === false) || (node.ssh == null) || (typeof node.ssh != 'object') || (typeof node.ssh.u != 'string') || ((typeof node.ssh.p != 'string') && (typeof node.ssh.k != 'string'))) { // Send a request for SSH authentication try { ws.send(JSON.stringify({ action: 'sshauth' })) } catch (ex) { } } else { diff --git a/meshcentral-config-schema.json b/meshcentral-config-schema.json index 60ca628b..60fa30ad 100644 --- a/meshcentral-config-schema.json +++ b/meshcentral-config-schema.json @@ -321,6 +321,7 @@ "hide": { "type": "integer", "default": 0, "description": "Sum of: 1 = Hide header, 2 = Hide tab, 4 = Hide footer, 8 = Hide title, 16 = Hide left bar, 32 = Hide back buttons" }, "footer": { "type": "string", "default": null, "description": "This is a HTML string displayed at the bottom of the web page when a user is logged in." }, "loginfooter": { "type": "string", "default": null, "description": "This is a HTML string displayed at the bottom of the web page when a user is not logged in." }, + "allowSavingDeviceCredentials": { "type": "boolean", "default": true, "description": "Allow users to save SSH, RDP, VNC device credentials on the server that can be used by any other user." }, "guestDeviceSharing": { "type": [ "boolean", "object" ], "default": true, diff --git a/views/default.handlebars b/views/default.handlebars index 9dba7a3c..71b3ac22 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -2434,14 +2434,14 @@ if (nodes != null) { for (var i in nodes) { if (nodes[i]._id == message.nodeid) { index = i; break; } } } if (index != -1) { // Node was found, dispatch the message - if ((message.type == 'cpuinfo') && (currentNode != null) && (currentNode._id == message.nodeid)) { + if ((message.type === 'cpuinfo') && (currentNode != null) && (currentNode._id == message.nodeid)) { var now = (Date.now() / 1000), cpu = 0, memory = 0; if (typeof message.cpu.total == 'number') { cpu = message.cpu.total; } if (typeof message.memory.percentConsumed == 'number') { memory = message.memory.percentConsumed; } deviceDetailsStatsData.push([now, cpu, memory]); deviceDetailsStatsDraw(message); - } else if (message.type == 'console') { p15consoleReceive(nodes[index], message.value, message.source); } // This is a console message. - else if (message.type == 'notify') { // This is a notification message. + } else if (message.type === 'console') { p15consoleReceive(nodes[index], message.value, message.source); } // This is a console message. + else if (message.type === 'notify') { // This is a notification message. var n = getstore('notifications', 0); if (((n & 8) == 0) && (message.amtMessage != null)) { break; } // Intel AMT desktop & terminal messages should be ignored. var n = { text: message.value, title: message.title, icon: message.icon, titleid: message.titleid, msgid: message.msgid, args: message.args }; @@ -2450,40 +2450,40 @@ if (message.tag != null) { n.tag = message.tag; } if (message.url != null) { n.url = message.url; } if (message.username != null) { n.username = message.username; } - if (typeof message.maxtime == 'number') { n.maxtime = message.maxtime; } + if (typeof message.maxtime === 'number') { n.maxtime = message.maxtime; } addNotification(n); - } else if (message.type == 'ps') { + } else if (message.type === 'ps') { showDeskToolsProcesses(message); - } else if (message.type == 'services') { + } else if (message.type === 'services') { showDeskToolsServices(message); - } else if ((message.type == 'getclip') && (currentNode != null) && (currentNode._id == message.nodeid)) { - if ((message.tag == 1) && (xxdialogTag == 'clipboard')) { + } else if ((message.type === 'getclip') && (currentNode != null) && (currentNode._id == message.nodeid)) { + if ((message.tag == 1) && (xxdialogTag === 'clipboard')) { Q('d2clipText').value = message.data; // Put remote clipboard data into dialog box - } else if (message.tag == 2) { + } else if (message.tag === 2) { if (navigator.clipboard != null) { navigator.clipboard.writeText(message.data).then(function() { }).catch(function(err) { console.log(err); }) } // Put remote clipboard data into our clipboard } - } else if ((message.type == 'setclip') && (xxdialogTag == 'clipboard') && (currentNode != null) && (currentNode._id == message.nodeid)) { + } else if ((message.type === 'setclip') && (xxdialogTag === 'clipboard') && (currentNode != null) && (currentNode._id == message.nodeid)) { // Display success/fail on the clipboard dialog box. QH('dlgClipStatus', message.success ? '' + "Success" + '' : '' + "Failed" + '') setTimeout(function () { try { QH('dlgClipStatus', ''); } catch (ex) { } }, 2000); - } else if ((message.type == 'userSessions') && (currentNode != null) && (currentNode._id == message.nodeid) && (desktop == null)) { + } else if ((message.type === 'userSessions') && (currentNode != null) && (currentNode._id === message.nodeid) && (desktop == null)) { // Got list of user sessions var userSessions = []; - if (message.data != null) { for (var i in message.data) { if ((message.data[i].State == 'Active') || (message.data[i].StationName == 'Console') || (debugmode == 3)) { userSessions.push(message.data[i]); } } } - if (userSessions.length == 0) { connectDesktop(null, 1, null, message.tag); } // No active sessions, do a normal connection. - else if (userSessions.length == 1) { connectDesktop(null, 1, userSessions[0].SessionId, message.tag); } // One active session, connect to it + if (message.data != null) { for (var i in message.data) { if ((message.data[i].State == 'Active') || (message.data[i].State == 'Connected') || (message.data[i].StationName == 'Console') || (debugmode == 3)) { userSessions.push(message.data[i]); } } } + if (userSessions.length === 0) { connectDesktop(null, 1, null, message.tag); } // No active sessions, do a normal connection. + else if (userSessions.length === 1) { connectDesktop(null, 1, userSessions[0].SessionId, message.tag); } // One active session, connect to it else { var x = ''; for (var i in userSessions) { - x += '
' + userSessions[i].State + ', ' + userSessions[i].StationName; + x += '
' + userSessions[i].State + (userSessions[i].StationName ? (', ' + userSessions[i].StationName) : ''); if (userSessions[i].Username) { if (userSessions[i].Domain) { x += ' - ' + userSessions[i].Domain + '/' + userSessions[i].Username; } else { x += ' - ' + userSessions[i].Username; } } x += '
'; } QH('p11DeskSessionSelector', x); QV('p11DeskSessionSelector', true); } - } else if (message.type == 'psinfo') { - if (xxdialogTag == ('ps|' + message.nodeid + '|' + message.pid)) { + } else if (message.type === 'psinfo') { + if (xxdialogTag === ('ps|' + message.nodeid + '|' + message.pid)) { var x = '
'; //x += addHtmlValue4("Process ID", message.pid); if ((typeof message.value == 'object') && (Object.keys(message.value).length > 0)) { @@ -2518,7 +2518,7 @@ } } } else { - if (message.type == 'notify') { // This is a notification message. + if (message.type === 'notify') { // This is a notification message. var n = { text: message.value, title: message.title, icon: message.icon, titleid: message.titleid, msgid: message.msgid, args: message.args }; if (message.id != null) { n.id = message.id; } if (message.tag != null) { n.tag = message.tag; } @@ -9489,8 +9489,9 @@ x += addHtmlValue("Key File", '' + '
' + "Key file must be in OpenSSH format." + '
'); x += addHtmlValue("Key Password", ''); x += '
'; - x += addHtmlValue('', ''); + if ((features2 & 0x00400000) == 0) { x += addHtmlValue('', ''); } setDialogMode(2, "Authentication", 11, sshConnectEx, x, 'ssh'); + Q('dp2user').focus(); setTimeout(sshAuthUpdate, 50); break; } @@ -9525,15 +9526,25 @@ reader.readAsText(Q('dp2key').files[0]); } } + + // When the enter key is pressed, move to the next field + if (e && (e.keyCode == 13) && (e.target) && (Q('dp2authmethod').value == 1)) { + if (e.target.id == 'dp2user') { Q('dp2pass').focus(); } + if (e.target.id == 'dp2pass') { dialogclose(1); } + } } + function sshConnectEx(b) { if (b == 0) { if (terminal != null) { connectTerminal(); } // Disconnect } else { + var keep = false; + if ((features2 & 0x00400000) == 0) { keep = Q('dp2keep').checked; } + if (Q('dp2authmethod').value == 1) { - terminal.socket.send(JSON.stringify({ action: 'sshauth', username: Q('dp2user').value, password: Q('dp2pass').value, keep: Q('dp2keep').checked, cols: xterm.cols, rows: xterm.rows, width: Q('termarea3xdiv').offsetWidth, height: Q('termarea3xdiv').offsetHeight })); + terminal.socket.send(JSON.stringify({ action: 'sshauth', username: Q('dp2user').value, password: Q('dp2pass').value, keep: keep, cols: xterm.cols, rows: xterm.rows, width: Q('termarea3xdiv').offsetWidth, height: Q('termarea3xdiv').offsetHeight })); } else { - var reader = new FileReader(), username = Q('dp2user').value, keypass = Q('dp2keypass').value, keep = Q('dp2keep').checked; + var reader = new FileReader(), username = Q('dp2user').value, keypass = Q('dp2keypass').value; reader.onload = function (e) { terminal.socket.send(JSON.stringify({ action: 'sshauth', username: username, keypass: keypass, key: e.target.result, keep: keep, cols: xterm.cols, rows: xterm.rows, width: Q('termarea3xdiv').offsetWidth, height: Q('termarea3xdiv').offsetHeight })); } reader.readAsText(Q('dp2key').files[0]); } @@ -9881,8 +9892,9 @@ x += addHtmlValue("Key File", ''); x += addHtmlValue("Key Password", ''); x += '
'; - x += addHtmlValue('', ''); + if ((features2 & 0x00400000) == 0) { x += addHtmlValue('', ''); } setDialogMode(2, "Authentication", 11, p13sshConnectEx, x, 'ssh'); + Q('dp2user').focus(); setTimeout(sshAuthUpdate, 50); return; } @@ -9938,10 +9950,12 @@ if (b == 0) { if (files != null) { connectFiles(); } // Disconnect } else { + var keep = false; + if ((features2 & 0x00400000) == 0) { keep = Q('dp2keep').checked; } if (Q('dp2authmethod').value == 1) { - files.socket.send(JSON.stringify({ action: 'sshauth', username: Q('dp2user').value, password: Q('dp2pass').value, keep: Q('dp2keep').checked })); + files.socket.send(JSON.stringify({ action: 'sshauth', username: Q('dp2user').value, password: Q('dp2pass').value, keep: keep })); } else { - var reader = new FileReader(), username = Q('dp2user').value, keypass = Q('dp2keypass').value, keep = Q('dp2keep').checked; + var reader = new FileReader(), username = Q('dp2user').value, keypass = Q('dp2keypass').value; reader.onload = function (e) { files.socket.send(JSON.stringify({ action: 'sshauth', username: username, keypass: keypass, key: e.target.result, keep: keep })); } reader.readAsText(Q('dp2key').files[0]); } diff --git a/views/mstsc.handlebars b/views/mstsc.handlebars index 77b80bcb..6b656281 100644 --- a/views/mstsc.handlebars +++ b/views/mstsc.handlebars @@ -83,6 +83,7 @@ var serverCredentials = (decodeURIComponent('{{{serverCredentials}}}') == 'true'); var name = decodeURIComponent('{{{name}}}'); if (name != '') { document.title = name + ' - ' + document.title; } + var features = parseInt('{{{features}}}'); function load() { if (name != '') { QH('computerName', EscapeHtml(name)); } @@ -159,7 +160,7 @@ QV('rowdomain', newCreds); QV('rowusername', newCreds); QV('rowpassword', newCreds); - QV('rowremember', newCreds); + QV('rowremember', newCreds && ((features & 1) == 0)); if (newCreds) Q('inputUsername').focus(); } diff --git a/views/ssh.handlebars b/views/ssh.handlebars index f21591c8..c7ec39fb 100644 --- a/views/ssh.handlebars +++ b/views/ssh.handlebars @@ -75,6 +75,7 @@ if (urlargs.key && (isAlphaNumeric(urlargs.key) == false)) { delete urlargs.key; } var cookie = '{{{cookie}}}'; var domainurl = '{{{domainurl}}}'; + var features = parseInt('{{{features}}}'); var name = decodeURIComponent('{{{name}}}'); if (name != '') { document.title = name + ' - ' + document.title; } var StatusStrs = ["Disconnected", "Connecting...", "Setup...", "Connected"]; @@ -146,10 +147,17 @@ reader.readAsText(Q('dp2key').files[0]); } } + + // When the enter key is pressed, move to the next field + if (e && (e.keyCode == 13) && (e.target) && (Q('dp2authmethod').value == 1)) { + if (e.target.id == 'dp2user') { Q('dp2pass').focus(); } + if (e.target.id == 'dp2pass') { dialogclose(1); } + } } function connectEx() { - var cmd = { action: 'connect', cols: term.cols, rows: term.rows, width: Q('terminal').offsetWidth, height: Q('terminal').offsetHeight, username: Q('dp2user').value, keep: Q('dp2keep').checked }; + var cmd = { action: 'connect', cols: term.cols, rows: term.rows, width: Q('terminal').offsetWidth, height: Q('terminal').offsetHeight, username: Q('dp2user').value, keep: false }; + if ((features & 1) == 0) { cmd.keep = Q('dp2keep').checked; } if (Q('dp2authmethod').value == 1) { cmd.password = Q('dp2pass').value; @@ -191,7 +199,7 @@ x += addHtmlValue("Key File", '' + '
' + "Key file must be in OpenSSH format." + '
'); x += addHtmlValue("Key Password", ''); x += ''; - x += addHtmlValue('', ''); + if ((features & 1) == 0) { x += addHtmlValue('', ''); } setDialogMode(2, "Authentication", 3, connectEx, x); Q('dp2user').value = user; Q('dp2pass').value = pass; diff --git a/webserver.js b/webserver.js index 4ac3f421..5d416c39 100644 --- a/webserver.js +++ b/webserver.js @@ -1949,6 +1949,10 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF return; } + // Set features we want to send to this page + var features = 0; + if (domain.allowsavingdevicecredentials === false) { features |= 1; } + if (req.query.ws != null) { // This is a query with a websocket relay cookie, check that the cookie is valid and use it. var rcookie = parent.decodeCookie(req.query.ws, parent.loginCookieEncryptionKey, 60); // Cookie with 1 hour timeout @@ -1960,10 +1964,17 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF const node = nodes[0]; // Check if we have RDP credentials for this device - var serverCredentials = ((typeof node.rdp == 'object') && (typeof node.rdp.d == 'string') && (typeof node.rdp.u == 'string') && (typeof node.rdp.p == 'string')); + var serverCredentials = false; + if (domain.allowsavingdevicecredentials !== false) { + if (page == 'ssh') { + serverCredentials = ((typeof node.rdp == 'object') && (typeof node.rdp.d == 'string') && (typeof node.rdp.u == 'string') && (typeof node.rdp.p == 'string')) + } else { + serverCredentials = ((typeof node.ssh == 'object') && (typeof node.ssh.u == 'string')) + } + } // Render the page - render(req, res, getRenderPage(page, req, domain), getRenderArgs({ cookie: req.query.ws, name: encodeURIComponent(req.query.name).replace(/'/g, '%27'), serverCredentials: serverCredentials }, req, domain)); + render(req, res, getRenderPage(page, req, domain), getRenderArgs({ cookie: req.query.ws, name: encodeURIComponent(req.query.name).replace(/'/g, '%27'), serverCredentials: serverCredentials, features: features }, req, domain)); }); return; } @@ -2000,35 +2011,38 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF } // If there is no nodeid, exit now - if (req.query.node == null) { render(req, res, getRenderPage(page, req, domain), getRenderArgs({ cookie: '', name: '' }, req, domain)); return; } + if (req.query.node == null) { render(req, res, getRenderPage(page, req, domain), getRenderArgs({ cookie: '', name: '', features: features }, req, domain)); return; } // Fetch the node from the database obj.db.Get(req.query.node, function (err, nodes) { if ((err != null) || (nodes.length != 1)) { res.sendStatus(404); return; } const node = nodes[0]; - // Check if we have RDP credentials for this device - var serverCredentials = ((typeof node.rdp == 'object') && (typeof node.rdp.d == 'string') && (typeof node.rdp.u == 'string') && (typeof node.rdp.p == 'string')); - // Check access rights, must have remote control rights if ((obj.GetNodeRights(user, node.meshid, node._id) & MESHRIGHT_REMOTECONTROL) == 0) { res.sendStatus(401); return; } // Figure out the target port - var port = 0; + var port = 0, serverCredentials = false; if (page == 'ssh') { // SSH port port = 22; if (typeof node.sshport == 'number') { port = node.sshport; } + + // Check if we have SSH credentials for this device + if (domain.allowsavingdevicecredentials !== false) { serverCredentials = ((typeof node.ssh == 'object') && (typeof node.ssh.u == 'string')); } } else { // RDP port port = 3389; if (typeof node.rdpport == 'number') { port = node.rdpport; } + + // Check if we have RDP credentials for this device + if (domain.allowsavingdevicecredentials !== false) { serverCredentials = ((typeof node.rdp == 'object') && (typeof node.rdp.d == 'string') && (typeof node.rdp.u == 'string') && (typeof node.rdp.p == 'string')); } } if (req.query.port != null) { var qport = 0; try { qport = parseInt(req.query.port); } catch (ex) { } if ((typeof qport == 'number') && (qport > 0) && (qport < 65536)) { port = qport; } } // Generate a cookie and respond var cookie = parent.encodeCookie({ userid: user._id, domainid: user.domain, nodeid: node._id, tcpport: port }, parent.loginCookieEncryptionKey); - render(req, res, getRenderPage(page, req, domain), getRenderArgs({ cookie: cookie, name: encodeURIComponent(node.name).replace(/'/g, '%27'), serverCredentials: serverCredentials }, req, domain)); + render(req, res, getRenderPage(page, req, domain), getRenderArgs({ cookie: cookie, name: encodeURIComponent(node.name).replace(/'/g, '%27'), serverCredentials: serverCredentials, features: features }, req, domain)); }); } @@ -2942,6 +2956,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF if ((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.single2factorwarning === false)) { features2 += 0x00080000; } // Indicates no warning if a single 2FA is in use if (domain.nightmode === 1) { features2 += 0x00100000; } // Always night mode if (domain.nightmode === 2) { features2 += 0x00200000; } // Always day mode + if (domain.allowsavingdevicecredentials == false) { features2 += 0x00400000; } // Do not allow device credentials to be saved on the server return { features: features, features2: features2 }; }