Commit message: IMAP: refuse to work when STARTTLS is required but server sends PREAUTH

Oops, we cannot send STARTTLS when the connection is already authenticated.
This is serious enough to warrant an error; an attacker might be going after a
plaintext of a message we're going to APPEND, etc.

Thanks to Arnt Gulbrandsen on the imap-protocol ML for asking what happens when
we're configured to request STARTTLS and a PREAUTH is received, and to Michael M
Slusarz for starting that discussion.

Hope the error message is readable enough.

CVE: CVE-2014-2567
CVE: CVE-2014-2567
CWE: CWE-200
Vulnerability description: The OpenConnectionTask::handleStateHelper function in Imap/Tasks/OpenConnectionTask.cpp in Trojita before 0.4.1 allows man-in-the-middle attackers to trigger use of cleartext for saving a message into a (1) sent or (2) draft folder via a PREAUTH response that prevents later use of the STARTTLS command.

Project: trojita
File: test_Imap_Tasks_OpenConnection.cpp

void ImapModelOpenConnectionTest::testLoginDelaysOtherTasks()
{
    using namespace Imap::Mailbox;
    MemoryCache *cache = dynamic_cast<MemoryCache *>(model->cache());
    Q_ASSERT(cache);
    cache->setChildMailboxes(QString(),
                             QList<MailboxMetadata>() << MailboxMetadata(QString::fromUtf8("a"), QString(), QStringList())
                             << MailboxMetadata(QString::fromUtf8("b"), QString(), QStringList())
                             );
    m_enableAutoLogin = false;
    // The cache is not queried synchronously
    QCOMPARE(model->rowCount(QModelIndex()), 1);
    QCoreApplication::processEvents();
    // The cache should have been consulted by now
    QCOMPARE(model->rowCount(QModelIndex()), 3);
    QCoreApplication::processEvents();
    QVERIFY(SOCK->writtenStuff().isEmpty());
    SOCK->fakeReading("* OK [capability imap4rev1] hi there\r\n");
    QVERIFY(completedSpy->isEmpty());
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    // The login must not be sent (as per m_enableAutoLogin being false)
    QVERIFY(SOCK->writtenStuff().isEmpty());
    QModelIndex mailboxA = model->index(1, 0, QModelIndex());
    QVERIFY(mailboxA.isValid());
    QCOMPARE(mailboxA.data(RoleMailboxName).toString(), QString::fromUtf8("a"));
    QModelIndex mailboxB = model->index(2, 0, QModelIndex());
    QVERIFY(mailboxB.isValid());
    QCOMPARE(mailboxB.data(RoleMailboxName).toString(), QString::fromUtf8("b"));
    QModelIndex msgListA = mailboxA.child(0, 0);
    Q_ASSERT(msgListA.isValid());
    QModelIndex msgListB = mailboxB.child(0, 0);
    Q_ASSERT(msgListB.isValid());

    // Request syncing the mailboxes
    QCOMPARE(model->rowCount(msgListA), 0);
    QCOMPARE(model->rowCount(msgListB), 0);
    for (int i = 0; i < 10; ++i)
        QCoreApplication::processEvents();
    QVERIFY(SOCK->writtenStuff().isEmpty());

    // Unblock the login
    m_enableAutoLogin = true;
    provideAuthDetails();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE(SOCK->writtenStuff(), QByteArray("y0 LOGIN luzr sikrit\r\n"));
    SOCK->fakeReading("y0 OK [CAPABILITY IMAP4rev1] logged in\r\n");
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    // FIXME: selecting the "b" shall definitely wait until "a" is completed
    QCOMPARE(SOCK->writtenStuff(), QByteArray("y1 LIST \"\" \"%\"\r\ny2 SELECT a\r\ny3 SELECT b\r\n"));
    SOCK->fakeReading("y1 OK listed, nothing like that in there\r\n");
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE(completedSpy->size(), 1);
    QVERIFY(failedSpy->isEmpty());
    QCOMPARE(authSpy->size(), 1);
    QVERIFY(SOCK->writtenStuff().isEmpty());
    QVERIFY(startTlsUpgradeSpy->isEmpty());
}

Project: trojita
File: OpenConnectionTask.cpp

QList<QSslError> OpenConnectionTask::sslErrors() const
{
    return m_sslErrors;
}

Project: trojita
File: test_Imap_Tasks_OpenConnection.cpp

void ImapModelOpenConnectionTest::testOkLogindisabledLater()
{
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QVERIFY(SOCK->writtenStuff().isEmpty());
    SOCK->fakeReading( "* OK foo\r\n" );
    QVERIFY( completedSpy->isEmpty() );
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE( SOCK->writtenStuff(), QByteArray("y0 CAPABILITY\r\n") );
    QVERIFY( completedSpy->isEmpty() );
    SOCK->fakeReading( "* CAPABILITY IMAP4rev1 starttls LoGINDISABLED\r\ny0 OK capability completed\r\n" );
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QVERIFY( authSpy->isEmpty() );
    QCOMPARE( SOCK->writtenStuff(), QByteArray("y1 STARTTLS\r\n") );
    SOCK->fakeReading( "y1 OK will establish secure layer immediately\r\n");
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QVERIFY( authSpy->isEmpty() );
    QCOMPARE( SOCK->writtenStuff(), QByteArray("[*** STARTTLS ***]y2 CAPABILITY\r\n") );
    QVERIFY( completedSpy->isEmpty() );
    QVERIFY( authSpy->isEmpty() );
    SOCK->fakeReading( "* CAPABILITY IMAP4rev1\r\ny2 OK capability completed\r\n" );
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE( SOCK->writtenStuff(), QByteArray("y3 LOGIN luzr sikrit\r\n") );
    QCOMPARE( authSpy->size(), 1 );
    SOCK->fakeReading( "y3 OK [CAPABILITY IMAP4rev1] logged in\r\n");
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE( completedSpy->size(), 1 );
    QVERIFY(failedSpy->isEmpty());
    QCOMPARE( authSpy->size(), 1 );
    QCOMPARE(startTlsUpgradeSpy->size(), 1);
}

Project: trojita
File: OpenConnectionTask.cpp

void OpenConnectionTask::sslConnectionPolicyDecided(bool ok)
{
    switch (model->accessParser(parser).connState) {
    case CONN_STATE_SSL_VERIFYING:
        if (ok) {
            model->changeConnectionState(parser, CONN_STATE_CONNECTED_PRETLS_PRECAPS);
        } else {
            logout(tr("The security state of the SSL connection got rejected"));
        }
        break;
    case CONN_STATE_STARTTLS_VERIFYING:
        if (ok) {
            model->changeConnectionState(parser, CONN_STATE_ESTABLISHED_PRECAPS);
            model->accessParser(parser).capabilitiesFresh = false;
            capabilityCmd = parser->capability();
        } else {
            logout(tr("The security state of the connection after a STARTTLS operation got rejected"));
        }
        break;
    default:
        Q_ASSERT(false);
    }
    parser->unfreezeAfterEncryption();
}

Project: trojita
File: test_Imap_Tasks_OpenConnection.cpp

void ImapModelOpenConnectionTest::testOkStartTlsDiscardCaps()
{
    cleanup(); init(true); // yuck, but I can't come up with anything better...

    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QVERIFY(SOCK->writtenStuff().isEmpty());
    SOCK->fakeReading( "* OK [Capability imap4rev1 starttls] foo\r\n" );
    QVERIFY( completedSpy->isEmpty() );
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QVERIFY( authSpy->isEmpty() );
    QCOMPARE( SOCK->writtenStuff(), QByteArray("y0 STARTTLS\r\n") );
    SOCK->fakeReading( "y0 OK will establish secure layer immediately\r\n");
    QCoreApplication::processEvents();
    QVERIFY( authSpy->isEmpty() );
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE( SOCK->writtenStuff(), QByteArray("[*** STARTTLS ***]y1 CAPABILITY\r\n") );
    QVERIFY( completedSpy->isEmpty() );
    QVERIFY( authSpy->isEmpty() );
    SOCK->fakeReading( "* CAPABILITY IMAP4rev1\r\ny1 OK capability completed\r\n" );
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE( SOCK->writtenStuff(), QByteArray("y2 LOGIN luzr sikrit\r\n") );
    QCOMPARE( authSpy->size(), 1 );
    SOCK->fakeReading( "y2 OK [CAPABILITY IMAP4rev1] logged in\r\n");
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE( completedSpy->size(), 1 );
    QVERIFY(failedSpy->isEmpty());
    QCOMPARE( authSpy->size(), 1 );
    QVERIFY(startTlsUpgradeSpy->isEmpty());
}

Project: trojita
File: test_Imap_Tasks_OpenConnection.cpp

void ImapModelOpenConnectionTest::acceptSsl(const QList<QSslCertificate> &certificateChain, const QList<QSslError> &sslErrors)
{
    model->setSslPolicy(certificateChain, sslErrors, true);
}

Project: trojita
File: OpenConnectionTask.cpp

void OpenConnectionTask::logout(const QString &message)
{
    _failed(message);
    model->setNetworkPolicy(NETWORK_OFFLINE);
}

Project: trojita
File: test_Imap_Tasks_OpenConnection.cpp

void ImapModelOpenConnectionTest::testInitialGarbage_data()
{
    QTest::addColumn<QByteArray>("greetings");

    QTest::newRow("utter-garbage")
        << QByteArray("blesmrt trojita\r\n");

    QTest::newRow("pop3")
        << QByteArray("+OK InterMail POP3 server ready.\r\n");
}

Project: trojita
File: test_Imap_Tasks_OpenConnection.cpp

void ImapModelOpenConnectionTest::testOkLogindisabledWithoutStarttls()
{
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QVERIFY(SOCK->writtenStuff().isEmpty());
    SOCK->fakeReading( "* OK [CAPABILITY IMAP4rev1 LoginDisabled] foo\r\n" );
    QVERIFY( completedSpy->isEmpty() );
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QVERIFY( authSpy->isEmpty() );
    // The capabilities do not contain STARTTLS but LOGINDISABLED is in there
    QCOMPARE(failedSpy->size(), 1);
    QVERIFY(startTlsUpgradeSpy->isEmpty());
}

Project: trojita
File: OpenConnectionTask.cpp

bool OpenConnectionTask::handleSocketEncryptedResponse(const Responses::SocketEncryptedResponse *const resp)
{
    switch (model->accessParser(parser).connState) {
    case CONN_STATE_SSL_HANDSHAKE:
        model->changeConnectionState(parser, CONN_STATE_SSL_VERIFYING);
        m_sslChain = resp->sslChain;
        m_sslErrors = resp->sslErrors;
        model->processSslErrors(this);
        return true;
    case CONN_STATE_STARTTLS_HANDSHAKE:
        model->changeConnectionState(parser, CONN_STATE_STARTTLS_VERIFYING);
        m_sslChain = resp->sslChain;
        m_sslErrors = resp->sslErrors;
        model->processSslErrors(this);
        return true;
    default:
        qDebug() << model->accessParser(parser).connState;
        return false;
    }
}

Project: trojita
File: test_Imap_Tasks_OpenConnection.cpp

void ImapModelOpenConnectionTest::cleanup()
{
    delete model;
    model = 0;
    task = 0;
    delete completedSpy;
    completedSpy = 0;
    delete failedSpy;
    failedSpy = 0;
    delete authSpy;
    authSpy = 0;
    delete startTlsUpgradeSpy;
    startTlsUpgradeSpy = 0;
    QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
}

Project: trojita
File: test_Imap_Tasks_OpenConnection.cpp

void ImapModelOpenConnectionTest::testCapabilityAfterLogin()
{
    cleanup(); init(true); // yuck, but I can't come up with anything better...

    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QVERIFY(SOCK->writtenStuff().isEmpty());
    SOCK->fakeReading( "* OK foo\r\n" );
    QVERIFY( completedSpy->isEmpty() );
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE(SOCK->writtenStuff(), QByteArray("y0 CAPABILITY\r\n"));
    SOCK->fakeReading("* CAPABILITY imap4rev1 starttls\r\ny0 ok cap\r\n");
    QVERIFY( authSpy->isEmpty() );
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE( SOCK->writtenStuff(), QByteArray("y1 STARTTLS\r\n") );
    SOCK->fakeReading( "y1 OK will establish secure layer immediately\r\n");
    QCoreApplication::processEvents();
    QVERIFY( authSpy->isEmpty() );
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE( SOCK->writtenStuff(), QByteArray("[*** STARTTLS ***]y2 CAPABILITY\r\n") );
    QVERIFY( completedSpy->isEmpty() );
    QVERIFY( authSpy->isEmpty() );
    SOCK->fakeReading( "* CAPABILITY IMAP4rev1\r\ny2 OK capability completed\r\n" );
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE( SOCK->writtenStuff(), QByteArray("y3 LOGIN luzr sikrit\r\n") );
    QCOMPARE( authSpy->size(), 1 );
    SOCK->fakeReading("* CAPABILITY imap4rev1\r\n" "y3 OK logged in\r\n");
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE( completedSpy->size(), 1 );
    QVERIFY(failedSpy->isEmpty());
    QCOMPARE( authSpy->size(), 1 );
    QVERIFY(SOCK->writtenStuff().isEmpty());
    QVERIFY(startTlsUpgradeSpy->isEmpty());
}

Project: trojita
File: OpenConnectionTask.cpp

bool OpenConnectionTask::handleStateHelper(const Imap::Responses::State *const resp)
{
    if (_dead) {
        _failed("Asked to die");
        return true;
    }
    using namespace Imap::Responses;

    if (model->accessParser(parser).connState == CONN_STATE_CONNECTED_PRETLS_PRECAPS) {
        if (!resp->tag.isEmpty()) {
            throw Imap::UnexpectedResponseReceived("Waiting for initial OK/BYE/PREAUTH, but got tagged response instead", *resp);
        }
    } else if (model->accessParser(parser).connState > CONN_STATE_CONNECTED_PRETLS_PRECAPS) {
        if (resp->tag.isEmpty()) {
            return false;
        }
    }

    switch (model->accessParser(parser).connState) {

    case CONN_STATE_AUTHENTICATED:
    case CONN_STATE_SELECTING:
    case CONN_STATE_SYNCING:
    case CONN_STATE_SELECTED:
    case CONN_STATE_FETCHING_PART:
    case CONN_STATE_FETCHING_MSG_METADATA:
    case CONN_STATE_LOGOUT:
    {
        QByteArray message = "No response expected by the OpenConnectionTask in state " +
                Imap::connectionStateToString(model->accessParser(parser).connState).toUtf8();
        // These shall not ever be reached by this code
        throw Imap::UnexpectedResponseReceived(message.constData(), *resp);
    }

    case CONN_STATE_NONE:
    case CONN_STATE_HOST_LOOKUP:
    case CONN_STATE_CONNECTING:
        // Looks like the corresponding stateChanged() signal could be delayed, at least with QProcess-based sockets
    case CONN_STATE_CONNECTED_PRETLS_PRECAPS:
        // We're connected now -- this is our initial state.
    {
        switch (resp->kind) {
        case PREAUTH:
            if (model->m_startTls) {
                // Oops, we cannot send STARTTLS when the connection is already authenticated.
                // This is serious enough to warrant an error; an attacker might be going after a plaintext
                // of a message we're going to APPEND, etc.
                // Thanks to Arnt Gulbrandsen on the imap-protocol ML for asking what happens when we're configured
                // to request STARTTLS and a PREAUTH is received, and to Michael M Slusarz for starting that discussion.
                logout(tr("Configuration requires sending STARTTLS, but the IMAP server greets us with PREAUTH. "
                          "Encryption cannot be established. If this configuration worked previously, someone "
                          "is after your data and they are pretty smart."));
                return true;
            }
            // Cool, we're already authenticated. Now, let's see if we have to issue CAPABILITY or if we already know that
            if (model->accessParser(parser).capabilitiesFresh) {
                // We're alsmost done here, apart from compression
                if (TROJITA_COMPRESS_DEFLATE && model->accessParser(parser).capabilities.contains(QLatin1String("COMPRESS=DEFLATE"))) {
                    compressCmd = parser->compressDeflate();
                    model->changeConnectionState(parser, CONN_STATE_COMPRESS_DEFLATE);
                } else {
                    // really done
                    model->changeConnectionState(parser, CONN_STATE_AUTHENTICATED);
                    onComplete();
                }
            } else {
                model->changeConnectionState(parser, CONN_STATE_POSTAUTH_PRECAPS);
                capabilityCmd = parser->capability();
            }
            return true;

        case OK:
            if (!model->accessParser(parser).capabilitiesFresh) {
                model->changeConnectionState(parser, CONN_STATE_CONNECTED_PRETLS);
                capabilityCmd = parser->capability();
            } else {
                startTlsOrLoginNow();
            }
            return true;

        case BYE:
            logout(tr("Server has closed the connection"));
            return true;

        case BAD:
            model->changeConnectionState(parser, CONN_STATE_LOGOUT);
            // If it was an ALERT, we've already warned the user
            if (resp->respCode != ALERT) {
                emit model->alertReceived(tr("The server replied with the following BAD response:\n%1").arg(resp->message));
            }
            logout(tr("Server has greeted us with a BAD response"));
            return true;

        default:
            throw Imap::UnexpectedResponseReceived("Waiting for initial OK/BYE/BAD/PREAUTH, but got this instead", *resp);
        }
        break;
    }

    case CONN_STATE_CONNECTED_PRETLS:
        // We've asked for capabilities upon the initial interaction
    {
        bool wasCaps = checkCapabilitiesResult(resp);
        if (wasCaps && !_finished) {
            startTlsOrLoginNow();
        }
        return wasCaps;
    }

    case CONN_STATE_STARTTLS_ISSUED:
    {
        if (resp->tag == startTlsCmd) {
            if (resp->kind == OK) {
                model->changeConnectionState(parser, CONN_STATE_STARTTLS_HANDSHAKE);
                if (!model->m_startTls) {
                    // The model was not configured to perform STARTTLS, but we still did that for some reason.
                    // As suggested by Mike Cardwell on the trojita ML (http://article.gmane.org/gmane.mail.trojita.general/299),
                    // it makes sense to make this settings permanent, so that a user is not tricked into revealing their
                    // password when a MITM removes the LOGINDISABLED in future.
                    EMIT_LATER_NOARG(model, requireStartTlsInFuture);
                }
            } else {
                logout(tr("STARTTLS failed: %1").arg(resp->message));
            }
            return true;
        }
        return false;
    }

    case CONN_STATE_SSL_HANDSHAKE:
    case CONN_STATE_STARTTLS_HANDSHAKE:
        // nothing should really arrive at this point; the Parser is expected to wait for encryption and only after that
        // send the data
        Q_ASSERT(false);
        return false;

    case CONN_STATE_STARTTLS_VERIFYING:
    case CONN_STATE_SSL_VERIFYING:
    {
        // We're waiting for a decision based on a policy, so we do not really expect any network IO at this point
        // FIXME: an assert(false) here?
        qDebug() << "OpenConnectionTask: ignoring response, we're still waiting for SSL policy decision";
        return false;
    }

    case CONN_STATE_ESTABLISHED_PRECAPS:
        // Connection is established and we're waiting for updated capabilities
    {
        bool wasCaps = checkCapabilitiesResult(resp);
        if (wasCaps && !_finished) {
            if (model->accessParser(parser).capabilities.contains(QLatin1String("LOGINDISABLED"))) {
                logout(tr("Capabilities still contain LOGINDISABLED even after STARTTLS"));
            } else {
                model->changeConnectionState(parser, CONN_STATE_LOGIN);
                askForAuth();
            }
        }
        return wasCaps;
    }

    case CONN_STATE_LOGIN:
        // Check the result of the LOGIN command
    {
        if (resp->tag == loginCmd) {
            loginCmd.clear();
            // The LOGIN command is finished
            if (resp->kind == OK) {
                if (resp->respCode == CAPABILITIES || model->accessParser(parser).capabilitiesFresh) {
                    // Capabilities are already known
                    if (TROJITA_COMPRESS_DEFLATE && model->accessParser(parser).capabilities.contains(QLatin1String("COMPRESS=DEFLATE"))) {
                        compressCmd = parser->compressDeflate();
                        model->changeConnectionState(parser, CONN_STATE_COMPRESS_DEFLATE);
                    } else {
                        model->changeConnectionState(parser, CONN_STATE_AUTHENTICATED);
                        onComplete();
                    }
                } else {
                    // Got to ask for the capabilities
                    model->changeConnectionState(parser, CONN_STATE_POSTAUTH_PRECAPS);
                    capabilityCmd = parser->capability();
                }
            } else {
                // Login failed
                QString message;
                switch (resp->respCode) {
                case Responses::UNAVAILABLE:
                    message = tr("Temporary failure because a subsystem is down.");
                    break;
                case Responses::AUTHENTICATIONFAILED:
                    message = tr("Authentication failed.  This often happens due to bad password or wrong user name.");
                    break;
                case Responses::AUTHORIZATIONFAILED:
                    message = tr("Authentication succeeded in using the authentication identity, "
                                 "but the server cannot or will not allow the authentication "
                                 "identity to act as the requested authorization identity.");
                    break;
                case Responses::EXPIRED:
                    message = tr("Either authentication succeeded or the server no longer had the "
                                 "necessary data; either way, access is no longer permitted using "
                                 "that passphrase.  You should get a new passphrase.");
                    break;
                case Responses::PRIVACYREQUIRED:
                    message = tr("The operation is not permitted due to a lack of privacy.");
                    break;
                case Responses::CONTACTADMIN:
                    message = tr("You should contact the system administrator or support desk.");
                    break;
                default:
                    break;
                }

                if (message.isEmpty()) {
                    message = tr("Login failed: %1").arg(resp->message);
                } else {
                    message = tr("%1\n\n%2").arg(message, resp->message);
                }
                EMIT_LATER(model, authAttemptFailed, Q_ARG(QString, message));
                model->m_imapPassword.clear();
                model->m_hasImapPassword = false;
                if (model->accessParser(parser).connState == CONN_STATE_LOGOUT) {
                    // The server has closed the conenction
                    _failed(QLatin1String("Connection closed after a failed login"));
                    return true;
                }
                askForAuth();
            }
            return true;
        }
        return false;
    }

    case CONN_STATE_POSTAUTH_PRECAPS:
    {
        bool wasCaps = checkCapabilitiesResult(resp);
        if (wasCaps && !_finished) {
            model->changeConnectionState(parser, CONN_STATE_AUTHENTICATED);
            onComplete();
        }
        return wasCaps;
    }

    case CONN_STATE_COMPRESS_DEFLATE:
        if (resp->tag == compressCmd) {
            model->changeConnectionState(parser, CONN_STATE_AUTHENTICATED);
            onComplete();
            return true;
        } else {
            return false;
        }
        break;

    }

    // Required catch-all for OpenSuSE's build service (Tumbleweed, 2012-04-03)
    Q_ASSERT(false);
    return false;
}

Project: trojita
File: OpenConnectionTask.cpp

QVariant OpenConnectionTask::taskData(const int role) const
{
    return role == RoleTaskCompactName ? QVariant(tr("Connecting to mail server")) : QVariant();
}

Project: trojita
File: test_Imap_Tasks_OpenConnection.cpp

void ImapModelOpenConnectionTest::testOkMissingImap4rev1()
{
    SOCK->fakeReading( "* OK [CAPABILITY pwn] foo\r\n" );
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE(SOCK->writtenStuff(), QByteArray("y0 LOGOUT\r\n"));
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QVERIFY(authSpy->isEmpty());
    QVERIFY(SOCK->writtenStuff().isEmpty());
    QVERIFY(startTlsUpgradeSpy->isEmpty());
}

Project: trojita
File: test_Imap_Tasks_OpenConnection.cpp

void ImapModelOpenConnectionTest::testInitialGarbage()
{
    QFETCH(QByteArray, greetings);

    model->rowCount(QModelIndex());
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QVERIFY(SOCK->writtenStuff().isEmpty());
    SOCK->fakeReading(greetings);
    for (int i = 0; i < 10; ++i)
        QCoreApplication::processEvents();
    //qDebug() << QList<QVariantList>(*connErrorSpy);
    QCOMPARE(connErrorSpy->size(), 1);
    QCOMPARE(failedSpy->size(), 1);
    QVERIFY(completedSpy->isEmpty());
    QVERIFY(startTlsUpgradeSpy->isEmpty());
}

Project: trojita
File: OpenConnectionTask.cpp

OpenConnectionTask::OpenConnectionTask(Model *model) :
    ImapTask(model)
{
    // Offline mode shall be checked by the caller who decides to create the connection
    Q_ASSERT(model->networkPolicy() != NETWORK_OFFLINE);
    parser = new Parser(model, model->m_socketFactory->create(), Common::ConnectionId::next());
    ParserState parserState(parser);
    connect(parser, SIGNAL(responseReceived(Imap::Parser *)), model, SLOT(responseReceived(Imap::Parser*)), Qt::QueuedConnection);
    connect(parser, SIGNAL(connectionStateChanged(Imap::Parser *,Imap::ConnectionState)), model, SLOT(handleSocketStateChanged(Imap::Parser *,Imap::ConnectionState)));
    connect(parser, SIGNAL(lineReceived(Imap::Parser *,QByteArray)), model, SLOT(slotParserLineReceived(Imap::Parser *,QByteArray)));
    connect(parser, SIGNAL(lineSent(Imap::Parser *,QByteArray)), model, SLOT(slotParserLineSent(Imap::Parser *,QByteArray)));
    model->m_parsers[ parser ] = parserState;
    model->m_taskModel->slotParserCreated(parser);
    markAsActiveTask();
}

Project: trojita
File: test_Imap_Tasks_OpenConnection.cpp

void ImapModelOpenConnectionTest::testPreauthWithCapability()
{
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QVERIFY(SOCK->writtenStuff().isEmpty());
    SOCK->fakeReading( "* PREAUTH [CAPABILITY IMAP4rev1] foo\r\n" );
    QVERIFY( completedSpy->isEmpty() );
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE( SOCK->writtenStuff(), QByteArray() );
    QCOMPARE( completedSpy->size(), 1 );
    QVERIFY(failedSpy->isEmpty());
    QVERIFY( authSpy->isEmpty() );
    QVERIFY(startTlsUpgradeSpy->isEmpty());
}

Project: trojita
File: test_Imap_Tasks_OpenConnection.cpp

void ImapModelOpenConnectionTest::provideAuthDetails()
{
    if (m_enableAutoLogin) {
        model->setImapUser(QLatin1String("luzr"));
        model->setImapPassword(QLatin1String("sikrit"));
    }
}

Project: trojita
File: OpenConnectionTask.cpp

OpenConnectionTask::OpenConnectionTask(Model *model, void *dummy):
    ImapTask(model)
{
    Q_UNUSED(dummy);
}

Project: trojita
File: test_Imap_Tasks_OpenConnection.cpp

void ImapModelOpenConnectionTest::initTestCase()
{
    model = 0;
    completedSpy = 0;
    m_enableAutoLogin = true;
}

Project: trojita
File: test_Imap_Tasks_OpenConnection.cpp

void ImapModelOpenConnectionTest::testCompressDeflateOk()
{
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QVERIFY(SOCK->writtenStuff().isEmpty());
    SOCK->fakeReading("* OK [capability imap4rev1] hi there\r\n");
    QVERIFY(completedSpy->isEmpty());
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE(SOCK->writtenStuff(), QByteArray("y0 LOGIN luzr sikrit\r\n"));
    QCOMPARE(authSpy->size(), 1);
    SOCK->fakeReading("y0 OK [CAPABILITY IMAP4rev1 compress=deflate id] logged in\r\n");
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
#if TROJITA_COMPRESS_DEFLATE
    QCOMPARE(SOCK->writtenStuff(), QByteArray("y1 COMPRESS DEFLATE\r\n"));
    SOCK->fakeReading("y1 OK compressing\r\n");
    QCoreApplication::processEvents();
    QCOMPARE(SOCK->writtenStuff(), QByteArray("[*** DEFLATE ***]"));
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE(SOCK->writtenStuff(), QByteArray("y2 ID NIL\r\n"));
    SOCK->fakeReading("* ID nil\r\ny2 OK you courious peer\r\n");
#else
    QCOMPARE(SOCK->writtenStuff(), QByteArray("y1 ID NIL\r\n"));
    SOCK->fakeReading("* ID nil\r\ny1 OK you courious peer\r\n");
#endif
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE(completedSpy->size(), 1);
    QVERIFY(failedSpy->isEmpty());
    QCOMPARE(authSpy->size(), 1);
    QVERIFY(SOCK->writtenStuff().isEmpty());
    QVERIFY(startTlsUpgradeSpy->isEmpty());
}

Project: trojita
File: test_Imap_Tasks_OpenConnection.cpp

void ImapModelOpenConnectionTest::testOkStartTls()
{
    cleanup(); init(true); // yuck, but I can't come up with anything better...

    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QVERIFY(SOCK->writtenStuff().isEmpty());
    SOCK->fakeReading( "* OK foo\r\n" );
    QVERIFY( completedSpy->isEmpty() );
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE(SOCK->writtenStuff(), QByteArray("y0 CAPABILITY\r\n"));
    SOCK->fakeReading("* CAPABILITY imap4rev1 starttls\r\ny0 ok cap\r\n");
    QVERIFY( authSpy->isEmpty() );
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE( SOCK->writtenStuff(), QByteArray("y1 STARTTLS\r\n") );
    SOCK->fakeReading( "y1 OK will establish secure layer immediately\r\n");
    QCoreApplication::processEvents();
    QVERIFY( authSpy->isEmpty() );
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE( SOCK->writtenStuff(), QByteArray("[*** STARTTLS ***]y2 CAPABILITY\r\n") );
    QVERIFY( completedSpy->isEmpty() );
    QVERIFY( authSpy->isEmpty() );
    SOCK->fakeReading( "* CAPABILITY IMAP4rev1\r\ny2 OK capability completed\r\n" );
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE( SOCK->writtenStuff(), QByteArray("y3 LOGIN luzr sikrit\r\n") );
    QCOMPARE( authSpy->size(), 1 );
    SOCK->fakeReading( "y3 OK [CAPABILITY IMAP4rev1] logged in\r\n");
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE( completedSpy->size(), 1 );
    QVERIFY(failedSpy->isEmpty());
    QCOMPARE( authSpy->size(), 1 );
    QVERIFY(startTlsUpgradeSpy->isEmpty());
}

Project: trojita
File: test_Imap_Tasks_OpenConnection.cpp

void ImapModelOpenConnectionTest::testCompressDeflateNo()
{
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QVERIFY(SOCK->writtenStuff().isEmpty());
    SOCK->fakeReading("* OK [capability imap4rev1] hi there\r\n");
    QVERIFY(completedSpy->isEmpty());
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE(SOCK->writtenStuff(), QByteArray("y0 LOGIN luzr sikrit\r\n"));
    QCOMPARE(authSpy->size(), 1);
    SOCK->fakeReading("y0 OK [CAPABILITY IMAP4rev1 compress=deflate id] logged in\r\n");
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
#if TROJITA_COMPRESS_DEFLATE
    QCOMPARE(SOCK->writtenStuff(), QByteArray("y1 COMPRESS DEFLATE\r\n"));
    SOCK->fakeReading("y1 NO I just don't want to\r\n");
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE(SOCK->writtenStuff(), QByteArray("y2 ID NIL\r\n"));
    SOCK->fakeReading("* ID nil\r\ny2 OK you courious peer\r\n");
#else
    QCOMPARE(SOCK->writtenStuff(), QByteArray("y1 ID NIL\r\n"));
    SOCK->fakeReading("* ID nil\r\ny1 OK you courious peer\r\n");
#endif
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE(completedSpy->size(), 1);
    QVERIFY(failedSpy->isEmpty());
    QCOMPARE(authSpy->size(), 1);
    QVERIFY(SOCK->writtenStuff().isEmpty());
    QVERIFY(startTlsUpgradeSpy->isEmpty());
}

Project: trojita
File: OpenConnectionTask.cpp

void OpenConnectionTask::perform()
{
    // nothing should happen here
}

Project: trojita
File: OpenConnectionTask.cpp

bool OpenConnectionTask::checkCapabilitiesResult(const Responses::State *const resp)
{
    if (resp->tag.isEmpty())
        return false;

    if (resp->tag == capabilityCmd) {
        if (!model->accessParser(parser).capabilitiesFresh) {
            logout(tr("Server did not provide useful capabilities"));
            return true;
        }
        if (resp->kind != Responses::OK) {
            logout(tr("CAPABILITIES command has failed"));
        }
        return true;
    }

    return false;
}

Project: trojita
File: OpenConnectionTask.cpp

void OpenConnectionTask::authCredentialsNowAvailable()
{
    if (model->accessParser(parser).connState == CONN_STATE_LOGIN && loginCmd.isEmpty()) {
        if (model->m_hasImapPassword) {
            loginCmd = parser->login(model->m_imapUser, model->m_imapPassword);
            model->accessParser(parser).capabilitiesFresh = false;
        } else {
            logout(tr("No credentials available"));
        }
    }
}

Project: trojita
File: OpenConnectionTask.cpp

void OpenConnectionTask::onComplete()
{
    // Optionally issue the ID command
    if (model->accessParser(parser).capabilities.contains(QLatin1String("ID"))) {
        Imap::Mailbox::ImapTask *task = model->m_taskFactory->createIdTask(model, this);
        task->perform();
    }
    // Optionally enable QRESYNC
    if (model->accessParser(parser).capabilities.contains(QLatin1String("QRESYNC")) &&
            model->accessParser(parser).capabilities.contains(QLatin1String("ENABLE"))) {
        Imap::Mailbox::ImapTask *task = model->m_taskFactory->createEnableTask(model, this,
                                                                               QList<QByteArray>() << QByteArray("QRESYNC"));
        task->perform();
    }

    // But do terminate this task
    _completed();
}

Project: trojita
File: test_Imap_Tasks_OpenConnection.cpp

void ImapModelOpenConnectionTest::init()
{
    Common::registerMetaTypes();
    qRegisterMetaType<Imap::Mailbox::ImapTask*>();
    init( false );
}

Project: trojita
File: test_Imap_Tasks_OpenConnection.cpp

void ImapModelOpenConnectionTest::testOk()
{
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QVERIFY(SOCK->writtenStuff().isEmpty());
    SOCK->fakeReading( "* OK foo\r\n" );
    QVERIFY( completedSpy->isEmpty() );
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE( SOCK->writtenStuff(), QByteArray("y0 CAPABILITY\r\n") );
    QVERIFY( completedSpy->isEmpty() );
    SOCK->fakeReading( "* CAPABILITY IMAP4rev1\r\ny0 OK capability completed\r\n" );
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE( authSpy->size(), 1 );
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE( SOCK->writtenStuff(), QByteArray("y1 LOGIN luzr sikrit\r\n") );
    SOCK->fakeReading( "y1 OK [CAPABILITY IMAP4rev1] logged in\r\n");
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE( completedSpy->size(), 1 );
    QVERIFY(failedSpy->isEmpty());
    QCOMPARE( authSpy->size(), 1 );
    QVERIFY(startTlsUpgradeSpy->isEmpty());
}

Project: trojita
File: test_Imap_Tasks_OpenConnection.cpp

void ImapModelOpenConnectionTest::testOkLogindisabled()
{
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QVERIFY(SOCK->writtenStuff().isEmpty());
    SOCK->fakeReading( "* OK [CAPABILITY IMAP4rev1 starttls LoginDisabled] foo\r\n" );
    QVERIFY( completedSpy->isEmpty() );
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QVERIFY( authSpy->isEmpty() );
    QCOMPARE( SOCK->writtenStuff(), QByteArray("y0 STARTTLS\r\n") );
    SOCK->fakeReading( "y0 OK will establish secure layer immediately\r\n");
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QVERIFY( authSpy->isEmpty() );
    QCOMPARE( SOCK->writtenStuff(), QByteArray("[*** STARTTLS ***]y1 CAPABILITY\r\n") );
    QVERIFY( completedSpy->isEmpty() );
    QVERIFY( authSpy->isEmpty() );
    SOCK->fakeReading( "* CAPABILITY IMAP4rev1\r\ny1 OK capability completed\r\n" );
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE( SOCK->writtenStuff(), QByteArray("y2 LOGIN luzr sikrit\r\n") );
    QCOMPARE( authSpy->size(), 1 );
    SOCK->fakeReading( "y2 OK [CAPABILITY IMAP4rev1] logged in\r\n");
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE( completedSpy->size(), 1 );
    QVERIFY(failedSpy->isEmpty());
    QCOMPARE( authSpy->size(), 1 );
    QCOMPARE(startTlsUpgradeSpy->size(), 1);
}

Project: trojita
File: OpenConnectionTask.cpp

QList<QSslCertificate> OpenConnectionTask::sslCertificateChain() const
{
    return m_sslChain;
}

Project: trojita
File: test_Imap_Tasks_OpenConnection.cpp

void ImapModelOpenConnectionTest::testOkWithCapability()
{
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QVERIFY(SOCK->writtenStuff().isEmpty());
    SOCK->fakeReading( "* OK [CAPABILITY IMAP4rev1] foo\r\n" );
    QVERIFY( completedSpy->isEmpty() );
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE( authSpy->size(), 1 );
    QCOMPARE( SOCK->writtenStuff(), QByteArray("y0 LOGIN luzr sikrit\r\n") );
    SOCK->fakeReading( "y0 OK [CAPABILITY IMAP4rev1] logged in\r\n");
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE( completedSpy->size(), 1 );
    QVERIFY(failedSpy->isEmpty());
    QCOMPARE( authSpy->size(), 1 );
    QVERIFY(startTlsUpgradeSpy->isEmpty());
}

Project: trojita
File: test_Imap_Tasks_OpenConnection.cpp

void ImapModelOpenConnectionTest::testInitialBye()
{
    model->rowCount(QModelIndex());
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QVERIFY(SOCK->writtenStuff().isEmpty());
    SOCK->fakeReading("* BYE Sorry, we're offline\r\n");
    for (int i = 0; i < 10; ++i)
        QCoreApplication::processEvents();
    QCOMPARE(failedSpy->size(), 1);
    QVERIFY(completedSpy->isEmpty());
    QVERIFY(connErrorSpy->isEmpty());
    QVERIFY(startTlsUpgradeSpy->isEmpty());
}

Project: trojita
File: OpenConnectionTask.cpp

void OpenConnectionTask::startTlsOrLoginNow()
{
    if (model->m_startTls || model->accessParser(parser).capabilities.contains(QLatin1String("LOGINDISABLED"))) {
        // Should run STARTTLS later and already have the capabilities
        Q_ASSERT(model->accessParser(parser).capabilitiesFresh);
        if (!model->accessParser(parser).capabilities.contains(QLatin1String("STARTTLS"))) {
            logout(tr("Server does not support STARTTLS"));
        } else {
            startTlsCmd = parser->startTls();
            model->changeConnectionState(parser, CONN_STATE_STARTTLS_ISSUED);
        }
    } else {
        // We're requested to authenticate even without STARTTLS
        Q_ASSERT(!model->accessParser(parser).capabilities.contains(QLatin1String("LOGINDISABLED")));
        model->changeConnectionState(parser, CONN_STATE_LOGIN);
        askForAuth();
    }
}

Project: trojita
File: OpenConnectionTask.cpp

QString OpenConnectionTask::debugIdentification() const
{
    if (parser)
        return QString::fromUtf8("OpenConnectionTask: %1").arg(Imap::connectionStateToString(model->accessParser(parser).connState));
    else
        return QLatin1String("OpenConnectionTask: no parser");
}

Project: trojita
File: test_Imap_Tasks_OpenConnection.cpp

void ImapModelOpenConnectionTest::init( bool startTlsRequired )
{
    Imap::Mailbox::AbstractCache* cache = new Imap::Mailbox::MemoryCache(this);
    factory = new Streams::FakeSocketFactory(Imap::CONN_STATE_CONNECTED_PRETLS_PRECAPS);
    factory->setStartTlsRequired( startTlsRequired );
    Imap::Mailbox::TaskFactoryPtr taskFactory( new Imap::Mailbox::TaskFactory() ); // yes, the real one
    model = new Imap::Mailbox::Model(this, cache, Imap::Mailbox::SocketFactoryPtr( factory ), std::move(taskFactory));
    connect(model, SIGNAL(authRequested()), this, SLOT(provideAuthDetails()), Qt::QueuedConnection);
    connect(model, SIGNAL(needsSslDecision(QList<QSslCertificate>,QList<QSslError>)),
            this, SLOT(acceptSsl(QList<QSslCertificate>,QList<QSslError>)), Qt::QueuedConnection);
    LibMailboxSync::setModelNetworkPolicy(model, Imap::Mailbox::NETWORK_ONLINE);
    QCoreApplication::processEvents();
    task = new Imap::Mailbox::OpenConnectionTask(model);
    completedSpy = new QSignalSpy(task, SIGNAL(completed(Imap::Mailbox::ImapTask*)));
    failedSpy = new QSignalSpy(task, SIGNAL(failed(QString)));
    authSpy = new QSignalSpy(model, SIGNAL(authRequested()));
    connErrorSpy = new QSignalSpy(model, SIGNAL(connectionError(QString)));
    startTlsUpgradeSpy = new QSignalSpy(model, SIGNAL(requireStartTlsInFuture()));
}

Project: trojita
File: test_Imap_Tasks_OpenConnection.cpp

void ImapModelOpenConnectionTest::testPreauth()
{
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QVERIFY(SOCK->writtenStuff().isEmpty());
    SOCK->fakeReading( "* PREAUTH foo\r\n" );
    QVERIFY( completedSpy->isEmpty() );
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE( SOCK->writtenStuff(), QByteArray("y0 CAPABILITY\r\n") );
    QVERIFY( completedSpy->isEmpty() );
    SOCK->fakeReading( "* CAPABILITY IMAP4rev1\r\ny0 OK capability completed\r\n" );
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE( completedSpy->size(), 1 );
    QVERIFY(failedSpy->isEmpty());
    QVERIFY( authSpy->isEmpty() );
    QVERIFY(startTlsUpgradeSpy->isEmpty());
}

Project: trojita
File: test_Imap_Tasks_OpenConnection.cpp

void ImapModelOpenConnectionTest::testOpenConnectionShallBlock()
{
    model->rowCount(QModelIndex());
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QVERIFY(SOCK->writtenStuff().isEmpty());
    SOCK->fakeReading("* OK [capability imap4rev1] hi there\r\n");
    QVERIFY(completedSpy->isEmpty());
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE(SOCK->writtenStuff(), QByteArray("y0 LOGIN luzr sikrit\r\n"));
    QCOMPARE(authSpy->size(), 1);
    SOCK->fakeReading("y0 OK [CAPABILITY IMAP4rev1 compress=deflate id] logged in\r\n");
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
#if TROJITA_COMPRESS_DEFLATE
    QCOMPARE(SOCK->writtenStuff(), QByteArray("y1 COMPRESS DEFLATE\r\n"));
    SOCK->fakeReading("y1 NO I just don't want to\r\n");
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE(SOCK->writtenStuff(), QByteArray("y2 ID NIL\r\ny3 LIST \"\" \"%\"\r\n"));
    SOCK->fakeReading("* ID nil\r\ny3 OK listed, nothing like that in there\r\ny2 OK you courious peer\r\n");
#else
    QCOMPARE(SOCK->writtenStuff(), QByteArray("y1 ID NIL\r\ny2 LIST \"\" \"%\"\r\n"));
    SOCK->fakeReading("* ID nil\r\ny2 OK listed, nothing like that in there\r\ny1 OK you courious peer\r\n");
#endif
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE(completedSpy->size(), 1);
    QVERIFY(failedSpy->isEmpty());
    QCOMPARE(authSpy->size(), 1);
    QVERIFY(SOCK->writtenStuff().isEmpty());
    QVERIFY(startTlsUpgradeSpy->isEmpty());
}

Project: trojita
File: test_Imap_Tasks_OpenConnection.cpp

void ImapModelOpenConnectionTest::testOkStartTlsForbidden()
{
    cleanup(); init(true); // yuck, but I can't come up with anything better...

    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QVERIFY(SOCK->writtenStuff().isEmpty());
    SOCK->fakeReading( "* OK foo\r\n" );
    QVERIFY(completedSpy->isEmpty());
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE(SOCK->writtenStuff(), QByteArray("y0 CAPABILITY\r\n"));
    SOCK->fakeReading("* CAPABILITY imap4rev1\r\ny0 ok cap\r\n");
    QVERIFY(authSpy->isEmpty());
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE(failedSpy->size(), 1);
    QVERIFY(authSpy->isEmpty());
    QVERIFY(startTlsUpgradeSpy->isEmpty());
}

Project: trojita
File: OpenConnectionTask.cpp

void OpenConnectionTask::askForAuth()
{
    if (model->m_hasImapPassword) {
        Q_ASSERT(loginCmd.isEmpty());
        loginCmd = parser->login(model->m_imapUser, model->m_imapPassword);
        model->accessParser(parser).capabilitiesFresh = false;
    } else {
        EMIT_LATER_NOARG(model, authRequested);
    }
}

Project: trojita
File: test_Imap_Tasks_OpenConnection.cpp

void ImapModelOpenConnectionTest::testPreauthWithStartTlsWanted()
{
    cleanup(); init(true); // yuck, but I can't come up with anything better...

    cEmpty();
    cServer("* PREAUTH hi there\r\n");
    QCOMPARE(failedSpy->size(), 1);
    QVERIFY(completedSpy->isEmpty());
    QVERIFY(authSpy->isEmpty());
    QVERIFY(startTlsUpgradeSpy->isEmpty());
}