mirror of
https://github.com/Ylianst/MeshCentral.git
synced 2024-09-11 08:55:32 +03:00
More work on web based SSH support.
This commit is contained in:
parent
87dc3c354d
commit
abbb6be431
162
ssh.js
162
ssh.js
@ -21,13 +21,12 @@ module.exports.CreateSshRelay = function (parent, db, ws, req, args, domain) {
|
||||
var obj = {};
|
||||
obj.domain = domain;
|
||||
obj.ws = ws;
|
||||
obj.wsClient = null;
|
||||
obj.tcpServer = null;
|
||||
obj.tcpServerPort = 0;
|
||||
obj.relaySocket = null;
|
||||
obj.relayActive = false;
|
||||
obj.infos = null;
|
||||
var sshClient = null;
|
||||
obj.sshClient = null;
|
||||
obj.sshShell = null;
|
||||
obj.termSize = null;
|
||||
|
||||
parent.parent.debug('relay', 'SSH: Request for SSH relay (' + req.clientIp + ')');
|
||||
|
||||
@ -35,102 +34,75 @@ module.exports.CreateSshRelay = function (parent, db, ws, req, args, domain) {
|
||||
obj.close = function (arg) {
|
||||
if ((arg == 1) || (arg == null)) { try { ws.close(); } catch (e) { console.log(e); } } // Soft close, close the websocket
|
||||
if (arg == 2) { try { ws._socket._parent.end(); } catch (e) { console.log(e); } } // Hard close, close the TCP socket
|
||||
if (obj.wsClient) { obj.wsClient.close(); obj.wsClient = null; }
|
||||
if (obj.tcpServer) { obj.tcpServer.close(); obj.tcpServer = null; }
|
||||
if (sshClient) { sshClient.close(); sshClient = null; }
|
||||
//if (obj.wsClient) { obj.wsClient.close(); obj.wsClient = null; }
|
||||
//if (obj.tcpServer) { obj.tcpServer.close(); obj.tcpServer = null; }
|
||||
//if (sshClient) { sshClient.close(); sshClient = null; }
|
||||
|
||||
if (obj.sshClient != null) {
|
||||
try { obj.sshClient.end(); } catch (ex) { console.log(ex); }
|
||||
delete obj.sshClient;
|
||||
}
|
||||
if (obj.sshShell != null) {
|
||||
try { obj.sshShell.end(); } catch (ex) { console.log(ex); }
|
||||
delete obj.sshShell;
|
||||
}
|
||||
|
||||
delete obj.domain;
|
||||
delete obj.ws;
|
||||
};
|
||||
|
||||
// Start the looppback server
|
||||
function startTcpServer() {
|
||||
obj.tcpServer = new Net.Server();
|
||||
obj.tcpServer.listen(0, '127.0.0.1', function () { obj.tcpServerPort = obj.tcpServer.address().port; startSSH(obj.tcpServerPort); });
|
||||
obj.tcpServer.on('connection', function (socket) {
|
||||
if (obj.relaySocket != null) {
|
||||
socket.close();
|
||||
} else {
|
||||
obj.relaySocket = socket;
|
||||
obj.relaySocket.pause();
|
||||
obj.relaySocket.on('data', function (chunk) { // Make sure to handle flow control.
|
||||
if (obj.relayActive == true) { obj.relaySocket.pause(); obj.wsClient.send(chunk, function () { obj.relaySocket.resume(); }); }
|
||||
});
|
||||
obj.relaySocket.on('end', function () { obj.close(); });
|
||||
obj.relaySocket.on('error', function (err) { obj.close(); });
|
||||
|
||||
// Decode the authentication cookie
|
||||
var cookie = parent.parent.decodeCookie(obj.infos.ip, parent.parent.loginCookieEncryptionKey);
|
||||
if (cookie == null) return;
|
||||
|
||||
// Setup the correct URL with domain and use TLS only if needed.
|
||||
var options = { rejectUnauthorized: false };
|
||||
if (domain.dns != null) { options.servername = domain.dns; }
|
||||
var protocol = 'wss';
|
||||
if (args.tlsoffload) { protocol = 'ws'; }
|
||||
var domainadd = '';
|
||||
if ((domain.dns == null) && (domain.id != '')) { domainadd = domain.id + '/' }
|
||||
var url = protocol + '://127.0.0.1:' + args.port + '/' + domainadd + ((cookie.lc == 1)?'local':'mesh') + 'relay.ashx?noping=1&auth=' + obj.infos.ip;
|
||||
parent.parent.debug('relay', 'SSH: Connection websocket to ' + url);
|
||||
obj.wsClient = new WebSocket(url, options);
|
||||
obj.wsClient.on('open', function () { parent.parent.debug('relay', 'SSH: Relay websocket open'); });
|
||||
obj.wsClient.on('message', function (data) { // Make sure to handle flow control.
|
||||
if ((obj.relayActive == false) && (data == 'c')) {
|
||||
obj.relayActive = true; obj.relaySocket.resume();
|
||||
} else {
|
||||
obj.wsClient._socket.pause();
|
||||
obj.relaySocket.write(data, function () { obj.wsClient._socket.resume(); });
|
||||
}
|
||||
});
|
||||
obj.wsClient.on('close', function () { parent.parent.debug('relay', 'SSH: Relay websocket closed'); obj.close(); });
|
||||
obj.wsClient.on('error', function (err) { parent.parent.debug('relay', 'SSH: Relay websocket error: ' + err); obj.close(); });
|
||||
obj.tcpServer.close();
|
||||
obj.tcpServer = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Start the SSH client
|
||||
function startSSH(port) {
|
||||
parent.parent.debug('relay', 'SSH: Starting SSH client on loopback port ' + port);
|
||||
try {
|
||||
sshClient = require('node-rdpjs-2').createClient({
|
||||
logLevel: 'ERROR',
|
||||
domain: obj.infos.domain,
|
||||
userName: obj.infos.username,
|
||||
password: obj.infos.password,
|
||||
enablePerf: true,
|
||||
autoLogin: true,
|
||||
screen: obj.infos.screen,
|
||||
locale: obj.infos.locale
|
||||
}).on('connect', function () {
|
||||
send(['rdp-connect']);
|
||||
}).on('bitmap', function (bitmap) {
|
||||
try { ws.send(bitmap.data); } catch (ex) { } // Send the bitmap data as binary
|
||||
delete bitmap.data;
|
||||
send(['rdp-bitmap', bitmap]); // Send the bitmap metadata seperately, without bitmap data.
|
||||
}).on('close', function () {
|
||||
send(['rdp-close']);
|
||||
}).on('error', function (err) {
|
||||
send(['rdp-error', err]);
|
||||
}).connect('127.0.0.1', obj.tcpServerPort);
|
||||
} catch (ex) {
|
||||
console.log('startSshException', ex);
|
||||
obj.close();
|
||||
}
|
||||
}
|
||||
|
||||
// When data is received from the web socket
|
||||
// SSH default port is 22
|
||||
ws.on('message', function (msg) {
|
||||
try {
|
||||
msg = JSON.parse(msg);
|
||||
switch (msg[0]) {
|
||||
case 'infos': { obj.infos = msg[1]; startTcpServer(); break; }
|
||||
case 'mouse': { if (sshClient) { sshClient.sendPointerEvent(msg[1], msg[2], msg[3], msg[4]); } break; }
|
||||
case 'wheel': { if (sshClient) { sshClient.sendWheelEvent(msg[1], msg[2], msg[3], msg[4]); } break; }
|
||||
case 'scancode': { if (sshClient) { sshClient.sendKeyEventScancode(msg[1], msg[2]); } break; }
|
||||
case 'unicode': { if (sshClient) { sshClient.sendKeyEventUnicode(msg[1], msg[2]); } break; }
|
||||
case 'disconnect': { obj.close(); break; }
|
||||
if (typeof msg != 'string') return;
|
||||
if (msg[0] == '{') {
|
||||
// Control data
|
||||
msg = JSON.parse(msg);
|
||||
if (typeof msg.action != 'string') return;
|
||||
switch (msg.action) {
|
||||
case 'connect': {
|
||||
obj.termSize = msg;
|
||||
const Client = require('ssh2').Client;
|
||||
obj.sshClient = new Client();
|
||||
|
||||
obj.sshClient.on('ready', function () { // Authentication was successful.
|
||||
obj.sshClient.shell(function (err, stream) {
|
||||
if (err) { obj.close(); return; }
|
||||
obj.sshShell = stream;
|
||||
obj.sshShell.setWindow(obj.termSize.rows, obj.termSize.cols, obj.termSize.height, obj.termSize.width);
|
||||
obj.sshShell.on('close', function () { obj.close(); });
|
||||
obj.sshShell.on('data', function (data) { obj.ws.send('~' + data); });
|
||||
});
|
||||
obj.ws.send(JSON.stringify({ action: 'connected' }));
|
||||
});
|
||||
obj.sshClient.on('error', function (err) {
|
||||
if (err.level == 'client-authentication') { obj.ws.send(JSON.stringify({ action: 'autherror' })); }
|
||||
obj.close();
|
||||
});
|
||||
|
||||
var connectionOptions = {
|
||||
//debug: function (msg) { console.log(msg); },
|
||||
// sock: // TODO
|
||||
host: '192.168.2.205',
|
||||
port: 22
|
||||
}
|
||||
|
||||
if (typeof msg.username == 'string') { connectionOptions.username = msg.username; }
|
||||
if (typeof msg.password == 'string') { connectionOptions.password = msg.password; }
|
||||
|
||||
obj.sshClient.connect(connectionOptions);
|
||||
break;
|
||||
}
|
||||
case 'resize': {
|
||||
obj.termSize = msg;
|
||||
if (obj.sshShell != null) { obj.sshShell.setWindow(obj.termSize.rows, obj.termSize.cols, obj.termSize.height, obj.termSize.width); }
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (msg[0] == '~') {
|
||||
// Terminal data
|
||||
if (obj.sshShell != null) { obj.sshShell.write(msg.substring(1)); }
|
||||
}
|
||||
} catch (ex) {
|
||||
console.log('SSHMessageException', msg, ex);
|
||||
@ -146,8 +118,10 @@ module.exports.CreateSshRelay = function (parent, db, ws, req, args, domain) {
|
||||
|
||||
// Send an object with flow control
|
||||
function send(obj) {
|
||||
try { sshClient.bufferLayer.socket.pause(); } catch (ex) { }
|
||||
try { ws.send(JSON.stringify(obj), function () { try { sshClient.bufferLayer.socket.resume(); } catch (ex) { } }); } catch (ex) { }
|
||||
//try { sshClient.bufferLayer.socket.pause(); } catch (ex) { }
|
||||
//try { ws.send(JSON.stringify(obj), function () { try { sshClient.bufferLayer.socket.resume(); } catch (ex) { } }); } catch (ex) { }
|
||||
|
||||
try { ws.send(JSON.stringify(obj), function () { }); } catch (ex) { }
|
||||
}
|
||||
|
||||
// We are all set, start receiving data
|
||||
|
@ -653,7 +653,6 @@
|
||||
"sharing.handlebars->11->19",
|
||||
"sharing.handlebars->11->27",
|
||||
"sharing.handlebars->11->44",
|
||||
"ssh.handlebars->9->6",
|
||||
"terminal.handlebars->3->9",
|
||||
"xterm.handlebars->9->6"
|
||||
]
|
||||
@ -4363,7 +4362,6 @@
|
||||
"zh-cht": "管理員PowerShell",
|
||||
"xloc": [
|
||||
"default.handlebars->termShellContextMenu->3",
|
||||
"ssh.handlebars->termShellContextMenu->cxtermps",
|
||||
"xterm.handlebars->termShellContextMenu->cxtermps"
|
||||
]
|
||||
},
|
||||
@ -4405,7 +4403,6 @@
|
||||
"zh-cht": "管理控制台",
|
||||
"xloc": [
|
||||
"default.handlebars->termShellContextMenu->1->0",
|
||||
"ssh.handlebars->termShellContextMenu->cxtermnorm->0",
|
||||
"xterm.handlebars->termShellContextMenu->cxtermnorm->0"
|
||||
]
|
||||
},
|
||||
@ -9704,7 +9701,7 @@
|
||||
"default.handlebars->31->11",
|
||||
"desktop.handlebars->3->4",
|
||||
"sharing.handlebars->11->4",
|
||||
"ssh.handlebars->9->4",
|
||||
"ssh.handlebars->3->4",
|
||||
"terminal.handlebars->3->4",
|
||||
"xterm.handlebars->9->4"
|
||||
]
|
||||
@ -9818,7 +9815,7 @@
|
||||
"desktop.handlebars->3->2",
|
||||
"sharing.handlebars->11->2",
|
||||
"sharing.handlebars->11->93",
|
||||
"ssh.handlebars->9->2",
|
||||
"ssh.handlebars->3->2",
|
||||
"terminal.handlebars->3->2",
|
||||
"xterm.handlebars->9->2"
|
||||
]
|
||||
@ -13384,7 +13381,7 @@
|
||||
"sharing.handlebars->p11->deskarea0->deskarea1->3->deskstatus",
|
||||
"sharing.handlebars->p12->5->3->termstatus",
|
||||
"sharing.handlebars->p13->p13toolbar->1->3->p13Status",
|
||||
"ssh.handlebars->9->1",
|
||||
"ssh.handlebars->3->1",
|
||||
"terminal.handlebars->3->1",
|
||||
"terminal.handlebars->p12->5->3->termstatus",
|
||||
"xterm.handlebars->9->1"
|
||||
@ -17987,6 +17984,9 @@
|
||||
"default.handlebars->31->106"
|
||||
]
|
||||
},
|
||||
{
|
||||
"en": "Geen Intel® AMT apparaten in deze apparaatgroep"
|
||||
},
|
||||
{
|
||||
"cs": "Obecné",
|
||||
"de": "Allgemein",
|
||||
@ -20275,7 +20275,6 @@
|
||||
"default.handlebars->31->12",
|
||||
"desktop.handlebars->3->5",
|
||||
"sharing.handlebars->11->5",
|
||||
"ssh.handlebars->9->5",
|
||||
"terminal.handlebars->3->5",
|
||||
"xterm.handlebars->9->5"
|
||||
]
|
||||
@ -23925,7 +23924,6 @@
|
||||
"zh-cht": "登入控制台",
|
||||
"xloc": [
|
||||
"default.handlebars->termShellContextMenuLinux->5",
|
||||
"ssh.handlebars->termShellContextMenuLinux->cxtermps",
|
||||
"xterm.handlebars->termShellContextMenuLinux->cxtermps"
|
||||
]
|
||||
},
|
||||
@ -27216,7 +27214,6 @@
|
||||
},
|
||||
{
|
||||
"en": "No Intel® AMT devices in this device group",
|
||||
"en": "Geen Intel® AMT apparaten in deze apparaatgroep",
|
||||
"xloc": [
|
||||
"default.handlebars->31->262"
|
||||
]
|
||||
@ -33820,7 +33817,6 @@
|
||||
"zh-cht": "Root Shell",
|
||||
"xloc": [
|
||||
"default.handlebars->termShellContextMenuLinux->1->0",
|
||||
"ssh.handlebars->termShellContextMenuLinux->cxtermnorm->0",
|
||||
"xterm.handlebars->termShellContextMenuLinux->cxtermnorm->0"
|
||||
]
|
||||
},
|
||||
@ -36413,7 +36409,7 @@
|
||||
"default.handlebars->31->331",
|
||||
"desktop.handlebars->3->3",
|
||||
"sharing.handlebars->11->3",
|
||||
"ssh.handlebars->9->3",
|
||||
"ssh.handlebars->3->3",
|
||||
"terminal.handlebars->3->3",
|
||||
"xterm.handlebars->9->3"
|
||||
]
|
||||
@ -42715,7 +42711,6 @@
|
||||
"zh-cht": "用戶PowerShell",
|
||||
"xloc": [
|
||||
"default.handlebars->termShellContextMenu->7",
|
||||
"ssh.handlebars->termShellContextMenu->cxtermups",
|
||||
"xterm.handlebars->termShellContextMenu->cxtermups"
|
||||
]
|
||||
},
|
||||
@ -42758,8 +42753,6 @@
|
||||
"xloc": [
|
||||
"default.handlebars->termShellContextMenu->5",
|
||||
"default.handlebars->termShellContextMenuLinux->3",
|
||||
"ssh.handlebars->termShellContextMenu->cxtermunorm",
|
||||
"ssh.handlebars->termShellContextMenuLinux->cxtermps",
|
||||
"xterm.handlebars->termShellContextMenu->cxtermunorm",
|
||||
"xterm.handlebars->termShellContextMenuLinux->cxtermps"
|
||||
]
|
||||
@ -47962,4 +47955,4 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -80,6 +80,8 @@
|
||||
var StatusStrs = ["Disconnected", "Connecting...", "Setup...", "Connected"];
|
||||
var state = 0;
|
||||
var socket = null;
|
||||
var user = '';
|
||||
var pass = '';
|
||||
|
||||
function start() {
|
||||
// When the user resizes the window, re-fit
|
||||
@ -94,9 +96,7 @@
|
||||
term = new Terminal();
|
||||
if (termfit) { term.loadAddon(termfit); }
|
||||
term.open(Q('terminal'));
|
||||
term.onData(function (data) {
|
||||
//if (tunnel != null) { tunnel.sendText(data); }
|
||||
});
|
||||
term.onData(function (data) { if (state == 3) { socket.send('~' + data); } });
|
||||
if (termfit) { termfit.fit(); }
|
||||
term.onResize(function (size) {
|
||||
// Despam resize
|
||||
@ -109,27 +109,67 @@
|
||||
// Send the new terminal size to the agent
|
||||
function sendResize() {
|
||||
resizeTimer = null;
|
||||
//if ((term != null) && (tunnel != null)) { tunnel.sendCtrlMsg(JSON.stringify({ ctrlChannel: '102938', type: 'termsize', cols: term.cols, rows: term.rows })); }
|
||||
if (socket != null) { socket.send(JSON.stringify({ action: 'resize', cols: term.cols, rows: term.rows, width: Q('terminal').offsetWidth, height: Q('terminal').offsetHeight })); }
|
||||
}
|
||||
|
||||
function connectButton() {
|
||||
if (state == 0) {
|
||||
state = 1;
|
||||
var url = window.location.protocol.replace('http', 'ws') + '//' + window.location.host + domainurl + 'ssh/relay.ashx?auth=' + cookie + (urlargs.key ? ('&key=' + urlargs.key) : '');
|
||||
console.log('Connecting to ' + url);
|
||||
socket = new WebSocket(url);
|
||||
socket.onopen = function (e) { console.log('open'); state = 2; updateState(); }
|
||||
socket.onmessage = function (e) { console.log('message'); }
|
||||
socket.onclose = function (e) { console.log('close'); disconnect(); }
|
||||
socket.onerror = function (e) { console.log('error'); disconnect(); }
|
||||
updateState();
|
||||
var x = '';
|
||||
x += addHtmlValue("Username", '<input id=dp2user style=width:230px maxlength=64 autocomplete=off onkeyup=authKeyUp(event) />');
|
||||
x += addHtmlValue("Password", '<input type=password id=dp2pass style=width:230px maxlength=64 autocomplete=off onkeyup=authKeyUp(event) />');
|
||||
setDialogMode(2, "Authentication", 3, connectEx, x);
|
||||
Q('dp2user').value = user;
|
||||
Q('dp2pass').value = pass;
|
||||
if (user == '') { Q('dp2user').focus(); } else { Q('dp2pass').focus(); }
|
||||
setTimeout(authKeyUp, 50);
|
||||
} else {
|
||||
disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
function authKeyUp(e) { QE('idx_dlgOkButton', (Q('dp2user').value.length > 0) && (Q('dp2pass').value.length > 0)); }
|
||||
|
||||
function connectEx() {
|
||||
user = Q('dp2user').value;
|
||||
pass = Q('dp2pass').value;
|
||||
state = 1;
|
||||
var url = window.location.protocol.replace('http', 'ws') + '//' + window.location.host + domainurl + 'ssh/relay.ashx?auth=' + cookie + (urlargs.key ? ('&key=' + urlargs.key) : '');
|
||||
socket = new WebSocket(url);
|
||||
socket.onopen = function (e) {
|
||||
state = 2;
|
||||
updateState();
|
||||
term.reset();
|
||||
|
||||
// Send username and terminal width and height
|
||||
socket.send(JSON.stringify({ action: 'connect', username: user, password: pass, cols: term.cols, rows: term.rows, width: Q('terminal').offsetWidth, height: Q('terminal').offsetHeight }));
|
||||
pass = '';
|
||||
}
|
||||
socket.onmessage = function (data) {
|
||||
if (typeof data.data != 'string') return;
|
||||
if (data.data[0] == '{') {
|
||||
var json = JSON.parse(data.data);
|
||||
switch (json.action) {
|
||||
case 'connected': {
|
||||
state = 3;
|
||||
updateState();
|
||||
term.focus();
|
||||
break;
|
||||
}
|
||||
case 'autherror': {
|
||||
setDialogMode(2, "Authentication", 1, null, "Unable to authenticate.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (data.data[0] == '~') {
|
||||
term.writeUtf8(data.data.substring(1));
|
||||
}
|
||||
}
|
||||
socket.onclose = function (e) { disconnect(); }
|
||||
socket.onerror = function (e) { disconnect(); }
|
||||
updateState();
|
||||
}
|
||||
|
||||
function disconnect() {
|
||||
console.log('disconnect');
|
||||
if (socket != null) { socket.close(); socket = null; }
|
||||
state = 0;
|
||||
updateState();
|
||||
@ -186,6 +226,7 @@
|
||||
function format(format) { var args = Array.prototype.slice.call(arguments, 1); return format.replace(/{(\d+)}/g, function (match, number) { return typeof args[number] != 'undefined' ? args[number] : match; }); };
|
||||
function isAlphaNumeric(str) { return (str.match(/^[A-Za-z0-9]+$/) != null); };
|
||||
function isSafeString(str) { return ((typeof str == 'string') && (str.indexOf('<') == -1) && (str.indexOf('>') == -1) && (str.indexOf('&') == -1) && (str.indexOf('"') == -1) && (str.indexOf('\'') == -1) && (str.indexOf('+') == -1) && (str.indexOf('(') == -1) && (str.indexOf(')') == -1) && (str.indexOf('#') == -1) && (str.indexOf('%') == -1) && (str.indexOf(':') == -1)) };
|
||||
function addHtmlValue(t, v) { return '<table><td style=width:120px>' + t + '<td><b>' + v + '</b></table>'; }
|
||||
|
||||
// Parse URL arguments, only keep safe values
|
||||
function parseUriArgs() {
|
||||
|
@ -5576,7 +5576,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||
}
|
||||
|
||||
// Setup SSH if needed
|
||||
if (domain.mstsc === true) {
|
||||
if (domain.ssh === true) {
|
||||
obj.app.get(url + 'ssh.html', function (req, res) { handleMSTSCRequest(req, res, 'ssh'); });
|
||||
obj.app.ws(url + 'ssh/relay.ashx', function (ws, req) {
|
||||
const domain = getDomain(req);
|
||||
|
Loading…
Reference in New Issue
Block a user