diff --git a/autotests/fakeserver.cpp b/autotests/fakeserver.cpp index dbba154..9875a0b 100644 --- a/autotests/fakeserver.cpp +++ b/autotests/fakeserver.cpp @@ -1,237 +1,235 @@ /* 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) +FakeServer::FakeServer(QObject *parent) + : QThread(parent) { 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"; + << "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 #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()); 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); foreach (const QList &scenario, 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: "))) { + && (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/autotests/smtptest.cpp b/autotests/smtptest.cpp index e0c52ff..6348dd7 100644 --- a/autotests/smtptest.cpp +++ b/autotests/smtptest.cpp @@ -1,274 +1,267 @@ /* 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 "smtptest.h" #include #include "fakeserver.h" #include "session.h" #include "loginjob.h" #include "sendjob.h" - void SmtpTest::testHello_data() { QTest::addColumn >("scenario"); QTest::addColumn("hostname"); QList scenario; scenario << FakeServer::greeting() << "C: EHLO 127.0.0.1" << "S: 250 Localhost ready to roll" << FakeServer::bye(); QTest::newRow("EHLO OK") << scenario << QStringLiteral("127.0.0.1"); scenario.clear(); scenario << FakeServer::greeting() << "C: EHLO 127.0.0.1" << "S: 500 Command was not recognized" << "C: HELO 127.0.0.1" << "S: 250 Localhost ready to roll" << FakeServer::bye(); QTest::newRow("EHLO unknown") << scenario << QStringLiteral("127.0.0.1"); scenario.clear(); scenario << FakeServer::greeting() << "C: EHLO 127.0.0.1" << "S: 502 Command not implemented" << "C: HELO 127.0.0.1" << "S: 250 Localhost ready to roll" << FakeServer::bye(); QTest::newRow("EHLO not implemented") << scenario << QStringLiteral("127.0.0.1"); scenario.clear(); scenario << FakeServer::greeting() << "C: EHLO 127.0.0.1" << "S: 502 Command not implemented" << "C: HELO 127.0.0.1" << "S: 500 Command was not recognized" << FakeServer::bye(); QTest::newRow("ERROR") << scenario << QStringLiteral("127.0.0.1"); scenario.clear(); scenario << FakeServer::greeting() << "C: EHLO random.stranger" << "S: 250 random.stranger ready to roll" << FakeServer::bye(); QTest::newRow("EHLO hostname") << scenario << QStringLiteral("random.stranger"); } void SmtpTest::testHello() { QFETCH(QList, scenario); QFETCH(QString, hostname); FakeServer fakeServer; fakeServer.setScenario(scenario); fakeServer.startAndWait(); KSmtp::Session session(QStringLiteral("127.0.0.1"), 5989); session.setCustomHostname(hostname); session.openAndWait(); qDebug() << "### Session state is:" << session.state(); - QEXPECT_FAIL("ERROR" , "Expected failure if HELO command not recognized", Continue); + QEXPECT_FAIL("ERROR", "Expected failure if HELO command not recognized", Continue); QVERIFY2(session.state() == KSmtp::Session::NotAuthenticated, "Handshake failed"); session.quitAndWait(); QVERIFY(fakeServer.isAllScenarioDone()); fakeServer.quit(); } - void SmtpTest::testLoginJob_data() { QTest::addColumn >("scenario"); QTest::addColumn("authMode"); QTest::addColumn("errorCode"); - QList scenario; scenario << FakeServer::greetingAndEhlo() << "S: 250 AUTH PLAIN LOGIN" << "C: AUTH PLAIN AGxvZ2luAHBhc3N3b3Jk" // [\0 + "login" + \0 + "password"].toBase64() << "S: 235 Authenticated" << FakeServer::bye(); QTest::newRow("Plain auth ok") << scenario << "Plain" << 0; scenario.clear(); scenario << FakeServer::greetingAndEhlo() << "S: 250 AUTH PLAIN LOGIN" << "C: AUTH LOGIN" << "S: 334 VXNlcm5hbWU6" // "Username:".toBase64() << "C: bG9naW4=" // "login".toBase64() << "S: 334 UGFzc3dvcmQ6" // "Password:".toBase64() << "C: cGFzc3dvcmQ=" // "password".toBase64() << "S: 235 Authenticated" << FakeServer::bye(); QTest::newRow("Login auth ok") << scenario << "Login" << 0; scenario.clear(); scenario << FakeServer::greetingAndEhlo() << "S: 250 AUTH PLAIN" << "C: AUTH PLAIN AGxvZ2luAHBhc3N3b3Jk" // [\0 + "login" + \0 + "password"].toBase64() << "S: 235 Authenticated" << FakeServer::bye(); QTest::newRow("Login not supported") << scenario << "Login" << 0; scenario.clear(); scenario << FakeServer::greetingAndEhlo(false) - // The login job won't even try to send AUTH, because it does not - // have any mechanisms to use - //<< "C: AUTH PLAIN AGxvZ2luAHBhc3N3b3Jk" // [\0 + "login" + \0 + "password"].toBase64() - //<< "S: 235 Authenticated" + // The login job won't even try to send AUTH, because it does not + // have any mechanisms to use + //<< "C: AUTH PLAIN AGxvZ2luAHBhc3N3b3Jk" // [\0 + "login" + \0 + "password"].toBase64() + //<< "S: 235 Authenticated" << FakeServer::bye(); QTest::newRow("Auth not supported") << scenario << "Login" << 100; scenario.clear(); scenario << FakeServer::greetingAndEhlo() << "S: 250 AUTH PLAIN" << "C: AUTH PLAIN AGxvZ2luAHBhc3N3b3Jk" // [\0 + "login" + \0 + "password"].toBase64() << "S: 535 Authorization failed" << FakeServer::bye(); QTest::newRow("Wrong password") << scenario << "Plain" << 100; } void SmtpTest::testLoginJob() { QFETCH(QList, scenario); QFETCH(QString, authMode); QFETCH(int, errorCode); KSmtp::LoginJob::AuthMode mode = KSmtp::LoginJob::UnknownAuth; if (authMode == QLatin1String("Plain")) { mode = KSmtp::LoginJob::Plain; } else if (authMode == QLatin1String("Login")) { mode = KSmtp::LoginJob::Login; } FakeServer fakeServer; fakeServer.setScenario(scenario); fakeServer.startAndWait(); KSmtp::Session session(QStringLiteral("127.0.0.1"), 5989); session.setCustomHostname(QStringLiteral("127.0.0.1")); session.openAndWait(); KSmtp::LoginJob *login = new KSmtp::LoginJob(&session); login->setPreferedAuthMode(mode); login->setUserName(QStringLiteral("login")); login->setPassword(QStringLiteral("password")); login->exec(); // Checking job error code: QVERIFY2(login->error() == errorCode, "Unexpected LoginJob error code"); // Checking session state: - QEXPECT_FAIL("Auth not supported" , "Expected failure if not authentication method suported", Continue); - QEXPECT_FAIL("Wrong password" , "Expected failure if wrong password", Continue); + QEXPECT_FAIL("Auth not supported", "Expected failure if not authentication method suported", Continue); + QEXPECT_FAIL("Wrong password", "Expected failure if wrong password", Continue); QVERIFY2(session.state() == KSmtp::Session::Authenticated, "Authentication failed"); session.quitAndWait(); QVERIFY(fakeServer.isAllScenarioDone()); fakeServer.quit(); } - void SmtpTest::testSendJob_data() { QTest::addColumn >("scenario"); QTest::addColumn("errorCode"); QList scenario; scenario << FakeServer::greetingAndEhlo(false) << "C: MAIL FROM:" << "S: 530 Not allowed" << FakeServer::bye(); QTest::newRow("Send not allowed") << scenario << 100; scenario.clear(); scenario << FakeServer::greetingAndEhlo(false) << "C: MAIL FROM:" << "S: 250 ok" << "C: RCPT TO:" << "S: 250 ok" << "C: DATA" << "S: 354 Ok go ahead" << "C: From: foo@bar.com" << "C: To: bar@foo.com" << "C: Hello world." << "C: .." // Single dot becomes two << "C: .." // Single dot becomes two << "C: ..." // Two dots become three << "C: ..Foo" // .Foo becomes ..Foo << "C: End" << "C: " << "C: ." << "S: 250 Ok transfer done" << FakeServer::bye(); QTest::newRow("ok") << scenario << 0; scenario.clear(); - } void SmtpTest::testSendJob() { QFETCH(QList, scenario); QFETCH(int, errorCode); FakeServer fakeServer; fakeServer.setScenario(scenario); fakeServer.startAndWait(); KSmtp::Session session(QStringLiteral("127.0.0.1"), 5989); session.setCustomHostname(QStringLiteral("127.0.0.1")); session.openAndWait(); KSmtp::SendJob *send = new KSmtp::SendJob(&session); send->setData("From: foo@bar.com\r\nTo: bar@foo.com\r\nHello world.\r\n.\r\n.\r\n..\r\n.Foo\r\nEnd"); send->setFrom(QStringLiteral("foo@bar.com")); send->setTo({QStringLiteral("bar@foo.com")}); send->exec(); // Checking job error code: QVERIFY2(send->error() == errorCode, qPrintable(QStringLiteral("Unexpected LoginJob error: ") + send->errorString())); session.quitAndWait(); QVERIFY(fakeServer.isAllScenarioDone()); fakeServer.quit(); } - SmtpTest::SmtpTest() { } void SmtpTest::initTestCase() { } void SmtpTest::cleanupTestCase() { } - QTEST_MAIN(SmtpTest) diff --git a/autotests/smtptest.h b/autotests/smtptest.h index 1beac20..e035c24 100644 --- a/autotests/smtptest.h +++ b/autotests/smtptest.h @@ -1,49 +1,48 @@ /* 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_SMTPTEST_H #define KSMTP_SMTPTEST_H #include "QObject" - class SmtpTest : public QObject { Q_OBJECT public: SmtpTest(); private Q_SLOTS: void initTestCase(); void cleanupTestCase(); void testHello(); void testHello_data(); void testLoginJob(); void testLoginJob_data(); void testSendJob(); void testSendJob_data(); //TODO: (CL) Check if SendJob parses properly the data it gets before sending }; #endif // KSMTP_SMTPTEST_H diff --git a/src/job.cpp b/src/job.cpp index 5b2a1d5..da19f33 100644 --- a/src/job.cpp +++ b/src/job.cpp @@ -1,81 +1,82 @@ /* 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 "job.h" #include "job_p.h" #include "serverresponse_p.h" #include "session_p.h" #include using namespace KSmtp; Job::Job(Session *session) - : KJob(session), - d_ptr(new JobPrivate(session, QStringLiteral("Job"))) + : KJob(session) + , d_ptr(new JobPrivate(session, QStringLiteral("Job"))) { } Job::Job(JobPrivate &dd) - : KJob(dd.m_session), d_ptr(&dd) + : KJob(dd.m_session) + , d_ptr(&dd) { } Job::~Job() { delete d_ptr; } Session *Job::session() const { Q_D(const Job); return d->m_session; } void Job::start() { Q_D(Job); d->sessionInternal()->addJob(this); } void Job::sendCommand(const QByteArray &cmd) { Q_D(Job); d->sessionInternal()->sendData(cmd); } void Job::handleErrors(const ServerResponse &r) { if (r.isCode(4) || r.isCode(5)) { setError(KJob::UserDefinedError); if (r.isCode(4)) { setErrorText(i18n("Server time out")); } else { setErrorText(i18n("Server error")); } emitResult(); } } void Job::connectionLost() { setError(KJob::UserDefinedError); setErrorText(i18n("Connection to server lost.")); emitResult(); } diff --git a/src/job.h b/src/job.h index 0af59cd..31f00d2 100644 --- a/src/job.h +++ b/src/job.h @@ -1,64 +1,60 @@ /* 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_JOB_H #define KSMTP_JOB_H #include "ksmtp_export.h" #include -namespace KSmtp -{ - +namespace KSmtp { class Session; class SessionPrivate; class JobPrivate; class ServerResponse; class KSMTP_EXPORT Job : public KJob { Q_OBJECT Q_DECLARE_PRIVATE(Job) friend class SessionPrivate; public: ~Job() override; Q_REQUIRED_RESULT Session *session() const; void start() override; protected: void sendCommand(const QByteArray &cmd); virtual void doStart() = 0; virtual void handleResponse(const ServerResponse &response) = 0; void handleErrors(const ServerResponse &response); void connectionLost(); explicit Job(Session *session); explicit Job(JobPrivate &dd); - JobPrivate *const d_ptr; }; - } #endif diff --git a/src/job_p.h b/src/job_p.h index 56ac8d4..df09832 100644 --- a/src/job_p.h +++ b/src/job_p.h @@ -1,51 +1,54 @@ /* Copyright 2010 BetterInbox Author: 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_JOB_P_H #define KSMTP_JOB_P_H #include "session.h" -namespace KSmtp -{ - +namespace KSmtp { class SessionPrivate; class JobPrivate { public: - JobPrivate(Session *session, const QString &name) : m_session(session), m_name(name) { } - virtual ~JobPrivate() { } + JobPrivate(Session *session, const QString &name) : m_session(session) + , m_name(name) + { + } + + virtual ~JobPrivate() + { + } inline SessionPrivate *sessionInternal() { return m_session->d; } inline const SessionPrivate *sessionInternal() const { return m_session->d; } Session *m_session = nullptr; QString m_name; }; - } #endif //KSMTP_JOB_P_H diff --git a/src/loginjob.cpp b/src/loginjob.cpp index 47c41f6..4772aa1 100644 --- a/src/loginjob.cpp +++ b/src/loginjob.cpp @@ -1,430 +1,430 @@ /* 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 "loginjob.h" #include "ksmtp_debug.h" #include "job_p.h" #include "serverresponse_p.h" #include "session_p.h" #include #include #include extern "C" { #include } namespace { - static const sasl_callback_t callbacks[] = { { SASL_CB_ECHOPROMPT, nullptr, nullptr }, { SASL_CB_NOECHOPROMPT, nullptr, nullptr }, { SASL_CB_GETREALM, nullptr, nullptr }, { SASL_CB_USER, nullptr, nullptr }, { SASL_CB_AUTHNAME, nullptr, nullptr }, { SASL_CB_PASS, nullptr, nullptr }, { SASL_CB_CANON_USER, nullptr, nullptr }, - { SASL_CB_LIST_END, nullptr , nullptr } + { SASL_CB_LIST_END, nullptr, nullptr } }; - } -namespace KSmtp -{ - +namespace KSmtp { class LoginJobPrivate : public JobPrivate { public: LoginJobPrivate(LoginJob *job, Session *session, const QString &name) : JobPrivate(session, name) , m_preferedAuthMode(LoginJob::Login) , m_actualAuthMode(LoginJob::UnknownAuth) , m_encryptionMode(LoginJob::Unencrypted) , m_saslConn(nullptr) , m_saslClient(nullptr) , q(job) { } - ~LoginJobPrivate() override { } + ~LoginJobPrivate() override + { + } bool sasl_interact(); bool sasl_init(); bool sasl_challenge(const QByteArray &data); bool authenticate(); bool selectAuthentication(); LoginJob::AuthMode authModeFromCommand(const QByteArray &mech) const; QByteArray authCommand(LoginJob::AuthMode mode) const; QString m_userName; QString m_password; LoginJob::AuthMode m_preferedAuthMode; LoginJob::AuthMode m_actualAuthMode; LoginJob::EncryptionMode m_encryptionMode; sasl_conn_t *m_saslConn = nullptr; sasl_interact_t *m_saslClient = nullptr; private: - LoginJob * const q; + LoginJob *const q; }; } using namespace KSmtp; LoginJob::LoginJob(Session *session) : Job(*new LoginJobPrivate(this, session, i18n("Login"))) { } LoginJob::~LoginJob() { } void LoginJob::setUserName(const QString &userName) { Q_D(LoginJob); d->m_userName = userName; } void LoginJob::setPassword(const QString &password) { Q_D(LoginJob); d->m_password = password; } void LoginJob::setPreferedAuthMode(AuthMode mode) { Q_D(LoginJob); if (mode == UnknownAuth) { qCWarning(KSMTP_LOG) << "LoginJob: Cannot set preferred authentication mode to Unknown"; return; } d->m_preferedAuthMode = mode; } LoginJob::AuthMode LoginJob::usedAuthMode() const { return d_func()->m_actualAuthMode; } void LoginJob::setEncryptionMode(EncryptionMode mode) { Q_D(LoginJob); d->m_encryptionMode = mode; } LoginJob::EncryptionMode LoginJob::encryptionMode() const { return d_func()->m_encryptionMode; } void LoginJob::doStart() { Q_D(LoginJob); const auto negotiatedEnc = d->sessionInternal()->negotiatedEncryption(); if (negotiatedEnc != KTcpSocket::UnknownSslVersion) { // Socket already encrypted, pretend we did not want any d->m_encryptionMode = Unencrypted; } if (d->m_encryptionMode == SSLorTLS) { d->sessionInternal()->startSsl(KTcpSocket::SecureProtocols); } else if (d->m_encryptionMode == STARTTLS) { if (session()->allowsTls()) { sendCommand(QByteArrayLiteral("STARTTLS")); } else { qCWarning(KSMTP_LOG) << "STARTTLS not supported by the server!"; setError(KJob::UserDefinedError); setErrorText(i18n("STARTTLS is not supported by the server, try using SSL/TLS instead.")); emitResult(); } } else { if (!d->authenticate()) { emitResult(); } } } void LoginJob::handleResponse(const ServerResponse &r) { Q_D(LoginJob); // Handle server errors handleErrors(r); // Server accepts TLS connection if (r.isCode(220)) { d->sessionInternal()->startSsl(KTcpSocket::SecureProtocols); return; } // Available authentication mechanisms if (r.isCode(25) && r.text().startsWith("AUTH ")) { //krazy:exclude=strings d->sessionInternal()->setAuthenticationMethods(r.text().remove(0, QByteArray("AUTH ").count()).split(' ')); d->authenticate(); return; } // Send account data if (r.isCode(334)) { if (d->m_actualAuthMode == Plain) { - const QByteArray challengeResponse = '\0' + d->m_userName.toUtf8() + - '\0' + d->m_password.toUtf8(); + const QByteArray challengeResponse = '\0' + d->m_userName.toUtf8() + +'\0' + d->m_password.toUtf8(); sendCommand(challengeResponse.toBase64()); } else { if (!d->sasl_challenge(QByteArray::fromBase64(r.text()))) { emitResult(); } } return; } // Final agreement if (r.isCode(235)) { d->sessionInternal()->setState(Session::Authenticated); emitResult(); } } bool LoginJobPrivate::selectAuthentication() { const QStringList availableModes = m_session->availableAuthModes(); if (availableModes.contains(QString::fromLatin1(authCommand(m_preferedAuthMode)))) { m_actualAuthMode = m_preferedAuthMode; } else if (availableModes.contains(QString::fromLatin1(authCommand(LoginJob::Login)))) { m_actualAuthMode = LoginJob::Login; } else if (availableModes.contains(QString::fromLatin1(authCommand(LoginJob::Plain)))) { m_actualAuthMode = LoginJob::Plain; } else { qCWarning(KSMTP_LOG) << "LoginJob: Couldn't choose an authentication method. Please retry with : " << availableModes; q->setError(KJob::UserDefinedError); q->setErrorText(i18n("Could not authenticate to the SMTP server because no matching authentication method has been found")); return false; } return true; } bool LoginJobPrivate::sasl_init() { if (sasl_client_init(nullptr) != SASL_OK) { qCWarning(KSMTP_LOG) << "Failed to initialize SASL"; return false; } return true; } bool LoginJobPrivate::sasl_interact() { sasl_interact_t *interact = m_saslClient; while (interact->id != SASL_CB_LIST_END) { qCDebug(KSMTP_LOG) << "SASL_INTERACT Id" << interact->id; switch (interact->id) { - case SASL_CB_AUTHNAME: { - //case SASL_CB_USER: + case SASL_CB_AUTHNAME: + { + //case SASL_CB_USER: qCDebug(KSMTP_LOG) << "SASL_CB_[USER|AUTHNAME]: '" << m_userName << "'"; const auto username = m_userName.toUtf8(); interact->result = strdup(username.constData()); interact->len = username.size(); break; } - case SASL_CB_PASS: { + case SASL_CB_PASS: + { qCDebug(KSMTP_LOG) << "SASL_CB_PASS: [hidden]"; const auto pass = m_password.toUtf8(); interact->result = strdup(pass.constData()); interact->len = pass.size(); break; } default: interact->result = nullptr; interact->len = 0; break; } ++interact; } return true; } bool LoginJobPrivate::sasl_challenge(const QByteArray &challenge) { int result = -1; const char *out = nullptr; uint outLen = 0; if (m_actualAuthMode == LoginJob::XOAuth2) { QJsonDocument doc = QJsonDocument::fromJson(challenge); if (!doc.isNull() && doc.isObject()) { const auto obj = doc.object(); if (obj.value(QLatin1String("status")).toString() == QLatin1String("400")) { q->setError(LoginJob::TokenExpired); q->setErrorText(i18n("Token expired")); // https://developers.google.com/gmail/imap/xoauth2-protocol#error_response_2 // "The client sends an empty response ("\r\n") to the challenge containing the error message." q->sendCommand(""); return false; } } } Q_FOREVER { result = sasl_client_step(m_saslConn, challenge.isEmpty() ? nullptr : challenge.constData(), challenge.size(), &m_saslClient, &out, &outLen); if (result == SASL_INTERACT) { - if (!sasl_interact()){ + if (!sasl_interact()) { q->setError(LoginJob::UserDefinedError); sasl_dispose(&m_saslConn); return false; } - } else { + } else { break; } } if (result != SASL_OK && result != SASL_CONTINUE) { const QString saslError = QString::fromUtf8(sasl_errdetail(m_saslConn)); qCWarning(KSMTP_LOG) << "sasl_client_step failed: " << result << saslError; q->setError(LoginJob::UserDefinedError); q->setErrorText(saslError); sasl_dispose(&m_saslConn); return false; } q->sendCommand(QByteArray::fromRawData(out, outLen).toBase64()); return true; } bool LoginJobPrivate::authenticate() { if (!selectAuthentication()) { return false; } if (!sasl_init()) { q->setError(LoginJob::UserDefinedError); q->setErrorText(i18n("Login failed, cannot initialize the SASL library")); return false; } int result = sasl_client_new("smtp", m_session->hostName().toUtf8().constData(), nullptr, nullptr, callbacks, 0, &m_saslConn); if (result != SASL_OK) { const auto saslError = QString::fromUtf8(sasl_errdetail(m_saslConn)); q->setError(LoginJob::UserDefinedError); q->setErrorText(saslError); return false; } uint outLen = 0; const char *out = nullptr; const char *actualMech = nullptr; const auto authMode = authCommand(m_actualAuthMode); Q_FOREVER { qCDebug(KSMTP_LOG) << "Trying authmod" << authMode; result = sasl_client_start(m_saslConn, authMode.constData(), &m_saslClient, &out, &outLen, &actualMech); if (result == SASL_INTERACT) { if (!sasl_interact()) { sasl_dispose(&m_saslConn); q->setError(LoginJob::UserDefinedError); return false; } } else { break; } } m_actualAuthMode = authModeFromCommand(actualMech); if (result != SASL_CONTINUE && result != SASL_OK) { const auto saslError = QString::fromUtf8(sasl_errdetail(m_saslConn)); qCWarning(KSMTP_LOG) << "sasl_client_start failed with:" << result << saslError; q->setError(LoginJob::UserDefinedError); q->setErrorText(saslError); sasl_dispose(&m_saslConn); return false; } if (outLen == 0) { q->sendCommand("AUTH " + authMode); } else { q->sendCommand("AUTH " + authMode + ' ' + QByteArray::fromRawData(out, outLen).toBase64()); } return true; } LoginJob::AuthMode LoginJobPrivate::authModeFromCommand(const QByteArray &mech) const { if (qstrnicmp(mech.constData(), "PLAIN", 5) == 0) { return LoginJob::Plain; } else if (qstrnicmp(mech.constData(), "LOGIN", 5) == 0) { return LoginJob::Login; } else if (qstrnicmp(mech.constData(), "CRAM-MD5", 8) == 0) { return LoginJob::CramMD5; } else if (qstrnicmp(mech.constData(), "DIGEST-MD5", 10) == 0) { return LoginJob::DigestMD5; } else if (qstrnicmp(mech.constData(), "GSSAPI", 6) == 0) { return LoginJob::GSSAPI; } else if (qstrnicmp(mech.constData(), "NTLM", 4) == 0) { return LoginJob::NTLM; } else if (qstrnicmp(mech.constData(), "ANONYMOUS", 9) == 0) { return LoginJob::Anonymous; } else if (qstrnicmp(mech.constData(), "XOAUTH2", 7) == 0) { return LoginJob::XOAuth2; } else { return LoginJob::UnknownAuth; } } QByteArray LoginJobPrivate::authCommand(LoginJob::AuthMode mode) const { switch (mode) { case LoginJob::Plain: return QByteArrayLiteral("PLAIN"); case LoginJob::Login: return QByteArrayLiteral("LOGIN"); case LoginJob::CramMD5: return QByteArrayLiteral("CRAM-MD5"); case LoginJob::DigestMD5: return QByteArrayLiteral("DIGEST-MD5"); case LoginJob::GSSAPI: return QByteArrayLiteral("GSSAPI"); case LoginJob::NTLM: return QByteArrayLiteral("NTLM"); case LoginJob::Anonymous: return QByteArrayLiteral("ANONYMOUS"); case LoginJob::XOAuth2: return QByteArrayLiteral("XOAUTH2"); case LoginJob::UnknownAuth: return ""; // Should not happen } return {}; } diff --git a/src/loginjob.h b/src/loginjob.h index 4bc0983..e813092 100644 --- a/src/loginjob.h +++ b/src/loginjob.h @@ -1,79 +1,76 @@ /* 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_LOGINJOB_H #define KSMTP_LOGINJOB_H #include "ksmtp_export.h" #include "job.h" -namespace KSmtp -{ - +namespace KSmtp { class LoginJobPrivate; class KSMTP_EXPORT LoginJob : public Job { Q_OBJECT Q_DECLARE_PRIVATE(LoginJob) public: enum EncryptionMode { Unencrypted, SSLorTLS, ///< Use SSL/TLS encryption STARTTLS ///< Use STARTTLS to upgrade an unencrypted connection to encrypted }; enum AuthMode { UnknownAuth, Plain, Login, CramMD5, DigestMD5, NTLM, GSSAPI, Anonymous, XOAuth2 }; enum LoginError { TokenExpired = KJob::UserDefinedError + 1 }; explicit LoginJob(Session *session); ~LoginJob() override; void setUserName(const QString &userName); void setPassword(const QString &password); void setPreferedAuthMode(AuthMode mode); Q_REQUIRED_RESULT AuthMode usedAuthMode() const; void setEncryptionMode(EncryptionMode mode); Q_REQUIRED_RESULT EncryptionMode encryptionMode() const; protected: void doStart() override; void handleResponse(const ServerResponse &r) override; }; - } #endif // KSMTP_LOGINJOB_H diff --git a/src/sendjob.cpp b/src/sendjob.cpp index 5b20a08..6fe390b 100644 --- a/src/sendjob.cpp +++ b/src/sendjob.cpp @@ -1,234 +1,230 @@ /* 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 "sendjob.h" #include "job_p.h" #include "serverresponse_p.h" #include "ksmtp_debug.h" #include -namespace KSmtp -{ - +namespace KSmtp { class SendJobPrivate : public JobPrivate { public: enum Status { Idle, SendingReturnPath, SendingRecipients, SendingData }; SendJobPrivate(SendJob *job, Session *session, const QString &name) - : JobPrivate(session, name), - q(job), - m_status(Idle) + : JobPrivate(session, name) + , q(job) + , m_status(Idle) { } SendJob *q = nullptr; void sendNextRecipient(); void addRecipients(const QStringList &rcpts); bool prepare(); typedef struct { QString contentType; QString name; QByteArray content; } MessagePart; QString m_returnPath; QStringList m_recipients; QByteArray m_data; QStringList m_recipientsCopy; Status m_status; }; } using namespace KSmtp; SendJob::SendJob(Session *session) : Job(*new SendJobPrivate(this, session, i18n("SendJob"))) { } void SendJob::setFrom(const QString &from) { Q_D(SendJob); const auto start = from.indexOf(QLatin1Char('<')); if (start > -1) { const auto end = qMax(start, from.indexOf(QLatin1Char('>'), start)); d->m_returnPath = QStringLiteral("<%1>").arg(from.mid(start + 1, end - start - 1)); } else { d->m_returnPath = QStringLiteral("<%1>").arg(from); } } void SendJob::setTo(const QStringList &to) { Q_D(SendJob); d->addRecipients(to); } void SendJob::setCc(const QStringList &cc) { Q_D(SendJob); d->addRecipients(cc); } void SendJob::setBcc(const QStringList &bcc) { Q_D(SendJob); d->addRecipients(bcc); } void SendJob::setData(const QByteArray &data) { Q_D(SendJob); d->m_data = data; // A line with a single dot would make SMTP think "end of message", so use two dots in that case, // as per https://tools.ietf.org/html/rfc5321#section-4.5.2 d->m_data.replace("\r\n.", "\r\n.."); } void SendJob::doStart() { Q_D(SendJob); if (!d->prepare()) { setError(KJob::UserDefinedError); setErrorText(i18n("Could not send the message because either the sender or recipient field is missing or invalid")); emitResult(); return; } int sizeLimit = session()->sizeLimit(); if (sizeLimit > 0 && size() > sizeLimit) { setError(KJob::UserDefinedError); setErrorText(i18n("Could not send the message because it exceeds the maximum allowed size of %1 bytes. (Message size: %2 bytes.)", sizeLimit, size())); emitResult(); return; } d->m_status = SendJobPrivate::SendingReturnPath; sendCommand("MAIL FROM:" + d->m_returnPath.toUtf8()); } void SendJob::handleResponse(const ServerResponse &r) { Q_D(SendJob); // Handle server errors handleErrors(r); switch (d->m_status) { - case SendJobPrivate::Idle: //TODO: anything to do here? break; case SendJobPrivate::SendingReturnPath: // Expected response: server agreement if (r.isCode(25)) { d->m_status = SendJobPrivate::SendingRecipients; d->sendNextRecipient(); } break; case SendJobPrivate::SendingRecipients: // Expected response: server agreement if (r.isCode(25)) { if (d->m_recipientsCopy.isEmpty()) { sendCommand("DATA"); d->m_status = SendJobPrivate::SendingData; } else { d->sendNextRecipient(); } } break; case SendJobPrivate::SendingData: // Expected responses: // 354: Go ahead sending data if (r.isCode(354)) { sendCommand(d->m_data); sendCommand("\r\n."); } // 25x: Data received correctly if (r.isCode(25)) { emitResult(); } break; } } void SendJobPrivate::sendNextRecipient() { q->sendCommand("RCPT TO:<" + m_recipientsCopy.takeFirst().toUtf8() + '>'); } void SendJobPrivate::addRecipients(const QStringList &rcpts) { for (const auto &rcpt : rcpts) { if (rcpt.isEmpty()) { continue; } const int start = rcpt.indexOf(QLatin1Char('<')); if (start > -1) { const int end = qMax(start, rcpt.indexOf(QLatin1Char('>'), start)); m_recipients.push_back(rcpt.mid(start + 1, end - start - 1)); } else { m_recipients.push_back(rcpt); } } } bool SendJobPrivate::prepare() { if (m_data.isEmpty()) { qCWarning(KSMTP_LOG) << "A message has to be set before starting a SendJob"; return false; } m_recipientsCopy = m_recipients; if (m_recipients.isEmpty()) { qCWarning(KSMTP_LOG) << "Message has no recipients"; return false; } return true; } - int SendJob::size() const { Q_D(const SendJob); return d->m_data.size(); } diff --git a/src/sendjob.h b/src/sendjob.h index 04b4454..548b8fb 100644 --- a/src/sendjob.h +++ b/src/sendjob.h @@ -1,78 +1,75 @@ /* 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_SENDJOB_H #define KSMTP_SENDJOB_H #include "ksmtp_export.h" #include "job.h" -namespace KSmtp -{ - +namespace KSmtp { class SendJobPrivate; class KSMTP_EXPORT SendJob : public Job { Q_OBJECT Q_DECLARE_PRIVATE(SendJob) public: explicit SendJob(Session *session); /** * Set the sender email address */ void setFrom(const QString &from); /** * Add recipients. * */ void setTo(const QStringList &to); /** * Add recipients. */ void setCc(const QStringList &cc); /** * Add recipients. */ void setBcc(const QStringList &bcc); /** * Set the actual message data. */ void setData(const QByteArray &data); /** * Returns size of the encoded message data. */ Q_REQUIRED_RESULT int size() const; protected: void doStart() override; void handleResponse(const ServerResponse &r) override; }; - } #endif // KSMTP_SENDJOB_H diff --git a/src/serverresponse_p.h b/src/serverresponse_p.h index 2e2c30e..a763f37 100644 --- a/src/serverresponse_p.h +++ b/src/serverresponse_p.h @@ -1,46 +1,42 @@ /* Copyright 2010 BetterInbox Author: 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_SERVERRESPONSE_P_H #define KSMTP_SERVERRESPONSE_P_H #include -namespace KSmtp -{ - +namespace KSmtp { class ServerResponse { public: - explicit ServerResponse(int code = 0, const QByteArray &text = QByteArray(), - bool multiline = false); + explicit ServerResponse(int code = 0, const QByteArray &text = QByteArray(), bool multiline = false); int code() const; QByteArray text() const; bool isCode(int other) const; bool isMultiline() const; private: QByteArray m_text; int m_code; bool m_multiline; }; - } #endif //KSMTP_SERVERRESPONSE_P_H diff --git a/src/session.cpp b/src/session.cpp index aaf79c8..73ec420 100644 --- a/src/session.cpp +++ b/src/session.cpp @@ -1,529 +1,531 @@ /* 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 "session.h" #include "session_p.h" #include "sessionthread_p.h" #include "job.h" #include "serverresponse_p.h" #include "loginjob.h" #include "sendjob.h" #include "ksmtp_debug.h" #include #include #include #include #include using namespace KSmtp; Q_DECLARE_METATYPE(KTcpSocket::SslVersion) Q_DECLARE_METATYPE(KSslErrorUiData) SessionPrivate::SessionPrivate(Session *session) - : QObject(session), - q(session), - m_state(Session::Disconnected), - m_thread(nullptr), - m_socketTimerInterval(60000), - m_startLoop(nullptr), - m_sslVersion(KTcpSocket::UnknownSslVersion), - m_jobRunning(false), - m_currentJob(nullptr), - m_ehloRejected(false), - m_size(0), - m_allowsTls(false) + : QObject(session) + , q(session) + , m_state(Session::Disconnected) + , m_thread(nullptr) + , m_socketTimerInterval(60000) + , m_startLoop(nullptr) + , m_sslVersion(KTcpSocket::UnknownSslVersion) + , m_jobRunning(false) + , m_currentJob(nullptr) + , m_ehloRejected(false) + , m_size(0) + , m_allowsTls(false) { qRegisterMetaType(); qRegisterMetaType(); } SessionPrivate::~SessionPrivate() { m_thread->quit(); m_thread->wait(10000); delete m_thread; } void SessionPrivate::handleSslError(const KSslErrorUiData &data) { QPointer _t = m_thread; const bool ignore = m_uiProxy && m_uiProxy->ignoreSslError(data); if (_t) { _t->handleSslErrorResponse(ignore); } } void SessionPrivate::setAuthenticationMethods(const QList &authMethods) { for (const QByteArray &method : authMethods) { QString m = QString::fromLatin1(method); if (!m_authModes.contains(m)) { m_authModes.append(m); } } } void SessionPrivate::startHandshake() { QString hostname = m_customHostname; if (hostname.isEmpty()) { // FIXME: QHostInfo::fromName can get a FQDN, but does a DNS lookup hostname = QHostInfo::localHostName(); if (hostname.isEmpty()) { hostname = QStringLiteral("localhost.invalid"); } else if (!hostname.contains(QLatin1Char('.'))) { hostname += QStringLiteral(".localnet"); } } QByteArray cmd; if (!m_ehloRejected) { - cmd = "EHLO "; + cmd = "EHLO "; } else { - cmd = "HELO "; + cmd = "HELO "; } setState(Session::Handshake); sendData(cmd + QUrl::toAce(hostname)); } - - Session::Session(const QString &hostName, quint16 port, QObject *parent) - : QObject(parent), - d(new SessionPrivate(this)) - + : QObject(parent) + , d(new SessionPrivate(this)) { qRegisterMetaType("ServerResponse"); QHostAddress ip; QString saneHostName = hostName; if (ip.setAddress(hostName)) { //saneHostName = QStringLiteral("[%1]").arg(hostName); } d->m_thread = new SessionThread(saneHostName, port, this); d->m_thread->start(); connect(d->m_thread, &SessionThread::sslError, d, &SessionPrivate::handleSslError); } Session::~Session() { } void Session::setUseNetworkProxy(bool useProxy) { d->m_thread->setUseNetworkProxy(useProxy); } void Session::setUiProxy(const SessionUiProxy::Ptr &uiProxy) { d->m_uiProxy = uiProxy; } SessionUiProxy::Ptr Session::uiProxy() const { return d->m_uiProxy; } QString Session::hostName() const { return d->m_thread->hostName(); } quint16 Session::port() const { return d->m_thread->port(); } Session::State Session::state() const { return d->m_state; } bool Session::allowsTls() const { return d->m_allowsTls; } QStringList Session::availableAuthModes() const { return d->m_authModes; } int Session::sizeLimit() const { return d->m_size; } void Session::setSocketTimeout(int ms) { bool timerActive = d->m_socketTimer.isActive(); if (timerActive) { d->stopSocketTimer(); } d->m_socketTimerInterval = ms; if (timerActive) { d->startSocketTimer(); } } int Session::socketTimeout() const { return d->m_socketTimerInterval; } void Session::setCustomHostname(const QString &hostname) { d->m_customHostname = hostname; } QString Session::customHostname() const { return d->m_customHostname; } void Session::open() { QTimer::singleShot(0, d->m_thread, &SessionThread::reconnect); d->startSocketTimer(); } void Session::openAndWait() { QEventLoop loop(nullptr); d->m_startLoop = &loop; open(); d->m_startLoop->exec(); d->m_startLoop = nullptr; } void Session::quit() { if (d->m_state == Session::Disconnected) { return; } d->setState(Quitting); d->sendData("QUIT"); } void Session::quitAndWait() { if (d->m_state == Session::Disconnected) { return; } QEventLoop loop; connect(this, &Session::stateChanged, this, [&](Session::State state) { - if (state == Session::Disconnected) { - loop.quit(); - } - }); + if (state == Session::Disconnected) { + loop.quit(); + } + }); d->setState(Quitting); d->sendData("QUIT"); loop.exec(); } void SessionPrivate::setState(Session::State s) { if (m_state == s) { return; } m_state = s; Q_EMIT q->stateChanged(m_state); // After a handshake success or failure, exit the startup event loop if any if (m_startLoop && (m_state == Session::NotAuthenticated || m_state == Session::Disconnected)) { m_startLoop->quit(); } } void SessionPrivate::sendData(const QByteArray &data) { #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) - QMetaObject::invokeMethod(m_thread, [this, data] { m_thread->sendData(data); }, Qt::QueuedConnection); + QMetaObject::invokeMethod(m_thread, [this, data] { + m_thread->sendData(data); + }, Qt::QueuedConnection); #else QMetaObject::invokeMethod(m_thread, "sendData", Qt::QueuedConnection, Q_ARG(QByteArray, data)); #endif } void SessionPrivate::responseReceived(const ServerResponse &r) { //qCDebug(KSMTP_LOG) << "S:: [" << r.code() << "]" << (r.isMultiline() ? "-" : " ") << r.text(); if (m_state == Session::Quitting) { m_thread->closeSocket(); return; } if (m_state == Session::Handshake) { if (r.isCode(500) || r.isCode(502)) { if (!m_ehloRejected) { setState(Session::Ready); m_ehloRejected = true; } else { qCWarning(KSMTP_LOG) << "KSmtp::Session: Handshake failed with both EHLO and HELO"; q->quit(); return; } } else if (r.isCode(25)) { if (r.text().startsWith("SIZE ")) { //krazy:exclude=strings m_size = r.text().remove(0, QByteArray("SIZE ").count()).toInt(); } else if (r.text() == "STARTTLS") { m_allowsTls = true; } else if (r.text().startsWith("AUTH ")) { //krazy:exclude=strings setAuthenticationMethods(r.text().remove(0, QByteArray("AUTH ").count()).split(' ')); } if (!r.isMultiline()) { setState(Session::NotAuthenticated); startNext(); } } } if (m_state == Session::Ready) { if (r.isCode(22) || m_ehloRejected) { startHandshake(); return; } } if (m_currentJob) { m_currentJob->handleResponse(r); } } void SessionPrivate::socketConnected() { stopSocketTimer(); setState(Session::Ready); bool useSsl = false; if (!m_queue.isEmpty()) { - if (auto login = qobject_cast(m_queue.first())) { + if (auto login = qobject_cast(m_queue.first())) { useSsl = login->encryptionMode() == LoginJob::SSLorTLS; } } if (q->state() == Session::Ready && useSsl) { startNext(); } } void SessionPrivate::socketDisconnected() { qCDebug(KSMTP_LOG) << "Socket disconnected"; setState(Session::Disconnected); m_thread->closeSocket(); if (m_currentJob) { m_currentJob->connectionLost(); } else if (!m_queue.isEmpty()) { m_currentJob = m_queue.takeFirst(); m_currentJob->connectionLost(); } auto copy = m_queue; qDeleteAll(copy); m_queue.clear(); } void SessionPrivate::startSsl(KTcpSocket::SslVersion version) { #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) - QMetaObject::invokeMethod(m_thread, [this, version] {m_thread->startSsl(version); }, Qt::QueuedConnection); + QMetaObject::invokeMethod(m_thread, [this, version] { + m_thread->startSsl(version); + }, Qt::QueuedConnection); #else QMetaObject::invokeMethod(m_thread, "startSsl", Qt::QueuedConnection, Q_ARG(KTcpSocket::SslVersion, version)); #endif } KTcpSocket::SslVersion SessionPrivate::negotiatedEncryption() const { return m_sslVersion; } void SessionPrivate::encryptionNegotiationResult(bool encrypted, KTcpSocket::SslVersion version) { if (encrypted) { // Get the updated auth methods startHandshake(); } m_sslVersion = version; } void SessionPrivate::addJob(Job *job) { m_queue.append(job); //Q_EMIT q->jobQueueSizeChanged( q->jobQueueSize() ); connect(job, &KJob::result, this, &SessionPrivate::jobDone); connect(job, &KJob::destroyed, this, &SessionPrivate::jobDestroyed); if (m_state >= Session::NotAuthenticated) { startNext(); } else { m_thread->reconnect(); } } void SessionPrivate::startNext() { - QTimer::singleShot(0, this, [this]() { doStartNext(); }); + QTimer::singleShot(0, this, [this]() { + doStartNext(); + }); } void SessionPrivate::doStartNext() { if (m_queue.isEmpty() || m_jobRunning || m_state == Session::Disconnected) { return; } startSocketTimer(); m_jobRunning = true; m_currentJob = m_queue.dequeue(); m_currentJob->doStart(); // sending can take a while depending on bandwidth - don't fail with timeout // if it takes longer if (qobject_cast(m_currentJob)) { stopSocketTimer(); } } void SessionPrivate::jobDone(KJob *job) { Q_UNUSED(job); Q_ASSERT(job == m_currentJob); // If we're in disconnected state it's because we ended up // here because the inactivity timer triggered, so no need to // stop it (it is single shot) if (m_state != Session::Disconnected) { if (!qobject_cast(m_currentJob)) { stopSocketTimer(); } } m_jobRunning = false; m_currentJob = nullptr; //Q_EMIT q->jobQueueSizeChanged( q->jobQueueSize() ); startNext(); } void SessionPrivate::jobDestroyed(QObject *job) { m_queue.removeAll(static_cast(job)); if (m_currentJob == job) { m_currentJob = nullptr; } } void SessionPrivate::startSocketTimer() { if (m_socketTimerInterval < 0) { return; } Q_ASSERT(!m_socketTimer.isActive()); connect(&m_socketTimer, &QTimer::timeout, this, &SessionPrivate::onSocketTimeout); m_socketTimer.setSingleShot(true); m_socketTimer.start(m_socketTimerInterval); } void SessionPrivate::stopSocketTimer() { if (m_socketTimerInterval < 0) { return; } Q_ASSERT(m_socketTimer.isActive()); m_socketTimer.stop(); disconnect(&m_socketTimer, &QTimer::timeout, this, &SessionPrivate::onSocketTimeout); } void SessionPrivate::restartSocketTimer() { stopSocketTimer(); startSocketTimer(); } void SessionPrivate::onSocketTimeout() { socketDisconnected(); } - ServerResponse::ServerResponse(int code, const QByteArray &text, bool multiline) - : m_text(text), - m_code(code), - m_multiline(multiline) + : m_text(text) + , m_code(code) + , m_multiline(multiline) { } bool ServerResponse::isMultiline() const { return m_multiline; } int ServerResponse::code() const { return m_code; } QByteArray ServerResponse::text() const { return m_text; } bool ServerResponse::isCode(int other) const { int otherCpy = other; int codeLength = 0; if (other == 0) { codeLength = 1; } else { while (otherCpy > 0) { otherCpy /= 10; codeLength++; } } int div = 1; for (int i = 0; i < 3 - codeLength; i++) { div *= 10; } return m_code / div == other; } diff --git a/src/session.h b/src/session.h index 2d374da..2c3c023 100644 --- a/src/session.h +++ b/src/session.h @@ -1,170 +1,166 @@ /* 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_SESSION_H #define KSMTP_SESSION_H #include "ksmtp_export.h" #include "sessionuiproxy.h" #include - -namespace KSmtp -{ - +namespace KSmtp { class SessionPrivate; class SessionThread; class KSMTP_EXPORT Session : public QObject { Q_OBJECT public: enum State { Disconnected = 0, /**< The Session is not connected to the server. */ Ready, /**< (internal) */ Handshake, /**< (internal) */ NotAuthenticated, /**< The Session is ready for login. @sa KSmtp::LoginJob */ Authenticated, /**< The Session is ready to send email. @sa KSmtp::SendJob */ Quitting /**< (internal) */ }; Q_ENUM(State) /** Creates a new SMTP session to the specified host and port. After creating the session, call setUseProxy() if necessary and then either open() or openAndWait() to open the connection. @sa open(), openAndWait() */ explicit Session(const QString &hostName, quint16 port, QObject *parent = nullptr); ~Session() override; void setUiProxy(const SessionUiProxy::Ptr &uiProxy); Q_REQUIRED_RESULT SessionUiProxy::Ptr uiProxy() const; /** Sets whether the SMTP network connection should use the system proxy settings The default is to not use the proxy. */ void setUseNetworkProxy(bool useProxy); /** Returns the host name that has been provided in the Session's constructor @sa port() */ Q_REQUIRED_RESULT QString hostName() const; /** Returns the port number that has been provided in the Session's constructor @sa hostName() */ Q_REQUIRED_RESULT quint16 port() const; Q_REQUIRED_RESULT State state() const; /** Returns true if the SMTP server has indicated that it allows TLS connections, false otherwise. The session must be at least in the NotAuthenticated state. Before that, allowsTls() always returns false. @sa KSmtp::LoginJob::setUseTls() */ Q_REQUIRED_RESULT bool allowsTls() const; /** @todo: return parsed auth modes, instead of strings. */ Q_REQUIRED_RESULT QStringList availableAuthModes() const; /** Returns the maximum message size in bytes that the server accepts. You can use SendJob::size() to get the size of the message that you are trying to send @sa KSmtp::SendJob::size() */ Q_REQUIRED_RESULT int sizeLimit() const; Q_REQUIRED_RESULT int socketTimeout() const; void setSocketTimeout(int ms); /** * Custom hostname to send in EHLO/HELO command */ void setCustomHostname(const QString &hostname); Q_REQUIRED_RESULT QString customHostname() const; /** Opens the connection to the server. You should connect to stateChanged() before calling this method, and wait until the session's state is NotAuthenticated (Session is ready for a LoginJob) or Disconnected (connecting to the server failed) @sa openAndWait() */ void open(); /** Opens the connection to the server and blocks the execution until the Session is in the NotAuthenticated state (ready for a LoginJob) or Disconnected (connecting to the server failed) @sa open() */ void openAndWait(); /** Requests the server to quit the connection. This sends a "QUIT" command to the server and will not close the connection until it receives a response. That means you should not delete this object right after calling close, instead wait for stateChanged() to change to Disconnected, or use quitAndWait(). See RFC 821, Chapter 4.1.1, "QUIT". @sa quitAndWait() */ void quit(); /** Requests the server to quit the connection and blocks the execution until the server replies and closes the connection. See RFC 821, Chapter 4.1.1, "QUIT". @sa quit() */ void quitAndWait(); Q_SIGNALS: void stateChanged(KSmtp::Session::State state); void connectionError(const QString &error); private: friend class SessionPrivate; friend class SessionThread; friend class JobPrivate; SessionPrivate *const d; }; - } #endif // KSMTP_SESSION_H diff --git a/src/session_p.h b/src/session_p.h index 90151f6..09b6d6a 100644 --- a/src/session_p.h +++ b/src/session_p.h @@ -1,108 +1,105 @@ /* Copyright 2010 BetterInbox Author: 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_SESSION_P_H #define KSMTP_SESSION_P_H #include "session.h" #include #include #include #include #include class KJob; class QEventLoop; -namespace KSmtp -{ - +namespace KSmtp { class Job; class SessionThread; class ServerResponse; class KSMTP_EXPORT SessionPrivate : public QObject { Q_OBJECT friend class Session; public: explicit SessionPrivate(Session *session); ~SessionPrivate() override; void addJob(Job *job); void sendData(const QByteArray &data); void setState(Session::State s); void startSsl(KTcpSocket::SslVersion version); KTcpSocket::SslVersion negotiatedEncryption() const; public Q_SLOTS: void handleSslError(const KSslErrorUiData &data); void socketDisconnected(); void encryptionNegotiationResult(bool encrypted, KTcpSocket::SslVersion version); void responseReceived(const ServerResponse &response); void socketConnected(); void setAuthenticationMethods(const QList &authMethods); private Q_SLOTS: void doStartNext(); void jobDone(KJob *job); void jobDestroyed(QObject *job); void onSocketTimeout(); private: void startHandshake(); void startNext(); void startSocketTimer(); void stopSocketTimer(); void restartSocketTimer(); Session *const q; // Smtp session Session::State m_state; SessionThread *m_thread = nullptr; SessionUiProxy::Ptr m_uiProxy; int m_socketTimerInterval = 0; QTimer m_socketTimer; QEventLoop *m_startLoop = nullptr; KTcpSocket::SslVersion m_sslVersion; // Jobs bool m_jobRunning = false; Job *m_currentJob = nullptr; QQueue m_queue; // Smtp info bool m_ehloRejected = false; int m_size = 0; bool m_allowsTls = false; QStringList m_authModes; QString m_customHostname; }; - } #endif //KSMTP_SESSION_P_H diff --git a/src/sessionthread.cpp b/src/sessionthread.cpp index a952726..96f8160 100644 --- a/src/sessionthread.cpp +++ b/src/sessionthread.cpp @@ -1,274 +1,273 @@ /* 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 "sessionthread_p.h" #include "serverresponse_p.h" #include "session.h" #include "session_p.h" #include "ksmtp_debug.h" #include #include #include #include #include using namespace KSmtp; SessionThread::SessionThread(const QString &hostName, quint16 port, Session *session) - : QThread(), - m_socket(nullptr), - m_logFile(nullptr), - m_parentSession(session), - m_hostName(hostName), - m_port(port), - m_useProxy(false) + : QThread() + , m_socket(nullptr) + , m_logFile(nullptr) + , m_parentSession(session) + , m_hostName(hostName) + , m_port(port) + , m_useProxy(false) { moveToThread(this); const auto logfile = qgetenv("KSMTP_SESSION_LOG"); if (!logfile.isEmpty()) { static uint sSessionCount = 0; const QString filename = QStringLiteral("%1.%2.%3").arg(QString::fromUtf8(logfile)) - .arg(qApp->applicationPid()) - .arg(++sSessionCount); + .arg(qApp->applicationPid()) + .arg(++sSessionCount); m_logFile = new QFile(filename); if (!m_logFile->open(QIODevice::WriteOnly | QIODevice::Truncate)) { qCWarning(KSMTP_LOG) << "Failed to open log file" << filename << ":" << m_logFile->errorString(); delete m_logFile; m_logFile = nullptr; } } } SessionThread::~SessionThread() { delete m_logFile; } QString SessionThread::hostName() const { return m_hostName; } quint16 SessionThread::port() const { return m_port; } void SessionThread::sendData(const QByteArray &payload) { QMutexLocker locker(&m_mutex); //qCDebug(KSMTP_LOG) << "C:: " << payload; if (m_logFile) { m_logFile->write("C: " + payload + '\n'); m_logFile->flush(); } m_dataQueue.enqueue(payload + "\r\n"); QTimer::singleShot(0, this, &SessionThread::writeDataQueue); } void SessionThread::writeDataQueue() { QMutexLocker locker(&m_mutex); while (!m_dataQueue.isEmpty()) { m_socket->write(m_dataQueue.dequeue()); } } void SessionThread::readResponse() { QMutexLocker locker(&m_mutex); if (!m_socket->bytesAvailable()) { return; } const QByteArray data = m_socket->readLine(); //qCDebug(KSMTP_LOG) << "S:" << data; if (m_logFile) { m_logFile->write("S: " + data); m_logFile->flush(); } const ServerResponse response = parseResponse(data); Q_EMIT responseReceived(response); if (m_socket->bytesAvailable()) { QTimer::singleShot(0, this, &SessionThread::readResponse); } } void SessionThread::closeSocket() { QTimer::singleShot(0, this, &SessionThread::doCloseSocket); } void SessionThread::doCloseSocket() { m_socket->close(); } void SessionThread::reconnect() { QMutexLocker locker(&m_mutex); - if (m_socket->state() != KTcpSocket::ConnectedState && - m_socket->state() != KTcpSocket::ConnectingState) { - + if (m_socket->state() != KTcpSocket::ConnectedState + && m_socket->state() != KTcpSocket::ConnectingState) { if (!m_useProxy) { qCDebug(KSMTP_LOG) << "using no proxy"; QNetworkProxy proxy; proxy.setType(QNetworkProxy::NoProxy); m_socket->setProxy(proxy); } else { qCDebug(KSMTP_LOG) << "using default system proxy"; } m_socket->connectToHost(hostName(), port()); } } void SessionThread::run() { m_socket = new KTcpSocket; connect(m_socket, &KTcpSocket::readyRead, this, &SessionThread::readResponse, Qt::QueuedConnection); connect(m_socket, &KTcpSocket::disconnected, m_parentSession->d, &SessionPrivate::socketDisconnected); connect(m_socket, &KTcpSocket::connected, m_parentSession->d, &SessionPrivate::socketConnected); connect(m_socket, QOverload::of(&KTcpSocket::error), this, [this](KTcpSocket::Error err) { - qCWarning(KSMTP_LOG) << "Socket error:" << err << m_socket->errorString(); - Q_EMIT m_parentSession->connectionError(m_socket->errorString()); - }); + qCWarning(KSMTP_LOG) << "Socket error:" << err << m_socket->errorString(); + Q_EMIT m_parentSession->connectionError(m_socket->errorString()); + }); connect(this, &SessionThread::encryptionNegotiationResult, m_parentSession->d, &SessionPrivate::encryptionNegotiationResult); connect(this, &SessionThread::responseReceived, m_parentSession->d, &SessionPrivate::responseReceived); exec(); delete m_socket; } - void SessionThread::setUseNetworkProxy(bool useProxy) { m_useProxy = useProxy; } - ServerResponse SessionThread::parseResponse(const QByteArray &resp) { QByteArray response(resp); // Remove useless CRLF int indexOfCR = response.indexOf("\r"); int indexOfLF = response.indexOf("\n"); if (indexOfCR > 0) { response.truncate(indexOfCR); } if (indexOfLF > 0) { response.truncate(indexOfLF); } // Server response code QByteArray code = response.left(3); bool ok = false; const int returnCode = code.toInt(&ok); if (!ok) { return ServerResponse(); } // RFC821, Appendix E const bool multiline = (response.at(3) == '-'); if (returnCode) { response = response.mid(4); // Keep the text part } return ServerResponse(returnCode, response, multiline); } void SessionThread::startSsl(KTcpSocket::SslVersion version) { QMutexLocker locker(&m_mutex); m_socket->setAdvertisedSslVersion(version); m_socket->ignoreSslErrors(); connect(m_socket, &KTcpSocket::encrypted, this, &SessionThread::sslConnected); m_socket->startClientEncryption(); } void SessionThread::sslConnected() { QMutexLocker locker(&m_mutex); KSslCipher cipher = m_socket->sessionCipher(); if (!m_socket->sslErrors().isEmpty() || m_socket->encryptionMode() != KTcpSocket::SslClientMode - || cipher.isNull() || cipher.usedBits() == 0) { + || cipher.isNull() || cipher.usedBits() == 0) { qCDebug(KSMTP_LOG) << "Initial SSL handshake failed. cipher.isNull() is" << cipher.isNull() << ", cipher.usedBits() is" << cipher.usedBits() << ", the socket says:" << m_socket->errorString() << "and the list of SSL errors contains" << m_socket->sslErrors().count() << "items."; KSslErrorUiData errorData(m_socket); Q_EMIT sslError(errorData); } else { qCDebug(KSMTP_LOG) << "TLS negotiation done."; Q_EMIT encryptionNegotiationResult(true, m_socket->negotiatedSslVersion()); } } void SessionThread::handleSslErrorResponse(bool ignoreError) { #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) - QMetaObject::invokeMethod(this, [this, ignoreError] {doHandleSslErrorResponse(ignoreError); }, Qt::QueuedConnection); + QMetaObject::invokeMethod(this, [this, ignoreError] { + doHandleSslErrorResponse(ignoreError); + }, Qt::QueuedConnection); #else QMetaObject::invokeMethod(this, "doHandleSslErrorResponse", Qt::QueuedConnection, Q_ARG(bool, ignoreError)); #endif } void SessionThread::doHandleSslErrorResponse(bool ignoreError) { Q_ASSERT(QThread::currentThread() == thread()); if (!m_socket) { return; } if (ignoreError) { Q_EMIT encryptionNegotiationResult(true, m_socket->negotiatedSslVersion()); } else { //reconnect in unencrypted mode, so new commands can be issued m_socket->disconnectFromHost(); m_socket->waitForDisconnected(); m_socket->connectToHost(m_hostName, m_port); Q_EMIT encryptionNegotiationResult(false, KTcpSocket::UnknownSslVersion); } } diff --git a/src/sessionthread_p.h b/src/sessionthread_p.h index 5413619..ead8843 100644 --- a/src/sessionthread_p.h +++ b/src/sessionthread_p.h @@ -1,88 +1,85 @@ /* 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_SESSIONTHREAD_P_H #define KSMTP_SESSIONTHREAD_P_H #include #include #include #include class QFile; -namespace KSmtp -{ - +namespace KSmtp { class ServerResponse; class Session; class SessionThread : public QThread { Q_OBJECT public: explicit SessionThread(const QString &hostName, quint16 port, Session *session); ~SessionThread() override; Q_REQUIRED_RESULT QString hostName() const; Q_REQUIRED_RESULT quint16 port() const; void setUseNetworkProxy(bool useProxy); void handleSslErrorResponse(bool ignoreError); public Q_SLOTS: void reconnect(); void closeSocket(); void startSsl(KTcpSocket::SslVersion version); void sendData(const QByteArray &payload); Q_SIGNALS: void encryptionNegotiationResult(bool encrypted, KTcpSocket::SslVersion version); void responseReceived(const ServerResponse &response); void sslError(const KSslErrorUiData &); protected: void run() override; private Q_SLOTS: void sslConnected(); void writeDataQueue(); void readResponse(); void doCloseSocket(); void doHandleSslErrorResponse(bool ignoreError); private: ServerResponse parseResponse(const QByteArray &response); KTcpSocket *m_socket = nullptr; QMutex m_mutex; QQueue m_dataQueue; QFile *m_logFile = nullptr; Session *m_parentSession = nullptr; QString m_hostName; quint16 m_port; bool m_useProxy; }; - } #endif // KSMTP_SESSIONTHREAD_H diff --git a/src/sessionuiproxy.cpp b/src/sessionuiproxy.cpp index cd4a7e1..da793f1 100644 --- a/src/sessionuiproxy.cpp +++ b/src/sessionuiproxy.cpp @@ -1,22 +1,24 @@ /* Copyright (c) 2009 Andras Mantia 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 "sessionuiproxy.h" -KSmtp::SessionUiProxy::~SessionUiProxy() {} +KSmtp::SessionUiProxy::~SessionUiProxy() +{ +} diff --git a/src/sessionuiproxy.h b/src/sessionuiproxy.h index ac054aa..90a28ee 100644 --- a/src/sessionuiproxy.h +++ b/src/sessionuiproxy.h @@ -1,69 +1,66 @@ /* Copyright (c) 2009 Andras Mantia 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. */ #ifndef KSMTP_SESSIONUIPROXY_H #define KSMTP_SESSIONUIPROXY_H #include "ksmtp_export.h" #include "job.h" #include class KSslErrorUiData; -namespace KSmtp -{ - +namespace KSmtp { /** @short Interface to display communication errors and wait for user feedback. */ class KSMTP_EXPORT SessionUiProxy { public: typedef QSharedPointer Ptr; virtual ~SessionUiProxy(); /** * Show an SSL error and ask the user whether it should be ignored or not. * The recommended KDE UI is the following: * @code * #include * class UiProxy: public SessionUiProxy { * public: * bool ignoreSslError(const KSslErrorUiData& errorData) { * if (KIO::SslUi::askIgnoreSslErrors(errorData)) { * return true; * } else { * return false; * } * } * }; * [...] * Session session(server, port); * UiProxy *proxy = new UiProxy(); * session.setUiProxy(proxy); * @endcode * @param errorData contains details about the error. * @return true if the error can be ignored */ virtual bool ignoreSslError(const KSslErrorUiData &errorData) = 0; }; - } #endif