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 };
}