webssh2/index.js
billchurch 17233f11de fixed auto fit and column/row settings for SSH, added experimental client-side logging
line wrapping should work correctly now.
experimental client side logging.
2017-03-23 18:12:33 -04:00

142 lines
5.9 KiB
JavaScript

/*
* WebSSH2 - Web to SSH2 gateway
* Bill Church - https://github.com/billchurch - April 2016
*
*/
var express = require('express');
var app = express();
var cookieParser = require('cookie-parser');
var server = require('http').Server(app);
var io = require('socket.io')(server);
var path = require('path');
var basicAuth = require('basic-auth');
var ssh = require('ssh2').Client;
var readConfig = require('read-config'),
config = readConfig(__dirname + '/config.json');
var myError = " - ";
var termCols;
var termRows;
function logErrors(err, req, res, next) {
console.error(err.stack);
next(err);
}
server.listen({
host: config.listen.ip,
port: config.listen.port
}).on('error', function (err) {
if (err.code === 'EADDRINUSE') {
config.listen.port++;
console.log('Address in use, retrying on port ' + config.listen.port);
setTimeout(function () {
server.listen(config.listen.port);
}, 250);
}
});
app.use(express.static(__dirname + '/public')).use(function(req, res, next) {
var myAuth = basicAuth(req);
if (myAuth === undefined) {
res.statusCode = 401;
res.setHeader('WWW-Authenticate', 'Basic realm="WebSSH"');
res.end('Username and password required for web SSH service.');
} else if (myAuth.name === "") {
res.statusCode = 401;
res.setHeader('WWW-Authenticate', 'Basic realm="WebSSH"');
res.end('Username and password required for web SSH service.');
} else {
config.user.name = myAuth.name;
config.user.password = myAuth.pass;
next();
}
}).use(cookieParser()).get('/ssh/host/:host?', function(req, res) {
res.sendFile(path.join(__dirname + '/public/client.htm'));
config.ssh.host = req.params.host;
if (typeof req.query.port !== 'undefined' && req.query.port !== null){ config.ssh.port = req.query.port;}
if (typeof req.query.header !== 'undefined' && req.query.header !== null){ config.header.text = req.query.header;}
if (typeof req.query.headerBackground !== 'undefined' && req.query.headerBackground !== null){ config.header.background = req.query.headerBackground;}
console.log ('webssh2 Login: user=' + config.user.name + ' from=' + req.ip + ' host=' + config.ssh.host + ' port=' + config.ssh.port + ' sessionID=' + req.headers.sessionid + ' allowreplay=' + req.headers.allowreplay);
console.log ('Headers: ' + JSON.stringify(req.headers));
config.options.allowreplay = req.headers.allowreplay;
}).use('/style',express.static(__dirname + '/public')).use('/src',express.static(__dirname + '/node_modules/xterm/dist')).use('/addons',express.static(__dirname + '/node_modules/xterm/dist/addons'));
io.on('connection', function(socket) {
var conn = new ssh();
socket.on('geometry', function (cols, rows) {
termCols = cols;
termRows = rows;
});
conn.on('banner', function(d) {
//need to convert to cr/lf for proper formatting
d = d.replace(/\r?\n/g, "\r\n");
socket.emit('data', d.toString('binary'));
}).on('ready', function() {
socket.emit('title', 'ssh://' + config.ssh.host);
socket.emit('headerBackground', config.header.background);
socket.emit('header', config.header.text);
socket.emit('footer', 'ssh://' + config.user.name + '@' + config.ssh.host + ':' + config.ssh.port);
socket.emit('status', 'SSH CONNECTION ESTABLISHED');
socket.emit('statusBackground', 'green');
socket.emit('allowreplay', config.options.allowreplay);
conn.shell( { term: config.ssh.term, cols: termCols, rows: termRows }, function(err, stream) {
if (err) {
console.log (err.message);
myError = myError + err.message;
return socket.emit('status', 'SSH EXEC ERROR: ' + err.message).emit('statusBackground', 'red');
}
socket.on('data', function(data) {
stream.write(data);
});
socket.on('control', function(controlData) {
switch(controlData) {
case 'replayCredentials':
stream.write(config.user.password + '\n');
/* falls through */
default:
console.log ('controlData: '+ controlData);
}
});
stream.on('data', function(d) {
socket.emit('data', d.toString('binary'));
}).on('close', function(code, signal) {
console.log('Stream :: close :: code: ' + code + ', signal: ' + signal);
conn.end();
socket.disconnect();
}).stderr.on('data', function(data) {
console.log('STDERR: ' + data);
});
});
}).on('end', function() {
socket.emit('status', 'SSH CONNECTION CLOSED BY HOST' + myError);
socket.emit('statusBackground', 'red');
socket.disconnect();
}).on('close', function() {
socket.emit('status', 'SSH CONNECTION CLOSE' + myError);
socket.emit('statusBackground', 'red');
socket.disconnect();
}).on('error', function(err) {
myError = myError + err;
socket.emit('status', 'SSH CONNECTION ERROR' + myError);
socket.emit('statusBackground', 'red');
console.log('on.error' + myError);
}).on('keyboard-interactive', function(name, instructions, instructionsLang, prompts, finish) {
console.log('Connection :: keyboard-interactive');
finish([config.user.password]);
}).connect({
host: config.ssh.host,
port: config.ssh.port,
username: config.user.name,
password: config.user.password,
tryKeyboard: true,
// some cisco routers need the these cipher strings
algorithms: {
'cipher': ['aes128-cbc', '3des-cbc', 'aes256-cbc'],
'hmac': ['hmac-sha1', 'hmac-sha1-96', 'hmac-md5-96']
}
});
});