refactoring, socket.io update
more refactoring, code standards, updated socket.io to 1.7.4, increment release to 0.1.1
This commit is contained in:
parent
9dace2501c
commit
7cbfed20e9
21 changed files with 286 additions and 211 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -3,6 +3,9 @@ logs
|
|||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Editor preference files
|
||||
*.sublime-*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
|
|
20
ChangeLog.md
20
ChangeLog.md
|
@ -1,4 +1,18 @@
|
|||
# Change Log
|
||||
## [0.1.1] 2017-06-03
|
||||
### Added
|
||||
- `serverlog.client` and `serverlog.server` options added to `config.json` to enable logging of client commands to server log (only client portion implemented at this time)
|
||||
- morgan express middleware for logging
|
||||
### Changed
|
||||
- Updated socket.io to 1.7.4
|
||||
- continued refactoring, breaking up `index.js`
|
||||
- revised error handling methods
|
||||
- revised session termination methods
|
||||
### Fixed
|
||||
### Removed
|
||||
- color console decorations from `util/index.js`
|
||||
- SanatizeHeaders function from `util/index.js`
|
||||
|
||||
## [0.1.0] 2017-05-27
|
||||
### Added
|
||||
- This ChangeLog.md file
|
||||
|
@ -6,7 +20,7 @@
|
|||
- Snyk, Bithound, Travis CI
|
||||
- Cross platform improvements (path mappings)
|
||||
- Session fixup between Express and Socket.io
|
||||
- Session secret settings in config.json
|
||||
- Session secret settings in `config.json`
|
||||
- env variable `DEBUG=ssh2` will put the `ssh2` module into debug mode
|
||||
- env variable `DEBUG=WebSSH2` will output additional debug messages for functions
|
||||
and events in the application (not including the ssh2 module debug)
|
||||
|
@ -19,7 +33,7 @@ and events in the application (not including the ssh2 module debug)
|
|||
- error handling in public/client.js
|
||||
- moved socket.io operations to their own file /socket/index.js, more changes like this to come (./socket/index.js)
|
||||
- all session based variables are now under the req.session.ssh property or socket.request.ssh (./index.js)
|
||||
- moved SSH algorithms to config.json and defined as a session variable (..session.ssh.algorithms)
|
||||
- moved SSH algorithms to `config.json` and defined as a session variable (..session.ssh.algorithms)
|
||||
-- prep for future feature to define algorithms in header or some other method to enable separate ciphers per host
|
||||
- minified and combined all js files to a single js in `./public/webssh2.min.js` also included a sourcemap `./public/webssh2.min.js` which maps to `./public/webssh2.js` for easier troubleshooting.
|
||||
- combined all css files to a single css in `./public/webssh2.css`
|
||||
|
@ -28,7 +42,7 @@ and events in the application (not including the ssh2 module debug)
|
|||
- sourcemaps of all minified code (in /public/src and /public/src/js)
|
||||
- renamed `client.htm` to `client-full.htm`
|
||||
- created `client-min.htm` to serve minified javascript
|
||||
- if header.text is null in config.json and header is not defined as a get parameter the Header will not be displayed. Both of these must be null / undefined and not specified as get parameters.
|
||||
- if header.text is null in `config.json` and header is not defined as a get parameter the Header will not be displayed. Both of these must be null / undefined and not specified as get parameters.
|
||||
|
||||
### Fixed
|
||||
- Multiple errors may overwrite status bar which would cause confusion as to what originally caused the error. Example, ssh server disconnects which prompts a cascade of events (conn.on('end'), socket.on('disconnect'), conn.on('close')) and the original reason (conn.on('end')) would be lost and the user would erroneously receive a WEBSOCKET error as the last event to fire would be the websocket connection closing from the app.
|
||||
|
|
|
@ -152,6 +152,11 @@ You will be prompted for credentials to use on the SSH server via HTTP Basic aut
|
|||
* zlib@openssh.com
|
||||
* zlib
|
||||
|
||||
* **serverlog.client** - _boolean_ - Enables client command logging on server log (console.log). Very simple at this point, buffers data from client until it receives a line-feed then dumps buffer to console.log with session information for tracking. Will capture anything send from client, including passwords, so use for testing only... Default: false. Example:
|
||||
* _serverlog.client: GcZDThwA4UahDiKO2gkMYd7YPIfVAEFW/mnf0NUugLMFRHhsWAAAA host: 192.168.99.80 command: ls -lat_
|
||||
|
||||
* **serverlog.server** - _boolean_ - not implemented, default: false.
|
||||
|
||||
# Experimental client-side logging
|
||||
Clicking `Start logging` on the status bar will log all data to the client. A `Download log` option will appear after starting the logging. You may download at any time to the client. You may stop logging at any time my pressing the `Logging - STOP LOG`. Note that clicking the `Start logging` option again will cause the current log to be overwritten, so be sure to download first.
|
||||
|
||||
|
|
80
app.js
Normal file
80
app.js
Normal file
|
@ -0,0 +1,80 @@
|
|||
// app.js
|
||||
|
||||
var path = require('path')
|
||||
var config = require('read-config')(path.join(__dirname, 'config.json'))
|
||||
var express = require('express')
|
||||
var logger = require('morgan')
|
||||
var session = require('express-session')({
|
||||
secret: config.session.secret,
|
||||
name: config.session.name,
|
||||
resave: true,
|
||||
saveUninitialized: false,
|
||||
unset: 'destroy'
|
||||
})
|
||||
var app = express()
|
||||
var server = require('http').Server(app)
|
||||
var myutil = require('./util')
|
||||
var validator = require('validator')
|
||||
var io = require('socket.io')(server, { serveClient: false })
|
||||
var socket = require('./socket')
|
||||
var expressOptions = require('./expressOptions')
|
||||
|
||||
// express
|
||||
app.use(session)
|
||||
app.use(myutil.basicAuth)
|
||||
if (config.accesslog) app.use(logger('common'))
|
||||
app.disable('x-powered-by')
|
||||
|
||||
// static files
|
||||
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', (config.useminified)
|
||||
? 'client-min.htm' : 'client-full.htm')))
|
||||
// capture, assign, and validated variables
|
||||
req.session.ssh = {
|
||||
host: (validator.isIP(req.params.host + '') && req.params.host) ||
|
||||
(validator.isFQDN(req.params.host) && req.params.host) ||
|
||||
(/^(([a-z]|[A-Z]|[0-9]|[!^(){}\-_~])+)?\w$/.test(req.params.host) &&
|
||||
req.params.host) || config.ssh.host,
|
||||
port: (validator.isInt(req.query.port + '', {min: 1, max: 65535}) &&
|
||||
req.query.port) || config.ssh.port,
|
||||
header: {
|
||||
name: req.query.header || config.header.text,
|
||||
background: req.query.headerBackground || config.header.background
|
||||
},
|
||||
algorithms: config.algorithms,
|
||||
term: (/^(([a-z]|[A-Z]|[0-9]|[!^(){}\-_~])+)?\w$/.test(req.query.sshterm) &&
|
||||
req.query.sshterm) || config.ssh.term,
|
||||
allowreplay: validator.isBoolean(req.headers.allowreplay + '') || false,
|
||||
serverlog: {
|
||||
client: config.serverlog.client || false,
|
||||
server: config.serverlog.server || false
|
||||
}
|
||||
}
|
||||
req.session.ssh.header.name && validator.escape(req.session.ssh.header.name)
|
||||
req.session.ssh.header.background &&
|
||||
validator.escape(req.session.ssh.header.background)
|
||||
})
|
||||
|
||||
// express error handling
|
||||
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
|
||||
// expose express session with socket.request.session
|
||||
io.use(function (socket, next) {
|
||||
(socket.request.res) ? session(socket.request, socket.request.res, next)
|
||||
: next()
|
||||
})
|
||||
|
||||
// bring up socket
|
||||
io.on('connection', socket)
|
||||
|
||||
module.exports = {server: server, config: config}
|
|
@ -52,5 +52,10 @@
|
|||
"zlib@openssh.com",
|
||||
"zlib"
|
||||
]
|
||||
}
|
||||
},
|
||||
"serverlog": {
|
||||
"client": false, // proof-of-concept to log commands from client to server
|
||||
"server": false // not yet implemented
|
||||
},
|
||||
"accesslog": false // http style access logging to console.log
|
||||
}
|
11
expressOptions.js
Normal file
11
expressOptions.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
module.exports = {
|
||||
dotfiles: 'ignore',
|
||||
etag: false,
|
||||
extensions: ['htm', 'html'],
|
||||
index: false,
|
||||
maxAge: '1s',
|
||||
redirect: false,
|
||||
setHeaders: function (res, path, stat) {
|
||||
res.set('x-timestamp', Date.now())
|
||||
}
|
||||
}
|
85
index.js
85
index.js
|
@ -1,97 +1,24 @@
|
|||
// server.js
|
||||
/*
|
||||
* 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 config = require('read-config')(path.join(__dirname, 'config.json'))
|
||||
var myutil = require('./util')
|
||||
var socket = require('./socket/index.js')
|
||||
var validator = require('validator')
|
||||
var session = require('express-session')({
|
||||
secret: config.session.secret,
|
||||
name: config.session.name,
|
||||
resave: true,
|
||||
saveUninitialized: false,
|
||||
unset: 'destroy'
|
||||
})
|
||||
|
||||
// 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())
|
||||
}
|
||||
}
|
||||
var config = require('./app').config
|
||||
var server = require('./app').server
|
||||
|
||||
app.use(session)
|
||||
app.use(myutil.basicAuth)
|
||||
|
||||
app.disable('x-powered-by')
|
||||
|
||||
app.get('/ssh/host/:host?', function (req, res, next) {
|
||||
res.sendFile(path.join(path.join(__dirname, 'public', (config.useminified) ? 'client-min.htm' : 'client-full.htm')))
|
||||
// capture and assign variables
|
||||
req.session.ssh = {
|
||||
host: (validator.isIP(req.params.host + '') && req.params.host) || (validator.isFQDN(req.params.host) && req.params.host) || (/^(([a-z]|[A-Z]|[0-9]|[!^(){}\-_~])+)?\w$/.test(req.params.host) && req.params.host) || config.ssh.host,
|
||||
port: (validator.isInt(req.query.port + '', {min: 1, max: 65535}) && req.query.port) || config.ssh.port,
|
||||
header: {
|
||||
name: req.query.header || config.header.text,
|
||||
background: req.query.headerBackground || config.header.background
|
||||
},
|
||||
algorithms: config.algorithms,
|
||||
term: (/^(([a-z]|[A-Z]|[0-9]|[!^(){}\-_~])+)?\w$/.test(req.query.sshterm) && req.query.sshterm) || config.ssh.term,
|
||||
allowreplay: validator.isBoolean(req.headers.allowreplay + '') || false
|
||||
}
|
||||
req.session.ssh.header.name && validator.escape(req.session.ssh.header.name)
|
||||
req.session.ssh.header.background && validator.escape(req.session.ssh.header.background)
|
||||
})
|
||||
|
||||
// static files
|
||||
app.use(express.static(path.join(__dirname, 'public'), expressOptions))
|
||||
|
||||
// express error handling
|
||||
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
|
||||
// expose express session with socket.request.session
|
||||
io.use(function (socket, next) {
|
||||
(socket.request.res) ? session(socket.request, socket.request.res, next) : next()
|
||||
})
|
||||
|
||||
// bring up socket
|
||||
io.on('connection', socket)
|
||||
|
||||
// server
|
||||
server.listen({
|
||||
host: config.listen.ip,
|
||||
port: config.listen.port
|
||||
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)
|
||||
console.warn('WebSSH2 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)
|
||||
console.log('WebSSH2 server.listen ERROR: ' + err.code)
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "WebSSH2",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"ignore": [
|
||||
".gitignore"
|
||||
],
|
||||
|
@ -32,8 +32,9 @@
|
|||
"debug": "^2.6.8",
|
||||
"express": "^4.15.3",
|
||||
"express-session": "^1.15.3",
|
||||
"morgan": "^1.8.2",
|
||||
"read-config": "^1.6.0",
|
||||
"socket.io": "^1.6.0",
|
||||
"socket.io": "^1.7.4",
|
||||
"ssh2": "^0.5.4",
|
||||
"validator": "^7.0.0"
|
||||
},
|
||||
|
|
|
@ -11,9 +11,12 @@
|
|||
<div id="bottomdiv">
|
||||
<div id="footer"></div>
|
||||
<div id="status"></div>
|
||||
<div id="credentials"><a class="credentials" href="javascript:void(0);" onclick="replayCredentials()">CREDENTIALS</a></div>
|
||||
<div id="downloadLog"><a class="downloadLog" href="javascript:void(0);" onclick="downloadLog()">Download Log</a></div>
|
||||
<div id="toggleLog"><a class="toggleLog" href="javascript:void(0);" onclick="toggleLog();">Start Log</a></div>
|
||||
<div id="credentials"><a class="credentials" href="javascript:void(0);"
|
||||
onclick="replayCredentials()">CREDENTIALS</a></div>
|
||||
<div id="downloadLog"><a class="downloadLog" href="javascript:void(0);"
|
||||
onclick="downloadLog()">Download Log</a></div>
|
||||
<div id="toggleLog"><a class="toggleLog" href="javascript:void(0);"
|
||||
onclick="toggleLog();">Start Log</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
|
|
@ -11,9 +11,12 @@
|
|||
<div id="bottomdiv">
|
||||
<div id="footer"></div>
|
||||
<div id="status"></div>
|
||||
<div id="credentials"><a class="credentials" href="javascript:void(0);" onclick="replayCredentials()">CREDENTIALS</a></div>
|
||||
<div id="downloadLog"><a class="downloadLog" href="javascript:void(0);" onclick="downloadLog()">Download Log</a></div>
|
||||
<div id="toggleLog"><a class="toggleLog" href="javascript:void(0);" onclick="toggleLog();">Start Log</a></div>
|
||||
<div id="credentials"><a class="credentials" href="javascript:void(0);"
|
||||
onclick="replayCredentials()">CREDENTIALS</a></div>
|
||||
<div id="downloadLog"><a class="downloadLog" href="javascript:void(0);"
|
||||
onclick="downloadLog()">Download Log</a></div>
|
||||
<div id="toggleLog"><a class="toggleLog" href="javascript:void(0);"
|
||||
onclick="toggleLog();">Start Log</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
|
|
@ -14,19 +14,27 @@ function replayCredentials () { // eslint-disable-line
|
|||
function toggleLog () { // eslint-disable-line
|
||||
if (sessionLogEnable === true) {
|
||||
sessionLogEnable = false
|
||||
document.getElementById('toggleLog').innerHTML = '<a class="toggleLog" href="javascript:void(0);" onclick="toggleLog();">Start Log</a>'
|
||||
document.getElementById('toggleLog').innerHTML =
|
||||
'<a class="toggleLog" href="javascript:void(0);" onclick="toggleLog();">Start Log</a>'
|
||||
console.log('stopping log, ' + sessionLogEnable)
|
||||
currentDate = new Date()
|
||||
sessionLog = sessionLog + '\r\n\r\nLog End for ' + sessionFooter + ': ' + currentDate.getFullYear() + '/' + (currentDate.getMonth() + 1) + '/' + currentDate.getDate() + ' @ ' + currentDate.getHours() + ':' + currentDate.getMinutes() + ':' + currentDate.getSeconds() + '\r\n'
|
||||
sessionLog = sessionLog + '\r\n\r\nLog End for ' + sessionFooter + ': ' +
|
||||
currentDate.getFullYear() + '/' + (currentDate.getMonth() + 1) + '/' +
|
||||
currentDate.getDate() + ' @ ' + currentDate.getHours() + ':' +
|
||||
currentDate.getMinutes() + ':' + currentDate.getSeconds() + '\r\n'
|
||||
logDate = currentDate
|
||||
return false
|
||||
} else {
|
||||
sessionLogEnable = true
|
||||
document.getElementById('toggleLog').innerHTML = '<a class="toggleLog" href="javascript:void(0)" onclick="toggleLog();">Logging - STOP LOG</a>'
|
||||
document.getElementById('toggleLog').innerHTML =
|
||||
'<a class="toggleLog" href="javascript:void(0)" onclick="toggleLog();">Logging - STOP LOG</a>'
|
||||
document.getElementById('downloadLog').style.display = 'inline'
|
||||
console.log('starting log, ' + sessionLogEnable)
|
||||
currentDate = new Date()
|
||||
sessionLog = 'Log Start for ' + sessionFooter + ': ' + currentDate.getFullYear() + '/' + (currentDate.getMonth() + 1) + '/' + currentDate.getDate() + ' @ ' + currentDate.getHours() + ':' + currentDate.getMinutes() + ':' + currentDate.getSeconds() + '\r\n\r\n'
|
||||
sessionLog = 'Log Start for ' + sessionFooter + ': ' +
|
||||
currentDate.getFullYear() + '/' + (currentDate.getMonth() + 1) + '/' +
|
||||
currentDate.getDate() + ' @ ' + currentDate.getHours() + ':' +
|
||||
currentDate.getMinutes() + ':' + currentDate.getSeconds() + '\r\n\r\n'
|
||||
logDate = currentDate
|
||||
return false
|
||||
}
|
||||
|
@ -35,7 +43,9 @@ function toggleLog () { // eslint-disable-line
|
|||
// cross browser method to "download" an element to the local system
|
||||
// used for our client-side logging feature
|
||||
function downloadLog () { // eslint-disable-line
|
||||
myFile = 'WebSSH2-' + logDate.getFullYear() + (logDate.getMonth() + 1) + logDate.getDate() + '_' + logDate.getHours() + logDate.getMinutes() + logDate.getSeconds() + '.log'
|
||||
myFile = 'WebSSH2-' + logDate.getFullYear() + (logDate.getMonth() + 1) +
|
||||
logDate.getDate() + '_' + logDate.getHours() + logDate.getMinutes() +
|
||||
logDate.getSeconds() + '.log'
|
||||
// regex should eliminate escape sequences from being logged.
|
||||
var blob = new Blob([sessionLog.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '')], {
|
||||
type: 'text/plain'
|
||||
|
@ -113,7 +123,8 @@ socket.on('connect', function () {
|
|||
}).on('disconnect', function (err) {
|
||||
if (!errorExists) {
|
||||
document.getElementById('status').style.backgroundColor = 'red'
|
||||
document.getElementById('status').innerHTML = 'WEBSOCKET SERVER DISCONNECTED: ' + err
|
||||
document.getElementById('status').innerHTML =
|
||||
'WEBSOCKET SERVER DISCONNECTED: ' + err
|
||||
}
|
||||
socket.io.reconnection(false)
|
||||
}).on('error', function (err) {
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -12520,19 +12520,27 @@ function replayCredentials () { // eslint-disable-line
|
|||
function toggleLog () { // eslint-disable-line
|
||||
if (sessionLogEnable === true) {
|
||||
sessionLogEnable = false
|
||||
document.getElementById('toggleLog').innerHTML = '<a class="toggleLog" href="javascript:void(0);" onclick="toggleLog();">Start Log</a>'
|
||||
document.getElementById('toggleLog').innerHTML =
|
||||
'<a class="toggleLog" href="javascript:void(0);" onclick="toggleLog();">Start Log</a>'
|
||||
console.log('stopping log, ' + sessionLogEnable)
|
||||
currentDate = new Date()
|
||||
sessionLog = sessionLog + '\r\n\r\nLog End for ' + sessionFooter + ': ' + currentDate.getFullYear() + '/' + (currentDate.getMonth() + 1) + '/' + currentDate.getDate() + ' @ ' + currentDate.getHours() + ':' + currentDate.getMinutes() + ':' + currentDate.getSeconds() + '\r\n'
|
||||
sessionLog = sessionLog + '\r\n\r\nLog End for ' + sessionFooter + ': ' +
|
||||
currentDate.getFullYear() + '/' + (currentDate.getMonth() + 1) + '/' +
|
||||
currentDate.getDate() + ' @ ' + currentDate.getHours() + ':' +
|
||||
currentDate.getMinutes() + ':' + currentDate.getSeconds() + '\r\n'
|
||||
logDate = currentDate
|
||||
return false
|
||||
} else {
|
||||
sessionLogEnable = true
|
||||
document.getElementById('toggleLog').innerHTML = '<a class="toggleLog" href="javascript:void(0)" onclick="toggleLog();">Logging - STOP LOG</a>'
|
||||
document.getElementById('toggleLog').innerHTML =
|
||||
'<a class="toggleLog" href="javascript:void(0)" onclick="toggleLog();">Logging - STOP LOG</a>'
|
||||
document.getElementById('downloadLog').style.display = 'inline'
|
||||
console.log('starting log, ' + sessionLogEnable)
|
||||
currentDate = new Date()
|
||||
sessionLog = 'Log Start for ' + sessionFooter + ': ' + currentDate.getFullYear() + '/' + (currentDate.getMonth() + 1) + '/' + currentDate.getDate() + ' @ ' + currentDate.getHours() + ':' + currentDate.getMinutes() + ':' + currentDate.getSeconds() + '\r\n\r\n'
|
||||
sessionLog = 'Log Start for ' + sessionFooter + ': ' +
|
||||
currentDate.getFullYear() + '/' + (currentDate.getMonth() + 1) + '/' +
|
||||
currentDate.getDate() + ' @ ' + currentDate.getHours() + ':' +
|
||||
currentDate.getMinutes() + ':' + currentDate.getSeconds() + '\r\n\r\n'
|
||||
logDate = currentDate
|
||||
return false
|
||||
}
|
||||
|
@ -12541,7 +12549,9 @@ function toggleLog () { // eslint-disable-line
|
|||
// cross browser method to "download" an element to the local system
|
||||
// used for our client-side logging feature
|
||||
function downloadLog () { // eslint-disable-line
|
||||
myFile = 'WebSSH2-' + logDate.getFullYear() + (logDate.getMonth() + 1) + logDate.getDate() + '_' + logDate.getHours() + logDate.getMinutes() + logDate.getSeconds() + '.log'
|
||||
myFile = 'WebSSH2-' + logDate.getFullYear() + (logDate.getMonth() + 1) +
|
||||
logDate.getDate() + '_' + logDate.getHours() + logDate.getMinutes() +
|
||||
logDate.getSeconds() + '.log'
|
||||
// regex should eliminate escape sequences from being logged.
|
||||
var blob = new Blob([sessionLog.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '')], {
|
||||
type: 'text/plain'
|
||||
|
@ -12619,7 +12629,8 @@ socket.on('connect', function () {
|
|||
}).on('disconnect', function (err) {
|
||||
if (!errorExists) {
|
||||
document.getElementById('status').style.backgroundColor = 'red'
|
||||
document.getElementById('status').innerHTML = 'WEBSOCKET SERVER DISCONNECTED: ' + err
|
||||
document.getElementById('status').innerHTML =
|
||||
'WEBSOCKET SERVER DISCONNECTED: ' + err
|
||||
}
|
||||
socket.io.reconnection(false)
|
||||
}).on('error', function (err) {
|
||||
|
|
2
public/webssh2.min.js
vendored
2
public/webssh2.min.js
vendored
File diff suppressed because one or more lines are too long
131
socket/index.js
131
socket/index.js
|
@ -1,3 +1,5 @@
|
|||
// socket/index.js
|
||||
|
||||
// private
|
||||
var debug = require('debug')
|
||||
var debugWebSSH2 = require('debug')('WebSSH2')
|
||||
|
@ -5,30 +7,7 @@ var SSH = require('ssh2').Client
|
|||
var termCols, termRows
|
||||
|
||||
// public
|
||||
module.exports = function (socket) {
|
||||
function SSHerror (myFunc, err) {
|
||||
socket.request.session.error = (socket.request.session.error) || ((err) ? err.message : undefined)
|
||||
var theError = (socket.request.session.error) ? ': ' + socket.request.session.error : ''
|
||||
// log unsuccessful login attempt
|
||||
if (err && (err.level === 'client-authentication')) {
|
||||
console.log('webssh2 ' + 'error: Authentication failure'.red.bold +
|
||||
' user=' + socket.request.session.username.yellow.bold.underline +
|
||||
' from=' + socket.handshake.address.yellow.bold.underline)
|
||||
}
|
||||
switch (myFunc) {
|
||||
case 'STREAM CLOSE':
|
||||
debugWebSSH2('SSH ' + myFunc + theError.red)
|
||||
socket.emit('ssherror', 'SSH ' + myFunc + theError)
|
||||
socket.disconnect(true)
|
||||
break
|
||||
default:
|
||||
debugWebSSH2('SSHerror: default'.red)
|
||||
debugWebSSH2('SSH ' + myFunc + theError)
|
||||
socket.emit('ssherror', 'SSH ' + myFunc + theError)
|
||||
socket.disconnect(true)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = function socket (socket) {
|
||||
// if websocket connection arrives without an express session, kill it
|
||||
if (!socket.request.session) {
|
||||
socket.emit('401 UNAUTHORIZED')
|
||||
|
@ -37,19 +16,18 @@ module.exports = function (socket) {
|
|||
return
|
||||
}
|
||||
var conn = new SSH()
|
||||
socket.on('geometry', function (cols, rows) {
|
||||
socket.on('geometry', function socketOnGeometry (cols, rows) {
|
||||
termCols = cols
|
||||
termRows = rows
|
||||
})
|
||||
console.log('webssh2 ' + 'IO ON:'.cyan.bold + ' user=' + socket.request.session.username + ' from=' + socket.handshake.address + ' host=' + socket.request.session.ssh.host + ' port=' + socket.request.session.ssh.port + ' sessionID=' + socket.request.sessionID + '/' + socket.id + ' allowreplay=' + socket.request.session.ssh.allowreplay + ' term=' + socket.request.session.ssh.term)
|
||||
conn.on('banner', function (d) {
|
||||
conn.on('banner', function connOnBanner (data) {
|
||||
// need to convert to cr/lf for proper formatting
|
||||
d = d.replace(/\r?\n/g, '\r\n')
|
||||
socket.emit('data', d.toString('binary'))
|
||||
data = data.replace(/\r?\n/g, '\r\n')
|
||||
socket.emit('data', data.toString('utf-8'))
|
||||
})
|
||||
|
||||
conn.on('ready', function () {
|
||||
console.log('webssh2 Login: user=' + socket.request.session.username + ' from=' + socket.handshake.address + ' host=' + socket.request.session.ssh.host + ' port=' + socket.request.session.ssh.port + ' sessionID=' + socket.request.sessionID + '/' + socket.id + ' allowreplay=' + socket.request.session.ssh.allowreplay + ' term=' + socket.request.session.ssh.term)
|
||||
conn.on('ready', function connOnReady () {
|
||||
console.log('WebSSH2 Login: user=' + socket.request.session.username + ' from=' + socket.handshake.address + ' host=' + socket.request.session.ssh.host + ' port=' + socket.request.session.ssh.port + ' sessionID=' + socket.request.sessionID + '/' + socket.id + ' allowreplay=' + socket.request.session.ssh.allowreplay + ' term=' + socket.request.session.ssh.term)
|
||||
socket.emit('title', 'ssh://' + socket.request.session.ssh.host)
|
||||
socket.request.session.ssh.header.background && socket.emit('headerBackground', socket.request.session.ssh.header.background)
|
||||
socket.request.session.ssh.header.name && socket.emit('header', socket.request.session.ssh.header.name)
|
||||
|
@ -61,25 +39,27 @@ module.exports = function (socket) {
|
|||
term: socket.request.session.ssh.term,
|
||||
cols: termCols,
|
||||
rows: termRows
|
||||
}, function (err, stream) {
|
||||
}, function connShell (err, stream) {
|
||||
if (err) {
|
||||
SSHerror('EXEC ERROR' + err)
|
||||
conn.end()
|
||||
return
|
||||
}
|
||||
// poc to log commands from client
|
||||
// var dataBuffer
|
||||
socket.on('data', function (data) {
|
||||
if (socket.request.session.ssh.serverlog.client) var dataBuffer
|
||||
socket.on('data', function socketOnData (data) {
|
||||
stream.write(data)
|
||||
// poc to log commands from client
|
||||
// if (data === '\r') {
|
||||
// console.log(socket.request.session.id + '/' + socket.id + ' command: ' + socket.request.session.ssh.host + ': ' + dataBuffer)
|
||||
// dataBuffer = undefined
|
||||
// } else {
|
||||
// dataBuffer = (dataBuffer) ? dataBuffer + data : data
|
||||
// }
|
||||
if (socket.request.session.ssh.serverlog.client) {
|
||||
if (data === '\r') {
|
||||
console.log('serverlog.client: ' + socket.request.session.id + '/' + socket.id + ' host: ' + socket.request.session.ssh.host + ' command: ' + dataBuffer)
|
||||
dataBuffer = undefined
|
||||
} else {
|
||||
dataBuffer = (dataBuffer) ? dataBuffer + data : data
|
||||
}
|
||||
}
|
||||
})
|
||||
socket.on('control', function (controlData) {
|
||||
socket.on('control', function socketOnControl (controlData) {
|
||||
switch (controlData) {
|
||||
case 'replayCredentials':
|
||||
stream.write(socket.request.session.userpassword + '\n')
|
||||
|
@ -88,55 +68,82 @@ module.exports = function (socket) {
|
|||
console.log('controlData: ' + controlData)
|
||||
}
|
||||
})
|
||||
|
||||
socket.on('disconnecting', function (reason) { debugWebSSH2('SOCKET DISCONNECTING: ' + reason) })
|
||||
|
||||
socket.on('disconnect', function (reason) {
|
||||
socket.on('disconnecting', function socketOnDisconnecting (reason) { debugWebSSH2('SOCKET DISCONNECTING: ' + reason) })
|
||||
socket.on('disconnect', function socketOnDisconnect (reason) {
|
||||
debugWebSSH2('SOCKET DISCONNECT: ' + reason)
|
||||
err = { message: reason }
|
||||
SSHerror('CLIENT SOCKET DISCONNECT', err)
|
||||
conn.end()
|
||||
// socket.request.session.destroy()
|
||||
})
|
||||
socket.on('error', function socketOnError (err) {
|
||||
SSHerror('SOCKET ERROR', err)
|
||||
conn.end()
|
||||
})
|
||||
|
||||
socket.on('error', function (error) { debugWebSSH2('SOCKET ERROR: ' + JSON.stringify(error)) })
|
||||
|
||||
stream.on('data', function (d) { socket.emit('data', d.toString('utf-8')) })
|
||||
|
||||
stream.on('close', function (code, signal) {
|
||||
stream.on('data', function streamOnData (data) { socket.emit('data', data.toString('utf-8')) })
|
||||
stream.on('close', function streamOnClose (code, signal) {
|
||||
err = { message: ((code || signal) ? (((code) ? 'CODE: ' + code : '') + ((code && signal) ? ' ' : '') + ((signal) ? 'SIGNAL: ' + signal : '')) : undefined) }
|
||||
SSHerror('STREAM CLOSE', err)
|
||||
conn.end()
|
||||
})
|
||||
|
||||
stream.stderr.on('data', function (data) {
|
||||
stream.stderr.on('data', function streamStderrOnData (data) {
|
||||
console.log('STDERR: ' + data)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
conn.on('end', function (err) { SSHerror('CONN END BY HOST', err) })
|
||||
conn.on('close', function (err) { SSHerror('CONN CLOSE', err) })
|
||||
conn.on('error', function (err) { SSHerror('CONN ERROR', err) })
|
||||
|
||||
conn.on('keyboard-interactive', function (name, instructions, instructionsLang, prompts, finish) {
|
||||
conn.on('end', function connOnEnd (err) { SSHerror('CONN END BY HOST', err) })
|
||||
conn.on('close', function connOnClose (err) { SSHerror('CONN CLOSE', err) })
|
||||
conn.on('error', function connOnError (err) { SSHerror('CONN ERROR', err) })
|
||||
conn.on('keyboard-interactive', function connOnKeyboardInteractive (name, instructions, instructionsLang, prompts, finish) {
|
||||
debugWebSSH2('conn.on(\'keyboard-interactive\')')
|
||||
finish([socket.request.session.userpassword])
|
||||
})
|
||||
if (socket.request.session.username && socket.request.session.userpassword) {
|
||||
if (socket.request.session.username && socket.request.session.userpassword && socket.request.session.ssh) {
|
||||
conn.connect({
|
||||
host: socket.request.session.ssh.host,
|
||||
port: socket.request.session.ssh.port,
|
||||
username: socket.request.session.username,
|
||||
password: socket.request.session.userpassword,
|
||||
tryKeyboard: true,
|
||||
// some cisco routers need the these cipher strings
|
||||
algorithms: socket.request.session.ssh.algorithms,
|
||||
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')
|
||||
debugWebSSH2('Attempt to connect without session.username/password or session varialbles defined, potentially previously abandoned client session. disconnecting websocket client.\r\nHandshake information: \r\n ' + JSON.stringify(socket.handshake))
|
||||
socket.emit('ssherror', 'WEBSOCKET ERROR - Refresh the browser and try again')
|
||||
socket.request.session.destroy()
|
||||
socket.disconnect(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Error handling for various events. Outputs error to client, logs to
|
||||
* server, destroys session and disconnects socket.
|
||||
* @param {string} myFunc Function calling this function
|
||||
* @param {object} err error object or error message
|
||||
*/
|
||||
function SSHerror (myFunc, err) {
|
||||
var theError
|
||||
if (socket.request.session) {
|
||||
// we just want the first error of the session to pass to the client
|
||||
socket.request.session.error = (socket.request.session.error) || ((err) ? err.message : undefined)
|
||||
theError = (socket.request.session.error) ? ': ' + socket.request.session.error : ''
|
||||
// log unsuccessful login attempt
|
||||
if (err && (err.level === 'client-authentication')) {
|
||||
console.log('WebSSH2 ' + 'error: Authentication failure'.red.bold +
|
||||
' user=' + socket.request.session.username.yellow.bold.underline +
|
||||
' from=' + socket.handshake.address.yellow.bold.underline)
|
||||
} else {
|
||||
console.log('WebSSH2 Logout: user=' + socket.request.session.username + ' from=' + socket.handshake.address + ' host=' + socket.request.session.ssh.host + ' port=' + socket.request.session.ssh.port + ' sessionID=' + socket.request.sessionID + '/' + socket.id + ' allowreplay=' + socket.request.session.ssh.allowreplay + ' term=' + socket.request.session.ssh.term)
|
||||
}
|
||||
socket.emit('ssherror', 'SSH ' + myFunc + theError)
|
||||
socket.request.session.destroy()
|
||||
socket.disconnect(true)
|
||||
} else {
|
||||
theError = (err) ? ': ' + err.message : ''
|
||||
socket.disconnect(true)
|
||||
}
|
||||
debugWebSSH2('SSHerror ' + myFunc + theError)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,19 +14,27 @@ function replayCredentials () { // eslint-disable-line
|
|||
function toggleLog () { // eslint-disable-line
|
||||
if (sessionLogEnable === true) {
|
||||
sessionLogEnable = false
|
||||
document.getElementById('toggleLog').innerHTML = '<a class="toggleLog" href="javascript:void(0);" onclick="toggleLog();">Start Log</a>'
|
||||
document.getElementById('toggleLog').innerHTML =
|
||||
'<a class="toggleLog" href="javascript:void(0);" onclick="toggleLog();">Start Log</a>'
|
||||
console.log('stopping log, ' + sessionLogEnable)
|
||||
currentDate = new Date()
|
||||
sessionLog = sessionLog + '\r\n\r\nLog End for ' + sessionFooter + ': ' + currentDate.getFullYear() + '/' + (currentDate.getMonth() + 1) + '/' + currentDate.getDate() + ' @ ' + currentDate.getHours() + ':' + currentDate.getMinutes() + ':' + currentDate.getSeconds() + '\r\n'
|
||||
sessionLog = sessionLog + '\r\n\r\nLog End for ' + sessionFooter + ': ' +
|
||||
currentDate.getFullYear() + '/' + (currentDate.getMonth() + 1) + '/' +
|
||||
currentDate.getDate() + ' @ ' + currentDate.getHours() + ':' +
|
||||
currentDate.getMinutes() + ':' + currentDate.getSeconds() + '\r\n'
|
||||
logDate = currentDate
|
||||
return false
|
||||
} else {
|
||||
sessionLogEnable = true
|
||||
document.getElementById('toggleLog').innerHTML = '<a class="toggleLog" href="javascript:void(0)" onclick="toggleLog();">Logging - STOP LOG</a>'
|
||||
document.getElementById('toggleLog').innerHTML =
|
||||
'<a class="toggleLog" href="javascript:void(0)" onclick="toggleLog();">Logging - STOP LOG</a>'
|
||||
document.getElementById('downloadLog').style.display = 'inline'
|
||||
console.log('starting log, ' + sessionLogEnable)
|
||||
currentDate = new Date()
|
||||
sessionLog = 'Log Start for ' + sessionFooter + ': ' + currentDate.getFullYear() + '/' + (currentDate.getMonth() + 1) + '/' + currentDate.getDate() + ' @ ' + currentDate.getHours() + ':' + currentDate.getMinutes() + ':' + currentDate.getSeconds() + '\r\n\r\n'
|
||||
sessionLog = 'Log Start for ' + sessionFooter + ': ' +
|
||||
currentDate.getFullYear() + '/' + (currentDate.getMonth() + 1) + '/' +
|
||||
currentDate.getDate() + ' @ ' + currentDate.getHours() + ':' +
|
||||
currentDate.getMinutes() + ':' + currentDate.getSeconds() + '\r\n\r\n'
|
||||
logDate = currentDate
|
||||
return false
|
||||
}
|
||||
|
@ -35,7 +43,9 @@ function toggleLog () { // eslint-disable-line
|
|||
// cross browser method to "download" an element to the local system
|
||||
// used for our client-side logging feature
|
||||
function downloadLog () { // eslint-disable-line
|
||||
myFile = 'WebSSH2-' + logDate.getFullYear() + (logDate.getMonth() + 1) + logDate.getDate() + '_' + logDate.getHours() + logDate.getMinutes() + logDate.getSeconds() + '.log'
|
||||
myFile = 'WebSSH2-' + logDate.getFullYear() + (logDate.getMonth() + 1) +
|
||||
logDate.getDate() + '_' + logDate.getHours() + logDate.getMinutes() +
|
||||
logDate.getSeconds() + '.log'
|
||||
// regex should eliminate escape sequences from being logged.
|
||||
var blob = new Blob([sessionLog.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '')], {
|
||||
type: 'text/plain'
|
||||
|
@ -113,7 +123,8 @@ socket.on('connect', function () {
|
|||
}).on('disconnect', function (err) {
|
||||
if (!errorExists) {
|
||||
document.getElementById('status').style.backgroundColor = 'red'
|
||||
document.getElementById('status').innerHTML = 'WEBSOCKET SERVER DISCONNECTED: ' + err
|
||||
document.getElementById('status').innerHTML =
|
||||
'WEBSOCKET SERVER DISCONNECTED: ' + err
|
||||
}
|
||||
socket.io.reconnection(false)
|
||||
}).on('error', function (err) {
|
||||
|
|
|
@ -1,28 +1,18 @@
|
|||
// util/index.js
|
||||
|
||||
// private
|
||||
require('colors') // allow for color property extensions in log messages
|
||||
var debug = require('debug')('WebSSH2')
|
||||
var Auth = require('basic-auth')
|
||||
var util = require('util')
|
||||
|
||||
console.warn = makeColorConsole(console.warn, 'yellow')
|
||||
console.error = makeColorConsole(console.error, 'red')
|
||||
|
||||
// public
|
||||
function makeColorConsole (fct, color) {
|
||||
return function () {
|
||||
for (var i in arguments) {
|
||||
if (arguments[i] instanceof Object) { arguments[i] = util.inspect(arguments[i]) }
|
||||
}
|
||||
fct(Array.prototype.join.call(arguments, ' ')[color])
|
||||
}
|
||||
}
|
||||
|
||||
exports.basicAuth = function (req, res, next) {
|
||||
exports.basicAuth = function basicAuth (req, res, next) {
|
||||
var myAuth = Auth(req)
|
||||
if (myAuth) {
|
||||
req.session.username = myAuth.name
|
||||
req.session.userpassword = myAuth.pass
|
||||
debug('myAuth.name: ' + myAuth.name.yellow.bold.underline + ' and password ' + ((myAuth.pass) ? 'exists'.yellow.bold.underline : 'is blank'.underline.red.bold))
|
||||
debug('myAuth.name: ' + myAuth.name.yellow.bold.underline +
|
||||
' and password ' + ((myAuth.pass) ? 'exists'.yellow.bold.underline
|
||||
: 'is blank'.underline.red.bold))
|
||||
next()
|
||||
} else {
|
||||
res.statusCode = 401
|
||||
|
@ -31,10 +21,3 @@ exports.basicAuth = function (req, res, next) {
|
|||
res.end('Username and password required for web SSH service.')
|
||||
}
|
||||
}
|
||||
|
||||
// expects headers to be a JSON object, will replace authroization header with 'Sanatized//Exists'
|
||||
// we don't want to log basic auth header since it contains a password...
|
||||
exports.SanatizeHeaders = function (headers) {
|
||||
if (headers.authorization) { headers.authorization = 'Sanitized//Exists' }
|
||||
return (headers)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue