216 lines
7.1 KiB
JavaScript
216 lines
7.1 KiB
JavaScript
/*
|
|
* WebSSH2 - Web to SSH2 gateway
|
|
* Bill Church - https://github.com/billchurch/WebSSH2 - May 2017
|
|
*
|
|
*/
|
|
var express = require('express')
|
|
var app = express()
|
|
var server = require('http').Server(app)
|
|
var io = require('socket.io')(server)
|
|
var path = require('path')
|
|
var SSH = require('ssh2').Client
|
|
var config = require('read-config')(path.join(__dirname, 'config.json'))
|
|
var debug = require('debug')
|
|
var debugWebSSH2 = debug('WebSSH2')
|
|
var util = require('./util')
|
|
var SocketUtil = require('./socket')
|
|
var session = require('express-session')({
|
|
secret: config.session.secret,
|
|
name: config.session.name,
|
|
resave: true,
|
|
saveUninitialized: false
|
|
})
|
|
var colors = require('colors/safe')
|
|
var termCols, termRows
|
|
// var LogPrefix
|
|
// var dataBuffer = ''
|
|
|
|
// server
|
|
|
|
server.listen({
|
|
host: config.listen.ip,
|
|
port: config.listen.port
|
|
})
|
|
|
|
server.on('error', function (err) {
|
|
if (err.code === 'EADDRINUSE') {
|
|
config.listen.port++
|
|
console.warn('Address in use, retrying on port ' + config.listen.port)
|
|
setTimeout(function () {
|
|
server.listen(config.listen.port)
|
|
}, 250)
|
|
} else {
|
|
console.log('server.listen ERROR: ' + err.code)
|
|
}
|
|
})
|
|
|
|
// express
|
|
var expressOptions = {
|
|
dotfiles: 'ignore',
|
|
etag: false,
|
|
extensions: ['htm', 'html'],
|
|
index: false,
|
|
maxAge: '1s',
|
|
redirect: false,
|
|
setHeaders: function (res, path, stat) {
|
|
res.set('x-timestamp', Date.now())
|
|
}
|
|
}
|
|
|
|
app.use(session)
|
|
app.use(util.basicAuth)
|
|
|
|
app.disable('x-powered-by')
|
|
|
|
app.use(express.static(path.join(__dirname, 'public'), expressOptions))
|
|
|
|
app.get('/ssh/host/:host?', function (req, res, next) {
|
|
res.sendFile(path.join(path.join(__dirname, 'public', 'client.htm')))
|
|
// capture url variables if defined
|
|
config.ssh.host = req.params.host || config.ssh.host
|
|
config.ssh.port = req.query.port || config.ssh.port
|
|
config.header.text = req.query.header || config.header.text
|
|
config.header.background = req.query.headerBackground || config.header.background
|
|
console.log('webssh2 Login: user=' + req.session.username + ' from=' + req.ip + ' host=' + config.ssh.host + ' port=' + config.ssh.port + ' sessionID=' + req.sessionID + ' allowreplay=' + req.headers.allowreplay)
|
|
// LogPrefix = req.session.username + '@' + req.ip + ' ssh://' + config.ssh.host + ':' + config.ssh.port + '/' + req.sessionID
|
|
debugWebSSH2('Headers: ' + colors.yellow(JSON.stringify(req.headers)))
|
|
config.options.allowreplay = req.headers.allowreplay
|
|
})
|
|
|
|
app.use('/style', express.static(path.join(__dirname, 'public')))
|
|
|
|
app.use('/src', express.static(path.join(__dirname, 'node_modules', 'xterm', 'dist')))
|
|
|
|
app.use('/addons', express.static(path.join(__dirname, 'node_modules', 'xterm', 'dist', 'addons')))
|
|
|
|
app.use(function (req, res, next) {
|
|
res.status(404).send("Sorry can't find that!")
|
|
})
|
|
|
|
app.use(function (err, req, res, next) {
|
|
console.error(err.stack)
|
|
res.status(500).send('Something broke!')
|
|
})
|
|
|
|
// socket.io
|
|
|
|
io.use(function (socket, next) {
|
|
if (socket.request.res) {
|
|
session(socket.request, socket.request.res, next)
|
|
} else {
|
|
next()
|
|
}
|
|
})
|
|
|
|
io.on('connection', function (socket) {
|
|
// if websocket connection arrives without an express session, kill it
|
|
if (!socket.request.session) {
|
|
socket.disconnect(true)
|
|
return
|
|
}
|
|
var socketutil = new SocketUtil(socket, io)
|
|
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'))
|
|
})
|
|
|
|
conn.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://' + socket.request.session.username + '@' + config.ssh.host + ':' + config.ssh.port)
|
|
socket.emit('status', 'SSH CONNECTION ESTABLISHED')
|
|
socket.emit('statusBackground', config.header.background)
|
|
socket.emit('allowreplay', config.options.allowreplay)
|
|
|
|
conn.shell({
|
|
term: config.ssh.term,
|
|
cols: termCols,
|
|
rows: termRows
|
|
}, function (err, stream) {
|
|
if (err) {
|
|
socketutil.SSHerror('EXEC ERROR' + err)
|
|
conn.end()
|
|
return
|
|
}
|
|
socket.on('data', function (data) {
|
|
stream.write(data)
|
|
// poc to log commands from client
|
|
// if (data === '\r') {
|
|
// console.log(LogPrefix + ': ' + dataBuffer)
|
|
// dataBuffer = ''
|
|
// } else {
|
|
// dataBuffer = dataBuffer + data
|
|
// }
|
|
})
|
|
socket.on('control', function (controlData) {
|
|
switch (controlData) {
|
|
case 'replayCredentials':
|
|
stream.write(socket.request.session.userpassword + '\n')
|
|
/* falls through */
|
|
default:
|
|
console.log('controlData: ' + controlData)
|
|
}
|
|
})
|
|
|
|
socket.on('disconnecting', function (reason) { debugWebSSH2('SOCKET DISCONNECTING: ' + reason) })
|
|
|
|
socket.on('disconnect', function (reason) {
|
|
debugWebSSH2('SOCKET DISCONNECT: ' + reason)
|
|
err = { message: reason }
|
|
socketutil.SSHerror('CLIENT SOCKET DISCONNECT', err)
|
|
conn.end()
|
|
})
|
|
|
|
socket.on('error', function (error) { debugWebSSH2('SOCKET ERROR: ' + JSON.stringify(error)) })
|
|
|
|
stream.on('data', function (d) { socket.emit('data', d.toString('binary')) })
|
|
|
|
stream.on('close', function (code, signal) {
|
|
err = { message: ((code || signal) ? (((code) ? 'CODE: ' + code : '') + ((code && signal) ? ' ' : '') + ((signal) ? 'SIGNAL: ' + signal : '')) : undefined) }
|
|
socketutil.SSHerror('STREAM CLOSE', err)
|
|
conn.end()
|
|
})
|
|
|
|
stream.stderr.on('data', function (data) {
|
|
console.log('STDERR: ' + data)
|
|
})
|
|
})
|
|
})
|
|
|
|
conn.on('end', function (err) { socketutil.SSHerror('CONN END BY HOST', err) })
|
|
conn.on('close', function (err) { socketutil.SSHerror('CONN CLOSE', err) })
|
|
conn.on('error', function (err) { socketutil.SSHerror('CONN ERROR', err) })
|
|
|
|
conn.on('keyboard-interactive', function (name, instructions, instructionsLang, prompts, finish) {
|
|
debugWebSSH2('Connection :: keyboard-interactive')
|
|
finish([socket.request.session.userpassword])
|
|
})
|
|
if (socket.request.session.username && socket.request.session.userpassword) {
|
|
conn.connect({
|
|
host: config.ssh.host,
|
|
port: config.ssh.port,
|
|
username: socket.request.session.username,
|
|
password: socket.request.session.userpassword,
|
|
tryKeyboard: true,
|
|
// some cisco routers need the these cipher strings
|
|
algorithms: {
|
|
'cipher': ['aes128-cbc', '3des-cbc', 'aes256-cbc', 'aes128-ctr', 'aes192-ctr', 'aes256-ctr'],
|
|
'hmac': ['hmac-sha1', 'hmac-sha1-96', 'hmac-md5-96']
|
|
},
|
|
debug: debug('ssh2')
|
|
})
|
|
} else {
|
|
console.warn('Attempt to connect without session.username/password defined, potentially previously abandoned client session. disconnecting websocket client.\r\nHandshake information: \r\n ' + JSON.stringify(socket.handshake))
|
|
socket.emit('statusBackground', 'red')
|
|
socket.emit('status', 'WEBSOCKET ERROR - Reload and try again')
|
|
socket.disconnect(true)
|
|
}
|
|
})
|