1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
16 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights. These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
32 **************************************************************************/
34 #include "sshconnection.h"
35 #include "sshconnection_p.h"
37 #include "sftpchannel.h"
38 #include "sshcapabilities_p.h"
39 #include "sshchannelmanager_p.h"
40 #include "sshcryptofacility_p.h"
41 #include "sshexception_p.h"
42 #include "sshkeyexchange_p.h"
44 #include <utils/qtcassert.h>
46 #include <botan/exceptn.h>
47 #include <botan/init.h>
49 #include <QtCore/QFile>
50 #include <QtCore/QMutex>
51 #include <QtNetwork/QNetworkProxy>
52 #include <QtNetwork/QTcpSocket>
57 const QByteArray ClientId("SSH-2.0-QtCreator\r\n");
59 bool staticInitializationsDone = false;
60 QMutex staticInitMutex;
62 void doStaticInitializationsIfNecessary()
64 if (!staticInitializationsDone) {
65 staticInitMutex.lock();
66 if (!staticInitializationsDone) {
67 Botan::LibraryInitializer::initialize("thread_safe=true");
68 qRegisterMetaType<Core::SshError>("Core::SshError");
69 qRegisterMetaType<Core::SftpJobId>("Core::SftpJobId");
70 staticInitializationsDone = true;
72 staticInitMutex.unlock();
75 } // anonymous namespace
78 SshConnectionParameters::SshConnectionParameters(ProxyType proxyType) :
79 timeout(0), authType(AuthByKey), port(0), proxyType(proxyType)
83 static inline bool equals(const SshConnectionParameters &p1, const SshConnectionParameters &p2)
85 return p1.host == p2.host && p1.uname == p2.uname
86 && p1.authType == p2.authType
87 && (p1.authType == SshConnectionParameters::AuthByPwd ?
88 p1.pwd == p2.pwd : p1.privateKeyFile == p2.privateKeyFile)
89 && p1.timeout == p2.timeout && p1.port == p2.port;
92 CORE_EXPORT bool operator==(const SshConnectionParameters &p1, const SshConnectionParameters &p2)
94 return equals(p1, p2);
97 CORE_EXPORT bool operator!=(const SshConnectionParameters &p1, const SshConnectionParameters &p2)
99 return !equals(p1, p2);
102 // TODO: Mechanism for checking the host key. First connection to host: save, later: compare
104 SshConnection::Ptr SshConnection::create()
106 doStaticInitializationsIfNecessary();
107 return Ptr(new SshConnection);
110 SshConnection::SshConnection() : d(new Internal::SshConnectionPrivate(this))
112 connect(d, SIGNAL(connected()), this, SIGNAL(connected()),
113 Qt::QueuedConnection);
114 connect(d, SIGNAL(dataAvailable(QString)), this,
115 SIGNAL(dataAvailable(QString)), Qt::QueuedConnection);
116 connect(d, SIGNAL(disconnected()), this, SIGNAL(disconnected()),
117 Qt::QueuedConnection);
118 connect(d, SIGNAL(error(Core::SshError)), this,
119 SIGNAL(error(Core::SshError)), Qt::QueuedConnection);
122 void SshConnection::connectToHost(const SshConnectionParameters &serverInfo)
124 d->connectToHost(serverInfo);
127 void SshConnection::disconnectFromHost()
129 d->closeConnection(Internal::SSH_DISCONNECT_BY_APPLICATION, SshNoError, "",
133 SshConnection::State SshConnection::state() const
135 switch (d->state()) {
136 case Internal::SocketUnconnected:
138 case Internal::ConnectionEstablished:
145 SshError SshConnection::errorState() const
150 QString SshConnection::errorString() const
152 return d->errorString();
155 SshConnectionParameters SshConnection::connectionParameters() const
157 return d->m_connParams;
160 SshConnection::~SshConnection()
163 disconnectFromHost();
167 QSharedPointer<SshRemoteProcess> SshConnection::createRemoteProcess(const QByteArray &command)
169 QTC_ASSERT(state() == Connected, return QSharedPointer<SshRemoteProcess>());
170 return d->createRemoteProcess(command);
173 QSharedPointer<SftpChannel> SshConnection::createSftpChannel()
175 QTC_ASSERT(state() == Connected, return QSharedPointer<SftpChannel>());
176 return d->createSftpChannel();
182 SshConnectionPrivate::SshConnectionPrivate(SshConnection *conn)
183 : m_socket(new QTcpSocket(this)), m_state(SocketUnconnected),
184 m_sendFacility(m_socket),
185 m_channelManager(new SshChannelManager(m_sendFacility, this)),
186 m_connParams(SshConnectionParameters::DefaultProxy),
187 m_error(SshNoError), m_ignoreNextPacket(false), m_conn(conn)
189 setupPacketHandlers();
190 m_timeoutTimer.setSingleShot(true);
191 connect(m_channelManager, SIGNAL(timeout()), this, SLOT(handleTimeout()));
194 SshConnectionPrivate::~SshConnectionPrivate()
199 void SshConnectionPrivate::setupPacketHandlers()
201 typedef SshConnectionPrivate This;
203 setupPacketHandler(SSH_MSG_KEXINIT, StateList() << SocketConnected,
204 &This::handleKeyExchangeInitPacket);
205 setupPacketHandler(SSH_MSG_KEXDH_REPLY, StateList() << KeyExchangeStarted,
206 &This::handleKeyExchangeReplyPacket);
208 setupPacketHandler(SSH_MSG_NEWKEYS, StateList() << KeyExchangeSuccess,
209 &This::handleNewKeysPacket);
210 setupPacketHandler(SSH_MSG_SERVICE_ACCEPT,
211 StateList() << UserAuthServiceRequested,
212 &This::handleServiceAcceptPacket);
213 setupPacketHandler(SSH_MSG_USERAUTH_PASSWD_CHANGEREQ,
214 StateList() << UserAuthRequested, &This::handlePasswordExpiredPacket);
215 setupPacketHandler(SSH_MSG_GLOBAL_REQUEST,
216 StateList() << ConnectionEstablished, &This::handleGlobalRequest);
218 const StateList authReqList = StateList() << UserAuthRequested;
219 setupPacketHandler(SSH_MSG_USERAUTH_BANNER, authReqList,
220 &This::handleUserAuthBannerPacket);
221 setupPacketHandler(SSH_MSG_USERAUTH_SUCCESS, authReqList,
222 &This::handleUserAuthSuccessPacket);
223 setupPacketHandler(SSH_MSG_USERAUTH_FAILURE, authReqList,
224 &This::handleUserAuthFailurePacket);
226 const StateList connectedList
227 = StateList() << ConnectionEstablished;
228 setupPacketHandler(SSH_MSG_CHANNEL_REQUEST, connectedList,
229 &This::handleChannelRequest);
230 setupPacketHandler(SSH_MSG_CHANNEL_OPEN, connectedList,
231 &This::handleChannelOpen);
232 setupPacketHandler(SSH_MSG_CHANNEL_OPEN_FAILURE, connectedList,
233 &This::handleChannelOpenFailure);
234 setupPacketHandler(SSH_MSG_CHANNEL_OPEN_CONFIRMATION, connectedList,
235 &This::handleChannelOpenConfirmation);
236 setupPacketHandler(SSH_MSG_CHANNEL_SUCCESS, connectedList,
237 &This::handleChannelSuccess);
238 setupPacketHandler(SSH_MSG_CHANNEL_FAILURE, connectedList,
239 &This::handleChannelFailure);
240 setupPacketHandler(SSH_MSG_CHANNEL_WINDOW_ADJUST, connectedList,
241 &This::handleChannelWindowAdjust);
242 setupPacketHandler(SSH_MSG_CHANNEL_DATA, connectedList,
243 &This::handleChannelData);
244 setupPacketHandler(SSH_MSG_CHANNEL_EXTENDED_DATA, connectedList,
245 &This::handleChannelExtendedData);
247 const StateList connectedOrClosedList
248 = StateList() << SocketUnconnected << ConnectionEstablished;
249 setupPacketHandler(SSH_MSG_CHANNEL_EOF, connectedOrClosedList,
250 &This::handleChannelEof);
251 setupPacketHandler(SSH_MSG_CHANNEL_CLOSE, connectedOrClosedList,
252 &This::handleChannelClose);
254 setupPacketHandler(SSH_MSG_DISCONNECT, StateList() << SocketConnected
255 << KeyExchangeStarted << KeyExchangeSuccess
256 << UserAuthServiceRequested << UserAuthRequested
257 << ConnectionEstablished, &This::handleDisconnect);
260 void SshConnectionPrivate::setupPacketHandler(SshPacketType type,
261 const SshConnectionPrivate::StateList &states,
262 SshConnectionPrivate::PacketHandler handler)
264 m_packetHandlers.insert(type, HandlerInStates(states, handler));
267 void SshConnectionPrivate::handleSocketConnected()
269 m_state = SocketConnected;
273 void SshConnectionPrivate::handleIncomingData()
275 if (m_state == SocketUnconnected)
276 return; // For stuff queued in the event loop after we've called closeConnection();
281 m_incomingData += m_socket->readAll();
282 #ifdef CREATOR_SSH_DEBUG
283 qDebug("state = %d, remote data size = %d", m_state,
284 m_incomingData.count());
286 if (m_state == SocketConnected)
289 } catch (SshServerException &e) {
290 closeConnection(e.error, SshProtocolError, e.errorStringServer,
291 tr("SSH Protocol error: %1").arg(e.errorStringUser));
292 } catch (SshClientException &e) {
293 closeConnection(SSH_DISCONNECT_BY_APPLICATION, e.error, "",
295 } catch (Botan::Exception &e) {
296 closeConnection(SSH_DISCONNECT_BY_APPLICATION, SshInternalError, "",
297 tr("Botan library exception: %1").arg(e.what()));
301 void SshConnectionPrivate::handleServerId()
303 const int idOffset = m_incomingData.indexOf("SSH-");
306 m_incomingData.remove(0, idOffset);
307 if (m_incomingData.size() < 7)
309 const QByteArray &version = m_incomingData.mid(4, 3);
310 if (version != "2.0") {
311 throw SshServerException(SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED,
312 "Invalid protocol version.",
313 tr("Invalid protocol version: Expected '2.0', got '%1'.")
314 .arg(SshPacketParser::asUserString(version)));
316 const int endOffset = m_incomingData.indexOf("\r\n");
319 if (m_incomingData.at(7) != '-') {
320 throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
321 "Invalid server id.", tr("Invalid server id '%1'.")
322 .arg(SshPacketParser::asUserString(m_incomingData)));
325 m_keyExchange.reset(new SshKeyExchange(m_sendFacility));
326 m_keyExchange->sendKexInitPacket(m_incomingData.left(endOffset));
327 m_incomingData.remove(0, endOffset + 2);
330 void SshConnectionPrivate::handlePackets()
332 m_incomingPacket.consumeData(m_incomingData);
333 while (m_incomingPacket.isComplete()) {
334 handleCurrentPacket();
335 m_incomingPacket.clear();
336 m_incomingPacket.consumeData(m_incomingData);
340 void SshConnectionPrivate::handleCurrentPacket()
342 Q_ASSERT(m_incomingPacket.isComplete());
343 Q_ASSERT(m_state == KeyExchangeStarted || !m_ignoreNextPacket);
345 if (m_ignoreNextPacket) {
346 m_ignoreNextPacket = false;
350 QHash<SshPacketType, HandlerInStates>::ConstIterator it
351 = m_packetHandlers.find(m_incomingPacket.type());
352 if (it == m_packetHandlers.end()) {
353 m_sendFacility.sendMsgUnimplementedPacket(m_incomingPacket.serverSeqNr());
356 if (!it.value().first.contains(m_state)) {
357 throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
358 "Unexpected packet.", tr("Unexpected packet of type %1.")
359 .arg(m_incomingPacket.type()));
361 (this->*it.value().second)();
364 void SshConnectionPrivate::handleKeyExchangeInitPacket()
366 // If the server sends a guessed packet, the guess must be wrong,
367 // because the algorithms we support requires us to initiate the
369 if (m_keyExchange->sendDhInitPacket(m_incomingPacket))
370 m_ignoreNextPacket = true;
371 m_state = KeyExchangeStarted;
374 void SshConnectionPrivate::handleKeyExchangeReplyPacket()
376 m_keyExchange->sendNewKeysPacket(m_incomingPacket,
377 ClientId.left(ClientId.size() - 2));
378 m_sendFacility.recreateKeys(*m_keyExchange);
379 m_state = KeyExchangeSuccess;
382 void SshConnectionPrivate::handleNewKeysPacket()
384 m_incomingPacket.recreateKeys(*m_keyExchange);
385 m_keyExchange.reset();
386 m_sendFacility.sendUserAuthServiceRequestPacket();
387 m_state = UserAuthServiceRequested;
390 void SshConnectionPrivate::handleServiceAcceptPacket()
392 if (m_connParams.authType == SshConnectionParameters::AuthByPwd) {
393 m_sendFacility.sendUserAuthByPwdRequestPacket(m_connParams.uname.toUtf8(),
394 SshCapabilities::SshConnectionService, m_connParams.pwd.toUtf8());
396 QFile privKeyFile(m_connParams.privateKeyFile);
397 bool couldOpen = privKeyFile.open(QIODevice::ReadOnly);
400 contents = privKeyFile.readAll();
401 if (!couldOpen || privKeyFile.error() != QFile::NoError) {
402 throw SshClientException(SshKeyFileError,
403 tr("Could not read private key file: %1")
404 .arg(privKeyFile.errorString()));
407 m_sendFacility.createAuthenticationKey(contents);
408 m_sendFacility.sendUserAuthByKeyRequestPacket(m_connParams.uname.toUtf8(),
409 SshCapabilities::SshConnectionService);
411 m_state = UserAuthRequested;
414 void SshConnectionPrivate::handlePasswordExpiredPacket()
416 if (m_connParams.authType == SshConnectionParameters::AuthByKey) {
417 throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
418 "Got SSH_MSG_USERAUTH_PASSWD_CHANGEREQ, but did not use password.");
421 throw SshClientException(SshAuthenticationError, tr("Password expired."));
424 void SshConnectionPrivate::handleUserAuthBannerPacket()
426 emit dataAvailable(m_incomingPacket.extractUserAuthBanner().message);
429 void SshConnectionPrivate::handleGlobalRequest()
431 m_sendFacility.sendRequestFailurePacket();
434 void SshConnectionPrivate::handleUserAuthSuccessPacket()
436 m_state = ConnectionEstablished;
437 m_timeoutTimer.stop();
441 void SshConnectionPrivate::handleUserAuthFailurePacket()
443 m_timeoutTimer.stop();
444 const QString errorMsg = m_connParams.authType == SshConnectionParameters::AuthByPwd
445 ? tr("Server rejected password.") : tr("Server rejected key.");
446 throw SshClientException(SshAuthenticationError, errorMsg);
448 void SshConnectionPrivate::handleDebugPacket()
450 const SshDebug &msg = m_incomingPacket.extractDebug();
452 emit dataAvailable(msg.message);
455 void SshConnectionPrivate::handleChannelRequest()
457 m_channelManager->handleChannelRequest(m_incomingPacket);
460 void SshConnectionPrivate::handleChannelOpen()
462 m_channelManager->handleChannelOpen(m_incomingPacket);
465 void SshConnectionPrivate::handleChannelOpenFailure()
467 m_channelManager->handleChannelOpenFailure(m_incomingPacket);
470 void SshConnectionPrivate::handleChannelOpenConfirmation()
472 m_channelManager->handleChannelOpenConfirmation(m_incomingPacket);
475 void SshConnectionPrivate::handleChannelSuccess()
477 m_channelManager->handleChannelSuccess(m_incomingPacket);
480 void SshConnectionPrivate::handleChannelFailure()
482 m_channelManager->handleChannelFailure(m_incomingPacket);
485 void SshConnectionPrivate::handleChannelWindowAdjust()
487 m_channelManager->handleChannelWindowAdjust(m_incomingPacket);
490 void SshConnectionPrivate::handleChannelData()
492 m_channelManager->handleChannelData(m_incomingPacket);
495 void SshConnectionPrivate::handleChannelExtendedData()
497 m_channelManager->handleChannelExtendedData(m_incomingPacket);
500 void SshConnectionPrivate::handleChannelEof()
502 m_channelManager->handleChannelEof(m_incomingPacket);
505 void SshConnectionPrivate::handleChannelClose()
507 m_channelManager->handleChannelClose(m_incomingPacket);
510 void SshConnectionPrivate::handleDisconnect()
512 const SshDisconnect msg = m_incomingPacket.extractDisconnect();
513 throw SshServerException(SSH_DISCONNECT_CONNECTION_LOST,
514 "", tr("Server closed connection: %1").arg(msg.description));
517 void SshConnectionPrivate::sendData(const QByteArray &data)
520 m_socket->write(data);
523 void SshConnectionPrivate::handleSocketDisconnected()
525 closeConnection(SSH_DISCONNECT_CONNECTION_LOST, SshClosedByServerError,
526 "Connection closed unexpectedly.",
527 tr("Connection closed unexpectedly."));
530 void SshConnectionPrivate::handleSocketError()
532 if (m_error == SshNoError) {
533 closeConnection(SSH_DISCONNECT_CONNECTION_LOST, SshSocketError,
534 "Network error", m_socket->errorString());
538 void SshConnectionPrivate::handleTimeout()
540 closeConnection(SSH_DISCONNECT_BY_APPLICATION, SshTimeoutError, "",
541 tr("Timeout waiting for reply from server."));
544 void SshConnectionPrivate::connectToHost(const SshConnectionParameters &serverInfo)
546 m_incomingData.clear();
547 m_incomingPacket.reset();
548 m_sendFacility.reset();
549 m_error = SshNoError;
550 m_ignoreNextPacket = false;
551 m_errorString.clear();
552 connect(m_socket, SIGNAL(connected()), this, SLOT(handleSocketConnected()));
553 connect(m_socket, SIGNAL(readyRead()), this, SLOT(handleIncomingData()));
554 connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this,
555 SLOT(handleSocketError()));
556 connect(m_socket, SIGNAL(disconnected()), this,
557 SLOT(handleSocketDisconnected()));
558 connect(&m_timeoutTimer, SIGNAL(timeout()), this, SLOT(handleTimeout()));
559 this->m_connParams = serverInfo;
560 m_state = SocketConnecting;
561 m_timeoutTimer.start(m_connParams.timeout * 1000);
562 m_socket->setProxy(m_connParams.proxyType == SshConnectionParameters::DefaultProxy
563 ? QNetworkProxy::DefaultProxy : QNetworkProxy::NoProxy);
564 m_socket->connectToHost(serverInfo.host, serverInfo.port);
567 void SshConnectionPrivate::closeConnection(SshErrorCode sshError,
568 SshError userError, const QByteArray &serverErrorString,
569 const QString &userErrorString)
571 // Prevent endless loops by recursive exceptions.
572 if (m_state == SocketUnconnected || m_error != SshNoError)
576 m_errorString = userErrorString;
577 m_timeoutTimer.stop();
578 disconnect(m_socket, 0, this, 0);
579 disconnect(&m_timeoutTimer, 0, this, 0);
581 m_channelManager->closeAllChannels();
582 m_sendFacility.sendDisconnectPacket(sshError, serverErrorString);
583 } catch (Botan::Exception &) {} // Nothing sensible to be done here.
584 if (m_error != SshNoError)
585 emit error(userError);
586 if (m_state == ConnectionEstablished)
589 m_socket->disconnectFromHost();
590 m_state = SocketUnconnected;
593 bool SshConnectionPrivate::canUseSocket() const
595 return m_socket->isValid()
596 && m_socket->state() == QAbstractSocket::ConnectedState;
599 QSharedPointer<SshRemoteProcess> SshConnectionPrivate::createRemoteProcess(const QByteArray &command)
601 return m_channelManager->createRemoteProcess(command);
604 QSharedPointer<SftpChannel> SshConnectionPrivate::createSftpChannel()
606 return m_channelManager->createSftpChannel();
609 } // namespace Internal