sshconnection.cpp 25.7 KB
Newer Older
ck's avatar
ck committed
1
2
3
4
/**************************************************************************
**
** This file is part of Qt Creator
**
hjk's avatar
hjk committed
5
** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
6
**
Eike Ziller's avatar
Eike Ziller committed
7
** Contact: http://www.qt-project.org/
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.
**
28
**
ck's avatar
ck committed
29
**************************************************************************/
30
31

#include "sshconnection.h"
ck's avatar
ck committed
32
#include "sshconnection_p.h"
33

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

Christian Kandeler's avatar
Christian Kandeler committed
41
#include <botan/botan.h>
42

43
44
45
46
#include <QFile>
#include <QMutex>
#include <QMutexLocker>
#include <QNetworkProxy>
47
#include <QRegExp>
48
#include <QTcpSocket>
49

50
/*!
51
    \class QSsh::SshConnection
52
53
54
55
56
57
58

    \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.
*/

59
namespace QSsh {
60
61

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

ck's avatar
ck committed
64
65
    bool staticInitializationsDone = false;
    QMutex staticInitMutex;
66

ck's avatar
ck committed
67
    void doStaticInitializationsIfNecessary()
68
    {
Christian Kandeler's avatar
Christian Kandeler committed
69
        QMutexLocker locker(&staticInitMutex);
ck's avatar
ck committed
70
        if (!staticInitializationsDone) {
Christian Kandeler's avatar
Christian Kandeler committed
71
            Botan::LibraryInitializer::initialize("thread_safe=true");
72
73
74
75
            qRegisterMetaType<QSsh::SshError>("QSsh::SshError");
            qRegisterMetaType<QSsh::SftpJobId>("QSsh::SftpJobId");
            qRegisterMetaType<QSsh::SftpFileInfo>("QSsh::SftpFileInfo");
            qRegisterMetaType<QList <QSsh::SftpFileInfo> >("QList<QSsh::SftpFileInfo>");
Christian Kandeler's avatar
Christian Kandeler committed
76
            staticInitializationsDone = true;
77
78
        }
    }
79
80
81
} // anonymous namespace


82
83
SshConnectionParameters::SshConnectionParameters() :
    timeout(0),  authenticationType(AuthenticationByKey), port(0), proxyType(NoProxy)
84
85
86
87
88
{
}

static inline bool equals(const SshConnectionParameters &p1, const SshConnectionParameters &p2)
{
89
    return p1.host == p2.host && p1.userName == p2.userName
Christian Kandeler's avatar
Christian Kandeler committed
90
91
            && p1.authenticationType == p2.authenticationType
            && (p1.authenticationType == SshConnectionParameters::AuthenticationByPassword ?
92
                    p1.password == p2.password : p1.privateKeyFile == p2.privateKeyFile)
93
94
95
            && p1.timeout == p2.timeout && p1.port == p2.port;
}

96
bool operator==(const SshConnectionParameters &p1, const SshConnectionParameters &p2)
97
98
99
100
{
    return equals(p1, p2);
}

101
bool operator!=(const SshConnectionParameters &p1, const SshConnectionParameters &p2)
102
103
{
    return !equals(p1, p2);
ck's avatar
ck committed
104
}
105

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

108
109
SshConnection::SshConnection(const SshConnectionParameters &serverInfo, QObject *parent)
    : QObject(parent)
110
{
111
112
113
    doStaticInitializationsIfNecessary();

    d = new Internal::SshConnectionPrivate(this, serverInfo);
ck's avatar
ck committed
114
115
116
117
118
119
    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);
120
121
    connect(d, SIGNAL(error(QSsh::SshError)), this,
        SIGNAL(error(QSsh::SshError)), Qt::QueuedConnection);
122
123
}

124
void SshConnection::connectToHost()
ck's avatar
ck committed
125
{
126
    d->connectToHost();
ck's avatar
ck committed
127
}
128

ck's avatar
ck committed
129
130
131
132
133
void SshConnection::disconnectFromHost()
{
    d->closeConnection(Internal::SSH_DISCONNECT_BY_APPLICATION, SshNoError, "",
        QString());
}
134

ck's avatar
ck committed
135
SshConnection::State SshConnection::state() const
136
{
ck's avatar
ck committed
137
138
139
140
141
142
143
144
145
    switch (d->state()) {
    case Internal::SocketUnconnected:
        return Unconnected;
    case Internal::ConnectionEstablished:
        return Connected;
    default:
        return Connecting;
    }
}
146

ck's avatar
ck committed
147
148
149
150
SshError SshConnection::errorState() const
{
    return d->error();
}
151

ck's avatar
ck committed
152
QString SshConnection::errorString() const
153
{
ck's avatar
ck committed
154
155
    return d->errorString();
}
156

ck's avatar
ck committed
157
158
159
160
SshConnectionParameters SshConnection::connectionParameters() const
{
    return d->m_connParams;
}
161

162
SshConnectionInfo SshConnection::connectionInfo() const
163
{
164
    QSSH_ASSERT_AND_RETURN_VALUE(state() == Connected, SshConnectionInfo());
165
166
167

    return SshConnectionInfo(d->m_socket->localAddress(), d->m_socket->localPort(),
        d->m_socket->peerAddress(), d->m_socket->peerPort());
168
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)
{
179
    QSSH_ASSERT_AND_RETURN_VALUE(state() == Connected, QSharedPointer<SshRemoteProcess>());
Christian Kandeler's avatar
Christian Kandeler committed
180
    return d->createRemoteProcess(command);
ck's avatar
ck committed
181
}
182

183
184
QSharedPointer<SshRemoteProcess> SshConnection::createRemoteShell()
{
185
    QSSH_ASSERT_AND_RETURN_VALUE(state() == Connected, QSharedPointer<SshRemoteProcess>());
186
187
188
    return d->createRemoteShell();
}

ck's avatar
ck committed
189
190
QSharedPointer<SftpChannel> SshConnection::createSftpChannel()
{
191
    QSSH_ASSERT_AND_RETURN_VALUE(state() == Connected, QSharedPointer<SftpChannel>());
Christian Kandeler's avatar
Christian Kandeler committed
192
    return d->createSftpChannel();
ck's avatar
ck committed
193
}
ck's avatar
ck committed
194

195
196
197
int SshConnection::closeAllChannels()
{
    try {
198
        return d->m_channelManager->closeAllChannels(Internal::SshChannelManager::CloseAllRegular);
199
200
201
202
203
    } catch (const Botan::Exception &e) {
        qDebug("%s: %s", Q_FUNC_INFO, e.what());
        return -1;
    }
}
204

205
206
207
208
209
int SshConnection::channelCount() const
{
    return d->m_channelManager->channelCount();
}

ck's avatar
ck committed
210
namespace Internal {
211

212
213
SshConnectionPrivate::SshConnectionPrivate(SshConnection *conn,
    const SshConnectionParameters &serverInfo)
ck's avatar
ck committed
214
215
    : m_socket(new QTcpSocket(this)), m_state(SocketUnconnected),
      m_sendFacility(m_socket),
216
      m_channelManager(new SshChannelManager(m_sendFacility, this)),
217
218
      m_connParams(serverInfo), m_error(SshNoError), m_ignoreNextPacket(false),
      m_conn(conn)
ck's avatar
ck committed
219
220
{
    setupPacketHandlers();
221
222
    m_socket->setProxy(m_connParams.proxyType == SshConnectionParameters::DefaultProxy
        ? QNetworkProxy::DefaultProxy : QNetworkProxy::NoProxy);
223
    m_timeoutTimer.setSingleShot(true);
224
    m_timeoutTimer.setInterval(m_connParams.timeout * 1000);
225
226
    m_keepAliveTimer.setSingleShot(true);
    m_keepAliveTimer.setInterval(10000);
227
    connect(m_channelManager, SIGNAL(timeout()), this, SLOT(handleTimeout()));
ck's avatar
ck committed
228
}
229

ck's avatar
ck committed
230
231
232
233
SshConnectionPrivate::~SshConnectionPrivate()
{
    disconnect();
}
234

ck's avatar
ck committed
235
236
237
238
void SshConnectionPrivate::setupPacketHandlers()
{
    typedef SshConnectionPrivate This;

239
240
241
242
    setupPacketHandler(SSH_MSG_KEXINIT, StateList() << SocketConnected
        << ConnectionEstablished, &This::handleKeyExchangeInitPacket);
    setupPacketHandler(SSH_MSG_KEXDH_REPLY, StateList() << SocketConnected
        << ConnectionEstablished, &This::handleKeyExchangeReplyPacket);
ck's avatar
ck committed
243

244
245
    setupPacketHandler(SSH_MSG_NEWKEYS, StateList() << SocketConnected
        << ConnectionEstablished, &This::handleNewKeysPacket);
ck's avatar
ck committed
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
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
    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);
293
294
295

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

ck's avatar
ck committed
298
299
300
301
302
303
void SshConnectionPrivate::setupPacketHandler(SshPacketType type,
    const SshConnectionPrivate::StateList &states,
    SshConnectionPrivate::PacketHandler handler)
{
    m_packetHandlers.insert(type, HandlerInStates(states, handler));
}
ck's avatar
ck committed
304

ck's avatar
ck committed
305
void SshConnectionPrivate::handleSocketConnected()
ck's avatar
ck committed
306
{
ck's avatar
ck committed
307
308
    m_state = SocketConnected;
    sendData(ClientId);
ck's avatar
ck committed
309
310
}

ck's avatar
ck committed
311
312
313
314
315
316
void SshConnectionPrivate::handleIncomingData()
{
    if (m_state == SocketUnconnected)
        return; // For stuff queued in the event loop after we've called closeConnection();

    try {
317
318
        if (!canUseSocket())
            return;
ck's avatar
ck committed
319
320
321
322
323
        m_incomingData += m_socket->readAll();
#ifdef CREATOR_SSH_DEBUG
        qDebug("state = %d, remote data size = %d", m_state,
            m_incomingData.count());
#endif
324
        if (m_serverId.isEmpty())
ck's avatar
ck committed
325
326
327
328
329
330
331
332
333
334
            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, "",
335
            tr("Botan library exception: %1").arg(QString::fromAscii(e.what())));
ck's avatar
ck committed
336
337
    }
}
ck's avatar
ck committed
338

339
// RFC 4253, 4.2.
ck's avatar
ck committed
340
341
void SshConnectionPrivate::handleServerId()
{
Christian Kandeler's avatar
Christian Kandeler committed
342
343
344
345
#ifdef CREATOR_SSH_DEBUG
    qDebug("%s: incoming data size = %d, incoming data = '%s'",
        Q_FUNC_INFO, m_incomingData.count(), m_incomingData.data());
#endif
346
347
348
349
350
351
352
353
    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;
ck's avatar
ck committed
354
        return;
355
356
357
358
359
    }

    if (newLinePos > 255 - 1) {
        throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
            "Identification string too long.",
Friedemann Kleint's avatar
Friedemann Kleint committed
360
361
            tr("Server identification string is %n characters long, but the maximum "
               "allowed length is 255.", 0, newLinePos + 1));
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
    }

    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")) {
ck's avatar
ck committed
388
389
        throw SshServerException(SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED,
            "Invalid protocol version.",
390
391
            tr("Server protocol version is '%1', but needs to be 2.0 or 1.99.")
                    .arg(serverProtoVersion));
ck's avatar
ck committed
392
    }
393
394
395
396
397
398
399
400
401

    // 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) {
ck's avatar
ck committed
402
        throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
403
404
405
            "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."));
ck's avatar
ck committed
406
    }
ck's avatar
ck committed
407

ck's avatar
ck committed
408
    m_keyExchange.reset(new SshKeyExchange(m_sendFacility));
409
    m_keyExchange->sendKexInitPacket(m_serverId);
410
    m_keyExchangeState = KexInitSent;
411
412
}

ck's avatar
ck committed
413
void SshConnectionPrivate::handlePackets()
414
{
ck's avatar
ck committed
415
416
417
418
419
420
    m_incomingPacket.consumeData(m_incomingData);
    while (m_incomingPacket.isComplete()) {
        handleCurrentPacket();
        m_incomingPacket.clear();
        m_incomingPacket.consumeData(m_incomingData);
    }
421
422
}

ck's avatar
ck committed
423
void SshConnectionPrivate::handleCurrentPacket()
424
{
ck's avatar
ck committed
425
    Q_ASSERT(m_incomingPacket.isComplete());
426
    Q_ASSERT(m_keyExchangeState == DhInitSent || !m_ignoreNextPacket);
427

ck's avatar
ck committed
428
429
430
431
    if (m_ignoreNextPacket) {
        m_ignoreNextPacket = false;
        return;
    }
432

ck's avatar
ck committed
433
434
435
436
437
438
439
440
441
442
443
444
    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)();
445
446
}

ck's avatar
ck committed
447
void SshConnectionPrivate::handleKeyExchangeInitPacket()
448
{
449
450
    if (m_keyExchangeState != NoKeyExchange
            && m_keyExchangeState != KexInitSent) {
451
452
453
454
455
456
        throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
            "Unexpected packet.", tr("Unexpected packet of type %1.")
            .arg(m_incomingPacket.type()));
    }

    // Server-initiated re-exchange.
457
    if (m_keyExchangeState == NoKeyExchange) {
458
459
460
461
        m_keyExchange.reset(new SshKeyExchange(m_sendFacility));
        m_keyExchange->sendKexInitPacket(m_serverId);
    }

ck's avatar
ck committed
462
    // If the server sends a guessed packet, the guess must be wrong,
Christian Kandeler's avatar
Christian Kandeler committed
463
    // because the algorithms we support require us to initiate the
ck's avatar
ck committed
464
    // key exchange.
465
    if (m_keyExchange->sendDhInitPacket(m_incomingPacket)) {
ck's avatar
ck committed
466
        m_ignoreNextPacket = true;
467
468
    }

469
    m_keyExchangeState = DhInitSent;
470
471
}

ck's avatar
ck committed
472
void SshConnectionPrivate::handleKeyExchangeReplyPacket()
473
{
474
    if (m_keyExchangeState != DhInitSent) {
475
476
477
478
479
        throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
            "Unexpected packet.", tr("Unexpected packet of type %1.")
            .arg(m_incomingPacket.type()));
    }

ck's avatar
ck committed
480
481
482
    m_keyExchange->sendNewKeysPacket(m_incomingPacket,
        ClientId.left(ClientId.size() - 2));
    m_sendFacility.recreateKeys(*m_keyExchange);
483
    m_keyExchangeState = NewKeysSent;
484
485
}

ck's avatar
ck committed
486
void SshConnectionPrivate::handleNewKeysPacket()
ck's avatar
ck committed
487
{
488
    if (m_keyExchangeState != NewKeysSent) {
489
490
491
492
493
        throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
            "Unexpected packet.", tr("Unexpected packet of type %1.")
            .arg(m_incomingPacket.type()));
    }

ck's avatar
ck committed
494
495
    m_incomingPacket.recreateKeys(*m_keyExchange);
    m_keyExchange.reset();
496
497
498
499
500
501
    m_keyExchangeState = NoKeyExchange;

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

ck's avatar
ck committed
504
505
void SshConnectionPrivate::handleServiceAcceptPacket()
{
Christian Kandeler's avatar
Christian Kandeler committed
506
    if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationByPassword) {
507
508
        m_sendFacility.sendUserAuthByPwdRequestPacket(m_connParams.userName.toUtf8(),
            SshCapabilities::SshConnectionService, m_connParams.password.toUtf8());
ck's avatar
ck committed
509
    } else {
510
        m_sendFacility.sendUserAuthByKeyRequestPacket(m_connParams.userName.toUtf8(),
ck's avatar
ck committed
511
512
513
            SshCapabilities::SshConnectionService);
    }
    m_state = UserAuthRequested;
514
515
}

ck's avatar
ck committed
516
void SshConnectionPrivate::handlePasswordExpiredPacket()
517
{
Christian Kandeler's avatar
Christian Kandeler committed
518
    if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationByKey) {
ck's avatar
ck committed
519
520
521
522
523
        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."));
524
525
}

ck's avatar
ck committed
526
void SshConnectionPrivate::handleUserAuthBannerPacket()
527
{
ck's avatar
ck committed
528
    emit dataAvailable(m_incomingPacket.extractUserAuthBanner().message);
529
530
}

ck's avatar
ck committed
531
void SshConnectionPrivate::handleGlobalRequest()
532
{
ck's avatar
ck committed
533
    m_sendFacility.sendRequestFailurePacket();
534
535
}

ck's avatar
ck committed
536
void SshConnectionPrivate::handleUserAuthSuccessPacket()
537
{
ck's avatar
ck committed
538
539
540
    m_state = ConnectionEstablished;
    m_timeoutTimer.stop();
    emit connected();
541
542
543
    m_lastInvalidMsgSeqNr = InvalidSeqNr;
    connect(&m_keepAliveTimer, SIGNAL(timeout()), SLOT(sendKeepAlivePacket()));
    m_keepAliveTimer.start();
ck's avatar
ck committed
544
}
545

ck's avatar
ck committed
546
547
void SshConnectionPrivate::handleUserAuthFailurePacket()
{
548
    m_timeoutTimer.stop();
Christian Kandeler's avatar
Christian Kandeler committed
549
    const QString errorMsg = m_connParams.authenticationType == SshConnectionParameters::AuthenticationByPassword
ck's avatar
ck committed
550
551
552
553
554
555
556
557
558
        ? 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);
}
559

560
561
562
563
564
565
566
567
568
569
570
571
572
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
573
void SshConnectionPrivate::handleChannelRequest()
574
{
ck's avatar
ck committed
575
    m_channelManager->handleChannelRequest(m_incomingPacket);
576
577
}

ck's avatar
ck committed
578
void SshConnectionPrivate::handleChannelOpen()
579
{
ck's avatar
ck committed
580
    m_channelManager->handleChannelOpen(m_incomingPacket);
581
582
}

ck's avatar
ck committed
583
void SshConnectionPrivate::handleChannelOpenFailure()
584
{
ck's avatar
ck committed
585
586
   m_channelManager->handleChannelOpenFailure(m_incomingPacket);
}
587

ck's avatar
ck committed
588
589
590
void SshConnectionPrivate::handleChannelOpenConfirmation()
{
    m_channelManager->handleChannelOpenConfirmation(m_incomingPacket);
591
592
}

ck's avatar
ck committed
593
void SshConnectionPrivate::handleChannelSuccess()
594
{
ck's avatar
ck committed
595
596
    m_channelManager->handleChannelSuccess(m_incomingPacket);
}
597

ck's avatar
ck committed
598
599
600
601
void SshConnectionPrivate::handleChannelFailure()
{
    m_channelManager->handleChannelFailure(m_incomingPacket);
}
602

ck's avatar
ck committed
603
604
605
void SshConnectionPrivate::handleChannelWindowAdjust()
{
   m_channelManager->handleChannelWindowAdjust(m_incomingPacket);
606
607
}

ck's avatar
ck committed
608
void SshConnectionPrivate::handleChannelData()
609
{
ck's avatar
ck committed
610
611
   m_channelManager->handleChannelData(m_incomingPacket);
}
612

ck's avatar
ck committed
613
614
615
616
void SshConnectionPrivate::handleChannelExtendedData()
{
   m_channelManager->handleChannelExtendedData(m_incomingPacket);
}
617

ck's avatar
ck committed
618
619
620
void SshConnectionPrivate::handleChannelEof()
{
   m_channelManager->handleChannelEof(m_incomingPacket);
621
622
}

ck's avatar
ck committed
623
void SshConnectionPrivate::handleChannelClose()
624
{
ck's avatar
ck committed
625
   m_channelManager->handleChannelClose(m_incomingPacket);
626
627
}

ck's avatar
ck committed
628
void SshConnectionPrivate::handleDisconnect()
629
{
ck's avatar
ck committed
630
631
632
    const SshDisconnect msg = m_incomingPacket.extractDisconnect();
    throw SshServerException(SSH_DISCONNECT_CONNECTION_LOST,
        "", tr("Server closed connection: %1").arg(msg.description));
633
634
}

635
636


ck's avatar
ck committed
637
void SshConnectionPrivate::sendData(const QByteArray &data)
638
{
639
640
    if (canUseSocket())
        m_socket->write(data);
641
642
}

ck's avatar
ck committed
643
void SshConnectionPrivate::handleSocketDisconnected()
644
{
ck's avatar
ck committed
645
646
647
    closeConnection(SSH_DISCONNECT_CONNECTION_LOST, SshClosedByServerError,
        "Connection closed unexpectedly.",
        tr("Connection closed unexpectedly."));
648
649
}

ck's avatar
ck committed
650
void SshConnectionPrivate::handleSocketError()
651
{
ck's avatar
ck committed
652
653
654
    if (m_error == SshNoError) {
        closeConnection(SSH_DISCONNECT_CONNECTION_LOST, SshSocketError,
            "Network error", m_socket->errorString());
655
656
657
    }
}

ck's avatar
ck committed
658
void SshConnectionPrivate::handleTimeout()
659
{
660
661
    closeConnection(SSH_DISCONNECT_BY_APPLICATION, SshTimeoutError, "",
        tr("Timeout waiting for reply from server."));
662
663
}

664
665
void SshConnectionPrivate::sendKeepAlivePacket()
{
666
667
668
669
670
671
    // This type of message is not allowed during key exchange.
    if (m_keyExchangeState != NoKeyExchange) {
        m_keepAliveTimer.start();
        return;
    }

672
673
674
    Q_ASSERT(m_lastInvalidMsgSeqNr == InvalidSeqNr);
    m_lastInvalidMsgSeqNr = m_sendFacility.nextClientSeqNr();
    m_sendFacility.sendInvalidPacket();
675
    m_timeoutTimer.start();
676
677
}

678
void SshConnectionPrivate::connectToHost()
ck's avatar
ck committed
679
{
680
    QSSH_ASSERT_AND_RETURN(m_state == SocketUnconnected);
681

ck's avatar
ck committed
682
683
684
685
686
687
    m_incomingData.clear();
    m_incomingPacket.reset();
    m_sendFacility.reset();
    m_error = SshNoError;
    m_ignoreNextPacket = false;
    m_errorString.clear();
688
689
    m_serverId.clear();
    m_serverHasSentDataBeforeId = false;
690
691
692
693
694
695
696
697
698
699
700

    try {
        if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationByKey)
            createPrivateKey();
    } catch (const SshClientException &ex) {
        m_error = ex.error;
        m_errorString = ex.errorString;
        emit error(m_error);
        return;
    }

ck's avatar
ck committed
701
702
703
704
705
706
    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()));
707
    connect(&m_timeoutTimer, SIGNAL(timeout()), this, SLOT(handleTimeout()));
ck's avatar
ck committed
708
    m_state = SocketConnecting;
709
    m_keyExchangeState = NoKeyExchange;
710
    m_timeoutTimer.start();
711
    m_socket->connectToHost(m_connParams.host, m_connParams.port);
712
713
}

ck's avatar
ck committed
714
715
716
717
718
719
720
721
722
723
724
725
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);
726
    disconnect(&m_timeoutTimer, 0, this, 0);
727
728
    m_keepAliveTimer.stop();
    disconnect(&m_keepAliveTimer, 0, this, 0);
ck's avatar
ck committed
729
    try {
730
        m_channelManager->closeAllChannels(SshChannelManager::CloseAllAndReset);
ck's avatar
ck committed
731
732
733
734
735
736
        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();
737
738
    if (canUseSocket())
        m_socket->disconnectFromHost();
ck's avatar
ck committed
739
    m_state = SocketUnconnected;
740
741
}

742
743
744
bool SshConnectionPrivate::canUseSocket() const
{
    return m_socket->isValid()
745
746
747
748
749
750
751
            && m_socket->state() == QAbstractSocket::ConnectedState;
}

void SshConnectionPrivate::createPrivateKey()
{
    if (m_connParams.privateKeyFile.isEmpty())
        throw SshClientException(SshKeyFileError, tr("No private key file given."));
752
753
    QFile keyFile(m_connParams.privateKeyFile);
    if (!keyFile.open(QIODevice::ReadOnly)) {
754
        throw SshClientException(SshKeyFileError,
755
756
757
            tr("Private key file error: %1").arg(keyFile.errorString()));
    }
    m_sendFacility.createAuthenticationKey(keyFile.readAll());
758
759
}

ck's avatar
ck committed
760
QSharedPointer<SshRemoteProcess> SshConnectionPrivate::createRemoteProcess(const QByteArray &command)
761
{
ck's avatar
ck committed
762
    return m_channelManager->createRemoteProcess(command);
763
764
}

765
766
767
768
769
QSharedPointer<SshRemoteProcess> SshConnectionPrivate::createRemoteShell()
{
    return m_channelManager->createRemoteShell();
}

ck's avatar
ck committed
770
QSharedPointer<SftpChannel> SshConnectionPrivate::createSftpChannel()
771
{
ck's avatar
ck committed
772
    return m_channelManager->createSftpChannel();
773
774
}

775
776
const quint64 SshConnectionPrivate::InvalidSeqNr = static_cast<quint64>(-1);

ck's avatar
ck committed
777
} // namespace Internal
778
} // namespace QSsh