error handling fixups

added some additional error handing functions and debugging points
- `DEBUG=ssh` will put the ssh2 module into debug mode
- `debug=WebSSH2` will output additional debug messages for functions
and events in the application (not including the ssh2 module debug)
- created socket/index.js to start the process of separating out app
functions, just holds error logging function at this point
- corrected some events on public/client.js so the primary error cause
is not overwritten
- ensure that ssh connection is terminated when websocked is
disconnected by the client
This commit is contained in:
billchurch 2017-05-20 17:26:16 -04:00
parent e3b8ff8189
commit 2c1c3ac911
3 changed files with 85 additions and 59 deletions

107
index.js
View file

@ -11,7 +11,9 @@ 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,
@ -22,17 +24,7 @@ var termCols, termRows, myError
// var LogPrefix
// var dataBuffer = ''
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())
}
}
// server
server.listen({
host: config.listen.ip,
@ -51,17 +43,22 @@ server.on('error', function (err) {
}
})
// 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)
io.use(function (socket, next) {
if (socket.request.res) {
session(socket.request, socket.request.res, next)
} else {
next()
}
})
app.disable('x-powered-by')
app.use(express.static(path.join(__dirname, 'public'), expressOptions))
@ -85,13 +82,32 @@ app.use('/src', express.static(path.join(__dirname, 'node_modules', 'xterm', 'di
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
@ -119,12 +135,9 @@ io.on('connection', function (socket) {
rows: termRows
}, function (err, stream) {
if (err) {
console.log(err.message)
myError = err.message
socket.emit('status', 'SSH EXEC ERROR: ' + err.message)
socket.emit('statusBackground', 'red')
console.log('conn.shell err: ' + err.message)
return socket.close(true)
socketutil.SSHerror('EXEC ERROR' + err)
conn.end()
return
}
socket.on('data', function (data) {
stream.write(data)
@ -145,15 +158,24 @@ io.on('connection', function (socket) {
console.log('controlData: ' + controlData)
}
})
socket.on('disconnecting', function (reason) { debugWebSSH2('SOCKET DISCONNECTING: ' + reason) })
stream.on('data', function (d) {
socket.emit('data', d.toString('binary'))
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) {
console.log('Stream :: close :: code: ' + code + ', signal: ' + signal)
err = { message: ((code||signal) ? (((code)? 'CODE: ' + code: '') + ((code&&signal)? ' ':'') + ((signal)? 'SIGNAL: ' + signal : '')) : undefined) }
socketutil.SSHerror('STREAM CLOSE', err)
conn.end()
socket.disconnect()
})
stream.stderr.on('data', function (data) {
@ -162,27 +184,12 @@ io.on('connection', function (socket) {
})
})
conn.on('end', function () {
socket.emit('status', 'SSH CONNECTION CLOSED BY HOST ' + myError)
socket.emit('statusBackground', 'red')
socket.disconnect()
})
conn.on('close', function () {
socket.emit('status', 'SSH CONNECTION CLOSE ' + myError)
socket.emit('statusBackground', 'red')
socket.disconnect()
})
conn.on('error', function (err) {
myError = err
socket.emit('status', 'SSH CONNECTION ERROR ' + myError)
socket.emit('statusBackground', 'red')
console.error('conn.on(\'error\'): ' + myError)
})
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) {
console.log('Connection :: keyboard-interactive')
debugWebSSH2('Connection :: keyboard-interactive')
finish([socket.request.session.userpassword])
})
if (socket.request.session.username && socket.request.session.userpassword) {
@ -197,7 +204,7 @@ io.on('connection', function (socket) {
'cipher': ['aes128-cbc', '3des-cbc', 'aes256-cbc', 'aes128-ctr', 'aes192-ctr', 'aes256-ctr'],
'hmac': ['hmac-sha1', 'hmac-sha1-96', 'hmac-md5-96']
},
debug: debug('WebSSH2:debug')
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))

View file

@ -1,5 +1,5 @@
var sessionLogEnable = false
var sessionLog, sessionFooter, logDate, currentDate, myFile
var sessionLog, sessionFooter, logDate, currentDate, myFile, errorExists
// replay password to server, requires
function replayCredentials () {
@ -84,6 +84,10 @@ socket.on('connect', function () {
document.title = data
}).on('status', function (data) {
document.getElementById('status').innerHTML = data
}).on('ssherror', function (data) {
document.getElementById('status').innerHTML = data
document.getElementById('status').style.backgroundColor = 'red'
errorExists = true
}).on('headerBackground', function (data) {
document.getElementById('header').style.backgroundColor = data
}).on('header', function (data) {
@ -105,12 +109,16 @@ socket.on('connect', function () {
if (sessionLogEnable) {
sessionLog = sessionLog + data
}
})// .on('disconnect', function (err) {
// document.getElementById('status').style.backgroundColor = 'red'
// document.getElementById('status').innerHTML = 'WEBSOCKET SERVER DISCONNECTED' + err
// socket.io.reconnection(false)
// })//.on('error', function (err) {
// document.getElementById('status').style.backgroundColor = 'red'
// document.getElementById('status').innerHTML = 'ERROR ' + err
// })
}).on('disconnect', function (err) {
if (!errorExists) {
document.getElementById('status').style.backgroundColor = 'red'
document.getElementById('status').innerHTML = 'WEBSOCKET SERVER DISCONNECTED: ' + err
}
socket.io.reconnection(false)
}).on('error', function (err) {
if (!errorExists) {
document.getElementById('status').style.backgroundColor = 'red'
document.getElementById('status').innerHTML = 'ERROR: ' + err
}
})
})

11
socket/index.js Normal file
View file

@ -0,0 +1,11 @@
var myError = myError
module.exports = function (socket, io) {
this.SSHerror = function (myFunc, err) {
myError = (myError) ? myError : ((err) ? err.message:undefined)
thisError = (myError) ? ': ' + myError : ''
console.error('SSH ' + myFunc + thisError)
socket.emit('ssherror', 'SSH ' + myFunc + thisError)
socket.disconnect(true)
}
}