diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,7 @@ set(KMIME_LIB_VERSION "5.5.80") set(AKONADI_LIB_VERSION "5.5.80") set(AKONADIMIME_LIB_VERSION "5.5.80") +set(KSMTP_LIB_VERSION "5.5.80") set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF5MailTransport") ########### Find packages ########### @@ -42,6 +43,7 @@ find_package(KF5Mime ${KMIME_LIB_VERSION} CONFIG REQUIRED) find_package(KF5Akonadi ${AKONADI_LIB_VERSION} CONFIG REQUIRED) find_package(KF5AkonadiMime ${AKONADIMIME_LIB_VERSION} CONFIG REQUIRED) +find_package(KPimSMTP ${KSMTP_LIB_VERSION} CONFIG REQUIRED) add_definitions("-DQT_NO_CAST_FROM_ASCII -DQT_NO_CAST_TO_ASCII") add_definitions(-DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT) diff --git a/src/kmailtransport/plugins/smtp/CMakeLists.txt b/src/kmailtransport/plugins/smtp/CMakeLists.txt --- a/src/kmailtransport/plugins/smtp/CMakeLists.txt +++ b/src/kmailtransport/plugins/smtp/CMakeLists.txt @@ -1,3 +1,7 @@ +if (BUILD_TESTING) + add_subdirectory(autotests) +endif() + set(mailtransport_smtpplugin_SRCS smtpmailtransportplugin.cpp smtpconfigdialog.cpp @@ -20,6 +24,7 @@ KF5::I18n Qt5::Widgets KF5::ConfigWidgets - KF5::KIOCore + KF5::KIOWidgets KF5::Completion + KPim::SMTP ) diff --git a/src/kmailtransport/plugins/smtp/autotests/CMakeLists.txt b/src/kmailtransport/plugins/smtp/autotests/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/src/kmailtransport/plugins/smtp/autotests/CMakeLists.txt @@ -0,0 +1,19 @@ +include(ECMAddTests) + +find_package(Qt5Test ${QT_REQUIRED_VERSION} REQUIRED) + +include_directories(${CMAKE_CURRENT_BINARY_DIR}/..) + +ecm_add_test(smtpjobtest.cpp + ../smtpjob.cpp + ${CMAKE_CURRENT_BINARY_DIR}/../mailtransportplugin_smtp_debug.cpp + fakeserver.cpp + LINK_LIBRARIES Qt5::Network + Qt5::Test + KF5::MailTransport + KF5::I18n + KF5::ConfigWidgets + KF5::KIOWidgets + KPim::SMTP + TEST_NAME smtpjobtest +) diff --git a/src/kmailtransport/plugins/smtp/autotests/fakeserver.h b/src/kmailtransport/plugins/smtp/autotests/fakeserver.h new file mode 100644 --- /dev/null +++ b/src/kmailtransport/plugins/smtp/autotests/fakeserver.h @@ -0,0 +1,67 @@ +/* + Copyright 2010 BetterInbox + Author: Christophe Laveault + Gregory Schlomoff + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#ifndef KSMTP_FAKESERVER_H +#define KSMTP_FAKESERVER_H + +#include +#include +#include +#include +#include + +Q_DECLARE_METATYPE(QList) + +class FakeServer : public QThread +{ + Q_OBJECT + +public: + explicit FakeServer(QObject *parent = nullptr); + ~FakeServer() override; + + void startAndWait(); + void run() override; + + static QByteArray greeting(); + static QList greetingAndEhlo(bool multiline = true); + static QList bye(); + + void setScenario(const QList &scenario); + void addScenario(const QList &scenario); + void addScenarioFromFile(const QString &fileName); + bool isScenarioDone(int scenarioNumber) const; + bool isAllScenarioDone() 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; +}; + +#endif // KSMTP_FAKESERVER_H diff --git a/src/kmailtransport/plugins/smtp/autotests/fakeserver.cpp b/src/kmailtransport/plugins/smtp/autotests/fakeserver.cpp new file mode 100644 --- /dev/null +++ b/src/kmailtransport/plugins/smtp/autotests/fakeserver.cpp @@ -0,0 +1,232 @@ +/* + Copyright 2010 BetterInbox + Author: Christophe Laveault + Gregory Schlomoff + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#include "fakeserver.h" + +#include +#include +#include + +FakeServer::FakeServer(QObject *parent) : + QThread(parent), + m_tcpServer(nullptr) +{ + moveToThread(this); +} + +QByteArray FakeServer::greeting() +{ + return "S: 220 localhost ESMTP xx777xx"; +} + +QList FakeServer::greetingAndEhlo(bool multiline) +{ + return QList() << greeting() + << "C: EHLO 127.0.0.1" + << QByteArray("S: 250") + (multiline ? '-' : ' ') + "Localhost ready to roll"; +} + +QList FakeServer::bye() +{ + return { "C: QUIT", + "S: 221 So long, and thanks for all the fish", + "X: " + }; +} + +FakeServer::~FakeServer() +{ + quit(); + wait(); +} + +void FakeServer::startAndWait() +{ + start(); + // this will block until the event queue starts + QMetaObject::invokeMethod(this, "started", Qt::BlockingQueuedConnection); +} + +void FakeServer::dataAvailable() +{ + QMutexLocker locker(&m_mutex); + + QTcpSocket *socket = qobject_cast(sender()); + Q_ASSERT(socket != nullptr); + + int scenarioNumber = m_clientSockets.indexOf(socket); + + QVERIFY(!m_scenarios[scenarioNumber].isEmpty()); + + readClientPart(scenarioNumber); + writeServerPart(scenarioNumber); +} + +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::run() +{ + m_tcpServer = new QTcpServer(); + if (!m_tcpServer->listen(QHostAddress(QHostAddress::LocalHost), 5989)) { + qFatal("Unable to start the server"); + return; + } + + connect(m_tcpServer, SIGNAL(newConnection()), this, SLOT(newConnection())); + + exec(); + + 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); + + for (const auto &scenario : qAsConst(m_scenarios)) { + if (!scenario.isEmpty()) { + qDebug() << scenario; + 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::readClientPart(int scenarioNumber) +{ + QList scenario = m_scenarios[scenarioNumber]; + QTcpSocket *clientSocket = m_clientSockets[scenarioNumber]; + + while (!scenario.isEmpty() && scenario.first().startsWith("C: ")) { + QByteArray line = clientSocket->readLine(); + QByteArray received = "C: " + line.trimmed(); + QByteArray expected = scenario.takeFirst(); + + if (expected == "C: SKIP" && !scenario.isEmpty()) { + expected = scenario.takeFirst(); + while (received != expected) { + received = "C: " + clientSocket->readLine().trimmed(); + } + } + + QCOMPARE(QString::fromUtf8(received), QString::fromUtf8(expected)); + QCOMPARE(received, expected); + } + + if (!scenario.isEmpty()) { + QVERIFY(scenario.first().startsWith("S: ")); + } + + m_scenarios[scenarioNumber] = scenario; +} diff --git a/src/kmailtransport/plugins/smtp/autotests/smtpjobtest.cpp b/src/kmailtransport/plugins/smtp/autotests/smtpjobtest.cpp new file mode 100644 --- /dev/null +++ b/src/kmailtransport/plugins/smtp/autotests/smtpjobtest.cpp @@ -0,0 +1,132 @@ +/* + Copyright (c) 2017 Daniel Vrátil + + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + This library 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 Library General Public + License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#include "../smtpjob.h" +#include "fakeserver.h" + +#include "transportbase.h" +#include "transportmanager.h" + +#include +#include +#include + +Q_DECLARE_METATYPE(MailTransport::TransportBase::EnumAuthenticationType::type) + +class SmtpJobTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase() + { + QStandardPaths::setTestModeEnabled(true); + } + + void smtpJobTest_data() + { + QTest::addColumn>("scenario"); + QTest::addColumn("authType"); + QTest::addColumn("from"); + QTest::addColumn("to"); + QTest::addColumn("cc"); + QTest::addColumn("data"); + QTest::addColumn("success"); + + QList scenario; + scenario << FakeServer::greetingAndEhlo() + << "S: 250 AUTH PLAIN LOGIN" + << "C: AUTH LOGIN" + << "S: 334 VXNlcm5hbWU6" + << "C: bG9naW4=" // "login".toBase64() + << "S: 334 UGFzc3dvcmQ6" + << "C: cGFzc3dvcmQ=" // "password".toBase64() + << "S: 235 Authenticated." + << "C: MAIL FROM:" + << "S: 250 ok" + << "C: RCPT TO:" + << "S: 250 ok" + << "C: RCPT TO:" + << "S: 250 ok" + << "C: DATA" + << "S: 354 Ok go ahead" + << "C: Hi Bob" + << "C: " + << "C: ." + << "S: 250 Ok transfer done" + << FakeServer::bye(); + QTest::newRow("simple") << scenario << MailTransport::TransportBase::EnumAuthenticationType::LOGIN + << QStringLiteral("Foo Bar ") + << QStringList{} + << QStringList{ QStringLiteral("bar@foo.com"), QStringLiteral("") } + << QByteArray("Hi Bob") + << true; + } + + void smtpJobTest() + { + QFETCH(QList, scenario); + QFETCH(MailTransport::TransportBase::EnumAuthenticationType::type, authType); + QFETCH(QString, from); + QFETCH(QStringList, to); + QFETCH(QStringList, cc); + QFETCH(QByteArray, data); + QFETCH(bool, success); + + FakeServer server; + server.setScenario(scenario); + server.startAndWait(); + + auto transport = MailTransport::TransportManager::self()->createTransport(); + transport->setHost(QStringLiteral("127.0.0.1")); + transport->setPort(5989); + transport->setRequiresAuthentication(true); + transport->setAuthenticationType(authType); + transport->setStorePassword(false); + transport->setUserName(QStringLiteral("login")); + transport->setPassword(QStringLiteral("password")); + + { + MailTransport::SmtpJob smtpJob(transport); + smtpJob.setSender(from); + smtpJob.setTo(to); + smtpJob.setCc(cc); + smtpJob.setData(data); + + QVERIFY(smtpJob.exec()); + if (success) { + QCOMPARE(smtpJob.error(), 0); + } else { + QVERIFY(smtpJob.error() > 0); + } + + // Make sure the smtpJob goes out-of-scope here and thus the + // internal session pool is destroyed + } + // KSMTP time to stop the session + QTest::qWait(10); + + QVERIFY(server.isAllScenarioDone()); + server.quit(); + } +}; + +QTEST_MAIN(SmtpJobTest) + +#include "smtpjobtest.moc" diff --git a/src/kmailtransport/plugins/smtp/sessionuiproxy.h b/src/kmailtransport/plugins/smtp/sessionuiproxy.h new file mode 100644 --- /dev/null +++ b/src/kmailtransport/plugins/smtp/sessionuiproxy.h @@ -0,0 +1,33 @@ +/* + Copyright (c) 2017 Daniel Vrátil + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + 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 SESSIONUIPROXY_H_ +#define SESSIONUIPROXY_H_ + +#include +#include + +class SmtpSessionUiProxy : public KSmtp::SessionUiProxy +{ +public: + bool ignoreSslError(const KSslErrorUiData &errorData) override + { + return KIO::SslUi::askIgnoreSslErrors(errorData, KIO::SslUi::RecallAndStoreRules); + } +}; + +#endif diff --git a/src/kmailtransport/plugins/smtp/smtpjob.h b/src/kmailtransport/plugins/smtp/smtpjob.h --- a/src/kmailtransport/plugins/smtp/smtpjob.h +++ b/src/kmailtransport/plugins/smtp/smtpjob.h @@ -24,6 +24,7 @@ #define MAILTRANSPORT_SMTPJOB_H #include +#include namespace KIO { class Job; @@ -65,13 +66,12 @@ protected Q_SLOTS: void slotResult(KJob *job) override; - void slaveError(KIO::Slave *slave, int errorCode, const QString &errorMsg); + void sessionStateChanged(KSmtp::Session::State state); private: void startSmtpJob(); - -private Q_SLOTS: - void dataRequest(KIO::Job *job, QByteArray &data); + void startLoginJob(); + void startSendJob(); private: friend class ::SmtpJobPrivate; diff --git a/src/kmailtransport/plugins/smtp/smtpjob.cpp b/src/kmailtransport/plugins/smtp/smtpjob.cpp --- a/src/kmailtransport/plugins/smtp/smtpjob.cpp +++ b/src/kmailtransport/plugins/smtp/smtpjob.cpp @@ -24,6 +24,7 @@ #include "transport.h" #include "mailtransport_defs.h" #include "precommandjob.h" +#include "sessionuiproxy.h" #include "mailtransportplugin_smtp_debug.h" #include @@ -34,36 +35,42 @@ #include #include #include "mailtransport_debug.h" -#include -#include #include +#include +#include +#include + using namespace MailTransport; -class SlavePool +class SessionPool { public: - SlavePool() : ref(0) + SessionPool() : ref(0) { } int ref; - QHash slaves; + QHash sessions; - void removeSlave(KIO::Slave *slave, bool disconnect = false) + void removeSession(KSmtp::Session *session) { - qCDebug(MAILTRANSPORT_SMTP_LOG) << "Removing slave" << slave << "from pool"; - const int slaveKey = slaves.key(slave); - if (slaveKey > 0) { - slaves.remove(slaveKey); - if (disconnect) { - KIO::Scheduler::disconnectSlave(slave); - } + qCDebug(MAILTRANSPORT_SMTP_LOG) << "Removing session" << session << "from the pool"; + int key = sessions.key(session); + if (key > 0) { + QObject::connect(session, &KSmtp::Session::stateChanged, + [session](KSmtp::Session::State state) { + if (state == KSmtp::Session::Disconnected) { + session->deleteLater(); + } + }); + session->quit(); + sessions.remove(key); } } }; -Q_GLOBAL_STATIC(SlavePool, s_slavePool) +Q_GLOBAL_STATIC(SessionPool, s_sessionPool) /** * Private class that helps to provide binary compatibility between releases. @@ -77,7 +84,8 @@ } SmtpJob *q; - KIO::Slave *slave; + KSmtp::Session *session; + KSmtp::SessionUiProxy::Ptr uiProxy; enum State { Idle, Precommand, Smtp } currentState; @@ -89,26 +97,23 @@ , d(new SmtpJobPrivate(this)) { d->currentState = SmtpJobPrivate::Idle; - d->slave = nullptr; + d->session = nullptr; d->finished = false; - if (!s_slavePool.isDestroyed()) { - s_slavePool->ref++; + d->uiProxy = KSmtp::SessionUiProxy::Ptr(new SmtpSessionUiProxy); + if (!s_sessionPool.isDestroyed()) { + s_sessionPool->ref++; } - KIO::Scheduler::connect(SIGNAL(slaveError(KIO::Slave *,int,QString)), this, SLOT(slaveError(KIO::Slave *,int,QString))); } SmtpJob::~SmtpJob() { - if (!s_slavePool.isDestroyed()) { - s_slavePool->ref--; - if (s_slavePool->ref == 0) { - qCDebug(MAILTRANSPORT_SMTP_LOG) << "clearing SMTP slave pool" << s_slavePool->slaves.count(); - foreach (KIO::Slave *slave, s_slavePool->slaves) { - if (slave) { - KIO::Scheduler::disconnectSlave(slave); - } + if (!s_sessionPool.isDestroyed()) { + s_sessionPool->ref--; + if (s_sessionPool->ref == 0) { + qCDebug(MAILTRANSPORT_SMTP_LOG) << "clearing SMTP session pool" << s_sessionPool->sessions.count(); + while (!s_sessionPool->sessions.isEmpty()) { + s_sessionPool->removeSession(*(s_sessionPool->sessions.begin())); } - s_slavePool->slaves.clear(); } } delete d; @@ -116,12 +121,12 @@ void SmtpJob::doStart() { - if (s_slavePool.isDestroyed()) { + if (s_sessionPool.isDestroyed()) { return; } - if ((!s_slavePool->slaves.isEmpty() - && s_slavePool->slaves.contains(transport()->id())) + if ((!s_sessionPool->sessions.isEmpty() + && s_sessionPool->sessions.contains(transport()->id())) || transport()->precommand().isEmpty()) { d->currentState = SmtpJobPrivate::Smtp; startSmtpJob(); @@ -135,118 +140,161 @@ void SmtpJob::startSmtpJob() { - if (s_slavePool.isDestroyed()) { + if (s_sessionPool.isDestroyed()) { return; } - QUrl destination; - destination.setScheme((transport()->encryption() == Transport::EnumEncryption::SSL) - ? SMTPS_PROTOCOL : SMTP_PROTOCOL); - destination.setHost(transport()->host().trimmed()); - destination.setPort(transport()->port()); + d->session = s_sessionPool->sessions.value(transport()->id()); + if (!d->session) { + d->session = new KSmtp::Session(transport()->host(), transport()->port()); + d->session->setUiProxy(d->uiProxy); + if (transport()->specifyHostname()) { + d->session->setCustomHostname(transport()->localHostname()); + } + s_sessionPool->sessions.insert(transport()->id(), d->session); + } - QUrlQuery destinationQuery(destination); - destinationQuery.addQueryItem(QStringLiteral("headers"), QStringLiteral("0")); - destinationQuery.addQueryItem(QStringLiteral("from"), sender()); + connect(d->session, &KSmtp::Session::stateChanged, + this, &SmtpJob::sessionStateChanged, Qt::UniqueConnection); + connect(d->session, &KSmtp::Session::connectionError, + this, [this](const QString &err) { + setError(KJob::UserDefinedError); + setErrorText(err); + s_sessionPool->removeSession(d->session); + emitResult(); + }); - for (const QString &str : to()) { - destinationQuery.addQueryItem(QStringLiteral("to"), str); - } - for (const QString &str : cc()) { - destinationQuery.addQueryItem(QStringLiteral("cc"), str); + if (d->session->state() == KSmtp::Session::Disconnected) { + d->session->open(); + } else { + if (d->session->state() != KSmtp::Session::Authenticated) { + startLoginJob(); + } + + startSendJob(); } - for (const QString &str : bcc()) { - destinationQuery.addQueryItem(QStringLiteral("bcc"), str); +} + +void SmtpJob::sessionStateChanged(KSmtp::Session::State state) +{ + if (state == KSmtp::Session::Ready) { + startLoginJob(); + } else if (state == KSmtp::Session::Authenticated) { + startSendJob(); } +} - if (transport()->specifyHostname()) { - destinationQuery.addQueryItem(QStringLiteral("hostname"), transport()->localHostname()); +void SmtpJob::startLoginJob() +{ + if (!transport()->requiresAuthentication()) { + startSendJob(); + return; } - if (transport()->requiresAuthentication()) { - QString user = transport()->userName(); - QString passwd = transport()->password(); - if ((user.isEmpty() || passwd.isEmpty()) - && transport()->authenticationType() != Transport::EnumAuthenticationType::GSSAPI) { - QPointer dlg - = new KPasswordDialog( - nullptr, - KPasswordDialog::ShowUsernameLine - |KPasswordDialog::ShowKeepPassword); - dlg->setPrompt(i18n("You need to supply a username and a password " - "to use this SMTP server.")); - dlg->setKeepPassword(transport()->storePassword()); - dlg->addCommentLine(QString(), transport()->name()); - dlg->setUsername(user); - dlg->setPassword(passwd); - - bool gotIt = false; - if (dlg->exec()) { - transport()->setUserName(dlg->username()); - transport()->setPassword(dlg->password()); - transport()->setStorePassword(dlg->keepPassword()); - transport()->save(); - gotIt = true; - } - delete dlg; + auto login = new KSmtp::LoginJob(d->session); + auto user = transport()->userName(); + auto passwd = transport()->password(); + if ((user.isEmpty() || passwd.isEmpty()) + && transport()->authenticationType() != Transport::EnumAuthenticationType::GSSAPI) { + QPointer dlg + = new KPasswordDialog( + nullptr, + KPasswordDialog::ShowUsernameLine + |KPasswordDialog::ShowKeepPassword); + dlg->setPrompt(i18n("You need to supply a username and a password " + "to use this SMTP server.")); + dlg->setKeepPassword(transport()->storePassword()); + dlg->addCommentLine(QString(), transport()->name()); + dlg->setUsername(user); + dlg->setPassword(passwd); + + bool gotIt = false; + if (dlg->exec()) { + transport()->setUserName(dlg->username()); + transport()->setPassword(dlg->password()); + transport()->setStorePassword(dlg->keepPassword()); + transport()->save(); + gotIt = true; + } + delete dlg; - if (!gotIt) { - setError(KilledJobError); - emitResult(); - return; - } + if (!gotIt) { + setError(KilledJobError); + emitResult(); + return; } - destination.setUserName(transport()->userName()); - destination.setPassword(transport()->password()); } - // dotstuffing is now done by the slave (see setting of metadata) - if (!data().isEmpty()) { - // allow +5% for subsequent LF->CRLF and dotstuffing (an average - // over 2G-lines gives an average line length of 42-43): - destinationQuery.addQueryItem(QStringLiteral("size"), - QString::number(qRound(data().length() * 1.05))); + login->setUserName(transport()->userName()); + login->setPassword(transport()->password()); + switch (transport()->authenticationType()) { + case TransportBase::EnumAuthenticationType::PLAIN: + login->setPreferedAuthMode(KSmtp::LoginJob::Plain); + break; + case TransportBase::EnumAuthenticationType::LOGIN: + login->setPreferedAuthMode(KSmtp::LoginJob::Login); + break; + case TransportBase::EnumAuthenticationType::CRAM_MD5: + login->setPreferedAuthMode(KSmtp::LoginJob::CramMD5); + break; + case TransportBase::EnumAuthenticationType::XOAUTH2: + login->setPreferedAuthMode(KSmtp::LoginJob::XOAuth); + break; + case TransportBase::EnumAuthenticationType::DIGEST_MD5: + login->setPreferedAuthMode(KSmtp::LoginJob::DigestMD5); + break; + case TransportBase::EnumAuthenticationType::NTLM: + login->setPreferedAuthMode(KSmtp::LoginJob::NTLM); + break; + case TransportBase::EnumAuthenticationType::GSSAPI: + login->setPreferedAuthMode(KSmtp::LoginJob::GSSAPI); + break; + default: + qCWarning(MAILTRANSPORT_SMTP_LOG) << "Unknown authentication mode" << transport()->authenticationTypeString(); + break; } - destination.setPath(QStringLiteral("/send")); - destination.setQuery(destinationQuery); - - d->slave = s_slavePool->slaves.value(transport()->id()); - if (!d->slave) { - KIO::MetaData slaveConfig; - slaveConfig.insert(QStringLiteral("tls"), - (transport()->encryption() == Transport::EnumEncryption::TLS) - ? QStringLiteral("on") : QStringLiteral("off")); - if (transport()->requiresAuthentication()) { - slaveConfig.insert(QStringLiteral("sasl"), transport()->authenticationTypeString()); - } - d->slave = KIO::Scheduler::getConnectedSlave(destination, slaveConfig); - qCDebug(MAILTRANSPORT_SMTP_LOG) << "Created new SMTP slave" << d->slave; - s_slavePool->slaves.insert(transport()->id(), d->slave); - } else { - qCDebug(MAILTRANSPORT_SMTP_LOG) << "Re-using existing slave" << d->slave; - } + switch (transport()->encryption()) { + case Transport::EnumEncryption::None: + login->setEncryptionMode(KSmtp::LoginJob::Unencrypted); + break; + case Transport::EnumEncryption::TLS: + login->setEncryptionMode(KSmtp::LoginJob::TlsV1); + break; + case Transport::EnumEncryption::SSL: + login->setEncryptionMode(KSmtp::LoginJob::AnySslVersion); + break; + default: + qCWarning(MAILTRANSPORT_SMTP_LOG) << "Unknown encryption mode" << transport()->encryption(); + break; - KIO::TransferJob *job = KIO::put(destination, -1, KIO::HideProgressInfo); - if (!d->slave || !job) { - setError(UserDefinedError); - setErrorText(i18n("Unable to create SMTP job.")); - emitResult(); - return; } - job->addMetaData(QStringLiteral("lf2crlf+dotstuff"), QStringLiteral("slave")); - connect(job, &KIO::TransferJob::dataReq, this, &SmtpJob::dataRequest); - - addSubjob(job); - KIO::Scheduler::assignJobToSlave(d->slave, job); + connect(login, &KJob::result, this, &SmtpJob::slotResult); + addSubjob(login); + login->start(); + qCDebug(MAILTRANSPORT_SMTP_LOG) << "Login started"; +} - setTotalAmount(KJob::Bytes, data().length()); +void SmtpJob::startSendJob() +{ + auto send = new KSmtp::SendJob(d->session); + send->setFrom(sender()); + send->setTo(to()); + send->setCc(cc()); + send->setBcc(bcc()); + send->setData(data()); + + connect(send, &KJob::result, this, &SmtpJob::slotResult); + addSubjob(send); + send->start(); + + qCDebug(MAILTRANSPORT_SMTP_LOG) << "Send started"; } bool SmtpJob::doKill() { - if (s_slavePool.isDestroyed()) { + if (s_sessionPool.isDestroyed()) { return false; } @@ -256,10 +304,8 @@ if (d->currentState == SmtpJobPrivate::Precommand) { return subjobs().first()->kill(); } else if (d->currentState == SmtpJobPrivate::Smtp) { - KIO::SimpleJob *job = static_cast(subjobs().first()); clearSubjobs(); - KIO::Scheduler::cancelJob(job); - s_slavePool->removeSlave(d->slave); + s_sessionPool->removeSession(d->session); return true; } return false; @@ -267,7 +313,7 @@ void SmtpJob::slotResult(KJob *job) { - if (s_slavePool.isDestroyed()) { + if (s_sessionPool.isDestroyed()) { return; } @@ -298,7 +344,7 @@ } if (errorCode && d->currentState == SmtpJobPrivate::Smtp) { - s_slavePool->removeSlave(d->slave, errorCode != KIO::ERR_SLAVE_DIED); + s_sessionPool->removeSession(d->session); TransportJob::slotResult(job); return; } @@ -309,38 +355,7 @@ startSmtpJob(); return; } - if (!error()) { - emitResult(); - } -} - -void SmtpJob::dataRequest(KIO::Job *job, QByteArray &data) -{ - if (s_slavePool.isDestroyed()) { - return; - } - - Q_UNUSED(job); - Q_ASSERT(job); - if (buffer()->atEnd()) { - data.clear(); - } else { - Q_ASSERT(buffer()->isOpen()); - data = buffer()->read(32 * 1024); - } - setProcessedAmount(KJob::Bytes, buffer()->pos()); -} - -void SmtpJob::slaveError(KIO::Slave *slave, int errorCode, const QString &errorMsg) -{ - if (s_slavePool.isDestroyed()) { - return; - } - - s_slavePool->removeSlave(slave, errorCode != KIO::ERR_SLAVE_DIED); - if (d->slave == slave && !d->finished) { - setError(errorCode); - setErrorText(KIO::buildErrorString(errorCode, errorMsg)); + if (!error() && !hasSubjobs()) { emitResult(); } }