servertest.cpp 23.2 KB
Newer Older
1
/*
2
  SPDX-FileCopyrightText: 2006-2007 Volker Krause <vkrause@kde.org>
3
4
5
6
  SPDX-FileCopyrightText: 2007 KovoKs <info@kovoks.nl>
  SPDX-FileCopyrightText: 2008 Thomas McGuire <thomas.mcguire@gmx.net>

  SPDX-License-Identifier: LGPL-2.0-or-later
7
8
9
10
*/

// Own
#include "servertest.h"
Tom Albers's avatar
Tom Albers committed
11
#include "socket.h"
12

Laurent Montel's avatar
Laurent Montel committed
13
#include <mailtransport_defs.h>
Laurent Montel's avatar
Laurent Montel committed
14
#include <transportbase.h>
15

16
// Qt
17
#include <QHash>
18
#include <QHostInfo>
19
#include <QProgressBar>
20
#include <QRegularExpression>
Pino Toscano's avatar
Pino Toscano committed
21
#include <QSet>
Laurent Montel's avatar
Laurent Montel committed
22
#include <QTimer>
23
24

// KDE
Laurent Montel's avatar
Laurent Montel committed
25
#include "mailtransport_debug.h"
26
27
28

using namespace MailTransport;

Laurent Montel's avatar
Laurent Montel committed
29
30
namespace MailTransport
{
Tom Albers's avatar
Tom Albers committed
31
class ServerTestPrivate
32
{
Laurent Montel's avatar
Laurent Montel committed
33
34
public:
    ServerTestPrivate(ServerTest *test);
35

Laurent Montel's avatar
Laurent Montel committed
36
37
38
39
    ServerTest *const q;
    QString server;
    QString fakeHostname;
    QString testProtocol;
40

Laurent Montel's avatar
Laurent Montel committed
41
42
    MailTransport::Socket *normalSocket = nullptr;
    MailTransport::Socket *secureSocket = nullptr;
43

Laurent Montel's avatar
Laurent Montel committed
44
45
46
47
    QSet<int> connectionResults;
    QHash<int, QVector<int>> authenticationResults;
    QSet<ServerTest::Capability> capabilityResults;
    QHash<int, uint> customPorts;
Laurent Montel's avatar
Laurent Montel committed
48
49
50
    QTimer *normalSocketTimer = nullptr;
    QTimer *secureSocketTimer = nullptr;
    QTimer *progressTimer = nullptr;
51

Laurent Montel's avatar
Laurent Montel committed
52
    QProgressBar *testProgress = nullptr;
53

Laurent Montel's avatar
Laurent Montel committed
54
55
56
    bool secureSocketFinished = false;
    bool normalSocketFinished = false;
    bool tlsFinished = false;
Laurent Montel's avatar
Laurent Montel committed
57
58
59
60
    bool popSupportsTLS;
    int normalStage;
    int secureStage;
    int encryptionMode;
Tom Albers's avatar
Tom Albers committed
61

Laurent Montel's avatar
Laurent Montel committed
62
63
64
    bool normalPossible = true;
    bool securePossible = true;

Tom Albers's avatar
Tom Albers committed
65
    void finalResult();
Laurent Montel's avatar
Laurent Montel committed
66
67
    void handleSMTPIMAPResponse(int type, const QString &text);
    void sendInitialCapabilityQuery(MailTransport::Socket *socket);
Laurent Montel's avatar
Laurent Montel committed
68
    bool handlePopConversation(MailTransport::Socket *socket, int type, int stage, const QString &response, bool *shouldStartTLS);
Pino Toscano's avatar
Pino Toscano committed
69
    bool handleNntpConversation(MailTransport::Socket *socket, int type, int *stage, const QString &response, bool *shouldStartTLS);
Laurent Montel's avatar
Laurent Montel committed
70
    QVector<int> parseAuthenticationList(const QStringList &authentications);
Tom Albers's avatar
Tom Albers committed
71

Laurent Montel's avatar
Laurent Montel committed
72
73
    inline bool isGmail(const QString &server) const
    {
74
75
76
        return server.endsWith(QLatin1String("gmail.com")) || server.endsWith(QLatin1String("googlemail.com"));
    }

Tom Albers's avatar
Tom Albers committed
77
78
79
80
81
    // slots
    void slotNormalPossible();
    void slotNormalNotPossible();
    void slotSslPossible();
    void slotSslNotPossible();
82
    void slotTlsDone();
Laurent Montel's avatar
Laurent Montel committed
83
84
    void slotReadNormal(const QString &text);
    void slotReadSecure(const QString &text);
Tom Albers's avatar
Tom Albers committed
85
86
    void slotUpdateProgress();
};
87
88
}

Laurent Montel's avatar
Laurent Montel committed
89
ServerTestPrivate::ServerTestPrivate(ServerTest *test)
Laurent Montel's avatar
Laurent Montel committed
90
    : q(test)
91
{
Tom Albers's avatar
Tom Albers committed
92
}
93

Tom Albers's avatar
Tom Albers committed
94
95
void ServerTestPrivate::finalResult()
{
Laurent Montel's avatar
Laurent Montel committed
96
97
98
99
    if (!secureSocketFinished || !normalSocketFinished || !tlsFinished) {
        return;
    }

Laurent Montel's avatar
Laurent Montel committed
100
101
    qCDebug(MAILTRANSPORT_LOG) << "Modes:" << connectionResults;
    qCDebug(MAILTRANSPORT_LOG) << "Capabilities:" << capabilityResults;
Laurent Montel's avatar
Laurent Montel committed
102
103
104
    qCDebug(MAILTRANSPORT_LOG) << "Normal:" << q->normalProtocols();
    qCDebug(MAILTRANSPORT_LOG) << "SSL:" << q->secureProtocols();
    qCDebug(MAILTRANSPORT_LOG) << "TLS:" << q->tlsProtocols();
Laurent Montel's avatar
Laurent Montel committed
105
106
107
108
109

    if (testProgress) {
        testProgress->hide();
    }
    progressTimer->stop();
Laurent Montel's avatar
Laurent Montel committed
110
111
112
    secureSocketFinished = false;
    normalSocketFinished = false;
    tlsFinished = false;
Laurent Montel's avatar
Laurent Montel committed
113

Pino Toscano's avatar
Pino Toscano committed
114
115
    QVector<int> resultsAsVector;
    resultsAsVector.reserve(connectionResults.size());
116
    for (int res : std::as_const(connectionResults)) {
Pino Toscano's avatar
Pino Toscano committed
117
118
119
        resultsAsVector.append(res);
    }

120
    Q_EMIT q->finished(resultsAsVector);
Laurent Montel's avatar
Laurent Montel committed
121
122
}

Laurent Montel's avatar
Laurent Montel committed
123
QVector<int> ServerTestPrivate::parseAuthenticationList(const QStringList &authentications)
Laurent Montel's avatar
Laurent Montel committed
124
{
Laurent Montel's avatar
Laurent Montel committed
125
    QVector<int> result;
Laurent Montel's avatar
Laurent Montel committed
126
    for (QStringList::ConstIterator it = authentications.begin(); it != authentications.end(); ++it) {
Laurent Montel's avatar
Laurent Montel committed
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
        QString current = (*it).toUpper();
        if (current == QLatin1String("LOGIN")) {
            result << Transport::EnumAuthenticationType::LOGIN;
        } else if (current == QLatin1String("PLAIN")) {
            result << Transport::EnumAuthenticationType::PLAIN;
        } else if (current == QLatin1String("CRAM-MD5")) {
            result << Transport::EnumAuthenticationType::CRAM_MD5;
        } else if (current == QLatin1String("DIGEST-MD5")) {
            result << Transport::EnumAuthenticationType::DIGEST_MD5;
        } else if (current == QLatin1String("NTLM")) {
            result << Transport::EnumAuthenticationType::NTLM;
        } else if (current == QLatin1String("GSSAPI")) {
            result << Transport::EnumAuthenticationType::GSSAPI;
        } else if (current == QLatin1String("ANONYMOUS")) {
            result << Transport::EnumAuthenticationType::ANONYMOUS;
142
        } else if (current == QLatin1String("XOAUTH2")) {
143
144
145
            if (isGmail(server)) {
                result << Transport::EnumAuthenticationType::XOAUTH2;
            }
Laurent Montel's avatar
Laurent Montel committed
146
147
        }
        // APOP is handled by handlePopConversation()
Allen Winter's avatar
Allen Winter committed
148
    }
Laurent Montel's avatar
Laurent Montel committed
149
    qCDebug(MAILTRANSPORT_LOG) << authentications << result;
Tom Albers's avatar
Tom Albers committed
150

Laurent Montel's avatar
Laurent Montel committed
151
152
153
154
155
156
    // LOGIN doesn't offer anything over PLAIN, requires more server
    // roundtrips and is not an official SASL mechanism, but a MS-ism,
    // so only enable it if PLAIN isn't available:
    if (result.contains(Transport::EnumAuthenticationType::PLAIN)) {
        result.removeAll(Transport::EnumAuthenticationType::LOGIN);
    }
157

Laurent Montel's avatar
Laurent Montel committed
158
    return result;
159
}
160

Laurent Montel's avatar
Laurent Montel committed
161
void ServerTestPrivate::handleSMTPIMAPResponse(int type, const QString &text)
162
{
Laurent Montel's avatar
Laurent Montel committed
163
    if (!text.contains(QLatin1String("AUTH"), Qt::CaseInsensitive)) {
Laurent Montel's avatar
Laurent Montel committed
164
        qCDebug(MAILTRANSPORT_LOG) << "No authentication possible";
Laurent Montel's avatar
Laurent Montel committed
165
166
        return;
    }
167

Laurent Montel's avatar
Laurent Montel committed
168
    QStringList protocols;
169
170
171
172
    if (isGmail(server)) {
        protocols << QStringLiteral("XOAUTH2");
    }

Laurent Montel's avatar
Laurent Montel committed
173
    protocols << QStringLiteral("LOGIN") << QStringLiteral("PLAIN") << QStringLiteral("CRAM-MD5") << QStringLiteral("DIGEST-MD5") << QStringLiteral("NTLM")
174
              << QStringLiteral("GSSAPI") << QStringLiteral("ANONYMOUS");
Laurent Montel's avatar
Laurent Montel committed
175
176
177
178
179
180

    QStringList results;
    for (int i = 0; i < protocols.count(); ++i) {
        if (text.contains(protocols.at(i), Qt::CaseInsensitive)) {
            results.append(protocols.at(i));
        }
181
182
    }

Laurent Montel's avatar
Laurent Montel committed
183
    authenticationResults[type] = parseAuthenticationList(results);
184

Laurent Montel's avatar
Laurent Montel committed
185
    // if we couldn't parse any authentication modes, default to clear-text
Laurent Montel's avatar
Laurent Montel committed
186
    if (authenticationResults[type].isEmpty()) {
Laurent Montel's avatar
Laurent Montel committed
187
188
        authenticationResults[type] << Transport::EnumAuthenticationType::CLEAR;
    }
189

Laurent Montel's avatar
Laurent Montel committed
190
    qCDebug(MAILTRANSPORT_LOG) << "For type" << type << ", we have:" << authenticationResults[type];
191
192
}

Tom Albers's avatar
Tom Albers committed
193
void ServerTestPrivate::slotNormalPossible()
194
{
Laurent Montel's avatar
Laurent Montel committed
195
196
    normalSocketTimer->stop();
    connectionResults << Transport::EnumEncryption::None;
197
198
}

Laurent Montel's avatar
Laurent Montel committed
199
void ServerTestPrivate::sendInitialCapabilityQuery(MailTransport::Socket *socket)
200
{
Laurent Montel's avatar
Laurent Montel committed
201
    if (testProtocol == IMAP_PROTOCOL) {
202
        socket->write(QStringLiteral("1 CAPABILITY"));
Laurent Montel's avatar
Laurent Montel committed
203
204
205
206
207
208
209
210
211
212
213
    } else if (testProtocol == SMTP_PROTOCOL) {
        // Detect the hostname which we send with the EHLO command.
        // If there is a fake one set, use that, otherwise use the
        // local host name (and make sure it contains a domain, so the
        // server thinks it is valid).
        QString hostname;
        if (!fakeHostname.isNull()) {
            hostname = fakeHostname;
        } else {
            hostname = QHostInfo::localHostName();
            if (hostname.isEmpty()) {
214
                hostname = QStringLiteral("localhost.invalid");
Laurent Montel's avatar
Laurent Montel committed
215
216
217
218
            } else if (!hostname.contains(QChar::fromLatin1('.'))) {
                hostname += QLatin1String(".localnet");
            }
        }
Laurent Montel's avatar
Laurent Montel committed
219
        qCDebug(MAILTRANSPORT_LOG) << "Hostname for EHLO is" << hostname;
220

Laurent Montel's avatar
Laurent Montel committed
221
222
        socket->write(QLatin1String("EHLO ") + hostname);
    }
223
224
}

225
226
void ServerTestPrivate::slotTlsDone()
{
Laurent Montel's avatar
Laurent Montel committed
227
228
229
    // The server will not send a response after starting TLS. Therefore, we have to manually
    // call slotReadNormal(), because this is not triggered by a data received signal this time.
    slotReadNormal(QString());
230
231
}

Laurent Montel's avatar
Laurent Montel committed
232
bool ServerTestPrivate::handlePopConversation(MailTransport::Socket *socket, int type, int stage, const QString &response, bool *shouldStartTLS)
233
{
Laurent Montel's avatar
Laurent Montel committed
234
    Q_ASSERT(shouldStartTLS != nullptr);
235

Laurent Montel's avatar
Laurent Montel committed
236
237
    // Initial Greeting
    if (stage == 0) {
Laurent Montel's avatar
Laurent Montel committed
238
        // Regexp taken from POP3 ioslave
239
        const QString responseWithoutCRLF = response.chopped(2);
Laurent Montel's avatar
Laurent Montel committed
240
        const QRegularExpression re(QStringLiteral("<[A-Za-z0-9\\.\\-_]+@[A-Za-z0-9\\.\\-_]+>$"), QRegularExpression::CaseInsensitiveOption);
Laurent Montel's avatar
Laurent Montel committed
241
242
243
        if (responseWithoutCRLF.indexOf(re) != -1) {
            authenticationResults[type] << Transport::EnumAuthenticationType::APOP;
        }
244

Laurent Montel's avatar
Laurent Montel committed
245
        // Each server is supposed to support clear text login
Laurent Montel's avatar
Laurent Montel committed
246
        authenticationResults[type] << Transport::EnumAuthenticationType::CLEAR;
247

Laurent Montel's avatar
Laurent Montel committed
248
249
        // If we are in TLS stage, the server does not send the initial greeting.
        // Assume that the APOP availability is the same as with an unsecured connection.
Laurent Montel's avatar
Laurent Montel committed
250
        if (type == Transport::EnumEncryption::TLS
Laurent Montel's avatar
Laurent Montel committed
251
252
            && authenticationResults[Transport::EnumEncryption::None].contains(Transport::EnumAuthenticationType::APOP)) {
            authenticationResults[Transport::EnumEncryption::TLS] << Transport::EnumAuthenticationType::APOP;
Laurent Montel's avatar
Laurent Montel committed
253
        }
254

255
        socket->write(QStringLiteral("CAPA"));
Laurent Montel's avatar
Laurent Montel committed
256
257
258
259
        return true;
    }
    // CAPA result
    else if (stage == 1) {
Laurent Montel's avatar
Laurent Montel committed
260
261
262
263
264
265
266
267
268
        //     Example:
        //     CAPA
        //     +OK
        //     TOP
        //     USER
        //     SASL LOGIN CRAM-MD5
        //     UIDL
        //     RESP-CODES
        //     .
Laurent Montel's avatar
Laurent Montel committed
269
270
271
272
273
274
275
276
277
278
279
280
281
        if (response.contains(QLatin1String("TOP"))) {
            capabilityResults += ServerTest::Top;
        }
        if (response.contains(QLatin1String("PIPELINING"))) {
            capabilityResults += ServerTest::Pipelining;
        }
        if (response.contains(QLatin1String("UIDL"))) {
            capabilityResults += ServerTest::UIDL;
        }
        if (response.contains(QLatin1String("STLS"))) {
            connectionResults << Transport::EnumEncryption::TLS;
            popSupportsTLS = true;
        }
282
        socket->write(QStringLiteral("AUTH"));
Laurent Montel's avatar
Laurent Montel committed
283
        return true;
Allen Winter's avatar
Allen Winter committed
284
    }
Laurent Montel's avatar
Laurent Montel committed
285
286
    // AUTH response
    else if (stage == 2) {
Laurent Montel's avatar
Laurent Montel committed
287
288
289
290
291
292
        //     Example:
        //     C: AUTH
        //     S: +OK List of supported authentication methods follows
        //     S: LOGIN
        //     S: CRAM-MD5
        //     S:.
Laurent Montel's avatar
Laurent Montel committed
293
        QString formattedReply = response;
294

Yuri Chornoivan's avatar
Yuri Chornoivan committed
295
        // Get rid of trailing ".CRLF"
Laurent Montel's avatar
Laurent Montel committed
296
        formattedReply.chop(3);
297

Laurent Montel's avatar
Laurent Montel committed
298
        // Get rid of the first +OK line
Laurent Montel's avatar
Laurent Montel committed
299
300
301
302
        formattedReply = formattedReply.right(formattedReply.size() - formattedReply.indexOf(QLatin1Char('\n')) - 1);
        formattedReply = formattedReply.replace(QLatin1Char(' '), QLatin1Char('-')).replace(QLatin1String("\r\n"), QLatin1String(" "));

        authenticationResults[type] += parseAuthenticationList(formattedReply.split(QLatin1Char(' ')));
Laurent Montel's avatar
Laurent Montel committed
303
    }
304

Laurent Montel's avatar
Laurent Montel committed
305
306
    *shouldStartTLS = popSupportsTLS;
    return false;
307
}
308

Pino Toscano's avatar
Pino Toscano committed
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
bool ServerTestPrivate::handleNntpConversation(MailTransport::Socket *socket, int type, int *stage, const QString &response, bool *shouldStartTLS)
{
    Q_ASSERT(shouldStartTLS != nullptr);
    Q_ASSERT(stage != nullptr);

    // Initial Greeting
    if (*stage == 0) {
        if (response.startsWith(QLatin1String("382 "))) {
            return true;
        }
        if (!response.isEmpty() && !response.startsWith(QLatin1String("200 "))) {
            return false;
        }

        socket->write(QStringLiteral("CAPABILITIES"));
        return true;
    }
    // CAPABILITIES result
    else if (*stage == 1) {
        // Check whether we got "500 command 'CAPABILITIES' not recognized"
        if (response.startsWith(QLatin1String("500 "))) {
            return false;
        }

Laurent Montel's avatar
Laurent Montel committed
333
334
335
336
337
338
339
340
341
342
343
344
345
        //     Example:
        //     101 Capability list:
        //     VERSION 2
        //     IMPLEMENTATION INN 2.5.4
        //     AUTHINFO USER SASL
        //     HDR
        //     LIST ACTIVE [etc]
        //     OVER
        //     POST
        //     READER
        //     SASL DIGEST-MD5 CRAM-MD5 NTLM PLAIN LOGIN
        //     STARTTLS
        //     .
Laurent Montel's avatar
Laurent Montel committed
346
347
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
        const QVector<QStringView> lines = QStringView(response).split(QStringLiteral("\r\n"), Qt::SkipEmptyParts);
Laurent Montel's avatar
Laurent Montel committed
348
        for (const QStringView line : lines) {
Laurent Montel's avatar
Laurent Montel committed
349
#else
350
        const QVector<QStringRef> lines = response.splitRef(QStringLiteral("\r\n"), Qt::SkipEmptyParts);
Pino Toscano's avatar
Pino Toscano committed
351
        for (const QStringRef &line : lines) {
Laurent Montel's avatar
Laurent Montel committed
352
#endif
Pino Toscano's avatar
Pino Toscano committed
353
354
355
            if (line.compare(QLatin1String("STARTTLS"), Qt::CaseInsensitive) == 0) {
                *shouldStartTLS = true;
            } else if (line.startsWith(QLatin1String("AUTHINFO "), Qt::CaseInsensitive)) {
356
                const QVector<QStringRef> authinfos = line.split(QLatin1Char(' '), Qt::SkipEmptyParts);
Pino Toscano's avatar
Pino Toscano committed
357
358
359
360
361
362
                const QString s(QStringLiteral("USER"));
                const QStringRef ref(&s);
                if (authinfos.contains(ref)) {
                    authenticationResults[type].append(Transport::EnumAuthenticationType::CLEAR); // XXX
                }
            } else if (line.startsWith(QLatin1String("SASL "), Qt::CaseInsensitive)) {
363
                const QStringList auths = line.mid(5).toString().split(QLatin1Char(' '), Qt::SkipEmptyParts);
Pino Toscano's avatar
Pino Toscano committed
364
                authenticationResults[type] += parseAuthenticationList(auths);
365
            } else if (line == QLatin1Char('.')) {
Pino Toscano's avatar
Pino Toscano committed
366
367
368
369
370
371
372
373
374
375
376
377
                return false;
            }
        }
        // We have not hit the end of the capabilities list yet,
        // so avoid the stage counter to rise without reason.
        --(*stage);
        return true;
    }

    return false;
}

378
379
380
381
// slotReadNormal() handles normal (no) encryption and TLS encryption.
// At first, the communication is not encrypted, but if the server supports
// the STARTTLS/STLS keyword, the same authentication query is done again
// with TLS.
Laurent Montel's avatar
Laurent Montel committed
382
383
384
385
386
void ServerTestPrivate::slotReadNormal(const QString &text)
{
    Q_ASSERT(encryptionMode != Transport::EnumEncryption::SSL);
    static const int tlsHandshakeStage = 42;

Laurent Montel's avatar
Laurent Montel committed
387
    qCDebug(MAILTRANSPORT_LOG) << "Stage" << normalStage + 1 << ", Mode" << encryptionMode;
Laurent Montel's avatar
Laurent Montel committed
388
389
390
391
392
393
394
395
396
397

    // If we are in stage 42, we just do the handshake for TLS encryption and
    // then reset the stage to -1, so that all authentication modes and
    // capabilities are queried again for TLS encryption (some servers have
    // different authentication  methods in normal and in TLS mode).
    if (normalStage == tlsHandshakeStage) {
        Q_ASSERT(encryptionMode == Transport::EnumEncryption::TLS);
        normalStage = -1;
        normalSocket->startTLS();
        return;
398
    }
399

Laurent Montel's avatar
Laurent Montel committed
400
401
402
    bool shouldStartTLS = false;
    normalStage++;

Yuri Chornoivan's avatar
Yuri Chornoivan committed
403
    // Handle the whole POP and NNTP conversations separately, as
Pino Toscano's avatar
Pino Toscano committed
404
    // they are very different from IMAP and SMTP
Laurent Montel's avatar
Laurent Montel committed
405
    if (testProtocol == POP_PROTOCOL) {
Laurent Montel's avatar
Laurent Montel committed
406
        if (handlePopConversation(normalSocket, encryptionMode, normalStage, text, &shouldStartTLS)) {
Laurent Montel's avatar
Laurent Montel committed
407
408
            return;
        }
Pino Toscano's avatar
Pino Toscano committed
409
    } else if (testProtocol == NNTP_PROTOCOL) {
Laurent Montel's avatar
Laurent Montel committed
410
        if (handleNntpConversation(normalSocket, encryptionMode, &normalStage, text, &shouldStartTLS)) {
Pino Toscano's avatar
Pino Toscano committed
411
412
            return;
        }
Allen Winter's avatar
Allen Winter committed
413
    } else {
Laurent Montel's avatar
Laurent Montel committed
414
415
416
417
418
419
420
421
422
423
424
425
        // Handle the SMTP/IMAP conversation here. We just send the EHLO command in
        // sendInitialCapabilityQuery.
        if (normalStage == 0) {
            sendInitialCapabilityQuery(normalSocket);
            return;
        }

        if (text.contains(QLatin1String("STARTTLS"), Qt::CaseInsensitive)) {
            connectionResults << Transport::EnumEncryption::TLS;
            shouldStartTLS = true;
        }
        handleSMTPIMAPResponse(encryptionMode, text);
Allen Winter's avatar
Allen Winter committed
426
    }
427

Laurent Montel's avatar
Laurent Montel committed
428
429
430
431
432
433
434
    // If we reach here, the normal authentication/capabilities query is completed.
    // Now do the same for TLS.
    normalSocketFinished = true;

    // If the server announced that STARTTLS/STLS is available, we'll add TLS to the
    // connection result, do the command and set the stage to 42 to start the handshake.
    if (shouldStartTLS && encryptionMode == Transport::EnumEncryption::None) {
Laurent Montel's avatar
Laurent Montel committed
435
        qCDebug(MAILTRANSPORT_LOG) << "Trying TLS...";
Laurent Montel's avatar
Laurent Montel committed
436
437
        connectionResults << Transport::EnumEncryption::TLS;
        if (testProtocol == POP_PROTOCOL) {
438
            normalSocket->write(QStringLiteral("STLS"));
Laurent Montel's avatar
Laurent Montel committed
439
        } else if (testProtocol == IMAP_PROTOCOL) {
440
            normalSocket->write(QStringLiteral("2 STARTTLS"));
Laurent Montel's avatar
Laurent Montel committed
441
        } else {
442
            normalSocket->write(QStringLiteral("STARTTLS"));
Laurent Montel's avatar
Laurent Montel committed
443
444
445
446
447
448
449
450
451
452
        }
        encryptionMode = Transport::EnumEncryption::TLS;
        normalStage = tlsHandshakeStage;
        return;
    }

    // If we reach here, either the TLS authentication/capabilities query is finished
    // or the server does not support the STARTTLS/STLS command.
    tlsFinished = true;
    finalResult();
453
454
}

Laurent Montel's avatar
Laurent Montel committed
455
void ServerTestPrivate::slotReadSecure(const QString &text)
456
{
Laurent Montel's avatar
Laurent Montel committed
457
458
459
    secureStage++;
    if (testProtocol == POP_PROTOCOL) {
        bool dummy;
Laurent Montel's avatar
Laurent Montel committed
460
        if (handlePopConversation(secureSocket, Transport::EnumEncryption::SSL, secureStage, text, &dummy)) {
Laurent Montel's avatar
Laurent Montel committed
461
462
            return;
        }
Pino Toscano's avatar
Pino Toscano committed
463
464
    } else if (testProtocol == NNTP_PROTOCOL) {
        bool dummy;
Laurent Montel's avatar
Laurent Montel committed
465
        if (handleNntpConversation(secureSocket, Transport::EnumEncryption::SSL, &secureStage, text, &dummy)) {
Pino Toscano's avatar
Pino Toscano committed
466
467
            return;
        }
Laurent Montel's avatar
Laurent Montel committed
468
469
470
471
472
473
    } else {
        if (secureStage == 0) {
            sendInitialCapabilityQuery(secureSocket);
            return;
        }
        handleSMTPIMAPResponse(Transport::EnumEncryption::SSL, text);
474
    }
Laurent Montel's avatar
Laurent Montel committed
475
476
    secureSocketFinished = true;
    finalResult();
477
478
}

Tom Albers's avatar
Tom Albers committed
479
void ServerTestPrivate::slotNormalNotPossible()
480
{
481
482
483
484
485
486
487
488
    if (testProtocol == SMTP_PROTOCOL && normalSocket->port() == SMTP_PORT) {
        // For SMTP, fallback to port 25
        normalSocket->setPort(SMTP_OLD_PORT);
        normalSocket->reconnect();
        normalSocketTimer->start(10000);
        return;
    }

Laurent Montel's avatar
Laurent Montel committed
489
490
491
492
493
    normalSocketTimer->stop();
    normalPossible = false;
    normalSocketFinished = true;
    tlsFinished = true;
    finalResult();
Tom Albers's avatar
Tom Albers committed
494
}
495

Tom Albers's avatar
Tom Albers committed
496
497
void ServerTestPrivate::slotSslPossible()
{
Laurent Montel's avatar
Laurent Montel committed
498
499
    secureSocketTimer->stop();
    connectionResults << Transport::EnumEncryption::SSL;
Tom Albers's avatar
Tom Albers committed
500
}
501

Tom Albers's avatar
Tom Albers committed
502
503
void ServerTestPrivate::slotSslNotPossible()
{
Laurent Montel's avatar
Laurent Montel committed
504
505
506
507
    secureSocketTimer->stop();
    securePossible = false;
    secureSocketFinished = true;
    finalResult();
Tom Albers's avatar
Tom Albers committed
508
}
509

Tom Albers's avatar
Tom Albers committed
510
511
void ServerTestPrivate::slotUpdateProgress()
{
Laurent Montel's avatar
Laurent Montel committed
512
513
514
    if (testProgress) {
        testProgress->setValue(testProgress->value() + 1);
    }
Tom Albers's avatar
Tom Albers committed
515
516
517
518
}

//---------------------- end private class -----------------------//

519
ServerTest::ServerTest(QObject *parent)
Laurent Montel's avatar
Laurent Montel committed
520
521
    : QObject(parent)
    , d(new ServerTestPrivate(this))
Tom Albers's avatar
Tom Albers committed
522
{
Laurent Montel's avatar
Laurent Montel committed
523
524
525
    d->normalSocketTimer = new QTimer(this);
    d->normalSocketTimer->setSingleShot(true);
    connect(d->normalSocketTimer, SIGNAL(timeout()), SLOT(slotNormalNotPossible()));
Tom Albers's avatar
Tom Albers committed
526

Laurent Montel's avatar
Laurent Montel committed
527
528
529
    d->secureSocketTimer = new QTimer(this);
    d->secureSocketTimer->setSingleShot(true);
    connect(d->secureSocketTimer, SIGNAL(timeout()), SLOT(slotSslNotPossible()));
530

Laurent Montel's avatar
Laurent Montel committed
531
532
    d->progressTimer = new QTimer(this);
    connect(d->progressTimer, SIGNAL(timeout()), SLOT(slotUpdateProgress()));
533
534
}

Tom Albers's avatar
Tom Albers committed
535
ServerTest::~ServerTest()
536
{
Laurent Montel's avatar
Laurent Montel committed
537
    delete d;
538
539
}

Tom Albers's avatar
Tom Albers committed
540
void ServerTest::start()
541
{
Laurent Montel's avatar
Laurent Montel committed
542
    qCDebug(MAILTRANSPORT_LOG) << d;
Laurent Montel's avatar
Laurent Montel committed
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563

    d->connectionResults.clear();
    d->authenticationResults.clear();
    d->capabilityResults.clear();
    d->popSupportsTLS = false;
    d->normalStage = -1;
    d->secureStage = -1;
    d->encryptionMode = Transport::EnumEncryption::None;
    d->normalPossible = true;
    d->securePossible = true;

    if (d->testProgress) {
        d->testProgress->setMaximum(20);
        d->testProgress->setValue(0);
        d->testProgress->setTextVisible(true);
        d->testProgress->show();
        d->progressTimer->start(1000);
    }

    d->normalSocket = new MailTransport::Socket(this);
    d->secureSocket = new MailTransport::Socket(this);
564
    d->normalSocket->setObjectName(QStringLiteral("normal"));
Laurent Montel's avatar
Laurent Montel committed
565
566
567
568
569
570
571
572
573
574
575
    d->normalSocket->setServer(d->server);
    d->normalSocket->setProtocol(d->testProtocol);
    if (d->testProtocol == IMAP_PROTOCOL) {
        d->normalSocket->setPort(IMAP_PORT);
        d->secureSocket->setPort(IMAPS_PORT);
    } else if (d->testProtocol == SMTP_PROTOCOL) {
        d->normalSocket->setPort(SMTP_PORT);
        d->secureSocket->setPort(SMTPS_PORT);
    } else if (d->testProtocol == POP_PROTOCOL) {
        d->normalSocket->setPort(POP_PORT);
        d->secureSocket->setPort(POPS_PORT);
Pino Toscano's avatar
Pino Toscano committed
576
577
578
    } else if (d->testProtocol == NNTP_PROTOCOL) {
        d->normalSocket->setPort(NNTP_PORT);
        d->secureSocket->setPort(NNTPS_PORT);
Laurent Montel's avatar
Laurent Montel committed
579
580
581
582
583
584
585
586
587
588
589
    }

    if (d->customPorts.contains(Transport::EnumEncryption::None)) {
        d->normalSocket->setPort(d->customPorts.value(Transport::EnumEncryption::None));
    }
    if (d->customPorts.contains(Transport::EnumEncryption::SSL)) {
        d->secureSocket->setPort(d->customPorts.value(Transport::EnumEncryption::SSL));
    }

    connect(d->normalSocket, SIGNAL(connected()), SLOT(slotNormalPossible()));
    connect(d->normalSocket, SIGNAL(failed()), SLOT(slotNormalNotPossible()));
Laurent Montel's avatar
Laurent Montel committed
590
    connect(d->normalSocket, SIGNAL(data(QString)), SLOT(slotReadNormal(QString)));
Laurent Montel's avatar
Laurent Montel committed
591
592
593
594
    connect(d->normalSocket, SIGNAL(tlsDone()), SLOT(slotTlsDone()));
    d->normalSocket->reconnect();
    d->normalSocketTimer->start(10000);

595
    if (d->secureSocket->port() > 0) {
596
        d->secureSocket->setObjectName(QStringLiteral("secure"));
597
598
599
600
601
        d->secureSocket->setServer(d->server);
        d->secureSocket->setProtocol(d->testProtocol + QLatin1Char('s'));
        d->secureSocket->setSecure(true);
        connect(d->secureSocket, SIGNAL(connected()), SLOT(slotSslPossible()));
        connect(d->secureSocket, SIGNAL(failed()), SLOT(slotSslNotPossible()));
Laurent Montel's avatar
Laurent Montel committed
602
        connect(d->secureSocket, SIGNAL(data(QString)), SLOT(slotReadSecure(QString)));
603
604
605
606
607
        d->secureSocket->reconnect();
        d->secureSocketTimer->start(10000);
    } else {
        d->slotSslNotPossible();
    }
Laurent Montel's avatar
Laurent Montel committed
608
609
610
611
612
}

void ServerTest::setFakeHostname(const QString &fakeHostname)
{
    d->fakeHostname = fakeHostname;
613
614
}

Volker Krause's avatar
Volker Krause committed
615
QString ServerTest::fakeHostname() const
616
{
Laurent Montel's avatar
Laurent Montel committed
617
    return d->fakeHostname;
618
619
}

Laurent Montel's avatar
Laurent Montel committed
620
void ServerTest::setServer(const QString &server)
621
{
Laurent Montel's avatar
Laurent Montel committed
622
    d->server = server;
623
624
}

Laurent Montel's avatar
Laurent Montel committed
625
void ServerTest::setPort(Transport::EnumEncryption::type encryptionMode, uint port)
626
{
Laurent Montel's avatar
Laurent Montel committed
627
    Q_ASSERT(encryptionMode == Transport::EnumEncryption::None || encryptionMode == Transport::EnumEncryption::SSL);
Laurent Montel's avatar
Laurent Montel committed
628
    d->customPorts.insert(encryptionMode, port);
629
630
}

Laurent Montel's avatar
Laurent Montel committed
631
void ServerTest::setProgressBar(QProgressBar *pb)
632
{
Laurent Montel's avatar
Laurent Montel committed
633
    d->testProgress = pb;
Tom Albers's avatar
Tom Albers committed
634
}
635

Laurent Montel's avatar
Laurent Montel committed
636
void ServerTest::setProtocol(const QString &protocol)
Tom Albers's avatar
Tom Albers committed
637
{
Laurent Montel's avatar
Laurent Montel committed
638
    d->testProtocol = protocol;
639
    d->customPorts.clear();
Tom Albers's avatar
Tom Albers committed
640
}
641

Volker Krause's avatar
Volker Krause committed
642
QString ServerTest::protocol() const
Tom Albers's avatar
Tom Albers committed
643
{
Laurent Montel's avatar
Laurent Montel committed
644
    return d->testProtocol;
Tom Albers's avatar
Tom Albers committed
645
}
646

Volker Krause's avatar
Volker Krause committed
647
QString ServerTest::server() const
Tom Albers's avatar
Tom Albers committed
648
{
Laurent Montel's avatar
Laurent Montel committed
649
    return d->server;
650
651
}

Volker Krause's avatar
Volker Krause committed
652
int ServerTest::port(TransportBase::EnumEncryption::type encryptionMode) const
653
{
Laurent Montel's avatar
Laurent Montel committed
654
    Q_ASSERT(encryptionMode == Transport::EnumEncryption::None || encryptionMode == Transport::EnumEncryption::SSL);
Laurent Montel's avatar
Laurent Montel committed
655
656
657
658
659
    if (d->customPorts.contains(encryptionMode)) {
        return d->customPorts.value(static_cast<int>(encryptionMode));
    } else {
        return -1;
    }
660
661
}

Volker Krause's avatar
Volker Krause committed
662
QProgressBar *ServerTest::progressBar() const
663
{
Laurent Montel's avatar
Laurent Montel committed
664
    return d->testProgress;
665
666
}

Laurent Montel's avatar
Laurent Montel committed
667
QVector<int> ServerTest::normalProtocols() const
668
{
Laurent Montel's avatar
Laurent Montel committed
669
    return d->authenticationResults[TransportBase::EnumEncryption::None];
670
671
}

Volker Krause's avatar
Volker Krause committed
672
bool ServerTest::isNormalPossible() const
673
{
Laurent Montel's avatar
Laurent Montel committed
674
    return d->normalPossible;
675
676
}

Laurent Montel's avatar
Laurent Montel committed
677
QVector<int> ServerTest::tlsProtocols() const
678
{
Laurent Montel's avatar
Laurent Montel committed
679
    return d->authenticationResults[TransportBase::EnumEncryption::TLS];
680
681
}

Laurent Montel's avatar
Laurent Montel committed
682
QVector<int> ServerTest::secureProtocols() const
683
{
Laurent Montel's avatar
Laurent Montel committed
684
    return d->authenticationResults[Transport::EnumEncryption::SSL];
685
686
}

Volker Krause's avatar
Volker Krause committed
687
bool ServerTest::isSecurePossible() const
688
{
Laurent Montel's avatar
Laurent Montel committed
689
    return d->securePossible;
690
691
}

Laurent Montel's avatar
Laurent Montel committed
692
QList<ServerTest::Capability> ServerTest::capabilities() const
693
{
694
    return d->capabilityResults.values();
695
696
}

697
#include "moc_servertest.cpp"