diff --git a/src/Composer/Submission.cpp b/src/Composer/Submission.cpp index ca9f278e..39058dde 100644 --- a/src/Composer/Submission.cpp +++ b/src/Composer/Submission.cpp @@ -1,460 +1,461 @@ /* Copyright (C) 2006 - 2014 Jan Kundrát Copyright (C) 2012 Peter Amidon This file is part of the Trojita Qt IMAP e-mail client, http://trojita.flaska.net/ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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, see . */ #include #include #include #include "Composer/Submission.h" #include "Composer/MessageComposer.h" #include "Imap/Model/Model.h" #include "Imap/Tasks/AppendTask.h" #include "Imap/Tasks/GenUrlAuthTask.h" #include "Imap/Tasks/UidSubmitTask.h" #include "MSA/Sendmail.h" #include "MSA/SMTP.h" namespace { const int PROGRESS_MAX = 1000; // We're very likely almost half-way there -- let's call it 45% const int PROGRESS_SAVING_DONE = PROGRESS_MAX * 0.45; // Updating flags might take roughly as much time as the URLAUTH const int PROGRESS_DELIVERY_DONE = PROGRESS_MAX * 0.95; const int PROGRESS_DELIVERY_START_WITHOUT_SAVING = PROGRESS_MAX * 0.10; const int PROGRESS_DELIVERY_START_WITH_SAVING = PROGRESS_MAX * 0.5; } namespace Composer { QString submissionProgressToString(const Submission::SubmissionProgress progress) { switch (progress) { case Submission::STATE_INIT: return QStringLiteral("INIT"); case Submission::STATE_BUILDING_MESSAGE: return QStringLiteral("BUILDING_MESSAGE"); case Submission::STATE_SAVING: return QStringLiteral("SAVING"); case Submission::STATE_PREPARING_URLAUTH: return QStringLiteral("PREPARING_URLAUTH"); case Submission::STATE_SUBMITTING: return QStringLiteral("SUBMITTING"); case Submission::STATE_UPDATING_FLAGS: return QStringLiteral("UPDATING_FLAGS"); case Submission::STATE_SENT: return QStringLiteral("SENT"); case Submission::STATE_FAILED: return QStringLiteral("FAILED"); } return QStringLiteral("[unknown: %1]").arg(QString::number(static_cast(progress))); } Submission::Submission(QObject *parent, std::shared_ptr composer, Imap::Mailbox::Model *model, MSA::MSAFactory *msaFactory, const QString &accountId) : QObject(parent) , m_appendUidReceived(false) , m_appendUidValidity(0) , m_appendUid(0) , m_genUrlAuthReceived(false) , m_saveToSentFolder(false) , m_useBurl(false) , m_useImapSubmit(false) , m_state(STATE_INIT) , m_msaMaximalProgress(0) , m_source(composer) , m_model(model) , m_msaFactory(msaFactory) , m_accountId(accountId) , m_updateReplyingToMessageFlagsTask(0) , m_updateForwardingMessageFlagsTask(0) { m_source->setPreloadEnabled(shouldBuildMessageLocally()); } Submission::~Submission() = default; QString Submission::accountId() const { return m_accountId; } void Submission::changeConnectionState(const SubmissionProgress state) { emit logged(Common::LOG_SUBMISSION, QStringLiteral("Submission"), QStringLiteral("Progress: %1 -> %2").arg( submissionProgressToString(m_state), submissionProgressToString(state))); m_state = state; // Now broadcast a human-readable message and update the progress dialog switch (state) { case STATE_INIT: emit progressMin(0); emit progressMax(0); emit progress(0); emit updateStatusMessage(tr("Preparing to send")); break; case STATE_BUILDING_MESSAGE: emit progressMax(0); emit progress(0); emit updateStatusMessage(tr("Creating message")); break; case STATE_SAVING: emit progressMax(0); emit progress(0); emit updateStatusMessage(tr("Saving to the sent folder")); break; case STATE_PREPARING_URLAUTH: emit progressMax(PROGRESS_MAX); emit progress(PROGRESS_SAVING_DONE); emit updateStatusMessage(tr("Preparing message for delivery")); break; case STATE_SUBMITTING: emit progressMax(PROGRESS_MAX); emit progress(m_saveToSentFolder ? PROGRESS_DELIVERY_START_WITH_SAVING : PROGRESS_DELIVERY_START_WITHOUT_SAVING); emit updateStatusMessage(tr("Submitting message")); break; case STATE_UPDATING_FLAGS: emit progressMax(PROGRESS_MAX); emit progress(PROGRESS_DELIVERY_DONE); emit updateStatusMessage(tr("Updating message keywords")); break; case STATE_SENT: emit progressMax(PROGRESS_MAX); emit progress(PROGRESS_MAX); emit updateStatusMessage(tr("Message sent")); break; case STATE_FAILED: // revert to the busy indicator emit progressMin(0); emit progressMax(0); emit progress(0); emit updateStatusMessage(tr("Sending failed")); break; } } void Submission::setImapOptions(const bool saveToSentFolder, const QString &sentFolderName, const QString &hostname, const QString &username, const bool useImapSubmit) { m_saveToSentFolder = saveToSentFolder; m_sentFolderName = sentFolderName; m_imapHostname = hostname; m_imapUsername = username; m_useImapSubmit = useImapSubmit; m_source->setPreloadEnabled(shouldBuildMessageLocally()); } void Submission::setSmtpOptions(const bool useBurl, const QString &smtpUsername) { m_useBurl = useBurl; if (m_useBurl && !m_model->isGenUrlAuthSupported()) { emit logged(Common::LOG_SUBMISSION, QStringLiteral("Submission"), QStringLiteral("Cannot BURL without the URLAUTH extension")); m_useBurl = false; } m_smtpUsername = smtpUsername; m_source->setPreloadEnabled(shouldBuildMessageLocally()); } void Submission::send() { if (!m_model) { gotError(tr("The IMAP connection has disappeared. " "You'll have close the composer, save the draft and re-open it later. " "The attachments will have to be added later. Sorry for the trouble, " "please see https://projects.flaska.net/issues/640 " "for details.")); return; } // this double-updating is needed in case the same Submission attempts to send a message more than once changeConnectionState(STATE_INIT); changeConnectionState(STATE_BUILDING_MESSAGE); if (shouldBuildMessageLocally() && !m_source->isReadyForSerialization()) { // we have to wait until the data arrive // FIXME: relax this to wait here gotError(tr("Some data are not available yet")); } else { slotMessageDataAvailable(); } } void Submission::slotMessageDataAvailable() { m_rawMessageData.clear(); QBuffer buf(&m_rawMessageData); buf.open(QIODevice::WriteOnly); QString errorMessage; QList catenateable; if (shouldBuildMessageLocally() && !m_source->asRawMessage(&buf, &errorMessage)) { gotError(tr("Cannot send right now -- saving failed:\n %1").arg(errorMessage)); return; } if (m_model->isCatenateSupported() && !m_source->asCatenateData(catenateable, &errorMessage)) { gotError(tr("Cannot send right now -- saving (CATENATE) failed:\n %1").arg(errorMessage)); return; } if (m_saveToSentFolder) { Q_ASSERT(m_model); m_appendUidReceived = false; m_genUrlAuthReceived = false; changeConnectionState(STATE_SAVING); QPointer appendTask = 0; if (m_model->isCatenateSupported()) { // FIXME: without UIDPLUS, there isn't much point in $SubmitPending... appendTask = QPointer( m_model->appendIntoMailbox( m_sentFolderName, catenateable, QStringList() << QStringLiteral("\\Seen"), m_source->timestamp())); } else { // FIXME: without UIDPLUS, there isn't much point in $SubmitPending... appendTask = QPointer( m_model->appendIntoMailbox( m_sentFolderName, m_rawMessageData, QStringList() << QStringLiteral("\\Seen"), m_source->timestamp())); } Q_ASSERT(appendTask); connect(appendTask.data(), &Imap::Mailbox::AppendTask::appendUid, this, &Submission::slotAppendUidKnown); connect(appendTask.data(), &Imap::Mailbox::ImapTask::completed, this, &Submission::slotAppendSucceeded); connect(appendTask.data(), &Imap::Mailbox::ImapTask::failed, this, &Submission::slotAppendFailed); } else { slotInvokeMsaNow(); } } void Submission::slotAskForUrl() { Q_ASSERT(m_appendUidReceived && m_useBurl); changeConnectionState(STATE_PREPARING_URLAUTH); Imap::Mailbox::GenUrlAuthTask *genUrlAuthTask = QPointer( m_model->generateUrlAuthForMessage(m_imapHostname, killDomainPartFromString(m_imapUsername), m_sentFolderName, m_appendUidValidity, m_appendUid, QString(), QStringLiteral("submit+%1").arg( killDomainPartFromString(m_smtpUsername)) )); connect(genUrlAuthTask, &Imap::Mailbox::GenUrlAuthTask::gotAuth, this, &Submission::slotGenUrlAuthReceived); connect(genUrlAuthTask, &Imap::Mailbox::ImapTask::failed, this, &Submission::gotError); } void Submission::slotInvokeMsaNow() { changeConnectionState(STATE_SUBMITTING); MSA::AbstractMSA *msa = m_msaFactory->create(this); connect(msa, &MSA::AbstractMSA::progressMax, this, &Submission::onMsaProgressMaxChanged); connect(msa, &MSA::AbstractMSA::progress, this, &Submission::onMsaProgressCurrentChanged); connect(msa, &MSA::AbstractMSA::sent, this, &Submission::sent); connect(msa, &MSA::AbstractMSA::error, this, &Submission::gotError); connect(msa, &MSA::AbstractMSA::passwordRequested, this, &Submission::passwordRequested); + connect(msa, &MSA::AbstractMSA::logged, this, &Submission::logged); connect(this, &Submission::gotPassword, msa, &MSA::AbstractMSA::setPassword); connect(this, &Submission::canceled, msa, &MSA::AbstractMSA::cancel); if (m_useImapSubmit && msa->supportsImapSending() && m_appendUidReceived) { Imap::Mailbox::UidSubmitOptionsList options; options.append(qMakePair("FROM", m_source->rawFromAddress())); Q_FOREACH(const QByteArray &recipient, m_source->rawRecipientAddresses()) { options.append(qMakePair("RECIPIENT", recipient)); } msa->sendImap(m_sentFolderName, m_appendUidValidity, m_appendUid, options); } else if (m_genUrlAuthReceived && m_useBurl) { msa->sendBurl(m_source->rawFromAddress(), m_source->rawRecipientAddresses(), m_urlauth.toUtf8()); } else { msa->sendMail(m_source->rawFromAddress(), m_source->rawRecipientAddresses(), m_rawMessageData); } } void Submission::setPassword(const QString &password) { emit gotPassword(password); } void Submission::cancelPassword() { emit canceled(); } void Submission::gotError(const QString &error) { emit logged(Common::LogKind::LOG_SUBMISSION, QStringLiteral("Submission"), QStringLiteral("Error: ") + error); changeConnectionState(STATE_FAILED); emit failed(error); } void Submission::sent() { if (m_source->replyingToMessage().isValid()) { m_updateReplyingToMessageFlagsTask = m_model->setMessageFlags(QModelIndexList() << m_source->replyingToMessage(), QStringLiteral("\\Answered"), Imap::Mailbox::FLAG_ADD); connect(m_updateReplyingToMessageFlagsTask, &Imap::Mailbox::ImapTask::completed, this, &Submission::onUpdatingFlagsOfReplyingToSucceded); connect(m_updateReplyingToMessageFlagsTask, &Imap::Mailbox::ImapTask::failed, this, &Submission::onUpdatingFlagsOfReplyingToFailed); changeConnectionState(STATE_UPDATING_FLAGS); } else if (m_source->forwardingMessage().isValid()) { m_updateForwardingMessageFlagsTask = m_model->setMessageFlags(QModelIndexList() << m_source->forwardingMessage(), QStringLiteral("$Forwarded"), Imap::Mailbox::FLAG_ADD); connect(m_updateForwardingMessageFlagsTask, &Imap::Mailbox::ImapTask::completed, this, &Submission::onUpdatingFlagsOfForwardingSucceeded); connect(m_updateForwardingMessageFlagsTask, &Imap::Mailbox::ImapTask::failed, this, &Submission::onUpdatingFlagsOfForwardingFailed); changeConnectionState(STATE_UPDATING_FLAGS); } else { changeConnectionState(STATE_SENT); emit succeeded(); } #if 0 if (m_appendUidReceived) { // FIXME: check the UIDVALIDITY!!! // FIXME: doesn't work at all; the messageIndexByUid() only works on already selected mailboxes QModelIndex message = m_mainWindow->imapModel()-> messageIndexByUid(QSettings().value(Common::SettingsNames::composerImapSentKey, QStringLiteral("Sent")).toString(), m_appendUid); if (message.isValid()) { m_mainWindow->imapModel()->setMessageFlags(QModelIndexList() << message, QLatin1String("\\Seen $Submitted"), Imap::Mailbox::FLAG_USE_THESE); } } #endif // FIXME: move back to the currently selected mailbox } /** @short Remember the APPENDUID as reported by the APPEND operation */ void Submission::slotAppendUidKnown(const uint uidValidity, const uint uid) { m_appendUidValidity = uidValidity; m_appendUid = uid; } void Submission::slotAppendFailed(const QString &error) { gotError(tr("APPEND failed: %1").arg(error)); } void Submission::slotAppendSucceeded() { if (m_appendUid && m_appendUidValidity) { // Only ever consider valid UIDVALIDITY/UID pair m_appendUidReceived = true; if (m_useBurl) { slotAskForUrl(); } else { slotInvokeMsaNow(); } } else { m_useBurl = false; emit logged(Common::LogKind::LOG_SUBMISSION, QStringLiteral("Submission"), QStringLiteral("APPEND does not contain APPENDUID or UIDVALIDITY, cannot use BURL or the SUBMIT command")); slotInvokeMsaNow(); } } /** @short Remember the GENURLAUTH response */ void Submission::slotGenUrlAuthReceived(const QString &url) { m_urlauth = url; if (!m_urlauth.isEmpty()) { m_genUrlAuthReceived = true; slotInvokeMsaNow(); } else { gotError(tr("The URLAUTH response does not contain a proper URL")); } } /** @short Remove the "@domain" from a string */ QString Submission::killDomainPartFromString(const QString &s) { return s.split(QLatin1Char('@'))[0]; } /** @short Return true if the message payload shall be built locally */ bool Submission::shouldBuildMessageLocally() const { if (!m_useImapSubmit) { // sending via SMTP or Sendmail // Unless all of URLAUTH, CATENATE and BURL is present and enabled, we will still have to download the data in the end return ! (m_useBurl && m_model->isCatenateSupported() && m_model->isGenUrlAuthSupported()); } else { return ! m_model->isCatenateSupported(); } } void Submission::onUpdatingFlagsOfReplyingToSucceded() { m_updateReplyingToMessageFlagsTask = 0; changeConnectionState(STATE_SENT); emit succeeded(); } void Submission::onUpdatingFlagsOfReplyingToFailed() { m_updateReplyingToMessageFlagsTask = 0; emit logged(Common::LogKind::LOG_OTHER, QStringLiteral("Submission"), QStringLiteral("Cannot update flags of the message we replied to -- interesting, but we cannot do anything at this point anyway")); changeConnectionState(STATE_SENT); emit succeeded(); } void Submission::onUpdatingFlagsOfForwardingSucceeded() { m_updateForwardingMessageFlagsTask = 0; changeConnectionState(STATE_SENT); emit succeeded(); } void Submission::onUpdatingFlagsOfForwardingFailed() { m_updateForwardingMessageFlagsTask = 0; emit logged(Common::LogKind::LOG_OTHER, QStringLiteral("Submission"), QStringLiteral("Cannot update flags of the message we forwarded -- interesting, but we cannot do anything at this point anyway")); changeConnectionState(STATE_SENT); emit succeeded(); } void Submission::onMsaProgressCurrentChanged(const int value) { if (m_msaMaximalProgress > 0) { // prevent division by zero or performing operations which do not make any sense int low = m_saveToSentFolder ? PROGRESS_DELIVERY_START_WITH_SAVING : PROGRESS_DELIVERY_START_WITHOUT_SAVING; int high = PROGRESS_DELIVERY_DONE; emit progress(1.0 * value / m_msaMaximalProgress * (high - low) + low); } } void Submission::onMsaProgressMaxChanged(const int max) { m_msaMaximalProgress = max; } } diff --git a/src/MSA/AbstractMSA.h b/src/MSA/AbstractMSA.h index 86e3329f..101a5ef4 100644 --- a/src/MSA/AbstractMSA.h +++ b/src/MSA/AbstractMSA.h @@ -1,66 +1,68 @@ /* Copyright (C) 2006 - 2014 Jan Kundrát This file is part of the Trojita Qt IMAP e-mail client, http://trojita.flaska.net/ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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, see . */ #ifndef ABSTRACTMSA_H #define ABSTRACTMSA_H #include #include +#include "Common/Logging.h" #include "Imap/Model/UidSubmitData.h" namespace MSA { class AbstractMSA : public QObject { Q_OBJECT public: explicit AbstractMSA(QObject *parent); virtual ~AbstractMSA(); virtual bool supportsBurl() const; virtual bool supportsImapSending() const; virtual void sendMail(const QByteArray &from, const QList &to, const QByteArray &data); virtual void sendBurl(const QByteArray &from, const QList &to, const QByteArray &imapUrl); virtual void sendImap(const QString &mailbox, const uint uidValidity, const uint uid, const Imap::Mailbox::UidSubmitOptionsList options); public slots: virtual void cancel() = 0; virtual void setPassword(const QString &password); signals: void connecting(); void passwordRequested(const QString &user, const QString &host); void sending(); void sent(); void error(const QString &message); void progressMax(int max); void progress(int num); + void logged(const Common::LogKind kind, const QString& source, const QString& message); }; /** @short Factory producing AbstractMSA instances */ class MSAFactory { public: virtual ~MSAFactory(); virtual AbstractMSA *create(QObject *parent) const = 0; }; } #endif // ABSTRACTMSA_H diff --git a/src/MSA/SMTP.cpp b/src/MSA/SMTP.cpp index cd7c4d23..88f9bdad 100644 --- a/src/MSA/SMTP.cpp +++ b/src/MSA/SMTP.cpp @@ -1,170 +1,179 @@ /* Copyright (C) 2006 - 2014 Jan Kundrát Copyright (C) 2013 Pali Rohár This file is part of the Trojita Qt IMAP e-mail client, http://trojita.flaska.net/ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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, see . */ #include "SMTP.h" namespace MSA { SMTP::SMTP(QObject *parent, const QString &host, quint16 port, bool encryptedConnect, bool startTls, bool auth, const QString &user): AbstractMSA(parent), host(host), port(port), encryptedConnect(encryptedConnect), startTls(startTls), auth(auth), user(user), failed(false), isWaitingForPassword(false), sendingMode(MODE_SMTP_INVALID) { qwwSmtp = new QwwSmtpClient(this); // FIXME: handle SSL errors properly connect(qwwSmtp, &QwwSmtpClient::sslErrors, qwwSmtp, &QwwSmtpClient::ignoreSslErrors); connect(qwwSmtp, &QwwSmtpClient::connected, this, &AbstractMSA::sending); connect(qwwSmtp, &QwwSmtpClient::done, this, &SMTP::handleDone); connect(qwwSmtp, &QwwSmtpClient::socketError, this, &SMTP::handleError); + connect(qwwSmtp, &QwwSmtpClient::logReceived, this, [this](const QByteArray& data) { + emit logged(Common::LogKind::LOG_IO_READ, QStringLiteral("SMTP"), QString::fromUtf8(data)); + }); + connect(qwwSmtp, &QwwSmtpClient::logSent, this, [this](const QByteArray& data) { + emit logged(Common::LogKind::LOG_IO_WRITTEN, QStringLiteral("SMTP"), QString::fromUtf8(data)); + }); + connect(qwwSmtp, &QwwSmtpClient::logSpecial, this, [this](const QString& data) { + emit logged(Common::LogKind::LOG_SUBMISSION, QStringLiteral("SMTP"), data); + }); } void SMTP::cancel() { qwwSmtp->disconnectFromHost(); if (!failed) { failed = true; emit error(tr("Sending of the message was cancelled")); } } void SMTP::handleDone(bool ok) { if (failed) { // This is a duplicate notification. The QwwSmtpClient is known to send contradicting results, see e.g. bug 321272. return; } if (ok) { emit sent(); } else { failed = true; if (qwwSmtp->errorString().isEmpty()) emit error(tr("Sending of the message failed.")); else emit error(tr("Sending of the message failed with the following error: %1").arg(qwwSmtp->errorString())); } } void SMTP::handleError(QAbstractSocket::SocketError err, const QString &msg) { Q_UNUSED(err); failed = true; emit error(msg); } void SMTP::setPassword(const QString &password) { pass = password; if (isWaitingForPassword) sendContinueGotPassword(); } void SMTP::sendMail(const QByteArray &from, const QList &to, const QByteArray &data) { this->from = from; this->to = to; this->data = data; this->sendingMode = MODE_SMTP_DATA; this->isWaitingForPassword = true; emit progressMax(data.size()); emit progress(0); emit connecting(); if (!auth || !pass.isEmpty()) { sendContinueGotPassword(); return; } emit passwordRequested(user, host); } void SMTP::sendContinueGotPassword() { isWaitingForPassword = false; if (encryptedConnect) qwwSmtp->connectToHostEncrypted(host, port); else qwwSmtp->connectToHost(host, port); if (startTls) qwwSmtp->startTls(); if (auth) qwwSmtp->authenticate(user, pass, QwwSmtpClient::AuthAny); emit sending(); // FIXME: later switch (sendingMode) { case MODE_SMTP_DATA: { //RFC5321 specifies to prepend a period to lines starting with a period in section 4.5.2 if (data.startsWith('.')) data.prepend('.'); data.replace("\n.", "\n.."); qwwSmtp->sendMail(from, to, data); } break; case MODE_SMTP_BURL: qwwSmtp->sendMailBurl(from, to, data); break; default: failed = true; emit error(tr("Unknown SMTP mode")); break; } qwwSmtp->disconnectFromHost(); } bool SMTP::supportsBurl() const { return true; } void SMTP::sendBurl(const QByteArray &from, const QList &to, const QByteArray &imapUrl) { this->from = from; this->to = to; this->data = imapUrl; this->sendingMode = MODE_SMTP_BURL; this->isWaitingForPassword = true; emit progressMax(1); emit progress(0); emit connecting(); if (!auth || !pass.isEmpty()) { sendContinueGotPassword(); return; } emit passwordRequested(user, host); } SMTPFactory::SMTPFactory(const QString &host, quint16 port, bool encryptedConnect, bool startTls, bool auth, const QString &user): m_host(host), m_port(port), m_encryptedConnect(encryptedConnect), m_startTls(startTls), m_auth(auth), m_user(user) { } SMTPFactory::~SMTPFactory() { } AbstractMSA *SMTPFactory::create(QObject *parent) const { return new SMTP(parent, m_host, m_port, m_encryptedConnect, m_startTls, m_auth, m_user); } } diff --git a/src/MSA/Sendmail.cpp b/src/MSA/Sendmail.cpp index 2e17baca..ff1b9a1a 100644 --- a/src/MSA/Sendmail.cpp +++ b/src/MSA/Sendmail.cpp @@ -1,120 +1,121 @@ /* Copyright (C) 2006 - 2014 Jan Kundrát This file is part of the Trojita Qt IMAP e-mail client, http://trojita.flaska.net/ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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, see . */ #include "Sendmail.h" namespace MSA { Sendmail::Sendmail(QObject *parent, const QString &command, const QStringList &args): AbstractMSA(parent), command(command), args(args) { proc = new QProcess(this); connect(proc, &QProcess::started, this, &Sendmail::handleStarted); connect(proc, static_cast(&QProcess::finished), this, &Sendmail::handleFinished); connect(proc, static_cast(&QProcess::error), this, &Sendmail::handleError); connect(proc, &QIODevice::bytesWritten, this, &Sendmail::handleBytesWritten); } Sendmail::~Sendmail() { proc->kill(); proc->waitForFinished(); } void Sendmail::sendMail(const QByteArray &from, const QList &to, const QByteArray &data) { // first +1 for the process startup // second +1 for waiting for the result emit progressMax(data.size() + 2); emit progress(0); QStringList myArgs = args; myArgs << QStringLiteral("-f") << QString::fromUtf8(from); for (QList::const_iterator it = to.begin(); it != to.end(); ++it) { // On posix systems, process args are bytearrays, not strings--- but QProcess // takes strings. myArgs << QString::fromUtf8(*it); } writtenSoFar = 0; emit connecting(); proc->start(command, myArgs); dataToSend = data; } void Sendmail::cancel() { proc->terminate(); } void Sendmail::handleStarted() { // The process has started already -> +1 emit progress(1); emit sending(); proc->write(dataToSend); + emit logged(Common::LogKind::LOG_IO_WRITTEN, QStringLiteral("sendmail"), QString::fromUtf8(dataToSend)); proc->closeWriteChannel(); } void Sendmail::handleError(QProcess::ProcessError e) { Q_UNUSED(e); emit error(tr("The sendmail process has failed: %1").arg(proc->errorString())); } void Sendmail::handleBytesWritten(qint64 bytes) { writtenSoFar += bytes; // +1 due to starting at one emit progress(writtenSoFar + 1); } void Sendmail::handleFinished(const int exitCode) { // that's the last one emit progressMax(dataToSend.size() + 2); if (exitCode == 0) { emit sent(); return; } QByteArray allStdout = proc->readAllStandardOutput(); QByteArray allStderr = proc->readAllStandardError(); emit error(tr("The sendmail process has failed (%1):\n%2\n%3").arg(QString::number(exitCode), QString::fromUtf8(allStdout), QString::fromUtf8(allStderr))); } SendmailFactory::SendmailFactory(const QString &command, const QStringList &args): m_command(command), m_args(args) { } SendmailFactory::~SendmailFactory() { } AbstractMSA *SendmailFactory::create(QObject *parent) const { return new Sendmail(parent, m_command, m_args); } } diff --git a/src/qwwsmtpclient/qwwsmtpclient.cpp b/src/qwwsmtpclient/qwwsmtpclient.cpp index 89d15b20..14593adb 100644 --- a/src/qwwsmtpclient/qwwsmtpclient.cpp +++ b/src/qwwsmtpclient/qwwsmtpclient.cpp @@ -1,719 +1,729 @@ // // C++ Implementation: qwwsmtpclient // // Description: // // // Author: Witold Wysota , (C) 2009 // // Copyright: See COPYING file that comes with this distribution // // #include "qwwsmtpclient.h" #include #include #include #include #include /* CONNECTION ESTABLISHMENT S: 220 E: 554 EHLO or HELO S: 250 E: 504, 550 MAIL S: 250 E: 552, 451, 452, 550, 553, 503 RCPT S: 250, 251 (but see section 3.4 for discussion of 251 and 551) E: 550, 551, 552, 553, 450, 451, 452, 503, 550 DATA I: 354 -> data -> S: 250 E: 552, 554, 451, 452 E: 451, 554, 503 RSET S: 250 VRFY S: 250, 251, 252 E: 550, 551, 553, 502, 504 EXPN S: 250, 252 E: 550, 500, 502, 504 HELP S: 211, 214 E: 502, 504 NOOP S: 250 QUIT S: 221 */ struct SMTPCommand { enum Type { Connect, Disconnect, StartTLS, Authenticate, Mail, MailBurl, RawCommand }; int id; Type type; QVariant data; QVariant extra; }; class QwwSmtpClientPrivate { public: QwwSmtpClientPrivate(QwwSmtpClient *qq) { q = qq; } QSslSocket *socket; QwwSmtpClient::State state; void setState(QwwSmtpClient::State s); void parseOption(const QString &buffer); void onConnected(); void onDisconnected(); void onError(QAbstractSocket::SocketError); void _q_readFromSocket(); void _q_encrypted(); void processNextCommand(bool ok = true); void abortDialog(); void sendAuthPlain(const QString &username, const QString &password); void sendAuthLogin(const QString &username, const QString &password, int stage); void sendEhlo(); void sendHelo(); void sendQuit(); void sendRcpt(); int lastId; bool inProgress; QString localName; QString localNameEncrypted; QString errorString; // server caps: QwwSmtpClient::Options options; QwwSmtpClient::AuthModes authModes; QQueue commandqueue; private: QwwSmtpClient *q; QwwSmtpClientPrivate(const QwwSmtpClientPrivate&); // don't implement QwwSmtpClientPrivate& operator=(const QwwSmtpClientPrivate&); // don't implement }; // private slot triggered upon connection to the server // - clears options // - notifies the environment void QwwSmtpClientPrivate::onConnected() { options = QwwSmtpClient::NoOptions; authModes = QwwSmtpClient::AuthNone; emit q->stateChanged(QwwSmtpClient::Connected); emit q->connected(); } // private slot triggered upon disconnection from the server // - checks the cause of disconnection // - aborts or continues processing void QwwSmtpClientPrivate::onDisconnected() { setState(QwwSmtpClient::Disconnected); if (commandqueue.isEmpty()) { inProgress = false; emit q->done(true); return; } if (commandqueue.head().type == SMTPCommand::Disconnect) { inProgress = false; emit q->done(true); return; } emit q->commandFinished(commandqueue.head().id, true); commandqueue.clear(); inProgress = false; emit q->done(false); } void QwwSmtpClientPrivate::onError(QAbstractSocket::SocketError e) { emit q->socketError(e, socket->errorString()); onDisconnected(); } // main logic of the component - a slot triggered upon data entering the socket // comments inline... void QwwSmtpClientPrivate::_q_readFromSocket() { while (socket->canReadLine()) { QString line = socket->readLine(); - qDebug() << "SMTP <<<" << line.toUtf8().constData(); + emit q->logReceived(line.toUtf8()); QRegExp rx("(\\d+)-(.*)\n"); // multiline response (aka 250-XYZ) QRegExp rxlast("(\\d+) (.*)\n"); // single or last line response (aka 250 XYZ) bool mid = rx.exactMatch(line); bool last = rxlast.exactMatch(line); // multiline if (mid){ int status = rx.cap(1).toInt(); SMTPCommand &cmd = commandqueue.head(); switch (cmd.type) { // trying to connect case SMTPCommand::Connect: { int stage = cmd.extra.toInt(); // stage 0 completed with success - socket is connected and EHLO was sent if(stage==1 && status==250){ QString arg = rx.cap(2).trimmed(); parseOption(arg); // we're probably receiving options } } break; // trying to establish deferred SSL handshake case SMTPCommand::StartTLS: { int stage = cmd.extra.toInt(); // stage 0 (negotiation) completed ok if(stage==1 && status==250){ QString arg = rx.cap(2).trimmed(); parseOption(arg); // we're probably receiving options } } default: break; } } else // single line if (last) { int status = rxlast.cap(1).toInt(); SMTPCommand &cmd = commandqueue.head(); switch (cmd.type) { // trying to connect case SMTPCommand::Connect: { int stage = cmd.extra.toInt(); // connection established, server sent its banner if (stage==0 && status==220) { sendEhlo(); // connect ok, send ehlo } // server responded to EHLO if (stage==1 && status==250){ // success (EHLO) parseOption(rxlast.cap(2).trimmed()); // we're probably receiving the last option errorString.clear(); setState(QwwSmtpClient::Connected); processNextCommand(); } // server responded to HELO (EHLO failed) if (state==2 && status==250) { // success (HELO) errorString.clear(); setState(QwwSmtpClient::Connected); processNextCommand(); } // EHLO failed, reason given in errorString if (stage==1 && (status==554 || status==501 || status==502 || status==421)) { errorString = rxlast.cap(2).trimmed(); sendHelo(); // ehlo failed, send helo cmd.extra = 2; } //abortDialog(); } break; // trying to establish a delayed SSL handshake case SMTPCommand::StartTLS: { int stage = cmd.extra.toInt(); // received an invitation from the server to enter TLS mode if (stage==0 && status==220) { - qDebug() << "SMTP ** startClientEncruption"; + emit q->logSpecial(QStringLiteral("startClientEncryption")); socket->startClientEncryption(); } // TLS established, connection is encrypted, EHLO was sent else if (stage==1 && status==250) { setState(QwwSmtpClient::Connected); parseOption(rxlast.cap(2).trimmed()); // we're probably receiving options errorString.clear(); emit q->tlsStarted(); processNextCommand(); } // starttls failed else { - qDebug() << "TLS failed at stage " << stage << ": " << line; + emit q->logSpecial(QStringLiteral("TLS failed at stage %1: %2").arg(QString::number(stage), line)); errorString = "TLS failed"; emit q->done(false); } } break; // trying to authenticate the client to the server case SMTPCommand::Authenticate: { int stage = cmd.extra.toInt(); if (stage==0 && status==334) { // AUTH mode was accepted by the server, 1st challenge sent QwwSmtpClient::AuthMode authmode = (QwwSmtpClient::AuthMode)cmd.data.toList().at(0).toInt(); errorString.clear(); switch (authmode) { case QwwSmtpClient::AuthPlain: sendAuthPlain(cmd.data.toList().at(1).toString(), cmd.data.toList().at(2).toString()); break; case QwwSmtpClient::AuthLogin: sendAuthLogin(cmd.data.toList().at(1).toString(), cmd.data.toList().at(2).toString(), 1); break; default: qWarning("I shouldn't be here"); setState(QwwSmtpClient::Connected); processNextCommand(); break; } cmd.extra = stage+1; } else if (stage==1 && status==334) { // AUTH mode and user names were acccepted by the server, 2nd challenge sent QwwSmtpClient::AuthMode authmode = (QwwSmtpClient::AuthMode)cmd.data.toList().at(0).toInt(); errorString.clear(); switch (authmode) { case QwwSmtpClient::AuthPlain: // auth failed setState(QwwSmtpClient::Connected); processNextCommand(); break; case QwwSmtpClient::AuthLogin: sendAuthLogin(cmd.data.toList().at(1).toString(), cmd.data.toList().at(2).toString(), 2); break; default: qWarning("I shouldn't be here"); setState(QwwSmtpClient::Connected); processNextCommand(); break; } } else if (stage==2 && status==334) { // auth failed errorString = rxlast.cap(2).trimmed(); setState(QwwSmtpClient::Connected); processNextCommand(); } else if (status==235) { // auth ok errorString.clear(); emit q->authenticated(); setState(QwwSmtpClient::Connected); processNextCommand(); } else { errorString = rxlast.cap(2).trimmed(); setState(QwwSmtpClient::Connected); emit q->done(false); } } break; // trying to send mail case SMTPCommand::Mail: case SMTPCommand::MailBurl: { int stage = cmd.extra.toInt(); // temporary failure upon receiving the sender address (greylisting probably) if (status==421 && stage==0) { errorString = rxlast.cap(2).trimmed(); // temporary envelope failure (greylisting) setState(QwwSmtpClient::Connected); processNextCommand(false); } if (status==250 && stage==0) { // sender accepted errorString.clear(); sendRcpt(); } else if (status==250 && stage==1) { // all receivers accepted if (cmd.type == SMTPCommand::MailBurl) { errorString.clear(); QByteArray url = cmd.data.toList().at(2).toByteArray(); - qDebug() << "SMTP >>> BURL" << url << "LAST"; - socket->write("BURL " + url + " LAST\r\n"); + auto data = "BURL " + url + " LAST\r\n"; + emit q->logSent(data); + socket->write(data); cmd.extra=2; } else { errorString.clear(); - qDebug() << "SMTP >>> DATA"; - socket->write("DATA\r\n"); + QByteArray data("DATA\r\n"); + emit q->logSent(data); + socket->write(data); cmd.extra=2; } } else if ((cmd.type == SMTPCommand::Mail && status==354 && stage==2)) { // DATA command accepted errorString.clear(); - QByteArray toBeWritten = cmd.data.toList().at(2).toByteArray(); - qDebug() << "SMTP >>>" << toBeWritten << "\r\n.\r\n"; + QByteArray toBeWritten = cmd.data.toList().at(2).toByteArray() + "\r\n.\r\n"; // termination token - CRLF.CRLF + emit q->logSent(toBeWritten); socket->write(toBeWritten); // expecting data to be already escaped (CRLF.CRLF) - socket->write("\r\n.\r\n"); // termination token - CRLF.CRLF cmd.extra=3; } else if ((cmd.type == SMTPCommand::MailBurl && status==250 && stage==2)) { // BURL succeeded setState(QwwSmtpClient::Connected); errorString.clear(); processNextCommand(); } else if ((cmd.type == SMTPCommand::Mail && status==250 && stage==3)) { // mail queued setState(QwwSmtpClient::Connected); errorString.clear(); processNextCommand(); } else { // something went wrong errorString = rxlast.cap(2).trimmed(); setState(QwwSmtpClient::Connected); emit q->done(false); processNextCommand(); } } default: break; } } else { qDebug() << "None of two regular expressions matched the input" << line; } } } void QwwSmtpClientPrivate::setState(QwwSmtpClient::State s) { QwwSmtpClient::State old = state; state = s; emit q->stateChanged(s); if (old == QwwSmtpClient::Connecting && s==QwwSmtpClient::Connected) emit q->connected(); if (s==QwwSmtpClient::Disconnected) emit q->disconnected(); } void QwwSmtpClientPrivate::processNextCommand(bool ok) { if (inProgress && !commandqueue.isEmpty()) { emit q->commandFinished(commandqueue.head().id, !ok); commandqueue.dequeue(); } if (commandqueue.isEmpty()) { inProgress = false; emit q->done(false); return; } SMTPCommand &cmd = commandqueue.head(); switch (cmd.type) { case SMTPCommand::Connect: { QString hostName = cmd.data.toList().at(0).toString(); uint port = cmd.data.toList().at(1).toUInt(); bool ssl = cmd.data.toList().at(2).toBool(); if(ssl){ - qDebug() << "SMTP ** connectToHostEncrypted"; + emit q->logSpecial(QStringLiteral("connectToHostEncrypted: %1:%2").arg(hostName, QString::number(port))); socket->connectToHostEncrypted(hostName, port); } else { - qDebug() << "SMTP ** connectToHost"; + emit q->logSpecial(QStringLiteral("connectToHost: %1:%2").arg(hostName, QString::number(port))); socket->connectToHost(hostName, port); } setState(QwwSmtpClient::Connecting); } break; case SMTPCommand::Disconnect: { sendQuit(); } break; case SMTPCommand::StartTLS: { - qDebug() << "SMTP >>> STARTTLS"; - socket->write("STARTTLS\r\n"); + QByteArray data("STARTTLS\r\n"); + emit q->logSent(data); + socket->write(data); setState(QwwSmtpClient::TLSRequested); } break; case SMTPCommand::Authenticate: { QwwSmtpClient::AuthMode authmode = (QwwSmtpClient::AuthMode)cmd.data.toList().at(0).toInt(); if (authmode == QwwSmtpClient::AuthAny){ bool modified = false; if (authModes.testFlag(QwwSmtpClient::AuthPlain)) { authmode = QwwSmtpClient::AuthPlain; modified = true; } else if (authModes.testFlag(QwwSmtpClient::AuthLogin)) { authmode = QwwSmtpClient::AuthLogin; modified = true; } if (modified) { QVariantList data = cmd.data.toList(); data[0] = (int)authmode; cmd.data = data; } } switch (authmode) { case QwwSmtpClient::AuthPlain: - qDebug() << "SMTP >>> AUTH PLAIN"; - socket->write("AUTH PLAIN\r\n"); + { + QByteArray buf("AUTH PLAIN\r\n"); + emit q->logSent(buf); + socket->write(buf); setState(QwwSmtpClient::Authenticating); break; + } case QwwSmtpClient::AuthLogin: - qDebug() << "SMTP >>> AUTH LOGIN"; - socket->write("AUTH LOGIN\r\n"); + { + QByteArray buf("AUTH LOGIN\r\n"); + emit q->logSent(buf); + socket->write(buf); setState(QwwSmtpClient::Authenticating); break; + } default: errorString = QwwSmtpClient::tr("Unsupported or unknown authentication scheme"); emit q->done(false); } } break; case SMTPCommand::Mail: case SMTPCommand::MailBurl: { setState(QwwSmtpClient::Sending); QByteArray buf = QByteArray("MAIL FROM:<").append(cmd.data.toList().at(0).toByteArray()).append(">\r\n"); - qDebug() << "SMTP >>>" << buf; + emit q->logSent(buf); socket->write(buf); break; } case SMTPCommand::RawCommand: { QString cont = cmd.data.toString(); if(!cont.endsWith("\r\n")) cont.append("\r\n"); setState(QwwSmtpClient::Sending); - qDebug() << "SMTP >>>" << cont; - socket->write(cont.toUtf8()); + auto buf = cont.toUtf8(); + emit q->logSent(buf); + socket->write(buf); } break; } inProgress = true; emit q->commandStarted(cmd.id); } void QwwSmtpClientPrivate::_q_encrypted() { options = QwwSmtpClient::NoOptions; // forget everything, restart ehlo // SMTPCommand &cmd = commandqueue.head(); sendEhlo(); } void QwwSmtpClientPrivate::sendEhlo() { SMTPCommand &cmd = commandqueue.head(); QString domain = localName; if (socket->isEncrypted() && !localNameEncrypted.isEmpty()) domain = localNameEncrypted; QByteArray buf = QString("EHLO "+domain+"\r\n").toUtf8(); - qDebug() << "SMTP >>>" << buf; + emit q->logSent(buf); socket->write(buf); cmd.extra = 1; } void QwwSmtpClientPrivate::sendHelo() { SMTPCommand &cmd = commandqueue.head(); QString domain = localName; if (socket->isEncrypted() && localNameEncrypted.isEmpty()) domain = localNameEncrypted; QByteArray buf = QString("HELO "+domain+"\r\n").toUtf8(); - qDebug() << "SMTP >>>" << buf; + emit q->logSent(buf); socket->write(buf); cmd.extra = 1; } void QwwSmtpClientPrivate::sendQuit() { - qDebug() << "SMTP >>> QUIT"; - socket->write("QUIT\r\n"); + QByteArray buf("QUIT\r\n"); + emit q->logSent(buf); + socket->write(buf); socket->waitForBytesWritten(1000); socket->disconnectFromHost(); setState(QwwSmtpClient::Disconnecting); } void QwwSmtpClientPrivate::sendRcpt() { SMTPCommand &cmd = commandqueue.head(); QVariantList vlist = cmd.data.toList(); QList rcptlist = vlist.at(1).toList(); QByteArray buf = QByteArray("RCPT TO:<").append(rcptlist.first().toByteArray()).append(">\r\n"); - qDebug() << "SMTP >>>" << buf; + emit q->logSent(buf); socket->write(buf); rcptlist.removeFirst(); vlist[1] = rcptlist; cmd.data = vlist; if (rcptlist.isEmpty()) cmd.extra = 1; } void QwwSmtpClientPrivate::sendAuthPlain(const QString & username, const QString & password) { QByteArray ba; ba.append('\0'); ba.append(username.toUtf8()); ba.append('\0'); ba.append(password.toUtf8()); QByteArray encoded = ba.toBase64(); - qDebug() << "SMTP <<< [authentication data]"; + emit q->logSpecial(QStringLiteral("[sending authentication data: username '%1']").arg(username)); socket->write(encoded); socket->write("\r\n"); } void QwwSmtpClientPrivate::sendAuthLogin(const QString & username, const QString & password, int stage) { if (stage==1) { - qDebug() << "SMTP <<< [login username]"; - socket->write(username.toUtf8().toBase64()); - socket->write("\r\n"); + auto buf = username.toUtf8().toBase64() + "\r\n"; + emit q->logSent(buf); + socket->write(buf); } else if (stage==2) { - qDebug() << "SMTP <<< [login password]"; + emit q->logSpecial(QStringLiteral("[AUTH LOGIN password]")); socket->write(password.toUtf8().toBase64()); socket->write("\r\n"); } } void QwwSmtpClientPrivate::parseOption(const QString &buffer){ if(buffer.toLower()=="pipelining"){ options |= QwwSmtpClient::PipeliningOption; } else if(buffer.toLower()=="starttls"){ options |= QwwSmtpClient::StartTlsOption; } else if(buffer.toLower()=="8bitmime"){ options |= QwwSmtpClient::EightBitMimeOption; } else if(buffer.toLower().startsWith("auth ")){ options |= QwwSmtpClient::AuthOption; // parse auth modes QStringList slist = buffer.mid(5).split(" "); foreach(const QString &s, slist){ if(s.toLower()=="plain"){ authModes |= QwwSmtpClient::AuthPlain; } if(s.toLower()=="login"){ authModes |= QwwSmtpClient::AuthLogin; } } } } QwwSmtpClient::QwwSmtpClient(QObject *parent) : QObject(parent), d(new QwwSmtpClientPrivate(this)) { d->state = Disconnected; d->lastId = 0; d->inProgress = false; d->localName = "localhost"; d->socket = new QSslSocket(this); connect(d->socket, SIGNAL(connected()), this, SLOT(onConnected())); connect(d->socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError)) ); connect(d->socket, SIGNAL(disconnected()), this, SLOT(onDisconnected())); connect(d->socket, SIGNAL(readyRead()), this, SLOT(_q_readFromSocket())); connect(d->socket, SIGNAL(sslErrors(const QList &)), this, SIGNAL(sslErrors(const QList&))); } QwwSmtpClient::~QwwSmtpClient() { delete d; } int QwwSmtpClient::connectToHost(const QString & hostName, quint16 port) { SMTPCommand cmd; cmd.type = SMTPCommand::Connect; cmd.data = QVariantList() << hostName << port << false; cmd.id = ++d->lastId; d->commandqueue.enqueue(cmd); if (!d->inProgress) d->processNextCommand(); return cmd.id; } // int QwwSmtpClient::connectToHost(const QHostAddress & address, quint16 port) { // d->socket->connectToHost(address, port); // d->setState(Connecting); // } int QwwSmtpClient::connectToHostEncrypted(const QString & hostName, quint16 port) { SMTPCommand cmd; cmd.type = SMTPCommand::Connect; cmd.data = QVariantList() << hostName << port << true; cmd.id = ++d->lastId; d->commandqueue.enqueue(cmd); if(!d->inProgress) d->processNextCommand(); return cmd.id; } int QwwSmtpClient::disconnectFromHost() { SMTPCommand cmd; cmd.type = SMTPCommand::Disconnect; cmd.id = ++d->lastId; d->commandqueue.enqueue(cmd); if (!d->inProgress) d->processNextCommand(); return cmd.id; } int QwwSmtpClient::startTls() { connect(d->socket, SIGNAL(encrypted()), this, SLOT(_q_encrypted()), Qt::UniqueConnection); SMTPCommand cmd; cmd.type = SMTPCommand::StartTLS; cmd.id = ++d->lastId; d->commandqueue.enqueue(cmd); if (!d->inProgress) d->processNextCommand(); return cmd.id; } void QwwSmtpClient::setLocalName(const QString & ln) { d->localName = ln; } void QwwSmtpClient::setLocalNameEncrypted(const QString & ln) { d->localNameEncrypted = ln; } int QwwSmtpClient::authenticate(const QString &user, const QString &password, AuthMode mode) { SMTPCommand cmd; cmd.type = SMTPCommand::Authenticate; cmd.data = QVariantList() << (int)mode << user << password; cmd.id = ++d->lastId; d->commandqueue.enqueue(cmd); if (!d->inProgress) d->processNextCommand(); return cmd.id; } int QwwSmtpClient::sendMail(const QByteArray &from, const QList &to, const QByteArray &content) { QList rcpts; for(QList::const_iterator it = to.begin(); it != to.end(); it ++) { rcpts.append(QVariant(*it)); } SMTPCommand cmd; cmd.type = SMTPCommand::Mail; cmd.data = QVariantList() << from << QVariant(rcpts) << content; cmd.id = ++d->lastId; d->commandqueue.enqueue(cmd); if (!d->inProgress) d->processNextCommand(); return cmd.id; } int QwwSmtpClient::sendMailBurl(const QByteArray &from, const QList &to, const QByteArray &url) { QList rcpts; for(QList::const_iterator it = to.begin(); it != to.end(); it ++) { rcpts.append(QVariant(*it)); } SMTPCommand cmd; cmd.type = SMTPCommand::MailBurl; cmd.data = QVariantList() << from << QVariant(rcpts) << url; cmd.id = ++d->lastId; d->commandqueue.enqueue(cmd); if (!d->inProgress) d->processNextCommand(); return cmd.id; } int QwwSmtpClient::rawCommand(const QString & raw) { SMTPCommand cmd; cmd.type = SMTPCommand::RawCommand; cmd.data = raw; cmd.id = ++d->lastId; d->commandqueue.enqueue(cmd); if (!d->inProgress) d->processNextCommand(); return cmd.id; } void QwwSmtpClientPrivate::abortDialog() { emit q->commandFinished(commandqueue.head().id, true); commandqueue.clear(); sendQuit(); } void QwwSmtpClient::ignoreSslErrors() {d->socket->ignoreSslErrors(); } QwwSmtpClient::AuthModes QwwSmtpClient::supportedAuthModes() const{ return d->authModes; } QwwSmtpClient::Options QwwSmtpClient::options() const{ return d->options; } QString QwwSmtpClient::errorString() const{ return d->errorString; } #include "moc_qwwsmtpclient.cpp" diff --git a/src/qwwsmtpclient/qwwsmtpclient.h b/src/qwwsmtpclient/qwwsmtpclient.h index 826b48b2..922afe62 100644 --- a/src/qwwsmtpclient/qwwsmtpclient.h +++ b/src/qwwsmtpclient/qwwsmtpclient.h @@ -1,105 +1,108 @@ // // C++ Interface: qwwsmtpclient // // Description: // // // Author: Witold Wysota , (C) 2009 // // Copyright: See COPYING file that comes with this distribution // // #ifndef QWWSMTPCLIENT_H #define QWWSMTPCLIENT_H #include #include #include #include class QwwSmtpClientPrivate; /*! \class QwwSmtpClient \author Witold Wysota \brief Cross-platform asynchronous handling of client side SMTP connections Features: Connection mode - open, TLS, SSL Authentication - PLAIN, LOGIN Handshake - HELO, EHLO - low-level mail sending (everything you pass, goes through to the server) - raw command sending - multiple rcpt - option reporting \todo CRAM-MD5 Authentication VRFY abort() SSL errors handling network errors error handling (status codes, etc.) */ class QwwSmtpClient : public QObject { Q_OBJECT Q_ENUMS(State); Q_FLAGS(Options); Q_ENUMS(AuthMode); Q_FLAGS(AuthModes); public: explicit QwwSmtpClient(QObject *parent = 0); ~QwwSmtpClient(); enum State { Disconnected, Connecting, Connected, TLSRequested, Authenticating, Sending, Disconnecting }; enum Option { NoOptions = 0, StartTlsOption, SizeOption, PipeliningOption, EightBitMimeOption, AuthOption }; Q_DECLARE_FLAGS ( Options, Option ); enum AuthMode { AuthNone = 0, AuthAny = 1, AuthPlain = 2, AuthLogin = 4 }; Q_DECLARE_FLAGS ( AuthModes, AuthMode ); void setLocalName(const QString &ln); void setLocalNameEncrypted(const QString &ln); int connectToHost ( const QString & hostName, quint16 port = 25); int connectToHostEncrypted(const QString &hostName, quint16 port = 465); // int connectToHost ( const QHostAddress & address, quint16 port = 25); int authenticate(const QString &user, const QString &password, AuthMode mode = AuthAny); int sendMail(const QByteArray &from, const QList &to, const QByteArray &content); int sendMailBurl(const QByteArray &from, const QList &to, const QByteArray &url); int rawCommand(const QString &cmd); AuthModes supportedAuthModes() const; Options options() const; QString errorString() const; public slots: int disconnectFromHost(); int startTls(); void ignoreSslErrors(); signals: void done(bool); void connected(); void disconnected(); void stateChanged(State); void commandFinished(int, bool error); void commandStarted(int); void tlsStarted(); void authenticated(); void rawCommandReply(int code, const QString &details); void sslErrors(const QList &); void socketError(QAbstractSocket::SocketError err, const QString& message); + void logReceived(const QByteArray& data); + void logSent(const QByteArray& data); + void logSpecial(const QString& message); private: QwwSmtpClientPrivate *d; Q_PRIVATE_SLOT(d, void onConnected()); Q_PRIVATE_SLOT(d, void onDisconnected()); Q_PRIVATE_SLOT(d, void onError(QAbstractSocket::SocketError)); Q_PRIVATE_SLOT(d, void _q_readFromSocket()); Q_PRIVATE_SLOT(d, void _q_encrypted()); friend class QwwSmtpClientPrivate; QwwSmtpClient(const QwwSmtpClient&); // don't implement QwwSmtpClient& operator=(const QwwSmtpClient&); // don't implement }; #endif