151 lines
5.5 KiB
JavaScript
151 lines
5.5 KiB
JavaScript
/*
|
|
* WebSSH2 - Web to SSH2 gateway
|
|
* Bill Church - https://github.com/billchurch - April 2016
|
|
*
|
|
*/
|
|
var express = require('express'),
|
|
app = express(),
|
|
cookieParser = require('cookie-parser'),
|
|
server = require('http').Server(app),
|
|
io = require('socket.io')(server),
|
|
path = require('path'),
|
|
basicAuth = require('basic-auth'),
|
|
SSH = require('ssh2').Client,
|
|
readConfig = require('read-config'),
|
|
config = readConfig(path.join(__dirname, 'config.json')),
|
|
myError = ' - ',
|
|
termCols,
|
|
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(path.join(__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(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(path.join(__dirname, 'public'))).use('/src', express.static(path.join(__dirname, 'node_modules', 'xterm', 'dist'))).use('/addons', express.static(path.join(__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']
|
|
}
|
|
})
|
|
})
|