diff --git a/apprelays.js b/apprelays.js index 31168edb..8c75eb9f 100644 --- a/apprelays.js +++ b/apprelays.js @@ -447,9 +447,9 @@ 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) { + function saveSshCredentials(keep) { + if (((keep != 1) && (keep != 2)) || (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]; const changed = (node.ssh == null); @@ -461,16 +461,17 @@ module.exports.CreateSshRelay = function (parent, db, ws, req, args, domain) { if (obj.password != null) { node.ssh = { u: obj.username, p: obj.password }; } else if (obj.privateKey != null) { - node.ssh = { u: obj.username, k: obj.privateKey, kp: obj.privateKeyPass }; + node.ssh = { u: obj.username, k: obj.privateKey }; + if (keep == 2) { node.ssh.kp = obj.privateKeyPass; } } else return; parent.parent.db.Set(node); // Event node change if needed if (changed) { // Event the node change - const event = { etype: 'node', action: 'changenode', nodeid: obj.cookie.nodeid, domain: domain.id, userid: obj.cookie.userid, node: parent.CloneSafeNode(node), msg: "Changed SSH credentials" }; + const event = { etype: 'node', action: 'changenode', nodeid: obj.nodeid, domain: domain.id, userid: obj.userid, node: parent.CloneSafeNode(node), msg: "Changed SSH credentials" }; if (parent.parent.db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the node. Another event will come. - parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(node.meshid, [obj.cookie.nodeid]), obj, event); + parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(node.meshid, [obj.nodeid]), obj, event); } }); } @@ -498,7 +499,7 @@ module.exports.CreateSshRelay = function (parent, db, ws, req, args, domain) { obj.sshClient = new Client(); obj.sshClient.on('ready', function () { // Authentication was successful. // If requested, save the credentials - if (obj.keep === true) saveSshCredentials(); + saveSshCredentials(obj.keep); obj.sessionid = Buffer.from(parent.crypto.randomBytes(9), 'binary').toString('base64'); obj.startTime = Date.now(); @@ -590,10 +591,15 @@ module.exports.CreateSshRelay = function (parent, db, ws, req, args, domain) { 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 if ((domain.allowsavingdevicecredentials !== false) && (node.ssh != null) && (typeof node.ssh.k == 'string') && (node.ssh.kp == null)) { + // Send a request for SSH authentication with option for only the private key password + obj.username = node.ssh.u; + obj.privateKey = node.ssh.k; + try { ws.send(JSON.stringify({ action: 'sshauth', askkeypass: true })) } catch (ex) { } } else { // Use our existing credentials obj.termSize = msg; - obj.keep = false; + delete obj.keep; obj.username = node.ssh.u; if (typeof node.ssh.p == 'string') { obj.password = node.ssh.p; @@ -610,7 +616,8 @@ module.exports.CreateSshRelay = function (parent, db, ws, req, args, domain) { if ((typeof msg.rows != 'number') || (typeof msg.cols != 'number') || (typeof msg.height != 'number') || (typeof msg.width != 'number')) break; obj.termSize = msg; - obj.keep = msg.keep; // If true, keep store credentials on the server if the SSH tunnel connected succesfully. + if (msg.keep === true) { msg.keep = 1; } // If true, change to 1. For user/pass, 1 to store user/pass in db. For user/key/pass, 1 to store user/key in db, 2 to store everything in db. + obj.keep = msg.keep; // If set, keep store credentials on the server if the SSH tunnel connected succesfully. obj.username = msg.username; obj.password = msg.password; obj.privateKey = msg.key; @@ -619,6 +626,24 @@ module.exports.CreateSshRelay = function (parent, db, ws, req, args, domain) { } break; } + case 'connectKeyPass': { + // Verify inputs + if (typeof msg.keypass != 'string') break; + + // Check if we have SSH credentials for this device + obj.privateKeyPass = msg.keypass; + obj.termSize = msg; + 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) { + obj.username = node.ssh.u; + obj.privateKey = node.ssh.k; + startRelayConnection(); + } + }); + break; + } case 'resize': { // Verify inputs if ((typeof msg.rows != 'number') || (typeof msg.cols != 'number') || (typeof msg.height != 'number') || (typeof msg.width != 'number')) break; @@ -752,8 +777,8 @@ module.exports.CreateSshTerminalRelay = function (parent, db, ws, req, domain, u }; // Save SSH credentials into device - function saveSshCredentials() { - if (domain.allowsavingdevicecredentials == false) return; + function saveSshCredentials(keep) { + if (((keep != 1) && (keep != 2)) || (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]; @@ -766,8 +791,9 @@ module.exports.CreateSshTerminalRelay = function (parent, db, ws, req, domain, u if (obj.password != null) { node.ssh = { u: obj.username, p: obj.password }; } else if (obj.privateKey != null) { - node.ssh = { u: obj.username, k: obj.privateKey, kp: obj.privateKeyPass }; - } + node.ssh = { u: obj.username, k: obj.privateKey }; + if (keep == 2) { node.ssh.kp = obj.privateKeyPass; } + } else return; parent.parent.db.Set(node); // Event node change if needed @@ -803,7 +829,7 @@ module.exports.CreateSshTerminalRelay = function (parent, db, ws, req, domain, u obj.sshClient = new Client(); obj.sshClient.on('ready', function () { // Authentication was successful. // If requested, save the credentials - if (obj.keep === true) saveSshCredentials(); + saveSshCredentials(obj.keep); obj.sessionid = Buffer.from(parent.crypto.randomBytes(9), 'binary').toString('base64'); obj.startTime = Date.now(); @@ -848,7 +874,7 @@ module.exports.CreateSshTerminalRelay = function (parent, db, ws, req, domain, u obj.relayActive = false; delete obj.sshClient; delete obj.ser.forwardwrite; - try { ws.send(JSON.stringify({ action: 'sshauth' })) } catch (ex) { } + try { ws.send(JSON.stringify({ action: 'sshauth', askkeypass: ((obj.username != null) && (obj.privateKey != null)) })) } catch (ex) { } } // We are all set, start receiving data @@ -894,7 +920,8 @@ module.exports.CreateSshTerminalRelay = function (parent, db, ws, req, domain, u if ((typeof msg.username != 'string') || ((typeof msg.password != 'string') && (typeof msg.key != 'string'))) break; if ((typeof msg.rows != 'number') || (typeof msg.cols != 'number') || (typeof msg.height != 'number') || (typeof msg.width != 'number')) break; - obj.keep = msg.keep; // If true, keep store credentials on the server if the SSH tunnel connected succesfully. + if (msg.keep === true) { msg.keep = 1; } // If true, change to 1. For user/pass, 1 to store user/pass in db. For user/key/pass, 1 to store user/key in db, 2 to store everything in db. + obj.keep = msg.keep; // If set, keep store credentials on the server if the SSH tunnel connected succesfully. obj.termSize = msg; obj.username = msg.username; obj.password = msg.password; @@ -912,6 +939,26 @@ module.exports.CreateSshTerminalRelay = function (parent, db, ws, req, domain, u startRelayConnection(parent.parent.encodeCookie(cookieContent, parent.parent.loginCookieEncryptionKey)); break; } + case 'sshkeyauth': { + // Verify inputs + if (typeof msg.keypass != 'string') break; + if ((typeof msg.rows != 'number') || (typeof msg.cols != 'number') || (typeof msg.height != 'number') || (typeof msg.width != 'number')) break; + + delete obj.keep; + obj.termSize = msg; + obj.privateKeyPass = msg.keypass; + + // Create a mesh relay authentication cookie + const cookieContent = { userid: user._id, domainid: user.domain, nodeid: obj.nodeid, tcpport: obj.tcpport }; + if (obj.relaynodeid) { + cookieContent.nodeid = obj.relaynodeid; + cookieContent.tcpaddr = obj.tcpaddr; + } else { + if (obj.mtype == 3) { cookieContent.lc = 1; } // This is a local device + } + startRelayConnection(parent.parent.encodeCookie(cookieContent, parent.parent.loginCookieEncryptionKey)); + break; + } case 'sshautoauth': { // Verify inputs if ((typeof msg.rows != 'number') || (typeof msg.cols != 'number') || (typeof msg.height != 'number') || (typeof msg.width != 'number')) break; @@ -984,6 +1031,11 @@ module.exports.CreateSshTerminalRelay = function (parent, db, ws, req, domain, u 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 if ((typeof node.ssh.k == 'string') && (typeof node.ssh.kp != 'string')) { + // Send a request for SSH authentication with option for only the private key password + obj.username = node.ssh.u; + obj.privateKey = node.ssh.k; + try { ws.send(JSON.stringify({ action: 'sshauth', askkeypass: true })) } catch (ex) { } } else { // Use our existing credentials obj.username = node.ssh.u; @@ -1071,8 +1123,8 @@ module.exports.CreateSshFilesRelay = function (parent, db, ws, req, domain, user }; // Save SSH credentials into device - function saveSshCredentials() { - if (domain.allowsavingdevicecredentials == false) return; + function saveSshCredentials(keep) { + if (((keep != 1) && (keep != 2)) || (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]; @@ -1085,8 +1137,9 @@ module.exports.CreateSshFilesRelay = function (parent, db, ws, req, domain, user if (obj.password != null) { node.ssh = { u: obj.username, p: obj.password }; } else if (obj.privateKey != null) { - node.ssh = { u: obj.username, k: obj.privateKey, kp: obj.privateKeyPass }; - } + node.ssh = { u: obj.username, k: obj.privateKey }; + if (keep == 2) { node.ssh.kp = obj.privateKeyPass; } + } else return; parent.parent.db.Set(node); // Event node change if needed @@ -1122,7 +1175,7 @@ module.exports.CreateSshFilesRelay = function (parent, db, ws, req, domain, user obj.sshClient = new Client(); obj.sshClient.on('ready', function () { // Authentication was successful. // If requested, save the credentials - if (obj.keep === true) saveSshCredentials(); + saveSshCredentials(obj.keep); obj.sessionid = Buffer.from(parent.crypto.randomBytes(9), 'binary').toString('base64'); obj.startTime = Date.now(); @@ -1161,7 +1214,7 @@ module.exports.CreateSshFilesRelay = function (parent, db, ws, req, domain, user obj.relayActive = false; delete obj.sshClient; delete obj.ser.forwardwrite; - try { ws.send(JSON.stringify({ action: 'sshauth' })) } catch (ex) { } + try { ws.send(JSON.stringify({ action: 'sshauth', askkeypass: ((obj.username != null) && (obj.privateKey != null)) })) } catch (ex) { } } // We are all set, start receiving data @@ -1390,12 +1443,33 @@ module.exports.CreateSshFilesRelay = function (parent, db, ws, req, domain, user // Verify inputs if ((typeof msg.username != 'string') || ((typeof msg.password != 'string') && (typeof msg.key != 'string'))) break; - obj.keep = (msg.keep === true); // If true, keep store credentials on the server if the SSH tunnel connected succesfully. + if (msg.keep === true) { msg.keep = 1; } // If true, change to 1. For user/pass, 1 to store user/pass in db. For user/key/pass, 1 to store user/key in db, 2 to store everything in db. + obj.keep = msg.keep; // If set, keep store credentials on the server if the SSH tunnel connected succesfully. obj.username = msg.username; obj.password = msg.password; obj.privateKey = msg.key; obj.privateKeyPass = msg.keypass; + // Create a mesh relay authentication cookie + const cookieContent = { userid: user._id, domainid: user.domain, nodeid: obj.nodeid, tcpport: obj.tcpport }; + if (obj.relaynodeid) { + cookieContent.nodeid = obj.relaynodeid; + cookieContent.tcpaddr = obj.tcpaddr; + } else { + if (obj.mtype == 3) { cookieContent.lc = 1; } // This is a local device + } + startRelayConnection(parent.parent.encodeCookie(cookieContent, parent.parent.loginCookieEncryptionKey)); + break; + } + case 'sshkeyauth': { + if (obj.sshClient != null) return; + + // Verify inputs + if (typeof msg.keypass != 'string') break; + + delete obj.keep; + obj.privateKeyPass = msg.keypass; + // Create a mesh relay authentication cookie const cookieContent = { userid: user._id, domainid: user.domain, nodeid: obj.nodeid, tcpport: obj.tcpport }; if (obj.relaynodeid) { @@ -1479,6 +1553,11 @@ module.exports.CreateSshFilesRelay = function (parent, db, ws, req, domain, user 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 if ((typeof node.ssh.k == 'string') && (typeof node.ssh.kp != 'string')) { + // Send a request for SSH authentication with option for only the private key password + obj.username = node.ssh.u; + obj.privateKey = node.ssh.k; + try { ws.send(JSON.stringify({ action: 'sshauth', askkeypass: true })) } catch (ex) { } } else { // Use our existing credentials obj.username = node.ssh.u; diff --git a/meshuser.js b/meshuser.js index fa6ec279..1e147db6 100644 --- a/meshuser.js +++ b/meshuser.js @@ -729,7 +729,16 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use if (docs[i].pmt != null) { docs[i].pmt = 1; } // Remove SSH credentials if present - if (docs[i].ssh != null) { docs[i].ssh = (docs[i].ssh.k) ? 2 : 1; } + if (docs[i].ssh != null) { + if (docs[i].ssh.u) { + if (docs[i].ssh.k && docs[i].ssh.kp) { docs[i].ssh = 2; } // Username, key and password + else if (docs[i].ssh.k) { docs[i].ssh = 3; } // Username and key. No password. + else if (docs[i].ssh.p) { docs[i].ssh = 1; } // Username and password + else { delete docs[i].ssh; } + } else { + delete docs[i].ssh; + } + } // Remove RDP credentials if present if (docs[i].rdp != null) { docs[i].rdp = 1; } diff --git a/views/default-mobile.handlebars b/views/default-mobile.handlebars index 22718d06..05c04dea 100644 --- a/views/default-mobile.handlebars +++ b/views/default-mobile.handlebars @@ -3528,10 +3528,10 @@ if ((node.ssh != null) || (node.rdp != null)) { var y = []; if ((meshrights & 4) != 0) { - if (node.ssh != null) { y.push('' + ((node.ssh == 2) ? "SSH-Key" : "SSH") + ' '); } + if (node.ssh != null) { y.push('' + ((node.ssh == 1) ? "SSH-User+Pass" : ((node.ssh == 2) ? "SSH-User+Key+Pass" : "SSH-User+Key")) + ' '); } if (node.rdp != null) { y.push('' + "RDP" + ' '); } } else { - if (node.ssh != null) { y.push(((node.ssh == 2) ? "SSH-Key" : "SSH")); } + if (node.ssh != null) { y.push(((node.ssh == 1) ? "SSH-User+Pass" : ((node.ssh == 2) ? "SSH-User+Key+Pass" : "SSH-User+Key"))); } if (node.rdp != null) { y.push("RDP"); } } x += addDeviceAttribute("Credentials", y.join(', ')); @@ -4750,12 +4750,46 @@ function tunnelUpdate(data) { if (typeof data == 'string') { xterm.writeUtf8(data); } else { xterm.writeUtf8(new Uint8Array(data)); } } + function sshTunnelAuthDialog(j, func) { + var x = ''; + if (j.askkeypass) { + x += addHtmlValue("Authentication", ''); + } else { + x += addHtmlValue("Authentication", ''); + } + x += ''; + x += ''; + if (j.askkeypass) { + x += ''; + } + setDialogMode(2, "Authentication", 11, func, x, 'ssh'); + Q('dp2user').focus(); + sshAuthUpdate(); + setTimeout(sshAuthUpdate, 50); + } + function sshTunnelUpdate(data) { if (typeof data == 'string') { if (data[0] == '{') { var j = JSON.parse(data); switch (j.action) { case 'sshauth': { + sshTunnelAuthDialog(j, sshConnectEx); + /* var x = ''; x += addHtmlValue("Authentication", '') x += addHtmlValue("Username", ''); @@ -4769,6 +4803,7 @@ x += '

' + "Key file must be in OpenSSH format." + '
'; setDialogMode(2, "Authentication", 11, sshConnectEx, x, 'ssh'); setTimeout(sshAuthUpdate, 50); + */ break; } case 'sshautoauth': { @@ -4783,6 +4818,7 @@ } } + /* function sshAuthUpdate(e) { QV('d2passauth', Q('dp2authmethod').value == 1); QV('d2keyauth', Q('dp2authmethod').value == 2); @@ -4815,6 +4851,57 @@ } } } + */ + + function sshAuthUpdate(e) { + QV('d2userauth', Q('dp2authmethod').value != 3); + QV('d2passauth', Q('dp2authmethod').value == 1); + QV('d2keyauth', Q('dp2authmethod').value == 2); + QV('d2keyauth2', Q('dp2authmethod').value == 3); + if (Q('dp2authmethod').value == 1) { + QE('idx_dlgOkButton', (Q('dp2user').value.length > 0) && (Q('dp2pass').value.length > 0)); + } else if (Q('dp2authmethod').value == 3) { + QE('idx_dlgOkButton', Q('dp2keypass2').value.length > 0); + } else { + QE('idx_dlgOkButton', false); + if ((features2 & 0x00400000) == 0) { QE('dp2keep2', Q('dp2keep1').checked); } + var ok = (Q('dp2user').value.length > 0) && (Q('dp2key').files != null) && (Q('dp2key').files.length == 1) && (Q('dp2key').files[0].size < 8000); + if (ok == true) { + var reader = new FileReader(); + reader.onload = function (e) { + var validkey = ((e.target.result.indexOf('-----BEGIN OPENSSH PRIVATE KEY-----') >= 0) && (e.target.result.indexOf('-----END OPENSSH PRIVATE KEY-----') >= 0)); + QE('idx_dlgOkButton', validkey); + QS('d2badkey')['color'] = validkey ? '#000' : '#F00'; + } + 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 = 0; + if (Q('dp2authmethod').value == 1) { + if ((features2 & 0x00400000) == 0) { keep = (Q('dp2keep').checked ? 1 : 0); } + 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 if (Q('dp2authmethod').value == 3) { + terminal.socket.send(JSON.stringify({ action: 'sshkeyauth', keypass: Q('dp2keypass2').value, cols: xterm.cols, rows: xterm.rows, width: Q('termarea3xdiv').offsetWidth, height: Q('termarea3xdiv').offsetHeight })); + } else { + if ((features2 & 0x00400000) == 0) { keep = (Q('dp2keep1').checked ? 1 : 0); if (keep == 1) { keep += (Q('dp2keep2').checked ? 1 : 0); } } // Keep: 1 = user & key, 2 = User, key and password + 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]); + } + } + } // Send the new terminal size to the agent function xTermSendResize() { @@ -5035,6 +5122,8 @@ // Process any SSH actions switch (data.action) { case 'sshauth': { + sshTunnelAuthDialog(data, p13sshConnectEx); + /* var x = ''; x += addHtmlValue("Authentication", '') x += addHtmlValue("Username", ''); @@ -5048,6 +5137,7 @@ x += '

' + "Key file must be in OpenSSH format." + '
'; setDialogMode(2, "Authentication", 11, p13sshConnectEx, x, 'ssh'); setTimeout(sshAuthUpdate, 50); + */ break; } case 'autherror': { p13setConsoleMsg("Authentication Error", 5000); return; } @@ -5088,10 +5178,15 @@ if (b == 0) { if (files != null) { connectFiles(); } // Disconnect } else { + var keep = 0; if (Q('dp2authmethod').value == 1) { - files.socket.send(JSON.stringify({ action: 'sshauth', username: Q('dp2user').value, password: Q('dp2pass').value, keep: Q('dp2keep').checked })); + if ((features2 & 0x00400000) == 0) { keep = (Q('dp2keep').checked ? 1 : 0); } + files.socket.send(JSON.stringify({ action: 'sshauth', username: Q('dp2user').value, password: Q('dp2pass').value, keep: keep })); + } else if (Q('dp2authmethod').value == 3) { + files.socket.send(JSON.stringify({ action: 'sshkeyauth', keypass: Q('dp2keypass2').value })); } else { - var reader = new FileReader(), username = Q('dp2user').value, keypass = Q('dp2keypass').value, keep = Q('dp2keep').checked; + if ((features2 & 0x00400000) == 0) { keep = (Q('dp2keep1').checked ? 1 : 0); if (keep == 1) { keep += (Q('dp2keep2').checked ? 1 : 0); } } // Keep: 1 = user & key, 2 = User, key and password + 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/default.handlebars b/views/default.handlebars index 88947859..5578782c 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -7007,10 +7007,10 @@ if ((node.ssh != null) || (node.rdp != null)) { var y = []; if ((meshrights & 4) != 0) { - if (node.ssh != null) { y.push('' + ((node.ssh == 2)?"SSH-Key":"SSH") + ' '); } + if (node.ssh != null) { y.push('' + ((node.ssh == 1)?"SSH-User+Pass":((node.ssh == 2)?"SSH-User+Key+Pass":"SSH-User+Key")) + ' '); } if (node.rdp != null) { y.push('' + "RDP" + ' '); } } else { - if (node.ssh != null) { y.push(((node.ssh == 2)?"SSH-Key":"SSH")); } + if (node.ssh != null) { y.push(((node.ssh == 1)?"SSH-User+Pass":((node.ssh == 2)?"SSH-User+Key+Pass":"SSH-User+Key"))); } if (node.rdp != null) { y.push("RDP"); } } x += addDeviceAttribute("Credentials", y.join(', ')); @@ -9660,25 +9660,45 @@ function tunnelUpdate(data) { if (typeof data == 'string') { xterm.writeUtf8(data); } else { xterm.writeUtf8(new Uint8Array(data)); } } + function sshTunnelAuthDialog(j, func) { + var x = ''; + if (j.askkeypass) { + x += addHtmlValue("Authentication", ''); + } else { + x += addHtmlValue("Authentication", ''); + } + x += ''; + x += ''; + if (j.askkeypass) { + x += ''; + } + setDialogMode(2, "Authentication", 11, func, x, 'ssh'); + Q('dp2user').focus(); + sshAuthUpdate(); + setTimeout(sshAuthUpdate, 50); + } + function sshTunnelUpdate(data) { if (typeof data == 'string') { if (data[0] == '{') { var j = JSON.parse(data); switch (j.action) { case 'sshauth': { - var x = ''; - x += addHtmlValue("Authentication", '') - x += addHtmlValue("Username", ''); - x += '
'; - x += addHtmlValue("Password", ''); - x += '
'; - if ((features2 & 0x00400000) == 0) { x += addHtmlValue('', ''); } - setDialogMode(2, "Authentication", 11, sshConnectEx, x, 'ssh'); - Q('dp2user').focus(); - setTimeout(sshAuthUpdate, 50); + sshTunnelAuthDialog(j, sshConnectEx); break; } case 'sshautoauth': { @@ -9695,12 +9715,17 @@ } function sshAuthUpdate(e) { + QV('d2userauth', Q('dp2authmethod').value != 3); QV('d2passauth', Q('dp2authmethod').value == 1); QV('d2keyauth', Q('dp2authmethod').value == 2); + QV('d2keyauth2', Q('dp2authmethod').value == 3); if (Q('dp2authmethod').value == 1) { QE('idx_dlgOkButton', (Q('dp2user').value.length > 0) && (Q('dp2pass').value.length > 0)); + } else if (Q('dp2authmethod').value == 3) { + QE('idx_dlgOkButton', Q('dp2keypass2').value.length > 0); } else { QE('idx_dlgOkButton', false); + if ((features2 & 0x00400000) == 0) { QE('dp2keep2', Q('dp2keep1').checked); } var ok = (Q('dp2user').value.length > 0) && (Q('dp2key').files != null) && (Q('dp2key').files.length == 1) && (Q('dp2key').files[0].size < 8000); if (ok == true) { var reader = new FileReader(); @@ -9724,12 +9749,14 @@ if (b == 0) { if (terminal != null) { connectTerminal(); } // Disconnect } else { - var keep = false; - if ((features2 & 0x00400000) == 0) { keep = Q('dp2keep').checked; } - + var keep = 0; if (Q('dp2authmethod').value == 1) { + if ((features2 & 0x00400000) == 0) { keep = (Q('dp2keep').checked ? 1 : 0); } 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 if (Q('dp2authmethod').value == 3) { + terminal.socket.send(JSON.stringify({ action: 'sshkeyauth', keypass: Q('dp2keypass2').value, cols: xterm.cols, rows: xterm.rows, width: Q('termarea3xdiv').offsetWidth, height: Q('termarea3xdiv').offsetHeight })); } else { + if ((features2 & 0x00400000) == 0) { keep = (Q('dp2keep1').checked ? 1 : 0); if (keep == 1) { keep += (Q('dp2keep2').checked ? 1 : 0); } } // Keep: 1 = user & key, 2 = User, key and password 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]); @@ -10068,22 +10095,7 @@ // Process any SSH actions switch (data.action) { - case 'sshauth': { - var x = ''; - x += addHtmlValue("Authentication", '') - x += addHtmlValue("Username", ''); - x += '
'; - x += addHtmlValue("Password", ''); - x += '
'; - if ((features2 & 0x00400000) == 0) { x += addHtmlValue('', ''); } - setDialogMode(2, "Authentication", 11, p13sshConnectEx, x, 'ssh'); - Q('dp2user').focus(); - setTimeout(sshAuthUpdate, 50); - return; - } + case 'sshauth': { sshTunnelAuthDialog(data, p13sshConnectEx); return; } case 'autherror': { p13setConsoleMsg("Authentication Error", 5000); return; } case 'connectionerror': { p13setConsoleMsg("Connection Error", 5000); return; } case 'sessionerror': { p13setConsoleMsg("Session expired", 5000); return; } @@ -10136,11 +10148,14 @@ if (b == 0) { if (files != null) { connectFiles(); } // Disconnect } else { - var keep = false; - if ((features2 & 0x00400000) == 0) { keep = Q('dp2keep').checked; } + var keep = 0; if (Q('dp2authmethod').value == 1) { + if ((features2 & 0x00400000) == 0) { keep = (Q('dp2keep').checked ? 1 : 0); } files.socket.send(JSON.stringify({ action: 'sshauth', username: Q('dp2user').value, password: Q('dp2pass').value, keep: keep })); + } else if (Q('dp2authmethod').value == 3) { + files.socket.send(JSON.stringify({ action: 'sshkeyauth', keypass: Q('dp2keypass2').value })); } else { + if ((features2 & 0x00400000) == 0) { keep = (Q('dp2keep1').checked ? 1 : 0); if (keep == 1) { keep += (Q('dp2keep2').checked ? 1 : 0); } } // Keep: 1 = user & key, 2 = User, key and password 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 6b656281..d384e033 100644 --- a/views/mstsc.handlebars +++ b/views/mstsc.handlebars @@ -80,7 +80,7 @@ var urlargs = parseUriArgs(); if (urlargs.key && (isAlphaNumeric(urlargs.key) == false)) { delete urlargs.key; } var cookie = '{{{cookie}}}'; - var serverCredentials = (decodeURIComponent('{{{serverCredentials}}}') == 'true'); + var serverCredentials = parseInt('{{{serverCredentials}}}'); var name = decodeURIComponent('{{{name}}}'); if (name != '') { document.title = name + ' - ' + document.title; } var features = parseInt('{{{features}}}'); @@ -97,7 +97,7 @@ QE('connectButton', false); } - if (serverCredentials == true) { + if (serverCredentials == 1) { QV('dropdowndomain', true); Q('d3coreMode').value = 1; } else { diff --git a/views/ssh.handlebars b/views/ssh.handlebars index 1ebffdf0..1c49cb34 100644 --- a/views/ssh.handlebars +++ b/views/ssh.handlebars @@ -130,12 +130,17 @@ } function sshAuthUpdate(e) { + QV('d2userauth', Q('dp2authmethod').value != 3); QV('d2passauth', Q('dp2authmethod').value == 1); QV('d2keyauth', Q('dp2authmethod').value == 2); + QV('d2keyauth2', Q('dp2authmethod').value == 3); if (Q('dp2authmethod').value == 1) { QE('idx_dlgOkButton', (Q('dp2user').value.length > 0) && (Q('dp2pass').value.length > 0)); + } else if (Q('dp2authmethod').value == 3) { + QE('idx_dlgOkButton', Q('dp2keypass2').value.length > 0); } else { QE('idx_dlgOkButton', false); + if ((features & 1) == 0) { QE('dp2keep2', Q('dp2keep1').checked); } var ok = (Q('dp2user').value.length > 0) && (Q('dp2key').files != null) && (Q('dp2key').files.length == 1) && (Q('dp2key').files[0].size < 8000); if (ok == true) { var reader = new FileReader(); @@ -156,13 +161,18 @@ } 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: false }; - if ((features & 1) == 0) { cmd.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: 0 }; if (Q('dp2authmethod').value == 1) { cmd.password = Q('dp2pass').value; + if ((features & 1) == 0) { cmd.keep = Q('dp2keep').checked ? 1 : 0; } + connectEx2(cmd); + } else if (Q('dp2authmethod').value == 3) { + cmd.action = 'connectKeyPass'; + cmd.keypass = Q('dp2keypass2').value; connectEx2(cmd); } else { + if ((features & 1) == 0) { cmd.keep = (Q('dp2keep1').checked ? 1 : 0); if (cmd.keep == 1) { cmd.keep += (Q('dp2keep2').checked ? 1 : 0); } } // Keep: 1 = user & key, 2 = User, key and password cmd.keypass = Q('dp2keypass').value; var reader = new FileReader(); reader.onload = function (e) { cmd.key = e.target.result; connectEx2(cmd); } @@ -170,6 +180,38 @@ } } + function sshTunnelAuthDialog(j, func) { + var x = ''; + if (j.askkeypass) { + x += addHtmlValue("Authentication", ''); + } else { + x += addHtmlValue("Authentication", ''); + } + x += ''; + x += ''; + if (j.askkeypass) { + x += ''; + } + setDialogMode(2, "Authentication", 11, func, x, 'ssh'); + Q('dp2user').focus(); + sshAuthUpdate(); + setTimeout(sshAuthUpdate, 50); + } + function connectEx2(cmd) { state = 1; var url = window.location.protocol.replace('http', 'ws') + '//' + window.location.host + domainurl + 'sshrelay.ashx?auth=' + cookie + (urlargs.key ? ('&key=' + urlargs.key) : ''); @@ -192,24 +234,7 @@ if ((json.ctrlChannel == 102938) && (json.type == 'ping')) { socket.send('{"ctrlChannel":"102938","type":"pong"}'); return; } switch (json.action) { case 'connected': { state = 3; updateState(); term.focus(); break; } - case 'sshauth': { - var x = ''; - x += addHtmlValue("Authentication", '') - x += addHtmlValue("Username", ''); - x += '
'; - x += addHtmlValue("Password", ''); - x += '
'; - if ((features & 1) == 0) { x += addHtmlValue('', ''); } - setDialogMode(2, "Authentication", 3, connectEx, x); - Q('dp2user').value = user; - Q('dp2pass').value = pass; - if (user == '') { Q('dp2user').focus(); } else { Q('dp2pass').focus(); } - setTimeout(sshAuthUpdate, 50); - break; - } + case 'sshauth': { sshTunnelAuthDialog(json, connectEx); break; } case 'autherror': { setDialogMode(2, "Authentication", 1, null, "Unable to authenticate."); break; } case 'sessionerror': { setDialogMode(2, "Session", 1, null, "Session expired."); break; } case 'sessiontimeout': { setDialogMode(2, "Session", 1, null, "Session timeout."); break; } diff --git a/webserver.js b/webserver.js index d16c95aa..49b45474 100644 --- a/webserver.js +++ b/webserver.js @@ -1962,13 +1962,15 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF 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 = false; + // Check if we have SSH/RDP credentials for this device + var serverCredentials = 0; if (domain.allowsavingdevicecredentials !== false) { if (page == 'ssh') { - serverCredentials = ((typeof node.ssh == 'object') && (typeof node.ssh.u == 'string')) + if ((typeof node.ssh == 'object') && (typeof node.ssh.u == 'string') && (typeof node.ssh.p == 'string')) { serverCredentials = 1; } // Username and password + else if ((typeof node.ssh == 'object') && (typeof node.ssh.k == 'string') && (typeof node.ssh.kp == 'string')) { serverCredentials = 2; } // Username, key and password + else if ((typeof node.ssh == 'object') && (typeof node.ssh.k == 'string')) { serverCredentials = 3; } // Username and key. No password. } else { - serverCredentials = ((typeof node.rdp == 'object') && (typeof node.rdp.d == 'string') && (typeof node.rdp.u == 'string') && (typeof node.rdp.p == 'string')) + if ((typeof node.rdp == 'object') && (typeof node.rdp.d == 'string') && (typeof node.rdp.u == 'string') && (typeof node.rdp.p == 'string')) { serverCredentials = 1; } // Username and password } } @@ -2028,14 +2030,20 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF 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')); } + if (domain.allowsavingdevicecredentials !== false) { + if ((typeof node.ssh == 'object') && (typeof node.ssh.u == 'string') && (typeof node.ssh.p == 'string')) { serverCredentials = 1; } // Username and password + else if ((typeof node.ssh == 'object') && (typeof node.ssh.k == 'string') && (typeof node.ssh.kp == 'string')) { serverCredentials = 2; } // Username, key and password + else if ((typeof node.ssh == 'object') && (typeof node.ssh.k == 'string')) { serverCredentials = 3; } // Username and key. No password. + } } 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 (domain.allowsavingdevicecredentials !== false) { + if ((typeof node.rdp == 'object') && (typeof node.rdp.d == 'string') && (typeof node.rdp.u == 'string') && (typeof node.rdp.p == 'string')) { serverCredentials = 1; } // Username and password + } } 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; } } @@ -7513,7 +7521,11 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF if ((r.pmt != null) || (r.ssh != null) || (r.rdp != null) || ((r.intelamt != null) && ((r.intelamt.pass != null) || (r.intelamt.mpspass != null)))) { r = Object.assign({}, r); // Shallow clone if (r.pmt != null) { r.pmt = 1; } - if (r.ssh != null) { r.ssh = (r.ssh.k != null) ? 2 : 1; } + if (r.ssh && r.ssh.u) { + if (r.ssh.p) { r.ssh = 1; } // Username and password + else if (r.ssh.k && r.ssh.kp) { r.ssh = 2; } // Username, key and password + else if (r.ssh.k) { r.ssh = 3; } // Username and key. No password. + } if (r.rdp != null) { r.rdp = 1; } if ((r.intelamt != null) && ((r.intelamt.pass != null) || (r.intelamt.mpspass != null))) { r.intelamt = Object.assign({}, r.intelamt); // Shallow clone