sshconnection.cpp 22.3 KB
Newer Older
ck's avatar
ck committed
1
2
3
4
/**************************************************************************
**
** This file is part of Qt Creator
**
con's avatar
con committed
5
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6
**
hjk's avatar
hjk committed
7
** Contact: Nokia Corporation (info@qt.nokia.com)
8
9
10
**
**
** GNU Lesser General Public License Usage
ck's avatar
ck committed
11
**
hjk's avatar
hjk committed
12
13
14
15
16
17
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this file.
** Please review the following information to ensure the GNU Lesser General
** Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18
**
con's avatar
con committed
19
** In addition, as a special exception, Nokia gives you certain additional
hjk's avatar
hjk committed
20
** rights. These rights are described in the Nokia Qt LGPL Exception
con's avatar
con committed
21
22
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
23
24
25
26
27
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
con's avatar
con committed
28
** If you have questions regarding the use of this file, please contact
Tobias Hunger's avatar
Tobias Hunger committed
29
** Nokia at info@qt.nokia.com.
30
**
ck's avatar
ck committed
31
**************************************************************************/
32
33

#include "sshconnection.h"
ck's avatar
ck committed
34
#include "sshconnection_p.h"
35

ck's avatar
ck committed
36
37
38
39
40
41
#include "sftpchannel.h"
#include "sshcapabilities_p.h"
#include "sshchannelmanager_p.h"
#include "sshcryptofacility_p.h"
#include "sshexception_p.h"
#include "sshkeyexchange_p.h"
42

Christian Kandeler's avatar
Christian Kandeler committed
43
#include <utils/qtcassert.h>
44
#include <utils/fileutils.h>
Christian Kandeler's avatar
Christian Kandeler committed
45

ck's avatar
ck committed
46
47
#include <botan/exceptn.h>
#include <botan/init.h>
48

ck's avatar
ck committed
49
50
#include <QtCore/QFile>
#include <QtCore/QMutex>
51
#include <QtNetwork/QNetworkProxy>
ck's avatar
ck committed
52
#include <QtNetwork/QTcpSocket>
53

54
55
56
57
58
59
60
61
62
/*!
    \class Utils::SshConnection

    \brief This class provides an SSH connection, implementing protocol version 2.0

    It can spawn channels for remote execution and SFTP operations (version 3).
    It operates asynchronously (non-blocking) and is not thread-safe.
*/

63
namespace Utils {
64
65

namespace {
ck's avatar
ck committed
66
    const QByteArray ClientId("SSH-2.0-QtCreator\r\n");
67

ck's avatar
ck committed
68
69
    bool staticInitializationsDone = false;
    QMutex staticInitMutex;
70

ck's avatar
ck committed
71
    void doStaticInitializationsIfNecessary()
72
    {
ck's avatar
ck committed
73
74
75
76
        if (!staticInitializationsDone) {
            staticInitMutex.lock();
            if (!staticInitializationsDone) {
                Botan::LibraryInitializer::initialize("thread_safe=true");
77
78
                qRegisterMetaType<Utils::SshError>("Utils::SshError");
                qRegisterMetaType<Utils::SftpJobId>("Utils::SftpJobId");
ck's avatar
ck committed
79
                staticInitializationsDone = true;
80
            }
ck's avatar
ck committed
81
            staticInitMutex.unlock();
82
83
        }
    }
84
85
86
} // anonymous namespace


87
SshConnectionParameters::SshConnectionParameters(ProxyType proxyType) :
Christian Kandeler's avatar
Christian Kandeler committed
88
    timeout(0),  authenticationType(AuthenticationByKey), port(0), proxyType(proxyType)
89
90
91
92
93
{
}

static inline bool equals(const SshConnectionParameters &p1, const SshConnectionParameters &p2)
{
94
    return p1.host == p2.host && p1.userName == p2.userName
Christian Kandeler's avatar
Christian Kandeler committed
95
96
            && p1.authenticationType == p2.authenticationType
            && (p1.authenticationType == SshConnectionParameters::AuthenticationByPassword ?
97
                    p1.password == p2.password : p1.privateKeyFile == p2.privateKeyFile)
98
99
100
            && p1.timeout == p2.timeout && p1.port == p2.port;
}

101
QTCREATOR_UTILS_EXPORT bool operator==(const SshConnectionParameters &p1, const SshConnectionParameters &p2)
102
103
104
105
{
    return equals(p1, p2);
}

106
QTCREATOR_UTILS_EXPORT bool operator!=(const SshConnectionParameters &p1, const SshConnectionParameters &p2)
107
108
{
    return !equals(p1, p2);
ck's avatar
ck committed
109
}
110

ck's avatar
ck committed
111
// TODO: Mechanism for checking the host key. First connection to host: save, later: compare
112

113
SshConnection::Ptr SshConnection::create(const SshConnectionParameters &serverInfo)
ck's avatar
ck committed
114
115
{
    doStaticInitializationsIfNecessary();
116
    return Ptr(new SshConnection(serverInfo));
ck's avatar
ck committed
117
}
118

119
120
SshConnection::SshConnection(const SshConnectionParameters &serverInfo)
    : d(new Internal::SshConnectionPrivate(this, serverInfo))
121
{
ck's avatar
ck committed
122
123
124
125
126
127
    connect(d, SIGNAL(connected()), this, SIGNAL(connected()),
        Qt::QueuedConnection);
    connect(d, SIGNAL(dataAvailable(QString)), this,
        SIGNAL(dataAvailable(QString)), Qt::QueuedConnection);
    connect(d, SIGNAL(disconnected()), this, SIGNAL(disconnected()),
        Qt::QueuedConnection);
128
129
    connect(d, SIGNAL(error(Utils::SshError)), this,
        SIGNAL(error(Utils::SshError)), Qt::QueuedConnection);
130
131
}

132
void SshConnection::connectToHost()
ck's avatar
ck committed
133
{
134
    d->connectToHost();
ck's avatar
ck committed
135
}
136

ck's avatar
ck committed
137
138
139
140
141
void SshConnection::disconnectFromHost()
{
    d->closeConnection(Internal::SSH_DISCONNECT_BY_APPLICATION, SshNoError, "",
        QString());
}
142

ck's avatar
ck committed
143
SshConnection::State SshConnection::state() const
144
{
ck's avatar
ck committed
145
146
147
148
149
150
151
152
153
    switch (d->state()) {
    case Internal::SocketUnconnected:
        return Unconnected;
    case Internal::ConnectionEstablished:
        return Connected;
    default:
        return Connecting;
    }
}
154

ck's avatar
ck committed
155
156
157
158
SshError SshConnection::errorState() const
{
    return d->error();
}
159

ck's avatar
ck committed
160
QString SshConnection::errorString() const
161
{
ck's avatar
ck committed
162
163
    return d->errorString();
}
164

ck's avatar
ck committed
165
166
167
168
SshConnectionParameters SshConnection::connectionParameters() const
{
    return d->m_connParams;
}
169

ck's avatar
ck committed
170
SshConnection::~SshConnection()
171
{
ck's avatar
ck committed
172
173
174
175
    disconnect();
    disconnectFromHost();
    delete d;
}
176

ck's avatar
ck committed
177
178
QSharedPointer<SshRemoteProcess> SshConnection::createRemoteProcess(const QByteArray &command)
{
Christian Kandeler's avatar
Christian Kandeler committed
179
180
    QTC_ASSERT(state() == Connected, return QSharedPointer<SshRemoteProcess>());
    return d->createRemoteProcess(command);
ck's avatar
ck committed
181
}
182

ck's avatar
ck committed
183
184
QSharedPointer<SftpChannel> SshConnection::createSftpChannel()
{
Christian Kandeler's avatar
Christian Kandeler committed
185
186
    QTC_ASSERT(state() == Connected, return QSharedPointer<SftpChannel>());
    return d->createSftpChannel();
ck's avatar
ck committed
187
}
ck's avatar
ck committed
188

189

ck's avatar
ck committed
190
namespace Internal {
191

192
193
SshConnectionPrivate::SshConnectionPrivate(SshConnection *conn,
    const SshConnectionParameters &serverInfo)
ck's avatar
ck committed
194
195
    : m_socket(new QTcpSocket(this)), m_state(SocketUnconnected),
      m_sendFacility(m_socket),
196
      m_channelManager(new SshChannelManager(m_sendFacility, this)),
197
198
      m_connParams(serverInfo), m_error(SshNoError), m_ignoreNextPacket(false),
      m_conn(conn)
ck's avatar
ck committed
199
200
{
    setupPacketHandlers();
201
202
    m_socket->setProxy(m_connParams.proxyType == SshConnectionParameters::DefaultProxy
        ? QNetworkProxy::DefaultProxy : QNetworkProxy::NoProxy);
203
    m_timeoutTimer.setSingleShot(true);
204
    m_timeoutTimer.setInterval(m_connParams.timeout * 1000);
205
206
    m_keepAliveTimer.setSingleShot(true);
    m_keepAliveTimer.setInterval(10000);
207
    connect(m_channelManager, SIGNAL(timeout()), this, SLOT(handleTimeout()));
ck's avatar
ck committed
208
}
209

ck's avatar
ck committed
210
211
212
213
SshConnectionPrivate::~SshConnectionPrivate()
{
    disconnect();
}
214

ck's avatar
ck committed
215
216
217
218
void SshConnectionPrivate::setupPacketHandlers()
{
    typedef SshConnectionPrivate This;

219
220
221
222
    setupPacketHandler(SSH_MSG_KEXINIT, StateList() << SocketConnected
        << ConnectionEstablished, &This::handleKeyExchangeInitPacket);
    setupPacketHandler(SSH_MSG_KEXDH_REPLY, StateList() << SocketConnected
        << ConnectionEstablished, &This::handleKeyExchangeReplyPacket);
ck's avatar
ck committed
223

224
225
    setupPacketHandler(SSH_MSG_NEWKEYS, StateList() << SocketConnected
        << ConnectionEstablished, &This::handleNewKeysPacket);
ck's avatar
ck committed
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
    setupPacketHandler(SSH_MSG_SERVICE_ACCEPT,
        StateList() << UserAuthServiceRequested,
        &This::handleServiceAcceptPacket);
    setupPacketHandler(SSH_MSG_USERAUTH_PASSWD_CHANGEREQ,
        StateList() << UserAuthRequested, &This::handlePasswordExpiredPacket);
    setupPacketHandler(SSH_MSG_GLOBAL_REQUEST,
        StateList() << ConnectionEstablished, &This::handleGlobalRequest);

    const StateList authReqList = StateList() << UserAuthRequested;
    setupPacketHandler(SSH_MSG_USERAUTH_BANNER, authReqList,
        &This::handleUserAuthBannerPacket);
    setupPacketHandler(SSH_MSG_USERAUTH_SUCCESS, authReqList,
        &This::handleUserAuthSuccessPacket);
    setupPacketHandler(SSH_MSG_USERAUTH_FAILURE, authReqList,
        &This::handleUserAuthFailurePacket);

    const StateList connectedList
        = StateList() << ConnectionEstablished;
    setupPacketHandler(SSH_MSG_CHANNEL_REQUEST, connectedList,
        &This::handleChannelRequest);
    setupPacketHandler(SSH_MSG_CHANNEL_OPEN, connectedList,
        &This::handleChannelOpen);
    setupPacketHandler(SSH_MSG_CHANNEL_OPEN_FAILURE, connectedList,
        &This::handleChannelOpenFailure);
    setupPacketHandler(SSH_MSG_CHANNEL_OPEN_CONFIRMATION, connectedList,
        &This::handleChannelOpenConfirmation);
    setupPacketHandler(SSH_MSG_CHANNEL_SUCCESS, connectedList,
        &This::handleChannelSuccess);
    setupPacketHandler(SSH_MSG_CHANNEL_FAILURE, connectedList,
        &This::handleChannelFailure);
    setupPacketHandler(SSH_MSG_CHANNEL_WINDOW_ADJUST, connectedList,
        &This::handleChannelWindowAdjust);
    setupPacketHandler(SSH_MSG_CHANNEL_DATA, connectedList,
        &This::handleChannelData);
    setupPacketHandler(SSH_MSG_CHANNEL_EXTENDED_DATA, connectedList,
        &This::handleChannelExtendedData);

    const StateList connectedOrClosedList
        = StateList() << SocketUnconnected << ConnectionEstablished;
    setupPacketHandler(SSH_MSG_CHANNEL_EOF, connectedOrClosedList,
        &This::handleChannelEof);
    setupPacketHandler(SSH_MSG_CHANNEL_CLOSE, connectedOrClosedList,
        &This::handleChannelClose);

    setupPacketHandler(SSH_MSG_DISCONNECT, StateList() << SocketConnected
        << UserAuthServiceRequested << UserAuthRequested
        << ConnectionEstablished, &This::handleDisconnect);
273
274
275

    setupPacketHandler(SSH_MSG_UNIMPLEMENTED,
        StateList() << ConnectionEstablished, &This::handleUnimplementedPacket);
ck's avatar
ck committed
276
}
277

ck's avatar
ck committed
278
279
280
281
282
283
void SshConnectionPrivate::setupPacketHandler(SshPacketType type,
    const SshConnectionPrivate::StateList &states,
    SshConnectionPrivate::PacketHandler handler)
{
    m_packetHandlers.insert(type, HandlerInStates(states, handler));
}
ck's avatar
ck committed
284

ck's avatar
ck committed
285
void SshConnectionPrivate::handleSocketConnected()
ck's avatar
ck committed
286
{
ck's avatar
ck committed
287
288
    m_state = SocketConnected;
    sendData(ClientId);
ck's avatar
ck committed
289
290
}

ck's avatar
ck committed
291
292
293
294
295
296
void SshConnectionPrivate::handleIncomingData()
{
    if (m_state == SocketUnconnected)
        return; // For stuff queued in the event loop after we've called closeConnection();

    try {
297
298
        if (!canUseSocket())
            return;
ck's avatar
ck committed
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
        m_incomingData += m_socket->readAll();
#ifdef CREATOR_SSH_DEBUG
        qDebug("state = %d, remote data size = %d", m_state,
            m_incomingData.count());
#endif
        if (m_state == SocketConnected)
            handleServerId();
        handlePackets();
    } catch (SshServerException &e) {
        closeConnection(e.error, SshProtocolError, e.errorStringServer,
            tr("SSH Protocol error: %1").arg(e.errorStringUser));
    } catch (SshClientException &e) {
        closeConnection(SSH_DISCONNECT_BY_APPLICATION, e.error, "",
            e.errorString);
    } catch (Botan::Exception &e) {
        closeConnection(SSH_DISCONNECT_BY_APPLICATION, SshInternalError, "",
315
            tr("Botan library exception: %1").arg(e.what()));
ck's avatar
ck committed
316
317
    }
}
ck's avatar
ck committed
318

ck's avatar
ck committed
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
void SshConnectionPrivate::handleServerId()
{
    const int idOffset = m_incomingData.indexOf("SSH-");
    if (idOffset == -1)
        return;
    m_incomingData.remove(0, idOffset);
    if (m_incomingData.size() < 7)
        return;
    const QByteArray &version = m_incomingData.mid(4, 3);
    if (version != "2.0") {
        throw SshServerException(SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED,
            "Invalid protocol version.",
            tr("Invalid protocol version: Expected '2.0', got '%1'.")
            .arg(SshPacketParser::asUserString(version)));
    }
    const int endOffset = m_incomingData.indexOf("\r\n");
    if (endOffset == -1)
        return;
    if (m_incomingData.at(7) != '-') {
        throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
            "Invalid server id.", tr("Invalid server id '%1'.")
            .arg(SshPacketParser::asUserString(m_incomingData)));
    }
ck's avatar
ck committed
342

ck's avatar
ck committed
343
    m_keyExchange.reset(new SshKeyExchange(m_sendFacility));
344
345
    m_serverId = m_incomingData.left(endOffset);
    m_keyExchange->sendKexInitPacket(m_serverId);
346
    m_keyExchangeState = KexInitSent;
ck's avatar
ck committed
347
    m_incomingData.remove(0, endOffset + 2);
348
349
}

ck's avatar
ck committed
350
void SshConnectionPrivate::handlePackets()
351
{
ck's avatar
ck committed
352
353
354
355
356
357
    m_incomingPacket.consumeData(m_incomingData);
    while (m_incomingPacket.isComplete()) {
        handleCurrentPacket();
        m_incomingPacket.clear();
        m_incomingPacket.consumeData(m_incomingData);
    }
358
359
}

ck's avatar
ck committed
360
void SshConnectionPrivate::handleCurrentPacket()
361
{
ck's avatar
ck committed
362
    Q_ASSERT(m_incomingPacket.isComplete());
363
    Q_ASSERT(m_keyExchangeState == DhInitSent || !m_ignoreNextPacket);
364

ck's avatar
ck committed
365
366
367
368
    if (m_ignoreNextPacket) {
        m_ignoreNextPacket = false;
        return;
    }
369

ck's avatar
ck committed
370
371
372
373
374
375
376
377
378
379
380
381
    QHash<SshPacketType, HandlerInStates>::ConstIterator it
        = m_packetHandlers.find(m_incomingPacket.type());
    if (it == m_packetHandlers.end()) {
        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()));
    }
    (this->*it.value().second)();
382
383
}

ck's avatar
ck committed
384
void SshConnectionPrivate::handleKeyExchangeInitPacket()
385
{
386
387
    if (m_keyExchangeState != NoKeyExchange
            && m_keyExchangeState != KexInitSent) {
388
389
390
391
392
393
        throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
            "Unexpected packet.", tr("Unexpected packet of type %1.")
            .arg(m_incomingPacket.type()));
    }

    // Server-initiated re-exchange.
394
    if (m_keyExchangeState == NoKeyExchange) {
395
396
397
398
        m_keyExchange.reset(new SshKeyExchange(m_sendFacility));
        m_keyExchange->sendKexInitPacket(m_serverId);
    }

ck's avatar
ck committed
399
    // If the server sends a guessed packet, the guess must be wrong,
Christian Kandeler's avatar
Christian Kandeler committed
400
    // because the algorithms we support require us to initiate the
ck's avatar
ck committed
401
    // key exchange.
402
    if (m_keyExchange->sendDhInitPacket(m_incomingPacket)) {
ck's avatar
ck committed
403
        m_ignoreNextPacket = true;
404
405
    }

406
    m_keyExchangeState = DhInitSent;
407
408
}

ck's avatar
ck committed
409
void SshConnectionPrivate::handleKeyExchangeReplyPacket()
410
{
411
    if (m_keyExchangeState != DhInitSent) {
412
413
414
415
416
        throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
            "Unexpected packet.", tr("Unexpected packet of type %1.")
            .arg(m_incomingPacket.type()));
    }

ck's avatar
ck committed
417
418
419
    m_keyExchange->sendNewKeysPacket(m_incomingPacket,
        ClientId.left(ClientId.size() - 2));
    m_sendFacility.recreateKeys(*m_keyExchange);
420
    m_keyExchangeState = NewKeysSent;
421
422
}

ck's avatar
ck committed
423
void SshConnectionPrivate::handleNewKeysPacket()
ck's avatar
ck committed
424
{
425
    if (m_keyExchangeState != NewKeysSent) {
426
427
428
429
430
        throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
            "Unexpected packet.", tr("Unexpected packet of type %1.")
            .arg(m_incomingPacket.type()));
    }

ck's avatar
ck committed
431
432
    m_incomingPacket.recreateKeys(*m_keyExchange);
    m_keyExchange.reset();
433
434
435
436
437
438
    m_keyExchangeState = NoKeyExchange;

    if (m_state == SocketConnected) {
        m_sendFacility.sendUserAuthServiceRequestPacket();
        m_state = UserAuthServiceRequested;
    }
ck's avatar
ck committed
439
440
}

ck's avatar
ck committed
441
442
void SshConnectionPrivate::handleServiceAcceptPacket()
{
Christian Kandeler's avatar
Christian Kandeler committed
443
    if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationByPassword) {
444
445
        m_sendFacility.sendUserAuthByPwdRequestPacket(m_connParams.userName.toUtf8(),
            SshCapabilities::SshConnectionService, m_connParams.password.toUtf8());
ck's avatar
ck committed
446
    } else {
447
448
        Utils::FileReader reader;
        if (!reader.fetch(m_connParams.privateKeyFile))
ck's avatar
ck committed
449
            throw SshClientException(SshKeyFileError,
450
                tr("Private key error: %1").arg(reader.errorString()));
ck's avatar
ck committed
451

452
        m_sendFacility.createAuthenticationKey(reader.data());
453
        m_sendFacility.sendUserAuthByKeyRequestPacket(m_connParams.userName.toUtf8(),
ck's avatar
ck committed
454
455
456
            SshCapabilities::SshConnectionService);
    }
    m_state = UserAuthRequested;
457
458
}

ck's avatar
ck committed
459
void SshConnectionPrivate::handlePasswordExpiredPacket()
460
{
Christian Kandeler's avatar
Christian Kandeler committed
461
    if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationByKey) {
ck's avatar
ck committed
462
463
464
465
466
        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
            "Got SSH_MSG_USERAUTH_PASSWD_CHANGEREQ, but did not use password.");
    }

    throw SshClientException(SshAuthenticationError, tr("Password expired."));
467
468
}

ck's avatar
ck committed
469
void SshConnectionPrivate::handleUserAuthBannerPacket()
470
{
ck's avatar
ck committed
471
    emit dataAvailable(m_incomingPacket.extractUserAuthBanner().message);
472
473
}

ck's avatar
ck committed
474
void SshConnectionPrivate::handleGlobalRequest()
475
{
ck's avatar
ck committed
476
    m_sendFacility.sendRequestFailurePacket();
477
478
}

ck's avatar
ck committed
479
void SshConnectionPrivate::handleUserAuthSuccessPacket()
480
{
ck's avatar
ck committed
481
482
483
    m_state = ConnectionEstablished;
    m_timeoutTimer.stop();
    emit connected();
484
485
486
    m_lastInvalidMsgSeqNr = InvalidSeqNr;
    connect(&m_keepAliveTimer, SIGNAL(timeout()), SLOT(sendKeepAlivePacket()));
    m_keepAliveTimer.start();
ck's avatar
ck committed
487
}
488

ck's avatar
ck committed
489
490
void SshConnectionPrivate::handleUserAuthFailurePacket()
{
491
    m_timeoutTimer.stop();
Christian Kandeler's avatar
Christian Kandeler committed
492
    const QString errorMsg = m_connParams.authenticationType == SshConnectionParameters::AuthenticationByPassword
ck's avatar
ck committed
493
494
495
496
497
498
499
500
501
        ? tr("Server rejected password.") : tr("Server rejected key.");
    throw SshClientException(SshAuthenticationError, errorMsg);
}
void SshConnectionPrivate::handleDebugPacket()
{
    const SshDebug &msg = m_incomingPacket.extractDebug();
    if (msg.display)
        emit dataAvailable(msg.message);
}
502

503
504
505
506
507
508
509
510
511
512
513
514
515
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();
}

ck's avatar
ck committed
516
void SshConnectionPrivate::handleChannelRequest()
517
{
ck's avatar
ck committed
518
    m_channelManager->handleChannelRequest(m_incomingPacket);
519
520
}

ck's avatar
ck committed
521
void SshConnectionPrivate::handleChannelOpen()
522
{
ck's avatar
ck committed
523
    m_channelManager->handleChannelOpen(m_incomingPacket);
524
525
}

ck's avatar
ck committed
526
void SshConnectionPrivate::handleChannelOpenFailure()
527
{
ck's avatar
ck committed
528
529
   m_channelManager->handleChannelOpenFailure(m_incomingPacket);
}
530

ck's avatar
ck committed
531
532
533
void SshConnectionPrivate::handleChannelOpenConfirmation()
{
    m_channelManager->handleChannelOpenConfirmation(m_incomingPacket);
534
535
}

ck's avatar
ck committed
536
void SshConnectionPrivate::handleChannelSuccess()
537
{
ck's avatar
ck committed
538
539
    m_channelManager->handleChannelSuccess(m_incomingPacket);
}
540

ck's avatar
ck committed
541
542
543
544
void SshConnectionPrivate::handleChannelFailure()
{
    m_channelManager->handleChannelFailure(m_incomingPacket);
}
545

ck's avatar
ck committed
546
547
548
void SshConnectionPrivate::handleChannelWindowAdjust()
{
   m_channelManager->handleChannelWindowAdjust(m_incomingPacket);
549
550
}

ck's avatar
ck committed
551
void SshConnectionPrivate::handleChannelData()
552
{
ck's avatar
ck committed
553
554
   m_channelManager->handleChannelData(m_incomingPacket);
}
555

ck's avatar
ck committed
556
557
558
559
void SshConnectionPrivate::handleChannelExtendedData()
{
   m_channelManager->handleChannelExtendedData(m_incomingPacket);
}
560

ck's avatar
ck committed
561
562
563
void SshConnectionPrivate::handleChannelEof()
{
   m_channelManager->handleChannelEof(m_incomingPacket);
564
565
}

ck's avatar
ck committed
566
void SshConnectionPrivate::handleChannelClose()
567
{
ck's avatar
ck committed
568
   m_channelManager->handleChannelClose(m_incomingPacket);
569
570
}

ck's avatar
ck committed
571
void SshConnectionPrivate::handleDisconnect()
572
{
ck's avatar
ck committed
573
574
575
    const SshDisconnect msg = m_incomingPacket.extractDisconnect();
    throw SshServerException(SSH_DISCONNECT_CONNECTION_LOST,
        "", tr("Server closed connection: %1").arg(msg.description));
576
577
}

578
579


ck's avatar
ck committed
580
void SshConnectionPrivate::sendData(const QByteArray &data)
581
{
582
583
    if (canUseSocket())
        m_socket->write(data);
584
585
}

ck's avatar
ck committed
586
void SshConnectionPrivate::handleSocketDisconnected()
587
{
ck's avatar
ck committed
588
589
590
    closeConnection(SSH_DISCONNECT_CONNECTION_LOST, SshClosedByServerError,
        "Connection closed unexpectedly.",
        tr("Connection closed unexpectedly."));
591
592
}

ck's avatar
ck committed
593
void SshConnectionPrivate::handleSocketError()
594
{
ck's avatar
ck committed
595
596
597
    if (m_error == SshNoError) {
        closeConnection(SSH_DISCONNECT_CONNECTION_LOST, SshSocketError,
            "Network error", m_socket->errorString());
598
599
600
    }
}

ck's avatar
ck committed
601
void SshConnectionPrivate::handleTimeout()
602
{
603
604
    closeConnection(SSH_DISCONNECT_BY_APPLICATION, SshTimeoutError, "",
        tr("Timeout waiting for reply from server."));
605
606
}

607
608
void SshConnectionPrivate::sendKeepAlivePacket()
{
609
610
611
612
613
614
    // This type of message is not allowed during key exchange.
    if (m_keyExchangeState != NoKeyExchange) {
        m_keepAliveTimer.start();
        return;
    }

615
616
617
    Q_ASSERT(m_lastInvalidMsgSeqNr == InvalidSeqNr);
    m_lastInvalidMsgSeqNr = m_sendFacility.nextClientSeqNr();
    m_sendFacility.sendInvalidPacket();
618
    m_timeoutTimer.start();
619
620
}

621
void SshConnectionPrivate::connectToHost()
ck's avatar
ck committed
622
623
624
625
626
627
628
629
630
631
632
633
634
{
    m_incomingData.clear();
    m_incomingPacket.reset();
    m_sendFacility.reset();
    m_error = SshNoError;
    m_ignoreNextPacket = false;
    m_errorString.clear();
    connect(m_socket, SIGNAL(connected()), this, SLOT(handleSocketConnected()));
    connect(m_socket, SIGNAL(readyRead()), this, SLOT(handleIncomingData()));
    connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this,
        SLOT(handleSocketError()));
    connect(m_socket, SIGNAL(disconnected()), this,
        SLOT(handleSocketDisconnected()));
635
    connect(&m_timeoutTimer, SIGNAL(timeout()), this, SLOT(handleTimeout()));
ck's avatar
ck committed
636
    m_state = SocketConnecting;
637
    m_keyExchangeState = NoKeyExchange;
638
    m_timeoutTimer.start();
639
    m_socket->connectToHost(m_connParams.host, m_connParams.port);
640
641
}

ck's avatar
ck committed
642
643
644
645
646
647
648
649
650
651
652
653
void SshConnectionPrivate::closeConnection(SshErrorCode sshError,
    SshError userError, const QByteArray &serverErrorString,
    const QString &userErrorString)
{
    // Prevent endless loops by recursive exceptions.
    if (m_state == SocketUnconnected || m_error != SshNoError)
        return;

    m_error = userError;
    m_errorString = userErrorString;
    m_timeoutTimer.stop();
    disconnect(m_socket, 0, this, 0);
654
    disconnect(&m_timeoutTimer, 0, this, 0);
655
656
    m_keepAliveTimer.stop();
    disconnect(&m_keepAliveTimer, 0, this, 0);
ck's avatar
ck committed
657
658
659
660
661
662
663
664
    try {
        m_channelManager->closeAllChannels();
        m_sendFacility.sendDisconnectPacket(sshError, serverErrorString);
    } catch (Botan::Exception &) {}  // Nothing sensible to be done here.
    if (m_error != SshNoError)
        emit error(userError);
    if (m_state == ConnectionEstablished)
        emit disconnected();
665
666
    if (canUseSocket())
        m_socket->disconnectFromHost();
ck's avatar
ck committed
667
    m_state = SocketUnconnected;
668
669
}

670
671
672
673
674
675
bool SshConnectionPrivate::canUseSocket() const
{
    return m_socket->isValid()
        && m_socket->state() == QAbstractSocket::ConnectedState;
}

ck's avatar
ck committed
676
QSharedPointer<SshRemoteProcess> SshConnectionPrivate::createRemoteProcess(const QByteArray &command)
677
{
ck's avatar
ck committed
678
    return m_channelManager->createRemoteProcess(command);
679
680
}

ck's avatar
ck committed
681
QSharedPointer<SftpChannel> SshConnectionPrivate::createSftpChannel()
682
{
ck's avatar
ck committed
683
    return m_channelManager->createSftpChannel();
684
685
}

686
687
const quint64 SshConnectionPrivate::InvalidSeqNr = static_cast<quint64>(-1);

ck's avatar
ck committed
688
} // namespace Internal
689
} // namespace Utils