forked from qt-creator/qt-creator
SSH: Implement and make use of RFC 4256.
There is now at least one Linux distribution (openSUSE 12.3) that
disables the "password" authentication method in its default
sshd_config, while others allow it, but disable "keyboard-interactive".
This patch tackles the problem as follows:
1) Implement RFC 4256 ("keyboard-interactive") and make this method
available in the API.
2) In addition, the API offers to try both password-based methods
one after the other, until one has succeeded or all have failed.
3) Dialogs continue to offer just the choice between "Password" and
"Key", as to not confuse users. Internally, "Password" uses the feature
described in 2).
Task-number: QTCREATORBUG-9568
Change-Id: Ic81bd5d2dc4b1332ea1a8be938c19811c21a9087
Reviewed-by: hjk <hjk121@nokiamail.com>
Reviewed-by: Christian Kandeler <christian.kandeler@digia.com>
This commit is contained in:
@@ -81,7 +81,7 @@ namespace {
|
||||
|
||||
|
||||
SshConnectionParameters::SshConnectionParameters() :
|
||||
timeout(0), authenticationType(AuthenticationByKey), port(0)
|
||||
timeout(0), authenticationType(AuthenticationTypePublicKey), port(0)
|
||||
{
|
||||
options |= SshIgnoreDefaultProxy;
|
||||
options |= SshEnableStrictConformanceChecks;
|
||||
@@ -91,7 +91,7 @@ static inline bool equals(const SshConnectionParameters &p1, const SshConnection
|
||||
{
|
||||
return p1.host == p2.host && p1.userName == p2.userName
|
||||
&& p1.authenticationType == p2.authenticationType
|
||||
&& (p1.authenticationType == SshConnectionParameters::AuthenticationByPassword ?
|
||||
&& (p1.authenticationType == SshConnectionParameters::AuthenticationTypePassword ?
|
||||
p1.password == p2.password : p1.privateKeyFile == p2.privateKeyFile)
|
||||
&& p1.timeout == p2.timeout && p1.port == p2.port;
|
||||
}
|
||||
@@ -255,8 +255,11 @@ void SshConnectionPrivate::setupPacketHandlers()
|
||||
setupPacketHandler(SSH_MSG_SERVICE_ACCEPT,
|
||||
StateList() << UserAuthServiceRequested,
|
||||
&This::handleServiceAcceptPacket);
|
||||
setupPacketHandler(SSH_MSG_USERAUTH_PASSWD_CHANGEREQ,
|
||||
StateList() << UserAuthRequested, &This::handlePasswordExpiredPacket);
|
||||
if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypePassword
|
||||
|| m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods) {
|
||||
setupPacketHandler(SSH_MSG_USERAUTH_PASSWD_CHANGEREQ,
|
||||
StateList() << UserAuthRequested, &This::handlePasswordExpiredPacket);
|
||||
}
|
||||
setupPacketHandler(SSH_MSG_GLOBAL_REQUEST,
|
||||
StateList() << ConnectionEstablished, &This::handleGlobalRequest);
|
||||
|
||||
@@ -267,6 +270,11 @@ void SshConnectionPrivate::setupPacketHandlers()
|
||||
&This::handleUserAuthSuccessPacket);
|
||||
setupPacketHandler(SSH_MSG_USERAUTH_FAILURE, authReqList,
|
||||
&This::handleUserAuthFailurePacket);
|
||||
if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypeKeyboardInteractive
|
||||
|| m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods) {
|
||||
setupPacketHandler(SSH_MSG_USERAUTH_INFO_REQUEST, authReqList,
|
||||
&This::handleUserAuthInfoRequestPacket);
|
||||
}
|
||||
|
||||
const StateList connectedList
|
||||
= StateList() << ConnectionEstablished;
|
||||
@@ -442,14 +450,13 @@ void SshConnectionPrivate::handleCurrentPacket()
|
||||
|
||||
QHash<SshPacketType, HandlerInStates>::ConstIterator it
|
||||
= m_packetHandlers.find(m_incomingPacket.type());
|
||||
if (it == m_packetHandlers.end()) {
|
||||
if (it == m_packetHandlers.constEnd()) {
|
||||
m_sendFacility.sendMsgUnimplementedPacket(m_incomingPacket.serverSeqNr());
|
||||
return;
|
||||
}
|
||||
if (!it.value().first.contains(m_state)) {
|
||||
throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Unexpected packet.", tr("Unexpected packet of type %1.")
|
||||
.arg(m_incomingPacket.type()));
|
||||
handleUnexpectedPacket();
|
||||
return;
|
||||
}
|
||||
(this->*it.value().second)();
|
||||
}
|
||||
@@ -512,24 +519,57 @@ void SshConnectionPrivate::handleNewKeysPacket()
|
||||
|
||||
void SshConnectionPrivate::handleServiceAcceptPacket()
|
||||
{
|
||||
if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationByPassword) {
|
||||
m_sendFacility.sendUserAuthByPwdRequestPacket(m_connParams.userName.toUtf8(),
|
||||
SshCapabilities::SshConnectionService, m_connParams.password.toUtf8());
|
||||
} else {
|
||||
m_sendFacility.sendUserAuthByKeyRequestPacket(m_connParams.userName.toUtf8(),
|
||||
SshCapabilities::SshConnectionService);
|
||||
switch (m_connParams.authenticationType) {
|
||||
case SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods:
|
||||
m_triedAllPasswordBasedMethods = false;
|
||||
// Fall-through.
|
||||
case SshConnectionParameters::AuthenticationTypePassword:
|
||||
m_sendFacility.sendUserAuthByPasswordRequestPacket(m_connParams.userName.toUtf8(),
|
||||
SshCapabilities::SshConnectionService, m_connParams.password.toUtf8());
|
||||
break;
|
||||
case SshConnectionParameters::AuthenticationTypeKeyboardInteractive:
|
||||
m_sendFacility.sendUserAuthByKeyboardInteractiveRequestPacket(m_connParams.userName.toUtf8(),
|
||||
SshCapabilities::SshConnectionService);
|
||||
break;
|
||||
case SshConnectionParameters::AuthenticationTypePublicKey:
|
||||
m_sendFacility.sendUserAuthByPublicKeyRequestPacket(m_connParams.userName.toUtf8(),
|
||||
SshCapabilities::SshConnectionService);
|
||||
break;
|
||||
}
|
||||
m_state = UserAuthRequested;
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handlePasswordExpiredPacket()
|
||||
{
|
||||
if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationByKey) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Got SSH_MSG_USERAUTH_PASSWD_CHANGEREQ, but did not use password.");
|
||||
if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods
|
||||
&& m_triedAllPasswordBasedMethods) {
|
||||
// This means we just tried to authorize via "keyboard-interactive", in which case
|
||||
// this type of packet is not allowed.
|
||||
handleUnexpectedPacket();
|
||||
return;
|
||||
}
|
||||
throw SshClientException(SshAuthenticationError, tr("Password expired."));
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handleUserAuthInfoRequestPacket()
|
||||
{
|
||||
if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods
|
||||
&& !m_triedAllPasswordBasedMethods) {
|
||||
// This means we just tried to authorize via "password", in which case
|
||||
// this type of packet is not allowed.
|
||||
handleUnexpectedPacket();
|
||||
return;
|
||||
}
|
||||
|
||||
throw SshClientException(SshAuthenticationError, tr("Password expired."));
|
||||
const SshUserAuthInfoRequestPacket requestPacket
|
||||
= m_incomingPacket.extractUserAuthInfoRequest();
|
||||
QStringList responses;
|
||||
responses.reserve(requestPacket.prompts.count());
|
||||
|
||||
// Not very interactive, admittedly, but we don't want to be for now.
|
||||
for (int i = 0; i < requestPacket.prompts.count(); ++i)
|
||||
responses << m_connParams.password;
|
||||
m_sendFacility.sendUserAuthInfoResponsePacket(responses);
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handleUserAuthBannerPacket()
|
||||
@@ -537,6 +577,13 @@ void SshConnectionPrivate::handleUserAuthBannerPacket()
|
||||
emit dataAvailable(m_incomingPacket.extractUserAuthBanner().message);
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handleUnexpectedPacket()
|
||||
{
|
||||
throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Unexpected packet.", tr("Unexpected packet of type %1.")
|
||||
.arg(m_incomingPacket.type()));
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handleGlobalRequest()
|
||||
{
|
||||
m_sendFacility.sendRequestFailurePacket();
|
||||
@@ -554,9 +601,20 @@ void SshConnectionPrivate::handleUserAuthSuccessPacket()
|
||||
|
||||
void SshConnectionPrivate::handleUserAuthFailurePacket()
|
||||
{
|
||||
// TODO: Evaluate "authentications that can continue" field and act on it.
|
||||
if (m_connParams.authenticationType
|
||||
== SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods
|
||||
&& !m_triedAllPasswordBasedMethods) {
|
||||
m_triedAllPasswordBasedMethods = true;
|
||||
m_sendFacility.sendUserAuthByKeyboardInteractiveRequestPacket(
|
||||
m_connParams.userName.toUtf8(),
|
||||
SshCapabilities::SshConnectionService);
|
||||
return;
|
||||
}
|
||||
|
||||
m_timeoutTimer.stop();
|
||||
const QString errorMsg = m_connParams.authenticationType == SshConnectionParameters::AuthenticationByPassword
|
||||
? tr("Server rejected password.") : tr("Server rejected key.");
|
||||
const QString errorMsg = m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypePublicKey
|
||||
? tr("Server rejected key.") : tr("Server rejected password.");
|
||||
throw SshClientException(SshAuthenticationError, errorMsg);
|
||||
}
|
||||
void SshConnectionPrivate::handleDebugPacket()
|
||||
@@ -698,7 +756,7 @@ void SshConnectionPrivate::connectToHost()
|
||||
m_serverHasSentDataBeforeId = false;
|
||||
|
||||
try {
|
||||
if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationByKey)
|
||||
if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypePublicKey)
|
||||
createPrivateKey();
|
||||
} catch (const SshClientException &ex) {
|
||||
m_error = ex.error;
|
||||
|
||||
Reference in New Issue
Block a user