diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 325738e..8cdeebc 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -1,50 +1,50 @@ kde_enable_exceptions() include(ECMMarkAsTest) find_package(Qt5Test CONFIG REQUIRED) add_subdirectory(kimaptest) macro(KIMAP_UNIT_TESTS) foreach(_testname ${ARGN}) add_executable(${_testname} ${_testname}.cpp) add_test(NAME ${_testname} COMMAND ${_testname}) ecm_mark_as_test(${_testname}) target_link_libraries(${_testname} KF5IMAP Qt5::Test kimaptest Qt5::Network) set_target_properties(${_testname} PROPERTIES COMPILE_FLAGS -DTEST_DATA="\\"${CMAKE_CURRENT_SOURCE_DIR}\\"") set_tests_properties(${_testname} PROPERTIES RUN_SERIAL TRUE) endforeach() endmacro() ########### automated tests ############### KIMAP_UNIT_TESTS( fakeservertest testrfccodecs testsession - # loginjobtest FIXME: test hangs forever + loginjobtest logoutjobtest capabilitiesjobtest selectjobtest createjobtest deletejobtest expungejobtest fetchjobtest renamejobtest subscribejobtest unsubscribejobtest listjobtest storejobtest imapsettest idjobtest idlejobtest quotarootjobtest searchjobtest getmetadatajobtest streamparsertest setmetadatajobtest appendjobtest statusjobtest movejobtest ) diff --git a/autotests/kimaptest/fakeserver.cpp b/autotests/kimaptest/fakeserver.cpp index 5f19817..9289162 100644 --- a/autotests/kimaptest/fakeserver.cpp +++ b/autotests/kimaptest/fakeserver.cpp @@ -1,257 +1,263 @@ /* Copyright (C) 2008 Omat Holding B.V. Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company Author: Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Own #include "fakeserver.h" #include "sslserver.h" // Qt #include #include #include #include #include #include "imapstreamparser.h" QByteArray FakeServer::preauth() { return "S: * PREAUTH localhost Test Library server ready"; } QByteArray FakeServer::greeting() { return "S: * OK localhost Test Library server ready"; } FakeServer::FakeServer(QObject *parent) : QThread(parent), m_encrypted(false), m_starttls(false) { moveToThread(this); } FakeServer::~FakeServer() { quit(); wait(); } void FakeServer::startAndWait() { start(); // this will block until the event queue starts #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) QMetaObject::invokeMethod(this, &FakeServer::started, Qt::BlockingQueuedConnection); #else QMetaObject::invokeMethod(this, "started", Qt::BlockingQueuedConnection); #endif } void FakeServer::dataAvailable() { QMutexLocker locker(&m_mutex); QTcpSocket *socket = qobject_cast(sender()); QVERIFY(socket != 0); int scenarioNumber = m_clientSockets.indexOf(socket); if (m_scenarios[scenarioNumber].isEmpty()) { KIMAP::ImapStreamParser *clientParser = m_clientParsers[scenarioNumber]; QByteArray received = "C: " + clientParser->readUntilCommandEnd().trimmed(); qWarning() << "Scenario" << scenarioNumber << "finished, but we got command" << received; QVERIFY(false); } readClientPart(scenarioNumber); writeServerPart(scenarioNumber); if (m_starttls) { m_starttls = false; qDebug() << "start tls"; static_cast(socket)->startServerEncryption(); } } void FakeServer::newConnection() { QMutexLocker locker(&m_mutex); m_clientSockets << m_tcpServer->nextPendingConnection(); connect(m_clientSockets.last(), SIGNAL(readyRead()), this, SLOT(dataAvailable())); m_clientParsers << new KIMAP::ImapStreamParser(m_clientSockets.last(), true); QVERIFY(m_clientSockets.size() <= m_scenarios.size()); writeServerPart(m_clientSockets.size() - 1); } void FakeServer::setEncrypted(QSsl::SslProtocol protocol) { m_encrypted = true; m_sslProtocol = protocol; } +void FakeServer::setWaitForStartTls(bool wait) +{ + m_waitForStartTls = wait; +} + + void FakeServer::run() { if (m_encrypted) { - m_tcpServer = new SslServer(m_sslProtocol); + m_tcpServer = new SslServer(m_sslProtocol, m_waitForStartTls); } else { m_tcpServer = new QTcpServer(); } if (!m_tcpServer->listen(QHostAddress(QHostAddress::LocalHost), 5989)) { qFatal("Unable to start the server"); } connect(m_tcpServer, SIGNAL(newConnection()), this, SLOT(newConnection())); exec(); qDeleteAll(m_clientParsers); qDeleteAll(m_clientSockets); delete m_tcpServer; } void FakeServer::started() { // do nothing: this is a dummy slot used by startAndWait() } void FakeServer::setScenario(const QList &scenario) { QMutexLocker locker(&m_mutex); m_scenarios.clear(); m_scenarios << scenario; } void FakeServer::addScenario(const QList &scenario) { QMutexLocker locker(&m_mutex); m_scenarios << scenario; } void FakeServer::addScenarioFromFile(const QString &fileName) { QFile file(fileName); file.open(QFile::ReadOnly); QList scenario; // When loading from files we never have the authentication phase // force jumping directly to authenticated state. scenario << preauth(); while (!file.atEnd()) { scenario << file.readLine().trimmed(); } file.close(); addScenario(scenario); } bool FakeServer::isScenarioDone(int scenarioNumber) const { QMutexLocker locker(&m_mutex); if (scenarioNumber < m_scenarios.size()) { return m_scenarios[scenarioNumber].isEmpty(); } else { return true; // Non existent hence empty, right? } } bool FakeServer::isAllScenarioDone() const { QMutexLocker locker(&m_mutex); foreach (const QList &scenario, m_scenarios) { if (!scenario.isEmpty()) { return false; } } return true; } void FakeServer::writeServerPart(int scenarioNumber) { QList scenario = m_scenarios[scenarioNumber]; QTcpSocket *clientSocket = m_clientSockets[scenarioNumber]; while (!scenario.isEmpty() && (scenario.first().startsWith("S: ") || scenario.first().startsWith("W: "))) { QByteArray rule = scenario.takeFirst(); if (rule.startsWith("S: ")) { QByteArray payload = rule.mid(3); clientSocket->write(payload + "\r\n"); } else { int timeout = rule.mid(3).toInt(); QTest::qWait(timeout); } } if (!scenario.isEmpty() && scenario.first().startsWith("X")) { scenario.takeFirst(); clientSocket->close(); } if (!scenario.isEmpty()) { QVERIFY(scenario.first().startsWith("C: ")); } m_scenarios[scenarioNumber] = scenario; } void FakeServer::compareReceived(const QByteArray &received, const QByteArray &expected) const { QCOMPARE(QString::fromUtf8(received), QString::fromUtf8(expected)); QCOMPARE(received, expected); } void FakeServer::readClientPart(int scenarioNumber) { QList scenario = m_scenarios[scenarioNumber]; KIMAP::ImapStreamParser *clientParser = m_clientParsers[scenarioNumber]; while (!scenario.isEmpty() && scenario.first().startsWith("C: ")) { QByteArray received = "C: " + clientParser->readUntilCommandEnd().trimmed(); QByteArray expected = scenario.takeFirst(); if (expected.contains("C: SKIP")) { continue; } compareReceived(received, expected); if (received.contains("STARTTLS")) { m_starttls = true; } } if (!scenario.isEmpty()) { QVERIFY(scenario.first().startsWith("S: ") || scenario.first().startsWith("X")); } m_scenarios[scenarioNumber] = scenario; } diff --git a/autotests/kimaptest/fakeserver.h b/autotests/kimaptest/fakeserver.h index 975dff3..900826b 100644 --- a/autotests/kimaptest/fakeserver.h +++ b/autotests/kimaptest/fakeserver.h @@ -1,235 +1,241 @@ /* Copyright (C) 2008 Omat Holding B.V. Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company Author: Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef FAKESERVER_H #define FAKESERVER_H #include #include #include #include #include namespace KIMAP { class ImapStreamParser; } Q_DECLARE_METATYPE(QList) /** * Pretends to be an IMAP server for the purposes of unit tests. * * FakeServer does not really understand the IMAP protocol. Instead, * you give it a script, or scenario, that lists how an IMAP session * exchange should go. When it receives the client parts of the * scenario, it will respond with the following server parts. * * The server can be furnished with several scenarios. The first * scenario will be played out to the first client that connects, the * second scenario to the second client connection and so on. * * The fake server runs as a separate thread in the same process it * is started from, and listens for connections on port 5989 on the * local machine. * * Scenarios are in the form of protocol messages, with a tag at the * start to indicate whether it is message that will be sent by the * client ("C:") or a response that should be sent by the server * ("S:"). For example: * @code * C: A000001 LIST "" * * S: * LIST ( \HasChildren ) / INBOX * S: * LIST ( \HasNoChildren ) / INBOX/&AOQ- &APY- &APw- @ &IKw- * S: * LIST ( \HasChildren ) / INBOX/lost+found * S: * LIST ( \HasNoChildren ) / "INBOX/lost+found/Calendar Public-20080128" * S: A000001 OK LIST completed * @endcode * * A line starting with X indicates that the connection should be * closed by the server. This should be the last line in the * scenario. For example, the following simulates the server closing * the connection after receiving too many bad commands: * @code * C: A000001 madhatter * S: A000001 BAD Command madhatter * X * @endcode * * FakeServer::preauth() and FakeServer::greeting() provide standard * PREAUTH and OK responses, respectively, that can be used (unmodified) * as the first line of a scenario. * * A typical usage is something like * @code * QList scenario; * scenario << FakeServer::preauth() * << "C: A000001 CAPABILITY" * << "S: * CAPABILITY IMAP4rev1 STARTTLS AUTH=GSSAPI" * << "S: A000001 OK CAPABILITY completed"; * * FakeServer fakeServer; * fakeServer.setScenario( scenario ); * fakeServer.startAndWait(); * * KIMAP::Session session( QStringLiteral("127.0.0.1"), 5989 ); * KIMAP::CapabilitiesJob *job = new KIMAP::CapabilitiesJob(&session); * QVERIFY( job->exec() ); * // check the returned capabilities * * fakeServer.quit(); * @endcode */ class FakeServer : public QThread { Q_OBJECT public: /** * Get the default PREAUTH response * * This is the initial PREAUTH message that the server * sends at the start of a session to indicate that the * user is already authenticated by some other mechanism. * * Can be used as the first line in a scenario where * you want to skip the LOGIN stage of the protocol. */ static QByteArray preauth(); /** * Get the default greeting * * This is the initial OK message that the server sends at the * start of a session to indicate that a LOGIN is required. * * Can be used as the first line in a scenario where * you want to use the LOGIN command. */ static QByteArray greeting(); explicit FakeServer(QObject *parent = nullptr); ~FakeServer(); /** * Sets the encryption mode used by the server socket. */ void setEncrypted(QSsl::SslProtocol protocol); + /** + * Won't start encryption until client sends STARTTLS + */ + void setWaitForStartTls(bool wait); + /** * Starts the server and waits for it to be ready * * You should use this instead of start() to avoid race conditions. */ void startAndWait(); /** * Starts the fake IMAP server * * You should not call this directly. Use start() instead. * * @reimp */ void run() override; /** * Removes any previously-added scenarios, and adds a new one * * After this, there will only be one scenario, and so the fake * server will only be able to service a single request. More * scenarios can be added with addScenario, though. * * @see addScenario()\n * addScenarioFromFile() */ void setScenario(const QList &scenario); /** * Adds a new scenario * * Note that scenarios will be used in the order that clients * connect. If this is the 5th scenario that has been added * (bearing in mind that setScenario() resets the scenario * count), it will be used to service the 5th client that * connects. * * @see addScenarioFromFile() * * @param scenario the scenario as a list of messages */ void addScenario(const QList &scenario); /** * Adds a new scenario from a local file * * Note that scenarios will be used in the order that clients * connect. If this is the 5th scenario that has been added * (bearing in mind that setScenario() resets the scenario * count), it will be used to service the 5th client that * connects. * * @see addScenario() * * @param fileName the name of the file that contains the * scenario; it will be split at line * boundaries, and excess whitespace will * be trimmed from the start and end of lines */ void addScenarioFromFile(const QString &fileName); /** * Checks whether a particular scenario has completed * * @param scenarioNumber the number of the scenario to check, * in order of addition/client connection */ bool isScenarioDone(int scenarioNumber) const; /** * Whether all the scenarios that were added to the fake * server have been completed. */ bool isAllScenarioDone() const; protected: /** * Whether the received content is the same as the expected. * Use QCOMPARE, if creating subclasses. */ virtual void compareReceived(const QByteArray &received, const QByteArray &expected) const; private Q_SLOTS: void newConnection(); void dataAvailable(); void started(); private: void writeServerPart(int scenarioNumber); void readClientPart(int scenarioNumber); QList< QList > m_scenarios; QTcpServer *m_tcpServer; mutable QMutex m_mutex; QList m_clientSockets; QList m_clientParsers; bool m_encrypted; bool m_starttls; + bool m_waitForStartTls = false; QSsl::SslProtocol m_sslProtocol; }; #endif diff --git a/autotests/kimaptest/sslserver.cpp b/autotests/kimaptest/sslserver.cpp index b26ab05..5dc10fa 100644 --- a/autotests/kimaptest/sslserver.cpp +++ b/autotests/kimaptest/sslserver.cpp @@ -1,115 +1,117 @@ /* Copyright (C) 2013 Christian Mollekopf This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "sslserver.h" #include #include static QByteArray staticCert() { //a dummy certificate return QByteArray( "-----BEGIN CERTIFICATE-----\n\ MIIB+zCCAWQCCQDBBi7xZ2944DANBgkqhkiG9w0BAQUFADBCMQswCQYDVQQGEwJY\n\ WDEVMBMGA1UEBwwMRGVmYXVsdCBDaXR5MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBh\n\ bnkgTHRkMB4XDTEzMTIwNTA5MDcxNVoXDTQxMDQyMjA5MDcxNVowQjELMAkGA1UE\n\ BhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UECgwTRGVmYXVsdCBD\n\ b21wYW55IEx0ZDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAyuZdeqTgzX2E\n\ Q+tOj8/QzT8jHOUvwleqv56hAOEbZ5pLhYPesaSqV0lADiYHKjCRVIrhJQXePf7y\n\ MrJ3zE6hbHEMoIj+ku6ttNQkfJif30wmbXxLXO+RqraYgJW730kcbi2Jyq7ciEC1\n\ SVeiIaaiV2yUFBc/ARDFBc7733Y053UCAwEAATANBgkqhkiG9w0BAQUFAAOBgQAE\n\ BmB+mGtQzdmOAPbRYegA2ybuUARnW467qMOQpj5dV2LN+bizCbqrsz2twFKWS7oK\n\ EiC1bd6EGHnF6inFksUwODqeb+rjQ85pFBWskG51LWvX2/hoS+0x2V37vUYMxnDH\n\ rOEQiDe3oerErB0x9FMWk7VivEqO5HGEdxy7fGl3vg==\n\ -----END CERTIFICATE-----"); } static QByteArray staticKey() { //a dummy key without password return QByteArray( "-----BEGIN RSA PRIVATE KEY-----\n\ MIICXgIBAAKBgQDK5l16pODNfYRD606Pz9DNPyMc5S/CV6q/nqEA4RtnmkuFg96x\n\ pKpXSUAOJgcqMJFUiuElBd49/vIysnfMTqFscQygiP6S7q201CR8mJ/fTCZtfEtc\n\ 75GqtpiAlbvfSRxuLYnKrtyIQLVJV6IhpqJXbJQUFz8BEMUFzvvfdjTndQIDAQAB\n\ AoGBAIdNfXLGtl5x4BzGspn2NEBaZRjkwKdxfJzRtH34nyTEYK5FVODTdQBGCaAl\n\ vctlndRp1F+y/RQMighCuN6WZM/SdkzxkGGJVzDDuMw0Cwc48aqtMA3A3x/3bQkK\n\ kk2A5sLBc1TuC4DYSP5zkoXDbvBsHHN+tGAaC348Df6of1J1AkEA7ye3W9JUN4uK\n\ 2cPrnh7EKwQ2pFypeE/UNQ+LXR9h8XK90mxwiShU9sRFlNIA+pPcZ2aBxoY9m3rJ\n\ 4GHitl4ajwJBANkw6xM9IdgjMD8OQonpZTHSrKki/MaSSe9eBJ+WiCkTKL+Y9aTm\n\ 28sU7I+j3V38kYf5zyWXkyWmmNaQ4VaU77sCQQDMx7BM0qPEUBtb7lQxt9x3jQsQ\n\ 4DtIxupJaP8HhRjDu2Fo7evKthtSlauTC+NErRl7/J1BFa9pE9IK7SZIy/lnAkB6\n\ ssga9k5IbJi1BrlQcCpbG0mvw7RJ+hsKv3KdNc12Zvx+QUuE/WbuM8Pw4gINNsKA\n\ rv/3nMnkW1m83dxvrXRBAkEAmy3nO9HXdcKtSseseLJ6h9RB/R0XE1EvG8CKvveS\n\ IiVUnppVeiLFa7ItwHOovgqvWVbePd5xl6+yBGxUXznjWA==\n\ -----END RSA PRIVATE KEY-----"); } -SslServer::SslServer(QSsl::SslProtocol protocol) +SslServer::SslServer(QSsl::SslProtocol protocol, bool waitForStartTls) : QTcpServer(), - mProtocol(protocol) + mProtocol(protocol), + mWaitForStartTls(waitForStartTls) { } void SslServer::incomingConnection(qintptr handle) { QSslSocket *socket = new QSslSocket(); socket->setSocketDescriptor(handle); socket->setProtocol(mProtocol); QSslKey ssl_key(staticKey(), QSsl::Rsa); QSslCertificate ssl_cert(staticCert()); Q_ASSERT(QDateTime::currentDateTime() >= ssl_cert.effectiveDate()); Q_ASSERT(QDateTime::currentDateTime() <= ssl_cert.expiryDate()); Q_ASSERT(!ssl_cert.isBlacklisted()); socket->setPrivateKey(ssl_key); socket->setLocalCertificate(ssl_cert); socket->addCaCertificates(QList() << ssl_cert); socket->setPeerVerifyMode(QSslSocket::VerifyNone); socket->ignoreSslErrors(); connect(socket, SIGNAL(sslErrors(QList)), this, SLOT(sslErrors(QList))); connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(error(QAbstractSocket::SocketError))); - if (mProtocol != QSsl::TlsV1_0) { + if (!mWaitForStartTls) { socket->startServerEncryption(); } + addPendingConnection(socket); } void SslServer::sslErrors(const QList &errors) { for (const QSslError &error : errors) { qWarning() << "Received ssl error: " << error.errorString(); } QSslSocket *socket = qobject_cast(QObject::sender()); if (socket) { socket->disconnectFromHost(); } } void SslServer::error(QAbstractSocket::SocketError error) { QSslSocket *socket = qobject_cast(QObject::sender()); if (socket) { qWarning() << socket->errorString(); } qWarning() << error; } diff --git a/autotests/kimaptest/sslserver.h b/autotests/kimaptest/sslserver.h index ddebc7f..38cebaf 100644 --- a/autotests/kimaptest/sslserver.h +++ b/autotests/kimaptest/sslserver.h @@ -1,41 +1,42 @@ /* Copyright (C) 2013 Christian Mollekopf This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef SSLSERVER_H #define SSLSERVER_H #include #include class SslServer: public QTcpServer { Q_OBJECT public: - SslServer(QSsl::SslProtocol); + SslServer(QSsl::SslProtocol, bool waitForStartTls); void incomingConnection(qintptr handle) override; private Q_SLOTS: void sslErrors(const QList &errors); void error(QAbstractSocket::SocketError); private: QSsl::SslProtocol mProtocol; QSslSocket mSocket; + bool mWaitForStartTls = false; }; #endif diff --git a/autotests/loginjobtest.cpp b/autotests/loginjobtest.cpp index 3a2f103..503caf1 100644 --- a/autotests/loginjobtest.cpp +++ b/autotests/loginjobtest.cpp @@ -1,312 +1,316 @@ /* Copyright (C) 2009 Andras Mantia Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company Author: Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "kimaptest/fakeserver.h" #include "kimap/session.h" #include "kimap/loginjob.h" #include class TestUiProxy: public KIMAP::SessionUiProxy { - virtual bool ignoreSslError(const KSslErrorUiData &) + bool ignoreSslError(const KSslErrorUiData &) override { return true; } }; class LoginJobTest: public QObject { Q_OBJECT private Q_SLOTS: void shouldHandleLogin_data() { QTest::addColumn("user"); QTest::addColumn("password"); QTest::addColumn< QList >("scenario"); QList scenario; scenario << FakeServer::greeting() << "C: A000001 LOGIN \"user\" \"password\"" << "S: A000001 OK User logged in"; - QTest::newRow("success") << "user" << "password" << scenario; + QTest::newRow("success") << QStringLiteral("user") << QStringLiteral("password") << scenario; scenario.clear(); scenario << FakeServer::greeting() << "C: A000001 LOGIN \"user_bad\" \"password\"" << "S: A000001 NO Login failed: authentication failure"; - QTest::newRow("wrong login") << "user_bad" << "password" << scenario; + QTest::newRow("wrong login") << "user_bad" << QStringLiteral("password") << scenario; scenario.clear(); scenario << FakeServer::greeting() << "C: A000001 LOGIN \"user\" \"aa\\\"bb\\\\cc[dd ee\"" << "S: A000001 OK User logged in"; - QTest::newRow("special chars") << "user" << "aa\"bb\\cc[dd ee" << scenario; + QTest::newRow("special chars") << QStringLiteral("user") << "aa\"bb\\cc[dd ee" << scenario; scenario.clear(); scenario << FakeServer::preauth(); - QTest::newRow("already authenticated") << "user" << "password" << scenario; + QTest::newRow("already authenticated") << QStringLiteral("user") << QStringLiteral("password") << scenario; } void shouldHandleLogin() { QFETCH(QString, user); QFETCH(QString, password); QFETCH(QList, scenario); FakeServer fakeServer; fakeServer.setScenario(scenario); fakeServer.startAndWait(); - KIMAP::Session *session = new KIMAP::Session("127.0.0.1", 5989); + KIMAP::Session *session = new KIMAP::Session(QStringLiteral("127.0.0.1"), 5989); KIMAP::LoginJob *login = new KIMAP::LoginJob(session); login->setUserName(user); login->setPassword(password); bool result = login->exec(); QEXPECT_FAIL("wrong login", "Login with bad user name", Continue); QEXPECT_FAIL("already authenticated", "Trying to log on an already authenticated session", Continue); QVERIFY(result); fakeServer.quit(); delete session; } void shouldHandleProxyLogin_data() { QTest::addColumn("user"); QTest::addColumn("proxy"); QTest::addColumn("password"); QTest::addColumn< QList >("scenario"); QList scenario; scenario << FakeServer::greeting() << "C: A000001 AUTHENTICATE PLAIN" << "S: A000001 OK (success)" << "C: A000001 LOGIN \"proxy\" \"user\" \"password\"" << "S: A000001 OK User logged in"; - QTest::newRow("success") << "user" << "proxy" << "password" << scenario; + QTest::newRow("success") << QStringLiteral("user") << "proxy" << QStringLiteral("password") << scenario; } void shouldHandleProxyLogin() { QFETCH(QString, user); QFETCH(QString, proxy); QFETCH(QString, password); QFETCH(QList, scenario); FakeServer fakeServer; fakeServer.setScenario(scenario); fakeServer.startAndWait(); - KIMAP::Session *session = new KIMAP::Session("127.0.0.1", 5989); + KIMAP::Session *session = new KIMAP::Session(QStringLiteral("127.0.0.1"), 5989); KIMAP::LoginJob *login = new KIMAP::LoginJob(session); login->setAuthenticationMode(KIMAP::LoginJob::Plain); login->setUserName(user); login->setAuthorizationName(proxy); login->setPassword(password); bool result = login->exec(); QVERIFY(result); fakeServer.quit(); delete session; } void shouldSaveServerGreeting_data() { QTest::addColumn("greeting"); QTest::addColumn< QList >("scenario"); QList scenario; scenario << FakeServer::greeting() << "C: A000001 LOGIN \"user\" \"password\"" << "S: A000001 OK Welcome John Smith"; QTest::newRow("greeting") << "Welcome John Smith" << scenario; scenario.clear(); scenario << FakeServer::greeting() << "C: A000001 LOGIN \"user\" \"password\"" << "S: A000001 OK Welcome John Smith (last login: Feb 21, 2010)"; QTest::newRow("greeting with parenthesis") << "Welcome John Smith (last login: Feb 21, 2010)" << scenario; scenario.clear(); scenario << FakeServer::greeting() << "C: A000001 LOGIN \"user\" \"password\"" << "S: A000001 OK"; QTest::newRow("no greeting") << "" << scenario; scenario.clear(); scenario << FakeServer::greeting() << "C: A000001 LOGIN \"user\" \"password\"" << "S: A000001 NO Login failed: authentication failure"; QTest::newRow("login failed") << "" << scenario; } void shouldSaveServerGreeting() { QFETCH(QString, greeting); QFETCH(QList, scenario); FakeServer fakeServer; fakeServer.setScenario(scenario); fakeServer.startAndWait(); - KIMAP::Session *session = new KIMAP::Session("127.0.0.1", 5989); + KIMAP::Session *session = new KIMAP::Session(QStringLiteral("127.0.0.1"), 5989); KIMAP::LoginJob *login = new KIMAP::LoginJob(session); - login->setUserName("user"); - login->setPassword("password"); + login->setUserName(QStringLiteral("user")); + login->setPassword(QStringLiteral("password")); login->exec(); QCOMPARE(login->serverGreeting(), greeting); fakeServer.quit(); delete session; } void shouldUseSsl_data() { QTest::addColumn< QList >("scenario"); QTest::addColumn< int >("serverEncryption"); - QTest::addColumn< int >("clientEncryption"); - { - QList scenario; - scenario << FakeServer::greeting() - << "C: A000001 STARTTLS" - << "S: A000001 OK" - << "C: A000002 CAPABILITY" - << "S: A000002 OK" - << "C: A000003 LOGIN \"user\" \"password\"" - << "S: A000003 OK"; - - //KIMAP ties tlsv1 to starttls - QTest::newRow("tlsv1") << scenario << static_cast(QSsl::TlsV1) << static_cast(KIMAP::LoginJob::TlsV1); - } { QList scenario; scenario << FakeServer::greeting() << "C: A000001 CAPABILITY" << "S: A000001 OK" << "C: A000002 LOGIN \"user\" \"password\"" << "S: A000002 OK"; - QTest::newRow("sslv3") << scenario << static_cast(QSsl::SslV3) << static_cast(KIMAP::LoginJob::SslV3); - QTest::newRow("sslv2") << scenario << static_cast(QSsl::SslV2) << static_cast(KIMAP::LoginJob::SslV2); - //AnySslVersion doesn't mean the server can force a specific version (e.g. openssl always starts with a sslv2 hello) - QTest::newRow("any protocol with anyssl version") << scenario << static_cast(QSsl::AnyProtocol) << static_cast(KIMAP::LoginJob::AnySslVersion); - //KIMAP and KTcpSocket use SslV3_1 but really mean tls without starttls - QTest::newRow("sslv3_1") << scenario << static_cast(QSsl::TlsV1SslV3) << static_cast(KIMAP::LoginJob::SslV3_1); + + // SSLv2 support was removed from openssl 1.1 + //QTest::newRow("sslv2") << scenario << static_cast(QSsl::SslV2); + + // FIXME: SSLv3-only server is failing, likely openssl configuration problem + //QTest::newRow("sslv3") << scenario << static_cast(QSsl::SslV3); + + //AnySslVersion doesn't mean the server can force a specific version (e.g. openssl always starts with a tls12 hello) + QTest::newRow("any protocol with anyssl version") << scenario << static_cast(QSsl::AnyProtocol); + + QTest::newRow("tlsv10") << scenario << static_cast(QSsl::TlsV1_0); + QTest::newRow("tlsv11") << scenario << static_cast(QSsl::TlsV1_1); + QTest::newRow("tlsv12") << scenario << static_cast(QSsl::TlsV1_2); + } } void shouldUseSsl() { QFETCH(QList, scenario); QFETCH(int, serverEncryption); - QFETCH(int, clientEncryption); FakeServer fakeServer; fakeServer.setEncrypted(static_cast(serverEncryption)); fakeServer.setScenario(scenario); fakeServer.startAndWait(); - KIMAP::Session *session = new KIMAP::Session("127.0.0.1", 5989); + KIMAP::Session *session = new KIMAP::Session(QStringLiteral("127.0.0.1"), 5989); KIMAP::SessionUiProxy::Ptr uiProxy(new TestUiProxy); session->setUiProxy(uiProxy); KIMAP::LoginJob *login = new KIMAP::LoginJob(session); - login->setUserName("user"); - login->setPassword("password"); - login->setEncryptionMode(static_cast(clientEncryption)); + login->setUserName(QStringLiteral("user")); + login->setPassword(QStringLiteral("password")); + login->setEncryptionMode(KIMAP::LoginJob::SSLorTLS); QVERIFY(login->exec()); fakeServer.quit(); delete session; } - void shouldFailOnWrongSslSettings_data() + void shouldUseStartTls_data() { - QTest::addColumn< QList >("scenario"); - QTest::addColumn< int >("serverEncryption"); - QTest::addColumn< int >("clientEncryption"); - QTest::addColumn< int >("expectedErrorCode"); + QTest::addColumn>("scenario"); + QTest::addColumn("success"); { QList scenario; - scenario << FakeServer::greeting(); + scenario << FakeServer::greeting() + << "C: A000001 CAPABILITY" + << "S: * CAPABILITY IMAP4rev1 STARTTLS" + << "S: A000001 OK CAPABILITY completed" + << "C: A000002 STARTTLS" + << "S: A000002 OK" + << "C: A000003 CAPABILITY" + << "S: * CAPABILITY IMAP4rev1" + << "S: A000003 OK CAPABILITY completed" + << "C: A000004 LOGIN \"user\" \"password\"" + << "S: A000004 OK"; + QTest::newRow("STARTTLS supported") << scenario << true; + } + + { + QList scenario; + scenario << FakeServer::greeting() + << "C: A000001 CAPABILITY" + << "S: * CAPABILITY IMAP4rev1" + << "S: A000001 OK CAPABILITY completed"; - //For some reason only connecting to tlsv1 results in an ssl handshake error, with the wrong version only the server detects the error and disconnects -// QTest::newRow( "ssl v3 v2" ) << scenario << static_cast(QSsl::SslV3) << static_cast(KIMAP::LoginJob::SslV2) << static_cast(KJob::UserDefinedError); - QTest::newRow("ssl tlsv1 v3") << scenario << static_cast(QSsl::TlsV1) << static_cast(KIMAP::LoginJob::SslV3) << static_cast(KJob::UserDefinedError); + QTest::newRow("STARTTLS not supported") << scenario << false; } } - void shouldFailOnWrongSslSettings() + void shouldUseStartTls() { QFETCH(QList, scenario); - QFETCH(int, serverEncryption); - QFETCH(int, clientEncryption); - QFETCH(int, expectedErrorCode); + QFETCH(bool, success); FakeServer fakeServer; - fakeServer.setEncrypted(static_cast(serverEncryption)); + fakeServer.setEncrypted(QSsl::AnyProtocol); + fakeServer.setWaitForStartTls(true); fakeServer.setScenario(scenario); fakeServer.startAndWait(); - KIMAP::Session *session = new KIMAP::Session("127.0.0.1", 5989); + KIMAP::Session session(QStringLiteral("127.0.0.1"), 5989); KIMAP::SessionUiProxy::Ptr uiProxy(new TestUiProxy); - session->setUiProxy(uiProxy); + session.setUiProxy(uiProxy); - KIMAP::LoginJob *login = new KIMAP::LoginJob(session); - login->setUserName("user"); - login->setPassword("password"); - login->setEncryptionMode(static_cast(clientEncryption)); - QVERIFY(!login->exec()); - QCOMPARE(static_cast(login->error()), expectedErrorCode); + KIMAP::LoginJob login(&session); + login.setUserName(QStringLiteral("user")); + login.setPassword(QStringLiteral("password")); + login.setEncryptionMode(KIMAP::LoginJob::STARTTLS); + QCOMPARE(login.exec(), success); fakeServer.quit(); - delete session; } - }; QTEST_GUILESS_MAIN(LoginJobTest) #include "loginjobtest.moc"