diff --git a/src/plugins/coreplugin/ssh/sshconnection.cpp b/src/plugins/coreplugin/ssh/sshconnection.cpp index e0fac86ea0e..802c2ae0035 100644 --- a/src/plugins/coreplugin/ssh/sshconnection.cpp +++ b/src/plugins/coreplugin/ssh/sshconnection.cpp @@ -188,6 +188,8 @@ SshConnectionPrivate::SshConnectionPrivate(SshConnection *conn) { setupPacketHandlers(); m_timeoutTimer.setSingleShot(true); + m_keepAliveTimer.setSingleShot(true); + m_keepAliveTimer.setInterval(10000); connect(m_channelManager, SIGNAL(timeout()), this, SLOT(handleTimeout())); } @@ -255,6 +257,9 @@ void SshConnectionPrivate::setupPacketHandlers() << KeyExchangeStarted << KeyExchangeSuccess << UserAuthServiceRequested << UserAuthRequested << ConnectionEstablished, &This::handleDisconnect); + + setupPacketHandler(SSH_MSG_UNIMPLEMENTED, + StateList() << ConnectionEstablished, &This::handleUnimplementedPacket); } void SshConnectionPrivate::setupPacketHandler(SshPacketType type, @@ -436,6 +441,9 @@ void SshConnectionPrivate::handleUserAuthSuccessPacket() m_state = ConnectionEstablished; m_timeoutTimer.stop(); emit connected(); + m_lastInvalidMsgSeqNr = InvalidSeqNr; + connect(&m_keepAliveTimer, SIGNAL(timeout()), SLOT(sendKeepAlivePacket())); + m_keepAliveTimer.start(); } void SshConnectionPrivate::handleUserAuthFailurePacket() @@ -452,6 +460,19 @@ void SshConnectionPrivate::handleDebugPacket() emit dataAvailable(msg.message); } +void SshConnectionPrivate::handleUnimplementedPacket() +{ + const SshUnimplemented &msg = m_incomingPacket.extractUnimplemented(); + if (msg.invalidMsgSeqNr != m_lastInvalidMsgSeqNr) { + throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR, + "Unexpected packet", tr("The server sent an unexpected SSH packet " + "of type SSH_MSG_UNIMPLEMENTED.")); + } + m_lastInvalidMsgSeqNr = InvalidSeqNr; + m_timeoutTimer.stop(); + m_keepAliveTimer.start(); +} + void SshConnectionPrivate::handleChannelRequest() { m_channelManager->handleChannelRequest(m_incomingPacket); @@ -514,6 +535,8 @@ void SshConnectionPrivate::handleDisconnect() "", tr("Server closed connection: %1").arg(msg.description)); } + + void SshConnectionPrivate::sendData(const QByteArray &data) { if (canUseSocket()) @@ -541,6 +564,14 @@ void SshConnectionPrivate::handleTimeout() tr("Timeout waiting for reply from server.")); } +void SshConnectionPrivate::sendKeepAlivePacket() +{ + Q_ASSERT(m_lastInvalidMsgSeqNr == InvalidSeqNr); + m_lastInvalidMsgSeqNr = m_sendFacility.nextClientSeqNr(); + m_sendFacility.sendInvalidPacket(); + m_timeoutTimer.start(5000); +} + void SshConnectionPrivate::connectToHost(const SshConnectionParameters &serverInfo) { m_incomingData.clear(); @@ -577,6 +608,8 @@ void SshConnectionPrivate::closeConnection(SshErrorCode sshError, m_timeoutTimer.stop(); disconnect(m_socket, 0, this, 0); disconnect(&m_timeoutTimer, 0, this, 0); + m_keepAliveTimer.stop(); + disconnect(&m_keepAliveTimer, 0, this, 0); try { m_channelManager->closeAllChannels(); m_sendFacility.sendDisconnectPacket(sshError, serverErrorString); @@ -606,5 +639,7 @@ QSharedPointer SshConnectionPrivate::createSftpChannel() return m_channelManager->createSftpChannel(); } +const quint64 SshConnectionPrivate::InvalidSeqNr = static_cast(-1); + } // namespace Internal } // namespace Core diff --git a/src/plugins/coreplugin/ssh/sshconnection_p.h b/src/plugins/coreplugin/ssh/sshconnection_p.h index a0ec0425cd9..9a89d99a78d 100644 --- a/src/plugins/coreplugin/ssh/sshconnection_p.h +++ b/src/plugins/coreplugin/ssh/sshconnection_p.h @@ -102,6 +102,7 @@ private: Q_SLOT void handleSocketError(); Q_SLOT void handleSocketDisconnected(); Q_SLOT void handleTimeout(); + Q_SLOT void sendKeepAlivePacket(); void handleServerId(); void handlePackets(); @@ -116,6 +117,7 @@ private: void handleUserAuthBannerPacket(); void handleGlobalRequest(); void handleDebugPacket(); + void handleUnimplementedPacket(); void handleChannelRequest(); void handleChannelOpen(); void handleChannelOpenFailure(); @@ -141,6 +143,8 @@ private: typedef QPair HandlerInStates; QHash m_packetHandlers; + static const quint64 InvalidSeqNr; + QTcpSocket *m_socket; SshStateInternal m_state; SshIncomingPacket m_incomingPacket; @@ -152,8 +156,10 @@ private: QString m_errorString; QScopedPointer m_keyExchange; QTimer m_timeoutTimer; + QTimer m_keepAliveTimer; bool m_ignoreNextPacket; SshConnection *m_conn; + quint64 m_lastInvalidMsgSeqNr; }; } // namespace Internal diff --git a/src/plugins/coreplugin/ssh/sshincomingpacket.cpp b/src/plugins/coreplugin/ssh/sshincomingpacket.cpp index 4126fe412f9..2e8bec0d4c0 100644 --- a/src/plugins/coreplugin/ssh/sshincomingpacket.cpp +++ b/src/plugins/coreplugin/ssh/sshincomingpacket.cpp @@ -257,7 +257,23 @@ SshDebug SshIncomingPacket::extractDebug() const return msg; } catch (SshPacketParseException &) { throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, - "Invalid SSH_MSG_USERAUTH_BANNER."); + "Invalid SSH_MSG_DEBUG."); + } +} + +SshUnimplemented SshIncomingPacket::extractUnimplemented() const +{ + Q_ASSERT(isComplete()); + Q_ASSERT(type() == SSH_MSG_UNIMPLEMENTED); + + try { + SshUnimplemented msg; + quint32 offset = TypeOffset + 1; + msg.invalidMsgSeqNr = SshPacketParser::asUint32(m_data, &offset); + return msg; + } catch (SshPacketParseException &) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Invalid SSH_MSG_UNIMPLEMENTED."); } } diff --git a/src/plugins/coreplugin/ssh/sshincomingpacket_p.h b/src/plugins/coreplugin/ssh/sshincomingpacket_p.h index c96cbdde18e..6c8ce46d84e 100644 --- a/src/plugins/coreplugin/ssh/sshincomingpacket_p.h +++ b/src/plugins/coreplugin/ssh/sshincomingpacket_p.h @@ -91,6 +91,11 @@ struct SshDebug QByteArray language; }; +struct SshUnimplemented +{ + quint32 invalidMsgSeqNr; +}; + struct SshChannelOpenFailure { quint32 localChannel; @@ -156,6 +161,7 @@ public: SshDisconnect extractDisconnect() const; SshUserAuthBanner extractUserAuthBanner() const; SshDebug extractDebug() const; + SshUnimplemented extractUnimplemented() const; SshChannelOpenFailure extractChannelOpenFailure() const; SshChannelOpenConfirmation extractChannelOpenConfirmation() const; diff --git a/src/plugins/coreplugin/ssh/sshoutgoingpacket.cpp b/src/plugins/coreplugin/ssh/sshoutgoingpacket.cpp index c93cf80e0da..c99db71acad 100644 --- a/src/plugins/coreplugin/ssh/sshoutgoingpacket.cpp +++ b/src/plugins/coreplugin/ssh/sshoutgoingpacket.cpp @@ -130,6 +130,16 @@ void SshOutgoingPacket::generateRequestFailurePacket() init(SSH_MSG_REQUEST_FAILURE).finalize(); } +void SshOutgoingPacket::generateIgnorePacket() +{ + init(SSH_MSG_IGNORE).finalize(); +} + +void SshOutgoingPacket::generateInvalidMessagePacket() +{ + init(SSH_MSG_INVALID).finalize(); +} + void SshOutgoingPacket::generateSessionPacket(quint32 channelId, quint32 windowSize, quint32 maxPacketSize) { diff --git a/src/plugins/coreplugin/ssh/sshoutgoingpacket_p.h b/src/plugins/coreplugin/ssh/sshoutgoingpacket_p.h index 479da5ba201..da7f6fcc03c 100644 --- a/src/plugins/coreplugin/ssh/sshoutgoingpacket_p.h +++ b/src/plugins/coreplugin/ssh/sshoutgoingpacket_p.h @@ -59,6 +59,8 @@ public: void generateUserAuthByKeyRequestPacket(const QByteArray &user, const QByteArray &service); void generateRequestFailurePacket(); + void generateIgnorePacket(); + void generateInvalidMessagePacket(); void generateSessionPacket(quint32 channelId, quint32 windowSize, quint32 maxPacketSize); void generateEnvPacket(quint32 remoteChannel, const QByteArray &var, diff --git a/src/plugins/coreplugin/ssh/sshpacket_p.h b/src/plugins/coreplugin/ssh/sshpacket_p.h index 6a981159281..1f66797a9ac 100644 --- a/src/plugins/coreplugin/ssh/sshpacket_p.h +++ b/src/plugins/coreplugin/ssh/sshpacket_p.h @@ -79,7 +79,12 @@ enum SshPacketType { SSH_MSG_CHANNEL_CLOSE = 97, SSH_MSG_CHANNEL_REQUEST = 98, SSH_MSG_CHANNEL_SUCCESS = 99, - SSH_MSG_CHANNEL_FAILURE = 100 + SSH_MSG_CHANNEL_FAILURE = 100, + + // Not completely safe, since the server may actually understand this + // message type as an extension. Switch to a different value in that case + // (between 128 and 191). + SSH_MSG_INVALID = 128 }; enum SshExtendedDataType { SSH_EXTENDED_DATA_STDERR = 1 }; diff --git a/src/plugins/coreplugin/ssh/sshsendfacility.cpp b/src/plugins/coreplugin/ssh/sshsendfacility.cpp index fc40d9176ef..150118fd8c5 100644 --- a/src/plugins/coreplugin/ssh/sshsendfacility.cpp +++ b/src/plugins/coreplugin/ssh/sshsendfacility.cpp @@ -133,6 +133,18 @@ void SshSendFacility::sendRequestFailurePacket() sendPacket(); } +void SshSendFacility::sendIgnorePacket() +{ + m_outgoingPacket.generateIgnorePacket(); + sendPacket(); +} + +void SshSendFacility::sendInvalidPacket() +{ + m_outgoingPacket.generateInvalidMessagePacket(); + sendPacket(); +} + void SshSendFacility::sendSessionPacket(quint32 channelId, quint32 windowSize, quint32 maxPacketSize) { diff --git a/src/plugins/coreplugin/ssh/sshsendfacility_p.h b/src/plugins/coreplugin/ssh/sshsendfacility_p.h index 21ff10bfbd6..366413917b3 100644 --- a/src/plugins/coreplugin/ssh/sshsendfacility_p.h +++ b/src/plugins/coreplugin/ssh/sshsendfacility_p.h @@ -66,6 +66,8 @@ public: void sendUserAuthByKeyRequestPacket(const QByteArray &user, const QByteArray &service); void sendRequestFailurePacket(); + void sendIgnorePacket(); + void sendInvalidPacket(); void sendSessionPacket(quint32 channelId, quint32 windowSize, quint32 maxPacketSize); void sendEnvPacket(quint32 remoteChannel, const QByteArray &var, @@ -78,6 +80,7 @@ public: const QByteArray &signalName); void sendChannelEofPacket(quint32 remoteChannel); void sendChannelClosePacket(quint32 remoteChannel); + quint32 nextClientSeqNr() const { return m_clientSeqNr; } private: void sendPacket();