* Rewrite SSH authentication - now works with PuTTY (thoguh there are some issues) and standard ssh. WIP still!
* Idle monitor not started until 'ready' signal
This commit is contained in:
parent
6a2d283fad
commit
e7e9746a85
|
@ -197,6 +197,8 @@ function startListening() {
|
||||||
|
|
||||||
client.on('ready', function onClientReady() {
|
client.on('ready', function onClientReady() {
|
||||||
|
|
||||||
|
client.startIdleMonitor();
|
||||||
|
|
||||||
// Go to module -- use default error handler
|
// Go to module -- use default error handler
|
||||||
prepareClient(client, function onPrepared() {
|
prepareClient(client, function onPrepared() {
|
||||||
require('./connect.js').connectEntry(client);
|
require('./connect.js').connectEntry(client);
|
||||||
|
|
|
@ -94,17 +94,6 @@ function Client(input, output) {
|
||||||
this.currentTheme = { info : { name : 'N/A', description : 'None' } };
|
this.currentTheme = { info : { name : 'N/A', description : 'None' } };
|
||||||
this.lastKeyPressMs = Date.now();
|
this.lastKeyPressMs = Date.now();
|
||||||
|
|
||||||
//
|
|
||||||
// Every 1m, check for idle.
|
|
||||||
//
|
|
||||||
this.idleCheck = setInterval(function checkForIdle() {
|
|
||||||
var nowMs = Date.now();
|
|
||||||
|
|
||||||
if(nowMs - self.lastKeyPressMs >= (Config.misc.idleLogoutSeconds * 1000)) {
|
|
||||||
self.emit('idle timeout');
|
|
||||||
}
|
|
||||||
}, 1000 * 60);
|
|
||||||
|
|
||||||
Object.defineProperty(this, 'node', {
|
Object.defineProperty(this, 'node', {
|
||||||
get : function() {
|
get : function() {
|
||||||
return self.session.id + 1;
|
return self.session.id + 1;
|
||||||
|
@ -112,7 +101,6 @@ function Client(input, output) {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Peek at incoming |data| and emit events for any special
|
// Peek at incoming |data| and emit events for any special
|
||||||
// handling that may include:
|
// handling that may include:
|
||||||
|
@ -408,6 +396,30 @@ Client.prototype.setInputOutput = function(input, output) {
|
||||||
this.term = new term.ClientTerminal(this.output);
|
this.term = new term.ClientTerminal(this.output);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Client.prototype.setTermType = function(termType) {
|
||||||
|
this.term.env.TERM = termType;
|
||||||
|
this.term.termType = termType;
|
||||||
|
|
||||||
|
this.log.debug( { termType : termType }, 'Set terminal type');
|
||||||
|
};
|
||||||
|
|
||||||
|
Client.prototype.startIdleMonitor = function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.lastKeyPressMs = Date.now();
|
||||||
|
|
||||||
|
//
|
||||||
|
// Every 1m, check for idle.
|
||||||
|
//
|
||||||
|
self.idleCheck = setInterval(function checkForIdle() {
|
||||||
|
var nowMs = Date.now();
|
||||||
|
|
||||||
|
if(nowMs - self.lastKeyPressMs >= (Config.misc.idleLogoutSeconds * 1000)) {
|
||||||
|
self.emit('idle timeout');
|
||||||
|
}
|
||||||
|
}, 1000 * 60);
|
||||||
|
};
|
||||||
|
|
||||||
Client.prototype.end = function () {
|
Client.prototype.end = function () {
|
||||||
this.detachCurrentMenuModule();
|
this.detachCurrentMenuModule();
|
||||||
|
|
||||||
|
|
|
@ -24,25 +24,12 @@ exports.moduleInfo = {
|
||||||
exports.getModule = SSHServerModule;
|
exports.getModule = SSHServerModule;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Hello,
|
TODO's
|
||||||
|
* Need to handle new user path
|
||||||
If you follow the first server example in the `ssh2` readme and substitute the `session.once('exec', ...)` with `session.once('shell', ...)`
|
=> [ new username(s) ] -> apply path ->
|
||||||
you should be fine. Just about all ssh clients default to an interactive shell session so that is what you will want to look for. As the
|
=> "new" or "apply" -> ....
|
||||||
documentation notes, the `shell` event handler is just passed `accept, reject` with `accept()` returning a duplex stream representing
|
|
||||||
stdin/stdout. You can write to stderr by using the `stderr` property of the duplex stream object.
|
|
||||||
|
|
||||||
You will probably also want to handle the `pty` event on the session, since most clients (by default) will request a pseudo-TTY before
|
|
||||||
requesting an interactive shell. I believe this event may be especially useful in your case because the ssh client can send certain terminal
|
|
||||||
modes which can have relevance with your telnet usage. The event info also contains window dimensions which may help in determining layout
|
|
||||||
of your display (there is also a `window-change` event that contains these same dimensions whenever the client's screen/window dimensions
|
|
||||||
change).
|
|
||||||
|
|
||||||
If you are still having problems after making these changes, post your code somewhere and I will see if there is anything out of place.
|
|
||||||
Additionally, you can set `debug: console.log` in the server config object to show debug output which may be useful to see what is or isn't
|
|
||||||
being sent/received ssh protocol-wise.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
function SSHClient(clientConn) {
|
function SSHClient(clientConn) {
|
||||||
baseClient.Client.apply(this, arguments);
|
baseClient.Client.apply(this, arguments);
|
||||||
|
|
||||||
|
@ -53,88 +40,54 @@ function SSHClient(clientConn) {
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
this.userLoginWithCredentials = function(username, password, ctx) {
|
clientConn.on('authentication', function authAttempt(ctx) {
|
||||||
userLogin(self, ctx.username, ctx.password, function authResult(err) {
|
self.log.trace( { method : ctx.method, username : ctx.username }, 'SSH authentication attempt');
|
||||||
if(err) {
|
|
||||||
if(err.existingConn) {
|
|
||||||
// :TODO: Already logged in - how to let the SSH client know?
|
|
||||||
//self.term.write('User already logged in');
|
|
||||||
ctx.reject();
|
|
||||||
} else {
|
|
||||||
ctx.reject();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ctx.accept();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
clientConn.on('authentication', function authentication(ctx) {
|
var username = ctx.username || '';
|
||||||
self.log.trace(
|
var password = ctx.password || '';
|
||||||
{
|
|
||||||
domain : ctx.domain,
|
|
||||||
username : ctx.username,
|
|
||||||
method : ctx.method,
|
|
||||||
}, 'SSH authentication');
|
|
||||||
|
|
||||||
// :TODO: check Config max failed logon attempts/etc.
|
if(0 === username.length > 0 && password.length > 0) {
|
||||||
|
userLogin(self, ctx.username, ctx.password, function authResult(err) {
|
||||||
switch(ctx.method) {
|
if(err) {
|
||||||
case 'password' :
|
if(err.existingConn) {
|
||||||
// :TODO: Proper userLogin() here
|
// :TODO: Can we display somthing here?
|
||||||
self.userLoginWithCredentials(ctx.username, ctx.password, ctx);
|
ctx.reject();
|
||||||
break;
|
clientConn.end();
|
||||||
|
return;
|
||||||
case 'publickey' :
|
|
||||||
// :TODO:
|
|
||||||
ctx.reject();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'keyboard-interactive' :
|
|
||||||
if(!_.isString(ctx.username)) {
|
|
||||||
// :TODO: Let client know a username is required!
|
|
||||||
ctx.reject()
|
|
||||||
}
|
|
||||||
|
|
||||||
var PASS_PROMPT = { prompt : 'Password: ', echo : false };
|
|
||||||
|
|
||||||
ctx.prompt(PASS_PROMPT, function promptResponse(responses) {
|
|
||||||
if(0 === responses.length) {
|
|
||||||
return ctx.reject( ['keyboard-interactive'] );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
userLogin(self, ctx.username, responses[0], function authResult(err) {
|
|
||||||
if(err) {
|
|
||||||
if(err.existingConn) {
|
|
||||||
// :TODO: Already logged in - how to let the SSH client know?
|
|
||||||
//self.term.write('User already logged in');
|
|
||||||
ctx.reject();
|
|
||||||
} else {
|
|
||||||
PASS_PROMPT.prompt = 'Invalid username or password\nPassword: ';
|
|
||||||
ctx.prompt(PASS_PROMPT, promptResponse);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ctx.accept();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
default :
|
|
||||||
//
|
|
||||||
// Some terminals such as EtherTERM send a 'none' auth type, but include
|
|
||||||
// a username and password. For now, allow this. This should be looked
|
|
||||||
// into further as it may be a security issue!
|
|
||||||
//
|
|
||||||
if('none' === ctx.method && _.isString(ctx.username) && _.isString(ctx.password)) {
|
|
||||||
self.log.warn('Attempting authentication with \'none\' method');
|
|
||||||
|
|
||||||
self.userLoginWithCredentials(ctx.username, ctx.password, ctx);
|
|
||||||
} else {
|
} else {
|
||||||
self.log.warn( { method : ctx.method }, 'Unsupported SSH authentication method');
|
ctx.accept();
|
||||||
ctx.reject( [ 'password', 'keyboard-interactive' ] );
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if('keyboard-interactive' !== ctx.method) {
|
||||||
|
return ctx.reject( ['keyboard-interactive'] );
|
||||||
|
}
|
||||||
|
|
||||||
|
if(0 === username.length) {
|
||||||
|
// :TODO: can we display something here?
|
||||||
|
return ctx.reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
var interactivePrompt = { prompt: ctx.username + '\'s password: ', echo : false };
|
||||||
|
|
||||||
|
ctx.prompt(interactivePrompt, function retryPrompt(answers) {
|
||||||
|
userLogin(self, username, (answers[0] || ''), function authResult(err) {
|
||||||
|
if(err) {
|
||||||
|
if(err.existingConn) {
|
||||||
|
// :TODO: can we display something here?
|
||||||
|
ctx.reject();
|
||||||
|
clientConn.end();
|
||||||
|
} else {
|
||||||
|
interactivePrompt.prompt = 'Access denied\n' + interactivePrompt.prompt;
|
||||||
|
return ctx.prompt(interactivePrompt, retryPrompt);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctx.accept();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.updateTermInfo = function(info) {
|
this.updateTermInfo = function(info) {
|
||||||
|
@ -142,8 +95,8 @@ function SSHClient(clientConn) {
|
||||||
// From ssh2 docs:
|
// From ssh2 docs:
|
||||||
// "rows and cols override width and height when rows and cols are non-zero."
|
// "rows and cols override width and height when rows and cols are non-zero."
|
||||||
//
|
//
|
||||||
var termHeight = 24;
|
var termHeight;
|
||||||
var termWidth = 80;
|
var termWidth;
|
||||||
|
|
||||||
if(info.rows > 0 && info.cols > 0) {
|
if(info.rows > 0 && info.cols > 0) {
|
||||||
termHeight = info.rows;
|
termHeight = info.rows;
|
||||||
|
@ -155,30 +108,40 @@ function SSHClient(clientConn) {
|
||||||
|
|
||||||
assert(_.isObject(self.term));
|
assert(_.isObject(self.term));
|
||||||
|
|
||||||
self.term.termHeight = termHeight;
|
//
|
||||||
self.term.termWidth = termWidth;
|
// Note that if we fail here, connect.js attempts some non-standard
|
||||||
|
// queries/etc., and ultimately will default to 80x24 if all else fails
|
||||||
|
//
|
||||||
|
if(termHeight > 0 && termWidth > 0) {
|
||||||
|
self.term.termHeight = termHeight;
|
||||||
|
self.term.termWidth = termWidth;
|
||||||
|
}
|
||||||
|
|
||||||
if(_.isString(info.term) && info.term.length > 0 && 'unknown' === self.term.termType) {
|
if(_.isString(info.term) && info.term.length > 0 && 'unknown' === self.term.termType) {
|
||||||
self.setTermType(info.term);
|
self.setTermType(info.term);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
clientConn.on('ready', function clientReady() {
|
clientConn.once('ready', function clientReady() {
|
||||||
self.log.info('SSH authentication success');
|
self.log.info('SSH authentication success');
|
||||||
|
|
||||||
clientConn.on('session', function sess(accept, reject) {
|
clientConn.once('session', function sess(accept, reject) {
|
||||||
|
|
||||||
var session = accept();
|
var session = accept();
|
||||||
|
|
||||||
session.on('pty', function pty(accept, reject, info) {
|
session.once('pty', function pty(accept, reject, info) {
|
||||||
self.log.debug(info, 'SSH pty event');
|
self.log.debug(info, 'SSH pty event');
|
||||||
|
|
||||||
|
accept();
|
||||||
|
|
||||||
if(self.input) { // do we have I/O?
|
if(self.input) { // do we have I/O?
|
||||||
self.updateTermInfo(info);
|
self.updateTermInfo(info);
|
||||||
|
} else {
|
||||||
|
self.cachedPtyInfo = info;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
session.on('shell', function shell(accept, reject) {
|
session.once('shell', function shell(accept, reject) {
|
||||||
self.log.debug('SSH shell event');
|
self.log.debug('SSH shell event');
|
||||||
|
|
||||||
var channel = accept();
|
var channel = accept();
|
||||||
|
@ -189,12 +152,13 @@ function SSHClient(clientConn) {
|
||||||
self.emit('data', data);
|
self.emit('data', data);
|
||||||
});
|
});
|
||||||
|
|
||||||
self.emit('ready')
|
if(self.cachedPtyInfo) {
|
||||||
});
|
self.updateTermInfo(self.cachedPtyInfo);
|
||||||
|
delete self.cachedPtyInfo;
|
||||||
|
}
|
||||||
|
|
||||||
session.on('subsystem', function subsystem(accept, reject, info) {
|
// we're ready!
|
||||||
console.log('subsystem')
|
self.emit('ready');
|
||||||
console.log(info)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
session.on('window-change', function windowChange(accept, reject, info) {
|
session.on('window-change', function windowChange(accept, reject, info) {
|
||||||
|
@ -207,7 +171,12 @@ function SSHClient(clientConn) {
|
||||||
});
|
});
|
||||||
|
|
||||||
clientConn.on('end', function clientEnd() {
|
clientConn.on('end', function clientEnd() {
|
||||||
//self.emit('end');
|
self.emit('end'); // remove client connection/tracking
|
||||||
|
});
|
||||||
|
|
||||||
|
clientConn.on('error', function connError(err) {
|
||||||
|
// :TODO: what to do here?
|
||||||
|
console.log(err)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -558,12 +558,14 @@ TelnetClient.prototype.handleDontCommand = function(evt) {
|
||||||
this.log.trace(evt, 'dont');
|
this.log.trace(evt, 'dont');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
TelnetClient.prototype.setTermType = function(ttype) {
|
TelnetClient.prototype.setTermType = function(ttype) {
|
||||||
this.term.env.TERM = ttype;
|
this.term.env.TERM = ttype;
|
||||||
this.term.termType = ttype;
|
this.term.termType = ttype;
|
||||||
|
|
||||||
this.log.debug( { termType : ttype }, 'Set terminal type');
|
this.log.debug( { termType : ttype }, 'Set terminal type');
|
||||||
};
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
TelnetClient.prototype.handleSbCommand = function(evt) {
|
TelnetClient.prototype.handleSbCommand = function(evt) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
Loading…
Reference in New Issue