OSDN Git Service

It's 2011 now.
[qt-creator-jp/qt-creator-jp.git] / src / plugins / coreplugin / ssh / sshconnection.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
8 **
9 ** No Commercial Usage
10 **
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
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 **
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.
24 **
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.
28 **
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
31 **
32 **************************************************************************/
33
34 #include "sshconnection.h"
35 #include "sshconnection_p.h"
36
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"
43
44 #include <utils/qtcassert.h>
45
46 #include <botan/exceptn.h>
47 #include <botan/init.h>
48
49 #include <QtCore/QFile>
50 #include <QtCore/QMutex>
51 #include <QtNetwork/QNetworkProxy>
52 #include <QtNetwork/QTcpSocket>
53
54 namespace Core {
55
56 namespace {
57     const QByteArray ClientId("SSH-2.0-QtCreator\r\n");
58
59     bool staticInitializationsDone = false;
60     QMutex staticInitMutex;
61
62     void doStaticInitializationsIfNecessary()
63     {
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;
71             }
72             staticInitMutex.unlock();
73         }
74     }
75 } // anonymous namespace
76
77
78 SshConnectionParameters::SshConnectionParameters(ProxyType proxyType) :
79     timeout(0),  authType(AuthByKey), port(0), proxyType(proxyType)
80 {
81 }
82
83 static inline bool equals(const SshConnectionParameters &p1, const SshConnectionParameters &p2)
84 {
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;
90 }
91
92 CORE_EXPORT bool operator==(const SshConnectionParameters &p1, const SshConnectionParameters &p2)
93 {
94     return equals(p1, p2);
95 }
96
97 CORE_EXPORT bool operator!=(const SshConnectionParameters &p1, const SshConnectionParameters &p2)
98 {
99     return !equals(p1, p2);
100 }
101
102 // TODO: Mechanism for checking the host key. First connection to host: save, later: compare
103
104 SshConnection::Ptr SshConnection::create()
105 {
106     doStaticInitializationsIfNecessary();
107     return Ptr(new SshConnection);
108 }
109
110 SshConnection::SshConnection() : d(new Internal::SshConnectionPrivate(this))
111 {
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);
120 }
121
122 void SshConnection::connectToHost(const SshConnectionParameters &serverInfo)
123 {
124     d->connectToHost(serverInfo);
125 }
126
127 void SshConnection::disconnectFromHost()
128 {
129     d->closeConnection(Internal::SSH_DISCONNECT_BY_APPLICATION, SshNoError, "",
130         QString());
131 }
132
133 SshConnection::State SshConnection::state() const
134 {
135     switch (d->state()) {
136     case Internal::SocketUnconnected:
137         return Unconnected;
138     case Internal::ConnectionEstablished:
139         return Connected;
140     default:
141         return Connecting;
142     }
143 }
144
145 SshError SshConnection::errorState() const
146 {
147     return d->error();
148 }
149
150 QString SshConnection::errorString() const
151 {
152     return d->errorString();
153 }
154
155 SshConnectionParameters SshConnection::connectionParameters() const
156 {
157     return d->m_connParams;
158 }
159
160 SshConnection::~SshConnection()
161 {
162     disconnect();
163     disconnectFromHost();
164     delete d;
165 }
166
167 QSharedPointer<SshRemoteProcess> SshConnection::createRemoteProcess(const QByteArray &command)
168 {
169     QTC_ASSERT(state() == Connected, return QSharedPointer<SshRemoteProcess>());
170     return d->createRemoteProcess(command);
171 }
172
173 QSharedPointer<SftpChannel> SshConnection::createSftpChannel()
174 {
175     QTC_ASSERT(state() == Connected, return QSharedPointer<SftpChannel>());
176     return d->createSftpChannel();
177 }
178
179
180 namespace Internal {
181
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)
188 {
189     setupPacketHandlers();
190     m_timeoutTimer.setSingleShot(true);
191     connect(m_channelManager, SIGNAL(timeout()), this, SLOT(handleTimeout()));
192 }
193
194 SshConnectionPrivate::~SshConnectionPrivate()
195 {
196     disconnect();
197 }
198
199 void SshConnectionPrivate::setupPacketHandlers()
200 {
201     typedef SshConnectionPrivate This;
202
203     setupPacketHandler(SSH_MSG_KEXINIT, StateList() << SocketConnected,
204         &This::handleKeyExchangeInitPacket);
205     setupPacketHandler(SSH_MSG_KEXDH_REPLY, StateList() << KeyExchangeStarted,
206         &This::handleKeyExchangeReplyPacket);
207
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);
217
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);
225
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);
246
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);
253
254     setupPacketHandler(SSH_MSG_DISCONNECT, StateList() << SocketConnected
255         << KeyExchangeStarted << KeyExchangeSuccess
256         << UserAuthServiceRequested << UserAuthRequested
257         << ConnectionEstablished, &This::handleDisconnect);
258 }
259
260 void SshConnectionPrivate::setupPacketHandler(SshPacketType type,
261     const SshConnectionPrivate::StateList &states,
262     SshConnectionPrivate::PacketHandler handler)
263 {
264     m_packetHandlers.insert(type, HandlerInStates(states, handler));
265 }
266
267 void SshConnectionPrivate::handleSocketConnected()
268 {
269     m_state = SocketConnected;
270     sendData(ClientId);
271 }
272
273 void SshConnectionPrivate::handleIncomingData()
274 {
275     if (m_state == SocketUnconnected)
276         return; // For stuff queued in the event loop after we've called closeConnection();
277
278     try {
279         if (!canUseSocket())
280             return;
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());
285 #endif
286         if (m_state == SocketConnected)
287             handleServerId();
288         handlePackets();
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, "",
294             e.errorString);
295     } catch (Botan::Exception &e) {
296         closeConnection(SSH_DISCONNECT_BY_APPLICATION, SshInternalError, "",
297             tr("Botan library exception: %1").arg(e.what()));
298     }
299 }
300
301 void SshConnectionPrivate::handleServerId()
302 {
303     const int idOffset = m_incomingData.indexOf("SSH-");
304     if (idOffset == -1)
305         return;
306     m_incomingData.remove(0, idOffset);
307     if (m_incomingData.size() < 7)
308         return;
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)));
315     }
316     const int endOffset = m_incomingData.indexOf("\r\n");
317     if (endOffset == -1)
318         return;
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)));
323     }
324
325     m_keyExchange.reset(new SshKeyExchange(m_sendFacility));
326     m_keyExchange->sendKexInitPacket(m_incomingData.left(endOffset));
327     m_incomingData.remove(0, endOffset + 2);
328 }
329
330 void SshConnectionPrivate::handlePackets()
331 {
332     m_incomingPacket.consumeData(m_incomingData);
333     while (m_incomingPacket.isComplete()) {
334         handleCurrentPacket();
335         m_incomingPacket.clear();
336         m_incomingPacket.consumeData(m_incomingData);
337     }
338 }
339
340 void SshConnectionPrivate::handleCurrentPacket()
341 {
342     Q_ASSERT(m_incomingPacket.isComplete());
343     Q_ASSERT(m_state == KeyExchangeStarted || !m_ignoreNextPacket);
344
345     if (m_ignoreNextPacket) {
346         m_ignoreNextPacket = false;
347         return;
348     }
349
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());
354         return;
355     }
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()));
360     }
361     (this->*it.value().second)();
362 }
363
364 void SshConnectionPrivate::handleKeyExchangeInitPacket()
365 {
366     // If the server sends a guessed packet, the guess must be wrong,
367     // because the algorithms we support requires us to initiate the
368     // key exchange.
369     if (m_keyExchange->sendDhInitPacket(m_incomingPacket))
370         m_ignoreNextPacket = true;
371     m_state = KeyExchangeStarted;
372 }
373
374 void SshConnectionPrivate::handleKeyExchangeReplyPacket()
375 {
376     m_keyExchange->sendNewKeysPacket(m_incomingPacket,
377         ClientId.left(ClientId.size() - 2));
378     m_sendFacility.recreateKeys(*m_keyExchange);
379     m_state = KeyExchangeSuccess;
380 }
381
382 void SshConnectionPrivate::handleNewKeysPacket()
383 {
384     m_incomingPacket.recreateKeys(*m_keyExchange);
385     m_keyExchange.reset();
386     m_sendFacility.sendUserAuthServiceRequestPacket();
387     m_state = UserAuthServiceRequested;
388 }
389
390 void SshConnectionPrivate::handleServiceAcceptPacket()
391 {
392     if (m_connParams.authType == SshConnectionParameters::AuthByPwd) {
393         m_sendFacility.sendUserAuthByPwdRequestPacket(m_connParams.uname.toUtf8(),
394             SshCapabilities::SshConnectionService, m_connParams.pwd.toUtf8());
395     } else {
396         QFile privKeyFile(m_connParams.privateKeyFile);
397         bool couldOpen = privKeyFile.open(QIODevice::ReadOnly);
398         QByteArray contents;
399         if (couldOpen)
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()));
405         }
406
407         m_sendFacility.createAuthenticationKey(contents);
408         m_sendFacility.sendUserAuthByKeyRequestPacket(m_connParams.uname.toUtf8(),
409             SshCapabilities::SshConnectionService);
410     }
411     m_state = UserAuthRequested;
412 }
413
414 void SshConnectionPrivate::handlePasswordExpiredPacket()
415 {
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.");
419     }
420
421     throw SshClientException(SshAuthenticationError, tr("Password expired."));
422 }
423
424 void SshConnectionPrivate::handleUserAuthBannerPacket()
425 {
426     emit dataAvailable(m_incomingPacket.extractUserAuthBanner().message);
427 }
428
429 void SshConnectionPrivate::handleGlobalRequest()
430 {
431     m_sendFacility.sendRequestFailurePacket();
432 }
433
434 void SshConnectionPrivate::handleUserAuthSuccessPacket()
435 {
436     m_state = ConnectionEstablished;
437     m_timeoutTimer.stop();
438     emit connected();
439 }
440
441 void SshConnectionPrivate::handleUserAuthFailurePacket()
442 {
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);
447 }
448 void SshConnectionPrivate::handleDebugPacket()
449 {
450     const SshDebug &msg = m_incomingPacket.extractDebug();
451     if (msg.display)
452         emit dataAvailable(msg.message);
453 }
454
455 void SshConnectionPrivate::handleChannelRequest()
456 {
457     m_channelManager->handleChannelRequest(m_incomingPacket);
458 }
459
460 void SshConnectionPrivate::handleChannelOpen()
461 {
462     m_channelManager->handleChannelOpen(m_incomingPacket);
463 }
464
465 void SshConnectionPrivate::handleChannelOpenFailure()
466 {
467    m_channelManager->handleChannelOpenFailure(m_incomingPacket);
468 }
469
470 void SshConnectionPrivate::handleChannelOpenConfirmation()
471 {
472     m_channelManager->handleChannelOpenConfirmation(m_incomingPacket);
473 }
474
475 void SshConnectionPrivate::handleChannelSuccess()
476 {
477     m_channelManager->handleChannelSuccess(m_incomingPacket);
478 }
479
480 void SshConnectionPrivate::handleChannelFailure()
481 {
482     m_channelManager->handleChannelFailure(m_incomingPacket);
483 }
484
485 void SshConnectionPrivate::handleChannelWindowAdjust()
486 {
487    m_channelManager->handleChannelWindowAdjust(m_incomingPacket);
488 }
489
490 void SshConnectionPrivate::handleChannelData()
491 {
492    m_channelManager->handleChannelData(m_incomingPacket);
493 }
494
495 void SshConnectionPrivate::handleChannelExtendedData()
496 {
497    m_channelManager->handleChannelExtendedData(m_incomingPacket);
498 }
499
500 void SshConnectionPrivate::handleChannelEof()
501 {
502    m_channelManager->handleChannelEof(m_incomingPacket);
503 }
504
505 void SshConnectionPrivate::handleChannelClose()
506 {
507    m_channelManager->handleChannelClose(m_incomingPacket);
508 }
509
510 void SshConnectionPrivate::handleDisconnect()
511 {
512     const SshDisconnect msg = m_incomingPacket.extractDisconnect();
513     throw SshServerException(SSH_DISCONNECT_CONNECTION_LOST,
514         "", tr("Server closed connection: %1").arg(msg.description));
515 }
516
517 void SshConnectionPrivate::sendData(const QByteArray &data)
518 {
519     if (canUseSocket())
520         m_socket->write(data);
521 }
522
523 void SshConnectionPrivate::handleSocketDisconnected()
524 {
525     closeConnection(SSH_DISCONNECT_CONNECTION_LOST, SshClosedByServerError,
526         "Connection closed unexpectedly.",
527         tr("Connection closed unexpectedly."));
528 }
529
530 void SshConnectionPrivate::handleSocketError()
531 {
532     if (m_error == SshNoError) {
533         closeConnection(SSH_DISCONNECT_CONNECTION_LOST, SshSocketError,
534             "Network error", m_socket->errorString());
535     }
536 }
537
538 void SshConnectionPrivate::handleTimeout()
539 {
540     closeConnection(SSH_DISCONNECT_BY_APPLICATION, SshTimeoutError, "",
541         tr("Timeout waiting for reply from server."));
542 }
543
544 void SshConnectionPrivate::connectToHost(const SshConnectionParameters &serverInfo)
545 {
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);
565 }
566
567 void SshConnectionPrivate::closeConnection(SshErrorCode sshError,
568     SshError userError, const QByteArray &serverErrorString,
569     const QString &userErrorString)
570 {
571     // Prevent endless loops by recursive exceptions.
572     if (m_state == SocketUnconnected || m_error != SshNoError)
573         return;
574
575     m_error = userError;
576     m_errorString = userErrorString;
577     m_timeoutTimer.stop();
578     disconnect(m_socket, 0, this, 0);
579     disconnect(&m_timeoutTimer, 0, this, 0);
580     try {
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)
587         emit disconnected();
588     if (canUseSocket())
589         m_socket->disconnectFromHost();
590     m_state = SocketUnconnected;
591 }
592
593 bool SshConnectionPrivate::canUseSocket() const
594 {
595     return m_socket->isValid()
596         && m_socket->state() == QAbstractSocket::ConnectedState;
597 }
598
599 QSharedPointer<SshRemoteProcess> SshConnectionPrivate::createRemoteProcess(const QByteArray &command)
600 {
601     return m_channelManager->createRemoteProcess(command);
602 }
603
604 QSharedPointer<SftpChannel> SshConnectionPrivate::createSftpChannel()
605 {
606     return m_channelManager->createSftpChannel();
607 }
608
609 } // namespace Internal
610 } // namespace Core