From 2c1c3ac9110dd69d8cd03042d5da5e305ff26ded Mon Sep 17 00:00:00 2001 From: billchurch Date: Sat, 20 May 2017 17:26:16 -0400 Subject: [PATCH] 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 --- index.js | 107 +++++++++++++++++++++++++---------------------- public/client.js | 26 ++++++++---- socket/index.js | 11 +++++ 3 files changed, 85 insertions(+), 59 deletions(-) create mode 100644 socket/index.js diff --git a/index.js b/index.js index 0625e83..1e7a190 100644 --- a/index.js +++ b/index.js @@ -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)) diff --git a/public/client.js b/public/client.js index a134a91..56f4545 100644 --- a/public/client.js +++ b/public/client.js @@ -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 + } + }) }) diff --git a/socket/index.js b/socket/index.js new file mode 100644 index 0000000..8769cbc --- /dev/null +++ b/socket/index.js @@ -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) + } +}