summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Kandeler <christian.kandeler@digia.com>2012-09-26 10:57:19 +0200
committerChristian Kandeler <christian.kandeler@digia.com>2012-09-26 14:45:25 +0200
commite2cb3acdaa3b2870dc7c234785d68366497f7bcc (patch)
tree65889d78d2b3bedc6e0520fbc56f48ea216f1f91
parent3df129d4f6f38a4586ccfb430b22f82aed031a76 (diff)
downloadqt-creator-e2cb3acdaa3b2870dc7c234785d68366497f7bcc.tar.gz
SSH: Fix parsing of server id.
The current implementation is a bit sloppy, so one can construct certain invalid strings that will be accepted as well as certain valid ones that will be rejected. The new checks are much more precise. Most importantly in practice, the version string "1.99" sent by some older servers is now accepted, as required by the RFC. Change-Id: Ib35c27d3a3bc4aea259b9a0dad66817435d95c0a Reviewed-by: Tobias Hunger <tobias.hunger@digia.com>
-rw-r--r--src/libs/ssh/sshconnection.cpp78
-rw-r--r--src/libs/ssh/sshconnection_p.h1
2 files changed, 61 insertions, 18 deletions
diff --git a/src/libs/ssh/sshconnection.cpp b/src/libs/ssh/sshconnection.cpp
index ebf233d3b6..7d748d0267 100644
--- a/src/libs/ssh/sshconnection.cpp
+++ b/src/libs/ssh/sshconnection.cpp
@@ -44,6 +44,7 @@
#include <QMutex>
#include <QMutexLocker>
#include <QNetworkProxy>
+#include <QRegExp>
#include <QTcpSocket>
/*!
@@ -320,7 +321,7 @@ void SshConnectionPrivate::handleIncomingData()
qDebug("state = %d, remote data size = %d", m_state,
m_incomingData.count());
#endif
- if (m_state == SocketConnected)
+ if (m_serverId.isEmpty())
handleServerId();
handlePackets();
} catch (SshServerException &e) {
@@ -335,39 +336,78 @@ void SshConnectionPrivate::handleIncomingData()
}
}
+// RFC 4253, 4.2.
void SshConnectionPrivate::handleServerId()
{
#ifdef CREATOR_SSH_DEBUG
qDebug("%s: incoming data size = %d, incoming data = '%s'",
Q_FUNC_INFO, m_incomingData.count(), m_incomingData.data());
#endif
- const int idOffset = m_incomingData.indexOf("SSH-");
- if (idOffset == -1)
+ const int newLinePos = m_incomingData.indexOf('\n');
+ if (newLinePos == -1)
+ return; // Not enough data yet.
+
+ // Lines not starting with "SSH-" are ignored.
+ if (!m_incomingData.startsWith("SSH-")) {
+ m_incomingData.remove(0, newLinePos + 1);
+ m_serverHasSentDataBeforeId = true;
return;
- m_incomingData.remove(0, idOffset);
- if (m_incomingData.size() < 7)
- return;
- const QByteArray &version = m_incomingData.mid(4, 3);
- if (version != "2.0") {
+ }
+
+ if (newLinePos > 255 - 1) {
+ throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Identification string too long.",
+ tr("Server identification string is %1 characters long, but the maximum "
+ "allowed length is 255.").arg(newLinePos + 1));
+ }
+
+ const bool hasCarriageReturn = m_incomingData.at(newLinePos - 1) == '\r';
+ m_serverId = m_incomingData.left(newLinePos);
+ if (hasCarriageReturn)
+ m_serverId.chop(1);
+ m_incomingData.remove(0, newLinePos + 1);
+
+ if (m_serverId.contains('\0')) {
+ throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Identification string contains illegal NUL character.",
+ tr("Server identification string contains illegal NUL character."));
+ }
+
+ // "printable US-ASCII characters, with the exception of whitespace characters
+ // and the minus sign"
+ QString legalString = QLatin1String("[]!\"#$!&'()*+,./0-9:;<=>?@A-Z[\\\\^_`a-z{|}~]+");
+ const QRegExp versionIdpattern(QString::fromLatin1("SSH-(%1)-%1(?: .+)?").arg(legalString));
+ if (!versionIdpattern.exactMatch(QString::fromLatin1(m_serverId))) {
+ throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Identification string is invalid.",
+ tr("Server Identification string '%1' is invalid.")
+ .arg(QString::fromLatin1(m_serverId)));
+ }
+ const QString serverProtoVersion = versionIdpattern.cap(1);
+ if (serverProtoVersion != QLatin1String("2.0") && serverProtoVersion != QLatin1String("1.99")) {
throw SshServerException(SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED,
"Invalid protocol version.",
- tr("Invalid protocol version: Expected '2.0', got '%1'.")
- .arg(SshPacketParser::asUserString(version)));
+ tr("Server protocol version is '%1', but needs to be 2.0 or 1.99.")
+ .arg(serverProtoVersion));
}
- const int endOffset = m_incomingData.indexOf("\r\n");
- if (endOffset == -1)
- return;
- if (m_incomingData.at(7) != '-') {
+
+ // Disable this check to accept older OpenSSH servers that do this wrong.
+ if (serverProtoVersion == QLatin1String("2.0") && !hasCarriageReturn) {
+ throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Identification string is invalid.",
+ tr("Server identification string is invalid (missing carriage return)."));
+ }
+
+ if (serverProtoVersion == QLatin1String("1.99") && m_serverHasSentDataBeforeId) {
throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
- "Invalid server id.", tr("Invalid server id '%1'.")
- .arg(SshPacketParser::asUserString(m_incomingData)));
+ "No extra data preceding identification string allowed for 1.99.",
+ tr("Server reports protocol version 1.99, but sends data "
+ "before the identification string, which is not allowed."));
}
m_keyExchange.reset(new SshKeyExchange(m_sendFacility));
- m_serverId = m_incomingData.left(endOffset);
m_keyExchange->sendKexInitPacket(m_serverId);
m_keyExchangeState = KexInitSent;
- m_incomingData.remove(0, endOffset + 2);
}
void SshConnectionPrivate::handlePackets()
@@ -645,6 +685,8 @@ void SshConnectionPrivate::connectToHost()
m_error = SshNoError;
m_ignoreNextPacket = false;
m_errorString.clear();
+ m_serverId.clear();
+ m_serverHasSentDataBeforeId = false;
try {
if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationByKey)
diff --git a/src/libs/ssh/sshconnection_p.h b/src/libs/ssh/sshconnection_p.h
index bdf0c26c96..797d736437 100644
--- a/src/libs/ssh/sshconnection_p.h
+++ b/src/libs/ssh/sshconnection_p.h
@@ -165,6 +165,7 @@ private:
SshConnection *m_conn;
quint64 m_lastInvalidMsgSeqNr;
QByteArray m_serverId;
+ bool m_serverHasSentDataBeforeId;
};
} // namespace Internal