diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,7 @@ set(KMIME_LIB_VERSION "5.6.40") set(AKONADI_LIB_VERSION "5.6.40") set(AKONADIMIME_LIB_VERSION "5.6.40") +set(KSMTP_LIB_VERSION "5.6.40") set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF5MailTransport") ########### Find packages ########### @@ -42,6 +43,7 @@ find_package(KF5Mime ${KMIME_LIB_VERSION} CONFIG REQUIRED) find_package(KF5Akonadi ${AKONADI_LIB_VERSION} CONFIG REQUIRED) find_package(KF5AkonadiMime ${AKONADIMIME_LIB_VERSION} CONFIG REQUIRED) +find_package(KPimSMTP ${KSMTP_LIB_VERSION} CONFIG VERSION) add_definitions("-DQT_NO_CAST_FROM_ASCII -DQT_NO_CAST_TO_ASCII") add_definitions(-DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT) @@ -61,7 +63,6 @@ ########### Targets ########### add_subdirectory(cmake) add_subdirectory(src) -add_subdirectory(kioslave) install( FILES kmailtransport.renamecategories kmailtransport.categories DESTINATION ${KDE_INSTALL_CONFDIR} ) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/kioslave/.krazy b/kioslave/.krazy deleted file mode 100644 --- a/kioslave/.krazy +++ /dev/null @@ -1 +0,0 @@ -SKIP /tests/ diff --git a/kioslave/CMakeLists.txt b/kioslave/CMakeLists.txt deleted file mode 100644 --- a/kioslave/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -add_definitions("-DQT_NO_CAST_FROM_ASCII -DQT_NO_CAST_TO_ASCII") -add_subdirectory(src) -add_subdirectory(doc) diff --git a/kioslave/doc/CMakeLists.txt b/kioslave/doc/CMakeLists.txt deleted file mode 100644 --- a/kioslave/doc/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -add_subdirectory(smtp) diff --git a/kioslave/doc/smtp/CMakeLists.txt b/kioslave/doc/smtp/CMakeLists.txt deleted file mode 100644 --- a/kioslave/doc/smtp/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -########### install files ############### -kdoctools_create_handbook(index.docbook INSTALL_DESTINATION ${KDE_INSTALL_DOCBUNDLEDIR}/en SUBDIR kioslave5/smtp) - diff --git a/kioslave/doc/smtp/index.docbook b/kioslave/doc/smtp/index.docbook deleted file mode 100644 --- a/kioslave/doc/smtp/index.docbook +++ /dev/null @@ -1,23 +0,0 @@ - - - -]> - -
-smtp - - -&Ferdinand.Gassauer; &Ferdinand.Gassauer.mail; - - - - -A protocol to send mail from the client workstation to the mail server. - - - See : Simple Mail Transfer Protocol . - - -
diff --git a/kioslave/src/CMakeLists.txt b/kioslave/src/CMakeLists.txt deleted file mode 100644 --- a/kioslave/src/CMakeLists.txt +++ /dev/null @@ -1,2 +0,0 @@ -add_subdirectory(smtp) - diff --git a/kioslave/src/common.h b/kioslave/src/common.h deleted file mode 100644 --- a/kioslave/src/common.h +++ /dev/null @@ -1,55 +0,0 @@ -/* This file is part of the KDE project - Copyright (C) 2008 Jarosław Staniek - - 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 _KIOSLAVE_COMMON_H -#define _KIOSLAVE_COMMON_H - -#include -#include -#include -#include - -extern "C" { -#include -} - -inline bool initSASL() -{ -#ifdef Q_OS_WIN //krazy:exclude=cpp - for (const auto &path : QCoreApplication::libraryPaths()) { - QDir dir(path); - if (dir.exists(QStringLiteral("sasl2"))) { - auto libInstallPath = QFile::encodeName(dir.absoluteFilePath(QStringLiteral("sasl2"))); - if (sasl_set_path(SASL_PATH_TYPE_PLUGIN, libInstallPath.data()) != SASL_OK) { - fprintf(stderr, "SASL path initialization failed!\n"); - return false; - } - break; - } - } -#endif - - if (sasl_client_init(NULL) != SASL_OK) { - fprintf(stderr, "SASL library initialization failed!\n"); - return false; - } - return true; -} - -#endif diff --git a/kioslave/src/smtp/CMakeLists.txt b/kioslave/src/smtp/CMakeLists.txt deleted file mode 100644 --- a/kioslave/src/smtp/CMakeLists.txt +++ /dev/null @@ -1,47 +0,0 @@ - - -if (BUILD_TESTING) -add_subdirectory(tests) -endif() -set(smtp_optional_includes) -set(smtp_optional_libs) - -if (Sasl2_FOUND) - set(smtp_optional_includes ${smtp_optional_includes} ${Sasl2_INCLUDE_DIRS}) - set(smtp_optional_libs ${smtp_optional_libs} ${Sasl2_LIBRARIES}) -endif() - - -include_directories( ${smtp_optional_includes} ) - - -########### next target ############### - -set(kio_smtp_PART_SRCS - smtp.cpp - request.cpp - response.cpp - capabilities.cpp - command.cpp - transactionstate.cpp - smtpsessioninterface.cpp - kioslavesession.cpp -) - -ecm_qt_declare_logging_category(kio_smtp_PART_SRCS HEADER smtp_debug.h IDENTIFIER SMTP_LOG CATEGORY_NAME org.kde.pim.smtp) - -add_library(kio_smtp MODULE ${kio_smtp_PART_SRCS}) - - -target_link_libraries(kio_smtp KF5::KIOCore KF5::I18n Qt5::Network ${smtp_optional_libs}) -if (WIN32) - target_link_libraries(kio_smtp ws2_32) -endif() -set_target_properties(kio_smtp PROPERTIES OUTPUT_NAME "smtp") - -install(TARGETS kio_smtp DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf5/kio/ ) - -########### install files ############### - -install( FILES smtp.protocol smtps.protocol DESTINATION ${KDE_INSTALL_KSERVICES5DIR} ) - diff --git a/kioslave/src/smtp/Messages.sh b/kioslave/src/smtp/Messages.sh deleted file mode 100644 --- a/kioslave/src/smtp/Messages.sh +++ /dev/null @@ -1,2 +0,0 @@ -#! /usr/bin/env bash -$XGETTEXT *.cpp -o $podir/kio_smtp.pot diff --git a/kioslave/src/smtp/TODO b/kioslave/src/smtp/TODO deleted file mode 100644 --- a/kioslave/src/smtp/TODO +++ /dev/null @@ -1,11 +0,0 @@ -1. Double check the error handling and review error message in various - failure modes. -2. Implement the CHUNKING extension (rfc 3030; as soon as I find an - SMTP server that supports it). -3. Better error message (translated standard meanings of the known - response codes, ENHANCEDSTATUSCODES extension (rfc2034)). -4. (KIO) MultiPutJob to support pipelining across messages. -5. Ged rid of slave's header generation after checking who on earth - uses that... - -and further refactoring to make the code pleasant to look at ;-) diff --git a/kioslave/src/smtp/capabilities.h b/kioslave/src/smtp/capabilities.h deleted file mode 100644 --- a/kioslave/src/smtp/capabilities.h +++ /dev/null @@ -1,86 +0,0 @@ -/* -*- c++ -*- - capabilities.h - - This file is part of kio_smtp, the KDE SMTP kioslave. - Copyright (c) 2003 Marc Mutz - - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - - In addition, as a special exception, the copyright holders give - permission to link the code of this program with any edition of - the Qt library by Trolltech AS, Norway (or with modified versions - of Qt that use the same license as Qt), and distribute linked - combinations including the two. You must obey the GNU General - Public License in all respects for all of the code used other than - Qt. If you modify this file, you may extend this exception to - your version of the file, but you are not obligated to do so. If - you do not wish to do so, delete this exception statement from - your version. -*/ - -#ifndef __KIOSMTP_CAPABILITIES_H__ -#define __KIOSMTP_CAPABILITIES_H__ - -#include - -#include - -namespace KioSMTP { -class Response; - -class Capabilities -{ -public: - Capabilities() - { - } - - static Capabilities fromResponse(const Response &response); - - void add(const QString &cap, bool replace = false); - void add(const QString &name, const QStringList &args, bool replace = false); - void clear() - { - mCapabilities.clear(); - } - - bool have(const QString &cap) const - { - return mCapabilities.find(cap.toUpper()) != mCapabilities.end(); - } - - bool have(const QByteArray &cap) const - { - return have(QString::fromLatin1(cap)); - } - - bool have(const char *cap) const - { - return have(QString::fromLatin1(cap)); - } - - QString asMetaDataString() const; - - QString authMethodMetaData() const; - - QString createSpecialResponse(bool tls) const; - - QStringList saslMethodsQSL() const; -private: - - QMap mCapabilities; -}; -} // namespace KioSMTP - -#endif // __KIOSMTP_CAPABILITIES_H__ diff --git a/kioslave/src/smtp/capabilities.cpp b/kioslave/src/smtp/capabilities.cpp deleted file mode 100644 --- a/kioslave/src/smtp/capabilities.cpp +++ /dev/null @@ -1,121 +0,0 @@ -/* -*- c++ -*- - capabilities.cc - - This file is part of kio_smtp, the KDE SMTP kioslave. - Copyright (c) 2003 Marc Mutz - - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - - In addition, as a special exception, the copyright holders give - permission to link the code of this program with any edition of - the Qt library by Trolltech AS, Norway (or with modified versions - of Qt that use the same license as Qt), and distribute linked - combinations including the two. You must obey the GNU General - Public License in all respects for all of the code used other than - Qt. If you modify this file, you may extend this exception to - your version of the file, but you are not obligated to do so. If - you do not wish to do so, delete this exception statement from - your version. -*/ - -#include "capabilities.h" -#include "response.h" - -namespace KioSMTP { -Capabilities Capabilities::fromResponse(const Response &ehlo) -{ - Capabilities c; - - // first, check whether the response was valid and indicates success: - if (!ehlo.isOk() - || ehlo.code() / 10 != 25 // ### restrict to 250 only? - || ehlo.lines().empty()) { - return c; - } - - QCStringList l = ehlo.lines(); - - for (QCStringList::const_iterator it = ++l.constBegin(), end(l.constEnd()); it != end; ++it) { - c.add(QString::fromLatin1(*it)); - } - - return c; -} - -void Capabilities::add(const QString &cap, bool replace) -{ - QStringList tokens = cap.toUpper().split(QLatin1Char(' ')); - if (tokens.empty()) { - return; - } - QString name = tokens.front(); - tokens.pop_front(); - add(name, tokens, replace); -} - -void Capabilities::add(const QString &name, const QStringList &args, bool replace) -{ - if (replace) { - mCapabilities[name] = args; - } else { - mCapabilities[name] += args; - } -} - -QString Capabilities::createSpecialResponse(bool tls) const -{ - QStringList result; - if (tls) { - result.push_back(QStringLiteral("STARTTLS")); - } - result += saslMethodsQSL(); - if (have("PIPELINING")) { - result.push_back(QStringLiteral("PIPELINING")); - } - if (have("8BITMIME")) { - result.push_back(QStringLiteral("8BITMIME")); - } - if (have("SIZE")) { - bool ok = false; - unsigned int size = 0; - if (!mCapabilities[QStringLiteral("SIZE")].isEmpty()) { - size = mCapabilities[QStringLiteral("SIZE")].front().toUInt(&ok); - } - if (ok && !size) { - result.push_back(QStringLiteral("SIZE=*")); // any size - } else if (ok) { - result.push_back(QStringLiteral("SIZE=%1").arg(size)); // fixed max - } else { - result.push_back(QStringLiteral("SIZE")); // indetermined - } - } - return result.join(QLatin1Char(' ')); -} - -QStringList Capabilities::saslMethodsQSL() const -{ - QStringList result; - for (QMap::const_iterator it = mCapabilities.begin(), end(mCapabilities.end()); - it != end; ++it) { - if (it.key() == QLatin1String("AUTH")) { - result += it.value(); - } else if (it.key().startsWith(QLatin1String("AUTH="))) { - result.push_back(it.key().mid(qstrlen("AUTH="))); - result += it.value(); - } - } - result.removeDuplicates(); - return result; -} -} // namespace KioSMTP diff --git a/kioslave/src/smtp/command.h b/kioslave/src/smtp/command.h deleted file mode 100644 --- a/kioslave/src/smtp/command.h +++ /dev/null @@ -1,331 +0,0 @@ -/* -*- c++ -*- - command.h - - This file is part of kio_smtp, the KDE SMTP kioslave. - Copyright (c) 2003 Marc Mutz - - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - - In addition, as a special exception, the copyright holders give - permission to link the code of this program with any edition of - the Qt library by Trolltech AS, Norway (or with modified versions - of Qt that use the same license as Qt), and distribute linked - combinations including the two. You must obey the GNU General - Public License in all respects for all of the code used other than - Qt. If you modify this file, you may extend this exception to - your version of the file, but you are not obligated to do so. If - you do not wish to do so, delete this exception statement from - your version. -*/ - -#ifndef __KIOSMTP_COMMAND_H__ -#define __KIOSMTP_COMMAND_H__ - -// workaround a bug in Cyrus-SASL 2.1.26 which is missing sys/types.h -// include in sasl.h -#include -extern "C" { -#include -} - -#include - -namespace KioSMTP { -class Response; -class TransactionState; -class SMTPSessionInterface; - -/** - * @short Represents an SMTP command - * - * Semantics: A command consists of a series of "command lines" - * (though that's stretching it a bit for @ref TransferJob and @ref - * AuthCommand) and responses. There's typically one response for - * one command line and the command is completed. - * - * However, some commands consist of a dialog (command line, - * response, command line, response,...) where each successive - * command line is dependant on the previously received response - * (and thus those commands are not pipelinable). That's why each - * command signals completion by having it's @ref #isComplete() - * method return true @em after the last command line to be sent, - * but @em before the last response to receive. @ref AuthCommand is - * the principal representative of this kind of command. Because - * @ref EHLOCommand automatically falls back to HELO in case EHLO - * isn't supported, it is also of this kind. If completion is - * signalled before the first command line is issued, it is not to - * be executed at all. - * - * Other commands need to send multiple "command lines" before - * receiving a single (final) response. @ref TransferCommand is the - * only representative of this kind of "command". That's why each - * command signals whether it now expects a response before being - * able to issue the next command line (if any) by having it's @ref - * #needsResponse() method return true. - * - * Commands whose @ref #nextCommandLine() does not support being - * called multiple times in a row without changing command state, - * must reimplement @ref #ungetCommandLine(). - **/ -class Command -{ -public: - enum Flags { - OnlyLastInPipeline = 1, - OnlyFirstInPipeline = 2, - CloseConnectionOnError = 4 - }; - - explicit Command(SMTPSessionInterface *smtp, int flags = 0); - virtual ~Command(); - - enum Type { - STARTTLS, - DATA, - NOOP, - RSET, - QUIT - }; - - static Command *createSimpleCommand(int which, SMTPSessionInterface *smtp); - - virtual QByteArray nextCommandLine(TransactionState *ts = nullptr) = 0; - /* Reimplement this if your @ref #nextCommandLine() implementation - changes state other than @ref mComplete. The default - implementation just resets @ref mComplete to false. */ - virtual void ungetCommandLine(const QByteArray &cmdLine, TransactionState *ts = nullptr); - /* Reimplement this if your command need more sophisicated - response processing than just checking for @ref - Response::isOk(). The default implementation sets @ref - mComplete to true, @ref mNeedResponse to false and returns - whether the response isOk(). */ - virtual bool processResponse(const Response &response, TransactionState *ts = nullptr); - - virtual bool doNotExecute(const TransactionState *ts) const - { - Q_UNUSED(ts) - return false; - } - - bool isComplete() const - { - return mComplete; - } - - /** - * @return whether the command expects a response now. Some - * commands (most notably AUTH) may consist of a series of - * commands and associated responses until they are - * complete. Others (most notably @ref TransferCommand usually - * send multiple "command lines" before expecting a response. - */ - bool needsResponse() const - { - return mNeedResponse; - } - - /** - * @return whether an error in executing this command is so fatal - * that closing the connection is the only option - */ - bool closeConnectionOnError() const - { - return mFlags & CloseConnectionOnError; - } - - bool mustBeLastInPipeline() const - { - return mFlags & OnlyLastInPipeline; - } - - bool mustBeFirstInPipeline() const - { - return mFlags & OnlyFirstInPipeline; - } - -protected: - SMTPSessionInterface *mSMTP; - bool mComplete; - bool mNeedResponse; - const int mFlags; - -protected: - // only relay methods to enable access to slave-protected methods - // for subclasses of Command: - void parseFeatures(const Response &r); - int startSsl(); - bool haveCapability(const char *cap) const; -}; - -class EHLOCommand : public Command -{ -public: - EHLOCommand(SMTPSessionInterface *smtp, const QString &hostname) - : Command(smtp, CloseConnectionOnError | OnlyLastInPipeline) - , mEHLONotSupported(false) - , mHostname(hostname.trimmed()) - { - } - - QByteArray nextCommandLine(TransactionState *ts) override; - bool processResponse(const Response &response, TransactionState *ts) override; -private: - bool mEHLONotSupported; - QString mHostname; -}; - -class StartTLSCommand : public Command -{ -public: - StartTLSCommand(SMTPSessionInterface *smtp) - : Command(smtp, CloseConnectionOnError | OnlyLastInPipeline) - { - } - - QByteArray nextCommandLine(TransactionState *ts) override; - bool processResponse(const Response &response, TransactionState *ts) override; -}; - -class AuthCommand : public Command -{ -public: - AuthCommand(SMTPSessionInterface *smtp, const char *mechanisms, const QString &aFQDN, KIO::AuthInfo &ai); - ~AuthCommand(); - bool doNotExecute(const TransactionState *ts) const override; - QByteArray nextCommandLine(TransactionState *ts) override; - void ungetCommandLine(const QByteArray &cmdLine, TransactionState *ts) override; - bool processResponse(const Response &response, TransactionState *ts) override; -private: - bool saslInteract(void *in); - - sasl_conn_t *conn; - sasl_interact_t *client_interact; - const char *mOut; - uint mOutlen; - bool mOneStep; - - const char *mMechusing; - KIO::AuthInfo *mAi; - QByteArray mLastChallenge; - QByteArray mUngetSASLResponse; - bool mFirstTime; -}; - -class MailFromCommand : public Command -{ -public: - MailFromCommand(SMTPSessionInterface *smtp, const QByteArray &addr, bool eightBit = false, unsigned int size = 0) - : Command(smtp) - , mAddr(addr) - , m8Bit(eightBit) - , mSize(size) - { - } - - QByteArray nextCommandLine(TransactionState *ts) override; - bool processResponse(const Response &response, TransactionState *ts) override; -private: - QByteArray mAddr; - bool m8Bit; - unsigned int mSize; -}; - -class RcptToCommand : public Command -{ -public: - RcptToCommand(SMTPSessionInterface *smtp, const QByteArray &addr) - : Command(smtp) - , mAddr(addr) - { - } - - QByteArray nextCommandLine(TransactionState *ts) override; - bool processResponse(const Response &response, TransactionState *ts) override; -private: - QByteArray mAddr; -}; - -/** Handles only the initial intermediate response and compltetes at - the point where the mail contents need to be sent */ -class DataCommand : public Command -{ -public: - DataCommand(SMTPSessionInterface *smtp) - : Command(smtp, OnlyLastInPipeline) - { - } - - QByteArray nextCommandLine(TransactionState *ts) override; - void ungetCommandLine(const QByteArray &cmd, TransactionState *ts) override; - bool processResponse(const Response &response, TransactionState *ts) override; -}; - -/** Handles the data transfer following a successful DATA command */ -class TransferCommand : public Command -{ -public: - TransferCommand(SMTPSessionInterface *smtp, const QByteArray &initialBuffer) - : Command(smtp, OnlyFirstInPipeline) - , mUngetBuffer(initialBuffer) - , mLastChar('\n') - , mWasComplete(false) - { - } - - bool doNotExecute(const TransactionState *ts) const override; - QByteArray nextCommandLine(TransactionState *ts) override; - void ungetCommandLine(const QByteArray &cmd, TransactionState *ts) override; - bool processResponse(const Response &response, TransactionState *ts) override; -private: - QByteArray prepare(const QByteArray &ba); - QByteArray mUngetBuffer; - char mLastChar; - bool mWasComplete; // ... before ungetting -}; - -class NoopCommand : public Command -{ -public: - NoopCommand(SMTPSessionInterface *smtp) - : Command(smtp, OnlyLastInPipeline) - { - } - - QByteArray nextCommandLine(TransactionState *ts) override; -}; - -class RsetCommand : public Command -{ -public: - RsetCommand(SMTPSessionInterface *smtp) - : Command(smtp, CloseConnectionOnError) - { - } - - QByteArray nextCommandLine(TransactionState *ts) override; -}; - -class QuitCommand : public Command -{ -public: - QuitCommand(SMTPSessionInterface *smtp) - : Command(smtp, CloseConnectionOnError | OnlyLastInPipeline) - { - } - - QByteArray nextCommandLine(TransactionState *ts) override; -}; -} // namespace KioSMTP - -#endif // __KIOSMTP_COMMAND_H__ diff --git a/kioslave/src/smtp/command.cpp b/kioslave/src/smtp/command.cpp deleted file mode 100644 --- a/kioslave/src/smtp/command.cpp +++ /dev/null @@ -1,648 +0,0 @@ -/* -*- c++ -*- - command.cc - - This file is part of kio_smtp, the KDE SMTP kioslave. - Copyright (c) 2003 Marc Mutz - - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - - In addition, as a special exception, the copyright holders give - permission to link the code of this program with any edition of - the Qt library by Trolltech AS, Norway (or with modified versions - of Qt that use the same license as Qt), and distribute linked - combinations including the two. You must obey the GNU General - Public License in all respects for all of the code used other than - Qt. If you modify this file, you may extend this exception to - your version of the file, but you are not obligated to do so. If - you do not wish to do so, delete this exception statement from - your version. -*/ - -#include "command.h" -#include "smtp_debug.h" - -#include "smtpsessioninterface.h" -#include "response.h" -#include "transactionstate.h" - -#include -#include // for test_commands, where SMTPProtocol is not derived from TCPSlaveBase - -#include - -#include - -namespace KioSMTP { -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 } -}; - -// -// Command (base class) -// - -Command::Command(SMTPSessionInterface *smtp, int flags) - : mSMTP(smtp) - , mComplete(false) - , mNeedResponse(false) - , mFlags(flags) -{ - assert(smtp); -} - -Command::~Command() -{ -} - -bool Command::processResponse(const Response &r, TransactionState *ts) -{ - Q_UNUSED(ts) - mComplete = true; - mNeedResponse = false; - return r.isOk(); -} - -void Command::ungetCommandLine(const QByteArray &cmdLine, TransactionState *ts) -{ - Q_UNUSED(cmdLine) - Q_UNUSED(ts) - mComplete = false; -} - -Command *Command::createSimpleCommand(int which, SMTPSessionInterface *smtp) -{ - switch (which) { - case STARTTLS: - return new StartTLSCommand(smtp); - case DATA: - return new DataCommand(smtp); - case NOOP: - return new NoopCommand(smtp); - case RSET: - return new RsetCommand(smtp); - case QUIT: - return new QuitCommand(smtp); - default: - return nullptr; - } -} - -// -// relay methods: -// - -void Command::parseFeatures(const Response &r) -{ - mSMTP->parseFeatures(r); -} - -int Command::startSsl() -{ - return mSMTP->startSsl(); -} - -bool Command::haveCapability(const char *cap) const -{ - return mSMTP->haveCapability(cap); -} - -// -// EHLO / HELO -// - -QByteArray EHLOCommand::nextCommandLine(TransactionState *ts) -{ - Q_UNUSED(ts) - mNeedResponse = true; - mComplete = mEHLONotSupported; - const char *cmd = mEHLONotSupported ? "HELO " : "EHLO "; - return cmd + QUrl::toAce(mHostname) + "\r\n"; //krazy:exclude=qclasses -} - -bool EHLOCommand::processResponse(const Response &r, TransactionState *ts) -{ - Q_UNUSED(ts) - mNeedResponse = false; - // "command not {recognized,implemented}" response: - if (r.code() == 500 || r.code() == 502) { - if (mEHLONotSupported) { // HELO failed... - mSMTP->error(KIO::ERR_INTERNAL_SERVER, - i18n("The server rejected both EHLO and HELO commands " - "as unknown or unimplemented.\n" - "Please contact the server's system administrator.")); - return false; - } - mEHLONotSupported = true; // EHLO failed, but that's ok. - return true; - } - mComplete = true; - if (r.code() / 10 == 25) { // 25x: success - parseFeatures(r); - return true; - } - mSMTP->error(KIO::ERR_UNKNOWN, - i18n("Unexpected server response to %1 command.\n%2", - (mEHLONotSupported ? QStringLiteral("HELO") : QStringLiteral("EHLO")), - r.errorMessage())); - return false; -} - -// -// STARTTLS - rfc 3207 -// - -QByteArray StartTLSCommand::nextCommandLine(TransactionState *ts) -{ - Q_UNUSED(ts) - mComplete = true; - mNeedResponse = true; - return QByteArrayLiteral("STARTTLS\r\n"); -} - -bool StartTLSCommand::processResponse(const Response &r, TransactionState *ts) -{ - Q_UNUSED(ts) - mNeedResponse = false; - if (r.code() != 220) { - mSMTP->error(r.errorCode(), - i18n("Your SMTP server does not support TLS. " - "Disable TLS, if you want to connect " - "without encryption.")); - return false; - } - - if (startSsl()) { - return true; - } else { - //qCDebug(SMTP_LOG) << "TLS negotiation failed!"; - mSMTP->informationMessageBox( - i18n("Your SMTP server claims to " - "support TLS, but negotiation " - "was unsuccessful.\nYou can " - "disable TLS in the SMTP account settings dialog."), - i18n("Connection Failed")); - return false; - } -} - -#define SASLERROR mSMTP->error(KIO::ERR_COULD_NOT_AUTHENTICATE, \ - i18n("An error occurred during authentication: %1", \ - QString::fromUtf8(sasl_errdetail(conn)))); - -// -// AUTH - rfc 2554 -// -AuthCommand::AuthCommand(SMTPSessionInterface *smtp, const char *mechanisms, const QString &aFQDN, KIO::AuthInfo &ai) - : Command(smtp, CloseConnectionOnError | OnlyLastInPipeline) - , mAi(&ai) - , mFirstTime(true) -{ - mMechusing = nullptr; - int result; - conn = nullptr; - client_interact = nullptr; - mOut = nullptr; - mOutlen = 0; - mOneStep = false; - - const QByteArray ba = aFQDN.toLatin1(); - result = sasl_client_new("smtp", ba.constData(), - nullptr, nullptr, callbacks, 0, &conn); - if (result != SASL_OK) { - SASLERROR - return; - } - do { - result = sasl_client_start(conn, mechanisms, - &client_interact, &mOut, &mOutlen, &mMechusing); - - if (result == SASL_INTERACT) { - if (!saslInteract(client_interact)) { - return; - } - } - } while (result == SASL_INTERACT); - if (result != SASL_CONTINUE && result != SASL_OK) { - SASLERROR - return; - } - if (result == SASL_OK) { - mOneStep = true; - } - qCDebug(SMTP_LOG) << "Mechanism: " << mMechusing << " one step: " << mOneStep; -} - -AuthCommand::~AuthCommand() -{ - if (conn) { - qCDebug(SMTP_LOG) << "dispose sasl connection"; - sasl_dispose(&conn); - conn = nullptr; - } -} - -bool AuthCommand::saslInteract(void *in) -{ - qCDebug(SMTP_LOG) << "saslInteract: "; - sasl_interact_t *interact = (sasl_interact_t *)in; - - //some mechanisms do not require username && pass, so don't need a popup - //window for getting this info - for (; interact->id != SASL_CB_LIST_END; ++interact) { - if (interact->id == SASL_CB_AUTHNAME - || interact->id == SASL_CB_PASS) { - if (mAi->username.isEmpty() || mAi->password.isEmpty()) { - if (!mSMTP->openPasswordDialog(*mAi)) { - mSMTP->error(KIO::ERR_ABORTED, i18n("No authentication details supplied.")); - return false; - } - } - break; - } - } - - interact = (sasl_interact_t *)in; - while (interact->id != SASL_CB_LIST_END) { - switch (interact->id) { - case SASL_CB_USER: - case SASL_CB_AUTHNAME: - { - qCDebug(SMTP_LOG) << "SASL_CB_[USER|AUTHNAME]: " << mAi->username; - const QByteArray baUserName = mAi->username.toUtf8(); - interact->result = strdup(baUserName.constData()); - interact->len = strlen((const char *)interact->result); - break; - } - case SASL_CB_PASS: - { - qCDebug(SMTP_LOG) << "SASL_CB_PASS: [HIDDEN]"; - const QByteArray baPassword = mAi->password.toUtf8(); - interact->result = strdup(baPassword.constData()); - interact->len = strlen((const char *)interact->result); - break; - } - default: - interact->result = nullptr; - interact->len = 0; - break; - } - interact++; - } - return true; -} - -bool AuthCommand::doNotExecute(const TransactionState *ts) const -{ - Q_UNUSED(ts) - return !mMechusing; -} - -void AuthCommand::ungetCommandLine(const QByteArray &s, TransactionState *ts) -{ - Q_UNUSED(ts) - mUngetSASLResponse = s; - mComplete = false; -} - -QByteArray AuthCommand::nextCommandLine(TransactionState *ts) -{ - Q_UNUSED(ts) - mNeedResponse = true; - QByteArray cmd; - - QByteArray challenge; - if (!mUngetSASLResponse.isNull()) { - // implement un-ungetCommandLine - cmd = mUngetSASLResponse; - mUngetSASLResponse = nullptr; - } else if (mFirstTime) { - QString firstCommand = QLatin1String("AUTH ") + QString::fromLatin1(mMechusing); - - challenge = QByteArray::fromRawData(mOut, mOutlen).toBase64(); - if (!challenge.isEmpty()) { - firstCommand += QLatin1Char(' '); - firstCommand += QString::fromLatin1(challenge.data(), challenge.size()); - } - cmd = firstCommand.toLatin1(); - - if (mOneStep) { - mComplete = true; - } - } else { -// qCDebug(SMTP_LOG) << "SS: '" << mLastChallenge << "'"; - challenge = QByteArray::fromBase64(mLastChallenge); - int result; - do { - result = sasl_client_step(conn, challenge.isEmpty() ? nullptr : challenge.data(), - challenge.size(), - &client_interact, - &mOut, &mOutlen); - if (result == SASL_INTERACT) { - if (!saslInteract(client_interact)) { - return ""; - } - } - } while (result == SASL_INTERACT); - if (result != SASL_CONTINUE && result != SASL_OK) { - qCDebug(SMTP_LOG) << "sasl_client_step failed with: " << result; - SASLERROR - return ""; - } - cmd = QByteArray::fromRawData(mOut, mOutlen).toBase64(); - -// qCDebug(SMTP_LOG) << "CC: '" << cmd << "'"; - mComplete = (result == SASL_OK); - } - cmd += QByteArrayLiteral("\r\n"); - return cmd; -} - -bool AuthCommand::processResponse(const Response &r, TransactionState *ts) -{ - Q_UNUSED(ts) - if (!r.isOk()) { - if (mFirstTime) { - if (haveCapability("AUTH")) { - QString chooseADifferentMsg(i18n("Choose a different authentication method.")); - mSMTP->error(KIO::ERR_COULD_NOT_LOGIN, - (mMechusing ? i18n("Your SMTP server does not support %1.", QString::fromLatin1(mMechusing)) - : i18n("Your SMTP server does not support (unspecified method).")) - + QLatin1Char('\n') + chooseADifferentMsg + QLatin1Char('\n') + r.errorMessage()); - } else { - mSMTP->error(KIO::ERR_COULD_NOT_LOGIN, - i18n("Your SMTP server does not support authentication.\n" - "%1", r.errorMessage())); - } - } else { - mSMTP->error(KIO::ERR_COULD_NOT_LOGIN, - i18n("Authentication failed.\n" - "Most likely the password is wrong.\n" - "%1", r.errorMessage())); - } - return false; - } - mFirstTime = false; - mLastChallenge = r.lines().at(0); // ### better join all lines with \n? - mNeedResponse = false; - return true; -} - -// -// MAIL FROM: -// - -QByteArray MailFromCommand::nextCommandLine(TransactionState *ts) -{ - Q_UNUSED(ts) - mComplete = true; - mNeedResponse = true; - QByteArray cmdLine = QByteArrayLiteral("MAIL FROM:<") + mAddr + '>'; - if (m8Bit && haveCapability("8BITMIME")) { - cmdLine += QByteArrayLiteral(" BODY=8BITMIME"); - } - if (mSize && haveCapability("SIZE")) { - cmdLine += QByteArrayLiteral(" SIZE=") + QByteArray().setNum(mSize); - } - return cmdLine + QByteArrayLiteral("\r\n"); -} - -bool MailFromCommand::processResponse(const Response &r, TransactionState *ts) -{ - assert(ts); - mNeedResponse = false; - - if (r.code() == 250) { - return true; - } - - ts->setMailFromFailed(QString::fromLatin1(mAddr), r); - return false; -} - -// -// RCPT TO: -// - -QByteArray RcptToCommand::nextCommandLine(TransactionState *ts) -{ - Q_UNUSED(ts) - mComplete = true; - mNeedResponse = true; - return QByteArrayLiteral("RCPT TO:<") + mAddr + QByteArrayLiteral(">\r\n"); -} - -bool RcptToCommand::processResponse(const Response &r, TransactionState *ts) -{ - assert(ts); - mNeedResponse = false; - - if (r.code() == 250) { - ts->setRecipientAccepted(); - return true; - } - - ts->addRejectedRecipient(QString::fromLatin1(mAddr), r.errorMessage()); - return false; -} - -// -// DATA (only initial processing!) -// - -QByteArray DataCommand::nextCommandLine(TransactionState *ts) -{ - assert(ts); - mComplete = true; - mNeedResponse = true; - ts->setDataCommandIssued(true); - return QByteArrayLiteral("DATA\r\n"); -} - -void DataCommand::ungetCommandLine(const QByteArray &cmd, TransactionState *ts) -{ - Q_UNUSED(cmd) - assert(ts); - mComplete = false; - ts->setDataCommandIssued(false); -} - -bool DataCommand::processResponse(const Response &r, TransactionState *ts) -{ - assert(ts); - mNeedResponse = false; - - if (r.code() == 354) { - ts->setDataCommandSucceeded(true, r); - return true; - } - - ts->setDataCommandSucceeded(false, r); - return false; -} - -// -// DATA (data transfer) -// -void TransferCommand::ungetCommandLine(const QByteArray &cmd, TransactionState *ts) -{ - Q_UNUSED(ts) - if (cmd.isEmpty()) { - return; // don't change state when we can't detect the unget in - } - // the next nextCommandLine !! - mWasComplete = mComplete; - mComplete = false; - mNeedResponse = false; - mUngetBuffer = cmd; -} - -bool TransferCommand::doNotExecute(const TransactionState *ts) const -{ - assert(ts); - return ts->failed(); -} - -QByteArray TransferCommand::nextCommandLine(TransactionState *ts) -{ - assert(ts); // let's rely on it ( at least for the moment ) - assert(!isComplete()); - assert(!ts->failed()); - - static const QByteArray dotCRLF = QByteArrayLiteral(".\r\n"); - static const QByteArray CRLFdotCRLF = QByteArrayLiteral("\r\n.\r\n"); - - if (!mUngetBuffer.isEmpty()) { - const QByteArray ret = mUngetBuffer; - mUngetBuffer = nullptr; - if (mWasComplete) { - mComplete = true; - mNeedResponse = true; - } - return ret; // don't prepare(), it's slave-generated or already prepare()d - } - - // normal processing: - - qCDebug(SMTP_LOG) << "requesting data"; - mSMTP->dataReq(); - QByteArray ba; - int result = mSMTP->readData(ba); - qCDebug(SMTP_LOG) << "got " << result << " bytes"; - if (result > 0) { - return prepare(ba); - } else if (result < 0) { - ts->setFailedFatally(KIO::ERR_INTERNAL, - i18n("Could not read data from application.")); - mComplete = true; - mNeedResponse = true; - return nullptr; - } - mComplete = true; - mNeedResponse = true; - return mLastChar == '\n' ? dotCRLF : CRLFdotCRLF; -} - -bool TransferCommand::processResponse(const Response &r, TransactionState *ts) -{ - mNeedResponse = false; - assert(ts); - ts->setComplete(); - if (!r.isOk()) { - ts->setFailed(); - mSMTP->error(r.errorCode(), - i18n("The message content was not accepted.\n" - "%1", r.errorMessage())); - return false; - } - return true; -} - -static QByteArray dotstuff_lf2crlf(const QByteArray &ba, char &last) -{ - QByteArray result(ba.size() * 2 + 1, 0); // worst case: repeated "[.]\n" - const char *s = ba.data(); - const char *const send = ba.data() + ba.size(); - char *d = result.data(); - - while (s < send) { - const char ch = *s++; - if (ch == '\n' && last != '\r') { - *d++ = '\r'; // lf2crlf - } else if (ch == '.' && last == '\n') { - *d++ = '.'; // dotstuff - } - last = *d++ = ch; - } - - result.truncate(d - result.data()); - return result; -} - -QByteArray TransferCommand::prepare(const QByteArray &ba) -{ - if (ba.isEmpty()) { - return nullptr; - } - if (mSMTP->lf2crlfAndDotStuffingRequested()) { - qCDebug(SMTP_LOG) << "performing dotstuffing and LF->CRLF transformation"; - return dotstuff_lf2crlf(ba, mLastChar); - } else { - mLastChar = ba[ba.size() - 1]; - return ba; - } -} - -// -// NOOP -// - -QByteArray NoopCommand::nextCommandLine(TransactionState *ts) -{ - Q_UNUSED(ts) - mComplete = true; - mNeedResponse = true; - return QByteArrayLiteral("NOOP\r\n"); -} - -// -// RSET -// - -QByteArray RsetCommand::nextCommandLine(TransactionState *ts) -{ - Q_UNUSED(ts) - mComplete = true; - mNeedResponse = true; - return QByteArrayLiteral("RSET\r\n"); -} - -// -// QUIT -// - -QByteArray QuitCommand::nextCommandLine(TransactionState *ts) -{ - Q_UNUSED(ts) - mComplete = true; - mNeedResponse = true; - return QByteArrayLiteral("QUIT\r\n"); -} -} // namespace KioSMTP diff --git a/kioslave/src/smtp/compliance.txt b/kioslave/src/smtp/compliance.txt deleted file mode 100644 --- a/kioslave/src/smtp/compliance.txt +++ /dev/null @@ -1,33 +0,0 @@ -The SMTP kioslave currently conforms to the following SMTP-related RFCs: - -Base Spec: -2821 Simple Mail Transfer Protocol. J. Klensin, Ed.. April 2001. - (Format: TXT=192504 bytes) (Obsoletes RFC0821, RFC0974, RFC1869) - (Status: PROPOSED STANDARD) - -Encryption/Auth: -3207 SMTP Service Extension for Secure SMTP over Transport Layer - Security. P. Hoffman. February 2002. (Format: TXT=18679 bytes) - (Obsoletes RFC2487) (Status: PROPOSED STANDARD) - -2554 SMTP Service Extension for Authentication. J. Myers. March 1999. - (Format: TXT=20534 bytes) (Status: PROPOSED STANDARD) -(with all SASL mechanisms supported by KDESasl) - -General: -1652 SMTP Service Extension for 8bit-MIMEtransport. J. Klensin, N. - Freed, M. Rose, E. Stefferud, D. Crocker. July 1994. (Format: - TXT=11842 bytes) (Obsoletes RFC1426) (Status: DRAFT STANDARD) - -1870 SMTP Service Extension for Message Size Declaration. J. Klensin, - N. Freed, K. Moore. November 1995. (Format: TXT=18226 bytes) - (Obsoletes RFC1653) (Also STD0010) (Status: STANDARD) - -2920 SMTP Service Extension for Command Pipelining. N. Freed. - September 2000. (Format: TXT=17065 bytes) (Obsoletes RFC2197) (Also - STD0060) (Status: STANDARD) - -Known shortcomings: -- Doesn't enforce the CRLF lineending convention on user-supplied data. -- Due to the lack of a Mulit_Put_ in the KIO infrastructure, pipelining - across messages isn't supported. diff --git a/kioslave/src/smtp/kioslavesession.h b/kioslave/src/smtp/kioslavesession.h deleted file mode 100644 --- a/kioslave/src/smtp/kioslavesession.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - Copyright (c) 2010 Volker Krause - - 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 KIOSMTP_KIOSLAVESESSION_H -#define KIOSMTP_KIOSLAVESESSION_H - -#include "smtpsessioninterface.h" -#include "smtp.h" - -namespace KioSMTP { -class KioSlaveSession : public SMTPSessionInterface -{ -public: - explicit KioSlaveSession(SMTPProtocol *protocol); - void error(int id, const QString &msg) override; - void informationMessageBox(const QString &msg, const QString &caption) override; - bool openPasswordDialog(KIO::AuthInfo &authInfo) override; - void dataReq() override; - int readData(QByteArray &ba) override; - bool startSsl() override; - - QString requestedSaslMethod() const override; - bool eightBitMimeRequested() const override; - bool lf2crlfAndDotStuffingRequested() const override; - bool pipeliningRequested() const override; - TLSRequestState tlsRequested() const override; - -private: - SMTPProtocol *m_protocol; -}; -} - -#endif diff --git a/kioslave/src/smtp/kioslavesession.cpp b/kioslave/src/smtp/kioslavesession.cpp deleted file mode 100644 --- a/kioslave/src/smtp/kioslavesession.cpp +++ /dev/null @@ -1,88 +0,0 @@ -/* - Copyright (c) 2010 Volker Krause - - 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 "kioslavesession.h" - -using namespace KioSMTP; - -KioSMTP::KioSlaveSession::KioSlaveSession(SMTPProtocol *protocol) - : m_protocol(protocol) -{ -} - -void KioSMTP::KioSlaveSession::error(int id, const QString &msg) -{ - m_protocol->error(id, msg); -} - -void KioSlaveSession::informationMessageBox(const QString &msg, const QString &caption) -{ - m_protocol->messageBox(KIO::SlaveBase::Information, msg, caption); -} - -bool KioSMTP::KioSlaveSession::openPasswordDialog(KIO::AuthInfo &authInfo) -{ - return m_protocol->openPasswordDialog(authInfo); -} - -void KioSMTP::KioSlaveSession::dataReq() -{ - m_protocol->dataReq(); -} - -int KioSMTP::KioSlaveSession::readData(QByteArray &ba) -{ - return m_protocol->readData(ba); -} - -bool KioSMTP::KioSlaveSession::startSsl() -{ - return m_protocol->startSsl(); -} - -bool KioSlaveSession::eightBitMimeRequested() const -{ - return m_protocol->metaData(QStringLiteral("8bitmime")) == QLatin1String("on"); -} - -bool KioSlaveSession::lf2crlfAndDotStuffingRequested() const -{ - return m_protocol->metaData(QStringLiteral("lf2crlf+dotstuff")) == QLatin1String("slave"); -} - -bool KioSlaveSession::pipeliningRequested() const -{ - return m_protocol->metaData(QStringLiteral("pipelining")) != QLatin1String("off"); -} - -QString KioSlaveSession::requestedSaslMethod() const -{ - return m_protocol->metaData(QStringLiteral("sasl")); -} - -KioSMTP::SMTPSessionInterface::TLSRequestState KioSMTP::KioSlaveSession::tlsRequested() const -{ - if (m_protocol->metaData(QStringLiteral("tls")) == QLatin1String("off")) { - return ForceNoTLS; - } - if (m_protocol->metaData(QStringLiteral("tls")) == QLatin1String("on")) { - return ForceTLS; - } - return UseTLSIfAvailable; -} diff --git a/kioslave/src/smtp/request.h b/kioslave/src/smtp/request.h deleted file mode 100644 --- a/kioslave/src/smtp/request.h +++ /dev/null @@ -1,192 +0,0 @@ -/* -*- c++ -*- - request.h - - This file is part of kio_smtp, the KDE SMTP kioslave. - Copyright (c) 2003 Marc Mutz - - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - - In addition, as a special exception, the copyright holders give - permission to link the code of this program with any edition of - the Qt library by Trolltech AS, Norway (or with modified versions - of Qt that use the same license as Qt), and distribute linked - combinations including the two. You must obey the GNU General - Public License in all respects for all of the code used other than - Qt. If you modify this file, you may extend this exception to - your version of the file, but you are not obligated to do so. If - you do not wish to do so, delete this exception statement from - your version. -*/ - -#ifndef __KIOSMTP_REQUEST_H__ -#define __KIOSMTP_REQUEST_H__ - -#include -#include - -class QUrl; - -namespace KioSMTP { -class Request -{ -public: - Request() - : mSubject(QStringLiteral("missing subject")) - , mEmitHeaders(true) - , m8Bit(false) - , mSize(0) - { - } - - static Request fromURL(const QUrl &url); - - QString profileName() const - { - return mProfileName; - } - - void setProfileName(const QString &profileName) - { - mProfileName = profileName; - } - - bool hasProfile() const - { - return !profileName().isNull(); - } - - QString subject() const - { - return mSubject; - } - - void setSubject(const QString &subject) - { - mSubject = subject; - } - - QString fromAddress() const - { - return mFromAddress; - } - - void setFromAddress(const QString &fromAddress) - { - mFromAddress = fromAddress; - } - - bool hasFromAddress() const - { - return !mFromAddress.isEmpty(); - } - - QStringList recipients() const - { - return to() + cc() + bcc(); - } - - bool hasRecipients() const - { - return !to().empty() || !cc().empty() || !bcc().empty(); - } - - QStringList to() const - { - return mTo; - } - - QStringList cc() const - { - return mCc; - } - - QStringList bcc() const - { - return mBcc; - } - - void addTo(const QString &to) - { - mTo.push_back(to); - } - - void addCc(const QString &cc) - { - mCc.push_back(cc); - } - - void addBcc(const QString &bcc) - { - mBcc.push_back(bcc); - } - - QString heloHostname() const - { - return mHeloHostname; - } - - QByteArray heloHostnameCString() const; - void setHeloHostname(const QString &hostname) - { - mHeloHostname = hostname; - } - - bool emitHeaders() const - { - return mEmitHeaders; - } - - void setEmitHeaders(bool emitHeaders) - { - mEmitHeaders = emitHeaders; - } - - bool is8BitBody() const - { - return m8Bit; - } - - void set8BitBody(bool a8Bit) - { - m8Bit = a8Bit; - } - - unsigned int size() const - { - return mSize; - } - - void setSize(unsigned int size) - { - mSize = size; - } - - /** - * If @ref #emitHeaders() is true, returns the rfc2822 - * serialization of the header fields "To", "Cc", "Subject" and - * "From", as determined by the respective settings. If @ref - * #emitHeaders() is false, returns a null string. - */ - QByteArray headerFields(const QString &fromRealName = QString()) const; - -private: - QStringList mTo, mCc, mBcc; - QString mProfileName, mSubject, mFromAddress, mHeloHostname; - bool mEmitHeaders; - bool m8Bit; - unsigned int mSize; -}; -} // namespace KioSMTP - -#endif // __KIOSMTP_REQUEST_H__ diff --git a/kioslave/src/smtp/request.cpp b/kioslave/src/smtp/request.cpp deleted file mode 100644 --- a/kioslave/src/smtp/request.cpp +++ /dev/null @@ -1,193 +0,0 @@ -/* -*- c++ -*- - request.cc - - This file is part of kio_smtp, the KDE SMTP kioslave. - Copyright (c) 2003 Marc Mutz - - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - - In addition, as a special exception, the copyright holders give - permission to link the code of this program with any edition of - the Qt library by Trolltech AS, Norway (or with modified versions - of Qt that use the same license as Qt), and distribute linked - combinations including the two. You must obey the GNU General - Public License in all respects for all of the code used other than - Qt. If you modify this file, you may extend this exception to - your version of the file, but you are not obligated to do so. If - you do not wish to do so, delete this exception statement from - your version. -*/ - -#include "request.h" -#include "smtp_debug.h" - -#include -#include -#include - -#include - -namespace KioSMTP { -Request Request::fromURL(const QUrl &url) -{ - Request request; - - const QStringList query = url.query().split(QLatin1Char('&')); -#ifndef NDEBUG - qCDebug(SMTP_LOG) << "Parsing request from query:\n" << query.join(QLatin1Char('\n')); -#endif - for (QStringList::const_iterator it = query.begin(), end(query.end()); it != end; ++it) { - int equalsPos = (*it).indexOf(QLatin1Char('=')); - if (equalsPos <= 0) { - continue; - } - - const QString key = (*it).left(equalsPos).toLower(); - const QString value = QUrl::fromPercentEncoding((*it).mid(equalsPos + 1).toLatin1()); //krazy:exclude=qclasses - - if (key == QLatin1String("to")) { - request.addTo(value); - } else if (key == QLatin1String("cc")) { - request.addCc(value); - } else if (key == QLatin1String("bcc")) { - request.addBcc(value); - } else if (key == QLatin1String("headers")) { - request.setEmitHeaders(value == QLatin1String("0")); - request.setEmitHeaders(false); // ### ??? - } else if (key == QLatin1String("subject")) { - request.setSubject(value); - } else if (key == QLatin1String("from")) { - request.setFromAddress(value); - } else if (key == QLatin1String("profile")) { - request.setProfileName(value); - } else if (key == QLatin1String("hostname")) { - request.setHeloHostname(value); - } else if (key == QLatin1String("body")) { - request.set8BitBody(value.toUpper() == QLatin1String("8BIT")); - } else if (key == QLatin1String("size")) { - request.setSize(value.toUInt()); - } else { - qCWarning(SMTP_LOG) << "while parsing query: unknown query item \"" - << key << "\" with value \"" << value << "\"" << endl; - } - } - - return request; -} - -QByteArray Request::heloHostnameCString() const -{ - return QUrl::toAce(heloHostname()); //krazy:exclude=qclasses -} - -static bool isUsAscii(const QString &s) -{ - for (int i = 0; i < s.length(); ++i) { - if (s[i].unicode() > 127) { - return false; - } - } - return true; -} - -static inline bool isSpecial(char ch) -{ - static const QByteArray specials = "()<>[]:;@\\,.\""; - return specials.indexOf(ch) >= 0; -} - -static inline bool needsQuoting(char ch) -{ - return ch == '\\' || ch == '"' || ch == '\n'; -} - -static inline QByteArray rfc2047Encode(const QString &s) -{ - QByteArray r = s.trimmed().toUtf8().toBase64(); - return "=?utf-8?b?" + r + "?="; // use base64 since that always gives a valid encoded-word -} - -static QByteArray quote(const QString &s) -{ - assert(isUsAscii(s)); - - QByteArray r(s.length() * 2, 0); - bool needsQuotes = false; - - unsigned int j = 0; - for (int i = 0; i < s.length(); ++i) { - char ch = s[i].toLatin1(); - if (isSpecial(ch)) { - if (needsQuoting(ch)) { - r[j++] = '\\'; - } - needsQuotes = true; - } - r[j++] = ch; - } - r.truncate(j); - - if (needsQuotes) { - return '"' + r + '"'; - } else { - return r; - } -} - -static QByteArray formatFromAddress(const QString &fromRealName, const QString &fromAddress) -{ - if (fromRealName.isEmpty()) { - return fromAddress.toLatin1(); // no real name: return "joe@user.org" - } - - // return "Joe User ", "\"User, Joe\" " - // or "=?utf-8?q?Joe_User?= ", depending on real name's nature. - QByteArray r = isUsAscii(fromRealName) ? quote(fromRealName) : rfc2047Encode(fromRealName); - return r + " <" + fromAddress.toLatin1() + '>'; -} - -static QByteArray formatSubject(QString s) -{ - if (isUsAscii(s)) { - return s.remove(QLatin1Char('\n')).toLatin1(); // don't break header folding, - } else { - // so remove any line break - // that happen to be around - return rfc2047Encode(s); - } -} - -QByteArray Request::headerFields(const QString &fromRealName) const -{ - if (!emitHeaders()) { - return nullptr; - } - - assert(hasFromAddress()); // should have been checked for by - // caller (MAIL FROM comes before DATA) - - QByteArray result = QByteArrayLiteral("From: ") + formatFromAddress(fromRealName, fromAddress()) + QByteArrayLiteral("\r\n"); - - if (!subject().isEmpty()) { - result += QByteArrayLiteral("Subject: ") + formatSubject(subject()) + QByteArrayLiteral("\r\n"); - } - if (!to().empty()) { - result += QByteArrayLiteral("To: ") + to().join(QStringLiteral(",\r\n\t") /* line folding */).toLatin1() + "\r\n"; - } - if (!cc().empty()) { - result += QByteArrayLiteral("Cc: ") + cc().join(QStringLiteral(",\r\n\t") /* line folding */).toLatin1() + "\r\n"; - } - return result; -} -} // namespace KioSMTP diff --git a/kioslave/src/smtp/response.h b/kioslave/src/smtp/response.h deleted file mode 100644 --- a/kioslave/src/smtp/response.h +++ /dev/null @@ -1,178 +0,0 @@ -/* -*- c++ -*- - response.h - - This file is part of kio_smtp, the KDE SMTP kioslave. - Copyright (c) 2003 Marc Mutz - - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - - In addition, as a special exception, the copyright holders give - permission to link the code of this program with any edition of - the Qt library by Trolltech AS, Norway (or with modified versions - of Qt that use the same license as Qt), and distribute linked - combinations including the two. You must obey the GNU General - Public License in all respects for all of the code used other than - Qt. If you modify this file, you may extend this exception to - your version of the file, but you are not obligated to do so. If - you do not wish to do so, delete this exception statement from - your version. -*/ - -#ifndef __KIOSMTP_RESPONSE_H__ -#define __KIOSMTP_RESPONSE_H__ - -#include -#include -typedef QList QCStringList; - -class QString; - -namespace KioSMTP { -class Response -{ -public: - Response() - : mCode(0) - , mValid(true) - , mSawLastLine(false) - , mWellFormed(true) - { - } - - void parseLine(const char *line) - { - parseLine(line, qstrlen(line)); - } - - void parseLine(const char *line, int len); - - /** Return an internationalized error message according to the - * response's code. */ - QString errorMessage() const; - /** Translate the SMTP error code into a KIO one */ - int errorCode() const; - - enum Reply { - UnknownReply = -1, - PositivePreliminary = 1, - PositiveCompletion = 2, - PositiveIntermediate = 3, - TransientNegative = 4, - PermanentNegative = 5 - }; - - enum Category { - UnknownCategory = -1, - SyntaxError = 0, - Information = 1, - Connections = 2, - MailSystem = 5 - }; - - unsigned int code() const - { - return mCode; - } - - unsigned int first() const - { - return code() / 100; - } - - unsigned int second() const - { - return (code() % 100) / 10; - } - - unsigned int third() const - { - return code() % 10; - } - - bool isPositive() const - { - return first() <= 3 && first() >= 1; - } - - bool isNegative() const - { - return first() == 4 || first() == 5; - } - - bool isUnknown() const - { - return !isPositive() && !isNegative(); - } - - QCStringList lines() const - { - return mLines; - } - - bool isValid() const - { - return mValid; - } - - bool isComplete() const - { - return mSawLastLine; - } - - /** Shortcut method. - * @return true iff the response is valid, complete and positive - */ - bool isOk() const - { - return isValid() && isComplete() && isPositive(); - } - - /** Indicates whether the response was well-formed, meaning it - * obeyed the syntax of smtp responses. That the response - * nevertheless is not valid may be caused by e.g. different - * response codes in a multilie response. A non-well-formed - * response is never valid. - */ - bool isWellFormed() const - { - return mWellFormed; - } - - void clear() - { - *this = Response(); - } - -#ifdef KIOSMTP_COMPARATORS - bool operator==(const Response &other) const - { - return mCode == other.mCode - && mValid == other.mValid - && mSawLastLine == other.mSawLastLine - && mWellFormed == other.mWellFormed - && mLines == other.mLines; - } - -#endif - -private: - unsigned int mCode; - QCStringList mLines; - bool mValid; - bool mSawLastLine; - bool mWellFormed; -}; -} // namespace KioSMTP - -#endif // __KIOSMTP_RESPONSE_H__ diff --git a/kioslave/src/smtp/response.cpp b/kioslave/src/smtp/response.cpp deleted file mode 100644 --- a/kioslave/src/smtp/response.cpp +++ /dev/null @@ -1,165 +0,0 @@ -/* -*- c++ -*- - response.cc - - This file is part of kio_smtp, the KDE SMTP kioslave. - Copyright (c) 2003 Marc Mutz - - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - - In addition, as a special exception, the copyright holders give - permission to link the code of this program with any edition of - the Qt library by Trolltech AS, Norway (or with modified versions - of Qt that use the same license as Qt), and distribute linked - combinations including the two. You must obey the GNU General - Public License in all respects for all of the code used other than - Qt. If you modify this file, you may extend this exception to - your version of the file, but you are not obligated to do so. If - you do not wish to do so, delete this exception statement from - your version. -*/ - -#include "response.h" - -#include -#include - -#include - -namespace KioSMTP { -void Response::parseLine(const char *line, int len) -{ - if (!isWellFormed()) { - return; // don't bother - } - - if (isComplete()) { - // if the response is already complete, there can't be another line - mValid = false; - } - - if (len > 1 && line[len - 1] == '\n' && line[len - 2] == '\r') { - len -= 2; - } - - if (len < 3) { - // can't be valid - too short - mValid = false; - mWellFormed = false; - return; - } - - bool ok = false; - unsigned int code = QByteArray(line, 3).toUInt(&ok); - if (!ok || code < 100 || code > 559) { - // not a number or number out of range - mValid = false; - if (!ok || code < 100) { - mWellFormed = false; - } - return; - } - if (mCode && code != mCode) { - // different codes in one response are not allowed. - mValid = false; - return; - } - mCode = code; - - if (len == 3 || line[3] == ' ') { - mSawLastLine = true; - } else if (line[3] != '-') { - // code must be followed by either SP or hyphen (len == 3 is - // also accepted since broken servers exist); all else is - // invalid - mValid = false; - mWellFormed = false; - return; - } - - mLines.push_back(len > 4 ? QByteArray(line + 4, len - 4).trimmed() : QByteArray()); -} - -// hackishly fixing QCStringList flaws... -static QByteArray join(char sep, const QCStringList &list) -{ - if (list.empty()) { - return QByteArray(); - } - QByteArray result = list.front(); - for (QCStringList::const_iterator it = ++list.begin(), end(list.end()); it != end; ++it) { - result += sep + *it; - } - return result; -} - -QString Response::errorMessage() const -{ - QString msg; - if (lines().count() > 1) { - msg = i18n("The server responded:\n%1", QString::fromLatin1(join('\n', lines()))); - } else { - msg = i18n("The server responded: \"%1\"", QString::fromLatin1(lines().at(0))); - } - if (first() == 4) { - msg += QLatin1Char('\n') + i18n("This is a temporary failure. You may try again later."); - } - return msg; -} - -int Response::errorCode() const -{ - switch (code()) { - case 421: // Service not available, closing transmission channel - case 454: // TLS not available due to temporary reason - // Temporary authentication failure - case 554: // Transaction failed / No SMTP service here / No valid recipients - return KIO::ERR_SERVICE_NOT_AVAILABLE; - - case 451: // Requested action aborted: local error in processing - return KIO::ERR_INTERNAL_SERVER; - - case 452: // Requested action not taken: insufficient system storage - case 552: // Requested mail action aborted: exceeded storage allocation - return KIO::ERR_DISK_FULL; - - case 500: // Syntax error, command unrecognized - case 501: // Syntax error in parameters or arguments - case 502: // Command not implemented - case 503: // Bad sequence of commands - case 504: // Command parameter not implemented - return KIO::ERR_INTERNAL; - - case 450: // Requested mail action not taken: mailbox unavailable - case 550: // Requested action not taken: mailbox unavailable - case 551: // User not local; please try - case 553: // Requested action not taken: mailbox name not allowed - return KIO::ERR_DOES_NOT_EXIST; - - case 530: // {STARTTLS,Authentication} required - case 538: // Encryption required for requested authentication mechanism - case 534: // Authentication mechanism is too weak - return KIO::ERR_UPGRADE_REQUIRED; - - case 432: // A password transition is needed - return KIO::ERR_COULD_NOT_AUTHENTICATE; - - default: - if (isPositive()) { - return 0; - } else { - return KIO::ERR_UNKNOWN; - } - } -} -} // namespace KioSMTP diff --git a/kioslave/src/smtp/smtp.h b/kioslave/src/smtp/smtp.h deleted file mode 100644 --- a/kioslave/src/smtp/smtp.h +++ /dev/null @@ -1,125 +0,0 @@ -/* -*- c++ -*- - * Copyright (c) 2000, 2001 Alex Zepeda - * Copyright (c) 2001 Michael H�kel - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - */ - -#ifndef _SMTP_H -#define _SMTP_H - -#include - -#include "capabilities.h" - -#include -#include - -class QUrl; - -namespace KioSMTP { -class Response; -class TransactionState; -class Command; -class SMTPSessionInterface; -class KioSlaveSession; -} - -class SMTPProtocol : public KIO::TCPSlaveBase -{ - friend class KioSMTP::KioSlaveSession; -public: - SMTPProtocol(const QByteArray &pool, const QByteArray &app, bool useSSL); - virtual ~SMTPProtocol(); - - virtual void setHost(const QString &host, quint16 port, const QString &user, const QString &pass) override; - - void special(const QByteArray &aData) override; - void put(const QUrl &url, int permissions, KIO::JobFlags flags) override; - void stat(const QUrl &url) override; - void openConnection() override; - void closeConnection() override; - -protected: - - bool smtp_open(const QString &fakeHostName); - - /** Closes the connection. If @p nice is true (default), then QUIT - is sent and it's reponse waited for. */ - void smtp_close(bool nice = true); - - /** Execute command @p cmd */ - bool execute(KioSMTP::Command *cmd, KioSMTP::TransactionState *ts = nullptr); - /** Execute a command of type @p type */ - bool execute(int type, KioSMTP::TransactionState *ts = nullptr); - /** Execute the queued commands. If something goes horribly wrong - (sending command oline fails, getting response fails or some - command raises the failedFatally() flag in @p ts, shuts down the - connection with smtp_close( false ). If The - transaction fails gracefully (ts->failed() is - true), issues a RSET command. - - @return true if transaction succeeded, false otherwise. - **/ - bool executeQueuedCommands(KioSMTP::TransactionState *ts); - - /** Parse a single response from the server. Single- vs. multiline - responses are correctly detected. - - @param ok if not 0, returns whether response parsing was - successful. Don't confuse this with negative responses - (e.g. 5xx), which you can check for using - @ref Response::isNegative() - @return the @ref Response object representing the server response. - **/ - KioSMTP::Response getResponse(bool *ok); - - bool authenticate(); - - bool sendCommandLine(const QByteArray &cmd); - QByteArray collectPipelineCommands(KioSMTP::TransactionState *ts); - bool batchProcessResponses(KioSMTP::TransactionState *ts); - - void queueCommand(KioSMTP::Command *command) - { - mPendingCommandQueue.enqueue(command); - } - - void queueCommand(int type); - - quint16 m_sOldPort; - quint16 m_port; - bool m_opened; - QString m_sServer, m_sOldServer; - QString m_sUser, m_sOldUser; - QString m_sPass, m_sOldPass; - QString m_hostname; - - typedef QQueue CommandQueue; - CommandQueue mPendingCommandQueue; - CommandQueue mSentCommandQueue; - KioSMTP::SMTPSessionInterface *m_sessionIface; -}; - -#endif // _SMTP_H diff --git a/kioslave/src/smtp/smtp.cpp b/kioslave/src/smtp/smtp.cpp deleted file mode 100644 --- a/kioslave/src/smtp/smtp.cpp +++ /dev/null @@ -1,652 +0,0 @@ -/* - * Copyright (c) 2000, 2001 Alex Zepeda - * Copyright (c) 2001 Michael H�kel - * Copyright (c) 2002 Aaron J. Seigo - * Copyright (c) 2003 Marc Mutz - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - */ - -#include "smtp.h" -#include "smtp_debug.h" - -extern "C" { -#include -} - -#include "../common.h" -#include "request.h" -#include "response.h" -#include "transactionstate.h" -#include "command.h" -#include "kioslavesession.h" -using KioSMTP::Capabilities; -using KioSMTP::Command; -using KioSMTP::EHLOCommand; -using KioSMTP::AuthCommand; -using KioSMTP::MailFromCommand; -using KioSMTP::RcptToCommand; -using KioSMTP::DataCommand; -using KioSMTP::TransferCommand; -using KioSMTP::Request; -using KioSMTP::Response; -using KioSMTP::TransactionState; -using KioSMTP::SMTPSessionInterface; - -#include - -#include -#include -#include -#include - -#include -#include - -#include -using std::unique_ptr; - -#include -#include -#include -#include -#include -#ifdef Q_OS_WIN -#include -#else -#include -#endif - -extern "C" { -Q_DECL_EXPORT int kdemain(int argc, char **argv); -} - -int kdemain(int argc, char **argv) -{ - QCoreApplication app(argc, argv); - app.setApplicationName(QStringLiteral("kio_smtp")); - - if (argc != 4) { - fprintf(stderr, - "Usage: kio_smtp protocol domain-socket1 domain-socket2\n"); - exit(-1); - } - - if (!initSASL()) { - exit(-1); - } - SMTPProtocol slave(argv[2], argv[3], qstricmp(argv[1], "smtps") == 0); - slave.dispatchLoop(); - sasl_done(); - return 0; -} - -SMTPProtocol::SMTPProtocol(const QByteArray &pool, const QByteArray &app, bool useSSL) - : TCPSlaveBase(useSSL ? "smtps" : "smtp", pool, app, useSSL) - , m_sOldPort(0) - , m_opened(false) - , m_sessionIface(new KioSMTP::KioSlaveSession(this)) -{ - //qCDebug(SMTP_LOG) << "SMTPProtocol::SMTPProtocol"; -} - -SMTPProtocol::~SMTPProtocol() -{ - //qCDebug(SMTP_LOG) << "SMTPProtocol::~SMTPProtocol"; - smtp_close(); - delete m_sessionIface; -} - -void SMTPProtocol::openConnection() -{ - // Don't actually call smtp_open() yet. Just pretend that we are connected. - // We can't call smtp_open() here, as that does EHLO, and the EHLO command - // needs the fake hostname. However, we only get the fake hostname in put(), so - // we call smtp_open() there. - connected(); -} - -void SMTPProtocol::closeConnection() -{ - smtp_close(); -} - -void SMTPProtocol::special(const QByteArray &aData) -{ - QDataStream s(aData); - int what; - s >> what; - if (what == 'c') { - const QString response = m_sessionIface->capabilities().createSpecialResponse( - (isUsingSsl() && !isAutoSsl()) - || m_sessionIface->haveCapability("STARTTLS")); - infoMessage(response); - } else if (what == 'N') { - if (!execute(Command::NOOP)) { - return; - } - } else { - error(KIO::ERR_INTERNAL, i18n("The application sent an invalid request.")); - return; - } - finished(); -} - -// Usage: smtp://smtphost:port/send?to=user@host.com&subject=blah -// If smtphost is the name of a profile, it'll use the information -// provided by that profile. If it's not a profile name, it'll use it as -// nature intended. -// One can also specify in the query: -// headers=0 (turns off header generation) -// to=emailaddress -// cc=emailaddress -// bcc=emailaddress -// subject=text -// profile=text (this will override the "host" setting) -// hostname=text (used in the HELO) -// body={7bit,8bit} (default: 7bit; 8bit activates the use of the 8BITMIME SMTP extension) -void SMTPProtocol::put(const QUrl &url, int permissions, KIO::JobFlags flags) -{ - Q_UNUSED(permissions); - Q_UNUSED(flags); - Request request = Request::fromURL(url); // parse settings from URL's query - - KEMailSettings mset; - QUrl open_url = url; - if (!request.hasProfile()) { - //qCDebug(SMTP_LOG) << "kio_smtp: Profile is null"; - bool hasProfile = mset.profiles().contains(open_url.host()); - if (hasProfile) { - mset.setProfile(open_url.host()); - open_url.setHost(mset.getSetting(KEMailSettings::OutServer)); - m_sUser = mset.getSetting(KEMailSettings::OutServerLogin); - m_sPass = mset.getSetting(KEMailSettings::OutServerPass); - - if (m_sUser.isEmpty()) { - m_sUser.clear(); - } - if (m_sPass.isEmpty()) { - m_sPass.clear(); - } - open_url.setUserName(m_sUser); - open_url.setPassword(m_sPass); - m_sServer = open_url.host(); - m_port = open_url.port(); - } else { - mset.setProfile(mset.defaultProfileName()); - } - } else { - mset.setProfile(request.profileName()); - } - - // Check KEMailSettings to see if we've specified an E-Mail address - // if that worked, check to see if we've specified a real name - // and then format accordingly (either: emailaddress@host.com or - // Real Name ) - if (!request.hasFromAddress()) { - const QString from = mset.getSetting(KEMailSettings::EmailAddress); - if (!from.isNull()) { - request.setFromAddress(from); - } else if (request.emitHeaders()) { - error(KIO::ERR_NO_CONTENT, i18n("The sender address is missing.")); - return; - } - } - - if (!smtp_open(request.heloHostname())) { - error(KIO::ERR_SERVICE_NOT_AVAILABLE, - i18n("SMTPProtocol::smtp_open failed (%1)", // ### better error message? - open_url.path())); - return; - } - - if (request.is8BitBody() - && !m_sessionIface->haveCapability("8BITMIME") && !m_sessionIface->eightBitMimeRequested()) { - error(KIO::ERR_SERVICE_NOT_AVAILABLE, - i18n("Your server (%1) does not support sending of 8-bit messages.\n" - "Please use base64 or quoted-printable encoding.", m_sServer)); - return; - } - - queueCommand(new MailFromCommand(m_sessionIface, request.fromAddress().toLatin1(), - request.is8BitBody(), request.size())); - - // Loop through our To and CC recipients, and send the proper - // SMTP commands, for the benefit of the server. - const QStringList recipients = request.recipients(); - for (QStringList::const_iterator it = recipients.begin(), end(recipients.end()); it != end; ++it) { - queueCommand(new RcptToCommand(m_sessionIface, (*it).toLatin1())); - } - - queueCommand(Command::DATA); - queueCommand(new TransferCommand(m_sessionIface, request.headerFields(mset.getSetting(KEMailSettings::RealName)))); - - TransactionState ts; - if (!executeQueuedCommands(&ts)) { - if (ts.errorCode()) { - error(ts.errorCode(), ts.errorMessage()); - } - } else { - finished(); - } -} - -void SMTPProtocol::setHost(const QString &host, quint16 port, const QString &user, const QString &pass) -{ - m_sServer = host; - m_port = port; - m_sUser = user; - m_sPass = pass; -} - -bool SMTPProtocol::sendCommandLine(const QByteArray &cmdline) -{ - //kDebug( cmdline.length() < 4096, 7112) << "C: " << cmdline.data(); - //kDebug( cmdline.length() >= 4096, 7112) << "C: <" << cmdline.length() << " bytes>"; - if (cmdline.length() < 4096) { - qCDebug(SMTP_LOG) << "C: >>" << cmdline.trimmed().data() << "<<"; - } else { - qCDebug(SMTP_LOG) << "C: <" << cmdline.length() << " bytes>"; - } - ssize_t numWritten, cmdline_len = cmdline.length(); - if ((numWritten = write(cmdline.data(), cmdline_len)) != cmdline_len) { - qCDebug(SMTP_LOG) << "Tried to write " << cmdline_len << " bytes, but only " - << numWritten << " were written!" << endl; - error(KIO::ERR_SLAVE_DEFINED, i18n("Writing to socket failed.")); - return false; - } - return true; -} - -Response SMTPProtocol::getResponse(bool *ok) -{ - if (ok) { - *ok = false; - } - - Response response; - char buf[2048]; - - int recv_len = 0; - do { - // wait for data... - if (!waitForResponse(600)) { - error(KIO::ERR_SERVER_TIMEOUT, m_sServer); - return response; - } - - // ...read data... - recv_len = readLine(buf, sizeof(buf) - 1); - if (recv_len < 1 && !isConnected()) { - error(KIO::ERR_CONNECTION_BROKEN, m_sServer); - return response; - } - - qCDebug(SMTP_LOG) << "S: >>" << QByteArray(buf, recv_len).trimmed().data() << "<<"; - // ...and parse lines... - response.parseLine(buf, recv_len); - - // ...until the response is complete or the parser is so confused - // that it doesn't think a RSET would help anymore: - } while (!response.isComplete() && response.isWellFormed()); - - if (!response.isValid()) { - error(KIO::ERR_NO_CONTENT, i18n("Invalid SMTP response (%1) received.", response.code())); - return response; - } - - if (ok) { - *ok = true; - } - - return response; -} - -bool SMTPProtocol::executeQueuedCommands(TransactionState *ts) -{ - assert(ts); - - if (m_sessionIface->canPipelineCommands()) { - qDebug() << "using pipelining"; - } - - while (!mPendingCommandQueue.isEmpty()) { - QByteArray cmdline = collectPipelineCommands(ts); - if (ts->failedFatally()) { - smtp_close(false); // _hard_ shutdown - return false; - } - if (ts->failed()) { - break; - } - if (cmdline.isEmpty()) { - continue; - } - if (!sendCommandLine(cmdline) - || !batchProcessResponses(ts) - || ts->failedFatally()) { - smtp_close(false); // _hard_ shutdown - return false; - } - } - - if (ts->failed()) { - if (!execute(Command::RSET)) { - smtp_close(false); - } - return false; - } - return true; -} - -QByteArray SMTPProtocol::collectPipelineCommands(TransactionState *ts) -{ - assert(ts); - - QByteArray cmdLine; - unsigned int cmdLine_len = 0; - - while (!mPendingCommandQueue.isEmpty()) { - Command *cmd = mPendingCommandQueue.head(); - - if (cmd->doNotExecute(ts)) { - delete mPendingCommandQueue.dequeue(); - if (cmdLine_len) { - break; - } else { - continue; - } - } - - if (cmdLine_len && cmd->mustBeFirstInPipeline()) { - break; - } - - if (cmdLine_len && !m_sessionIface->canPipelineCommands()) { - break; - } - - while (!cmd->isComplete() && !cmd->needsResponse()) { - const QByteArray currentCmdLine = cmd->nextCommandLine(ts); - if (ts->failedFatally()) { - return cmdLine; - } - const unsigned int currentCmdLine_len = currentCmdLine.length(); - - cmdLine_len += currentCmdLine_len; - cmdLine += currentCmdLine; - - // If we are executing the transfer command, don't collect the whole - // command line (which may be several MBs) before sending it, but instead - // send the data each time we have collected 32 KB of the command line. - // - // This way, the progress information in clients like KMail works correctly, - // because otherwise, the TransferCommand would read the whole data from the - // job at once, then sending it. The progress update on the client however - // happens when sending data to the job, not when this slave writes the data - // to the socket. Therefore that progress update is incorrect. - // - // 32 KB seems to be a sensible limit. Additionally, a job can only transfer - // 32 KB at once anyway. - if (dynamic_cast(cmd) != nullptr - && cmdLine_len >= 32 * 1024) { - return cmdLine; - } - } - - mSentCommandQueue.enqueue(mPendingCommandQueue.dequeue()); - - if (cmd->mustBeLastInPipeline()) { - break; - } - } - - return cmdLine; -} - -bool SMTPProtocol::batchProcessResponses(TransactionState *ts) -{ - assert(ts); - - while (!mSentCommandQueue.isEmpty()) { - Command *cmd = mSentCommandQueue.head(); - assert(cmd->isComplete()); - - bool ok = false; - Response r = getResponse(&ok); - if (!ok) { - return false; - } - cmd->processResponse(r, ts); - if (ts->failedFatally()) { - return false; - } - - delete mSentCommandQueue.dequeue(); - } - - return true; -} - -void SMTPProtocol::queueCommand(int type) -{ - queueCommand(Command::createSimpleCommand(type, m_sessionIface)); -} - -bool SMTPProtocol::execute(int type, TransactionState *ts) -{ - unique_ptr cmd(Command::createSimpleCommand(type, m_sessionIface)); - if (!cmd.get()) { - qCritical() << "Command::createSimpleCommand( " << type << " ) returned null!"; - } - return execute(cmd.get(), ts); -} - -// ### fold into pipelining engine? How? (execute() is often called -// ### when command queues are _not_ empty!) -bool SMTPProtocol::execute(Command *cmd, TransactionState *ts) -{ - if (!cmd) { - qCritical() << "SMTPProtocol::execute() called with no command to run!"; - } - - if (cmd->doNotExecute(ts)) { - return true; - } - - do { - while (!cmd->isComplete() && !cmd->needsResponse()) { - const QByteArray cmdLine = cmd->nextCommandLine(ts); - if (ts && ts->failedFatally()) { - smtp_close(false); - return false; - } - if (cmdLine.isEmpty()) { - continue; - } - if (!sendCommandLine(cmdLine)) { - smtp_close(false); - return false; - } - } - - bool ok = false; - Response r = getResponse(&ok); - if (!ok) { - // Only close without sending QUIT if the responce was incomplete - // rfc5321 forbidds a client from closing a connection without sending - // QUIT (section 4.1.1.10) - if (r.isComplete()) { - smtp_close(); - } else { - smtp_close(false); - } - return false; - } - if (!cmd->processResponse(r, ts)) { - if ((ts && ts->failedFatally()) - || cmd->closeConnectionOnError() - || !execute(Command::RSET)) { - smtp_close(false); - } - return false; - } - } while (!cmd->isComplete()); - - return true; -} - -bool SMTPProtocol::smtp_open(const QString &fakeHostname) -{ - if (m_opened - && m_sOldPort == m_port - && m_sOldServer == m_sServer - && m_sOldUser == m_sUser - && (fakeHostname.isNull() || m_hostname == fakeHostname)) { - return true; - } - - smtp_close(); - if (!connectToHost(isAutoSsl() ? QStringLiteral("smtps") : QStringLiteral("smtp"), m_sServer, m_port)) { - return false; // connectToHost has already send an error message. - } - m_opened = true; - - bool ok = false; - Response greeting = getResponse(&ok); - if (!ok || !greeting.isOk()) { - if (ok) { - error(KIO::ERR_COULD_NOT_LOGIN, - i18n("The server (%1) did not accept the connection.\n" - "%2", m_sServer, greeting.errorMessage())); - } - smtp_close(); - return false; - } - - if (!fakeHostname.isNull()) { - m_hostname = fakeHostname; - } else { - // FIXME: We need a way to find the FQDN again. Also change in servertest then. - m_hostname = QHostInfo::localHostName(); - if (m_hostname.isEmpty()) { - m_hostname = QStringLiteral("localhost.invalid"); - } else if (!m_hostname.contains(QLatin1Char('.'))) { - m_hostname += QLatin1String(".localnet"); - } - } - - EHLOCommand ehloCmdPreTLS(m_sessionIface, m_hostname); - if (!execute(&ehloCmdPreTLS)) { - smtp_close(); - return false; - } - - if ((m_sessionIface->haveCapability("STARTTLS") /*### && canUseTLS()*/ && m_sessionIface->tlsRequested() != SMTPSessionInterface::ForceNoTLS) - || m_sessionIface->tlsRequested() == SMTPSessionInterface::ForceTLS) { - // For now we're gonna force it on. - - if (execute(Command::STARTTLS)) { - // re-issue EHLO to refresh the capability list (could be have - // been faked before TLS was enabled): - EHLOCommand ehloCmdPostTLS(m_sessionIface, m_hostname); - if (!execute(&ehloCmdPostTLS)) { - smtp_close(); - return false; - } - } - } - // Now we try and login - if (!authenticate()) { - smtp_close(); - return false; - } - - m_sOldPort = m_port; - m_sOldServer = m_sServer; - m_sOldUser = m_sUser; - m_sOldPass = m_sPass; - - return true; -} - -bool SMTPProtocol::authenticate() -{ - // return with success if the server doesn't support SMTP-AUTH or an user - // name is not specified and metadata doesn't tell us to force it. - if ((m_sUser.isEmpty() || !m_sessionIface->haveCapability("AUTH")) - && m_sessionIface->requestedSaslMethod().isEmpty()) { - return true; - } - - KIO::AuthInfo authInfo; - authInfo.username = m_sUser; - authInfo.password = m_sPass; - authInfo.prompt = i18n("Username and password for your SMTP account:"); - - QStringList strList; - - if (!m_sessionIface->requestedSaslMethod().isEmpty()) { - strList.append(m_sessionIface->requestedSaslMethod()); - } else { - strList = m_sessionIface->capabilities().saslMethodsQSL(); - } - - const QByteArray ba = strList.join(QLatin1Char(' ')).toLatin1(); - AuthCommand authCmd(m_sessionIface, ba.constData(), m_sServer, authInfo); - bool ret = execute(&authCmd); - m_sUser = authInfo.username; - m_sPass = authInfo.password; - return ret; -} - -void SMTPProtocol::smtp_close(bool nice) -{ - if (!m_opened) { // We're already closed - return; - } - - if (nice) { - execute(Command::QUIT); - } - qCDebug(SMTP_LOG) << "closing connection"; - disconnectFromHost(); - m_sOldServer.clear(); - m_sOldUser.clear(); - m_sOldPass.clear(); - - m_sessionIface->clearCapabilities(); - qDeleteAll(mPendingCommandQueue); - mPendingCommandQueue.clear(); - qDeleteAll(mSentCommandQueue); - mSentCommandQueue.clear(); - - m_opened = false; -} - -void SMTPProtocol::stat(const QUrl &url) -{ - QString path = url.path(); - error(KIO::ERR_DOES_NOT_EXIST, url.path()); -} diff --git a/kioslave/src/smtp/smtp.protocol b/kioslave/src/smtp/smtp.protocol deleted file mode 100644 --- a/kioslave/src/smtp/smtp.protocol +++ /dev/null @@ -1,16 +0,0 @@ -[Protocol] -exec=kf5/kio/smtp -protocol=smtp -Capabilities=SASL -input=none -output=filesystem -listing=Name,Type,Size -reading=false -writing=true -deleting=false -source=true -makedir=false -linking=false -moving=false -X-DocPath=kioslave5/smtp/index.html -Icon=mail-folder-outbox diff --git a/kioslave/src/smtp/smtps.protocol b/kioslave/src/smtp/smtps.protocol deleted file mode 100644 --- a/kioslave/src/smtp/smtps.protocol +++ /dev/null @@ -1,16 +0,0 @@ -[Protocol] -exec=kf5/kio/smtp -protocol=smtps -Capabilities=SASL -input=none -output=filesystem -listing=Name,Type,Size -reading=false -writing=true -deleting=false -source=true -makedir=false -linking=false -moving=false -X-DocPath=kioslave5/smtp/index.html -Icon=mail-folder-outbox diff --git a/kioslave/src/smtp/smtpsessioninterface.h b/kioslave/src/smtp/smtpsessioninterface.h deleted file mode 100644 --- a/kioslave/src/smtp/smtpsessioninterface.h +++ /dev/null @@ -1,92 +0,0 @@ -/* - Copyright (c) 2010 Volker Krause - - 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 KIOSMTP_SMTPSESSIONINTERFACE_H -#define KIOSMTP_SMTPSESSIONINTERFACE_H - -#include "capabilities.h" - -class QByteArray; -class QString; - -namespace KIO { -class AuthInfo; -} - -namespace KioSMTP { -class Response; - -/** Interface to the SMTP session for command classes. - * There are sub-classes for the in-process mode, the KIO slave mode and for unit testing. - * @since 4.6 - */ -class SMTPSessionInterface -{ -public: - /** TLS request state. */ - enum TLSRequestState { - UseTLSIfAvailable, - ForceTLS, - ForceNoTLS - }; - - virtual ~SMTPSessionInterface(); - virtual bool startSsl() = 0; - - /** Parse capability response from the server. */ - void parseFeatures(const KioSMTP::Response &ehloResponse); - - /** Returns the server reported capabilities. */ - const Capabilities &capabilities() const; - - /** Clear the capabilities reported by the server (e.g. when reconnecting the session) */ - void clearCapabilities(); - - /** This is a pure convenience wrapper around - * @ref KioSMTP::Capabilities::have() - */ - virtual bool haveCapability(const char *cap) const; - - /** @return true is pipelining is available and allowed by metadata */ - bool canPipelineCommands() const; - - virtual void error(int id, const QString &msg) = 0; - /** Show information message box with message @p msg and caption @p caption. */ - virtual void informationMessageBox(const QString &msg, const QString &caption) = 0; - virtual bool openPasswordDialog(KIO::AuthInfo &authInfo) = 0; - virtual void dataReq() = 0; - virtual int readData(QByteArray &ba) = 0; - - /** SASL method requested for authentication. */ - virtual QString requestedSaslMethod() const = 0; - /** TLS requested for encryption. */ - virtual TLSRequestState tlsRequested() const = 0; - /** LF2CRLF and dot stuffing requested. */ - virtual bool lf2crlfAndDotStuffingRequested() const = 0; - /** 8bit MIME support requested. */ - virtual bool eightBitMimeRequested() const; - /** Pipelining has been requested. */ - virtual bool pipeliningRequested() const; - -private: - KioSMTP::Capabilities m_capabilities; -}; -} - -#endif diff --git a/kioslave/src/smtp/smtpsessioninterface.cpp b/kioslave/src/smtp/smtpsessioninterface.cpp deleted file mode 100644 --- a/kioslave/src/smtp/smtpsessioninterface.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/* - Copyright (c) 2010 Volker Krause - - 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 "smtpsessioninterface.h" - -using namespace KioSMTP; - -SMTPSessionInterface::~SMTPSessionInterface() -{ -} - -void SMTPSessionInterface::parseFeatures(const KioSMTP::Response &ehloResponse) -{ - m_capabilities = Capabilities::fromResponse(ehloResponse); -} - -const Capabilities &KioSMTP::SMTPSessionInterface::capabilities() const -{ - return m_capabilities; -} - -void SMTPSessionInterface::clearCapabilities() -{ - m_capabilities.clear(); -} - -bool SMTPSessionInterface::haveCapability(const char *cap) const -{ - return m_capabilities.have(cap); -} - -bool SMTPSessionInterface::canPipelineCommands() const -{ - return haveCapability("PIPELINING") && pipeliningRequested(); -} - -bool KioSMTP::SMTPSessionInterface::eightBitMimeRequested() const -{ - return false; -} - -bool KioSMTP::SMTPSessionInterface::pipeliningRequested() const -{ - return true; -} diff --git a/kioslave/src/smtp/tests/CMakeLists.txt b/kioslave/src/smtp/tests/CMakeLists.txt deleted file mode 100644 --- a/kioslave/src/smtp/tests/CMakeLists.txt +++ /dev/null @@ -1,71 +0,0 @@ -include(ECMMarkAsTest) - -set(QT_REQUIRED_VERSION "5.7.0") -find_package(Qt5Test ${QT_REQUIRED_VERSION} CONFIG REQUIRED) - -set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) - -########### next target ############### - -if (WIN32) - set(extra_LIB ws2_32) -endif() - -set(test_responseparser_SRCS test_responseparser.cpp ) - -add_executable( test_responseparser ${test_responseparser_SRCS} ) -add_test( test_responseparser test_responseparser ) -ecm_mark_as_test(smtp-responseparser) -target_link_libraries(test_responseparser Qt5::Test KF5::I18n KF5::KIOCore ${extra_LIB}) - -########### next target ############### - -set(test_headergeneration_SRCS test_headergeneration.cpp) -ecm_qt_declare_logging_category(test_headergeneration_SRCS HEADER smtp_debug.h IDENTIFIER SMTP_LOG CATEGORY_NAME org.kde.pim.smtp) - -add_executable( test_headergeneration ${test_headergeneration_SRCS} ) -add_test( test_headergeneration test_headergeneration ) -ecm_mark_as_test(smtp-headergeneration) - -target_link_libraries(test_headergeneration Qt5::Test ${extra_LIB}) - - -########### next target ############### -set(test_commands_SRCS test_commands.cpp ) -ecm_qt_declare_logging_category(test_commands_SRCS HEADER smtp_debug.h IDENTIFIER SMTP_LOG CATEGORY_NAME org.kde.pim.smtp) - -include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/../../ ${Sasl2_INCLUDE_DIRS} ) - -add_executable( test_commands ${test_commands_SRCS} ) -add_test( test_commands test_commands ) -ecm_mark_as_test(smtp-commands) -target_link_libraries(test_commands KF5::KIOCore ${Sasl2_LIBRARIES} Qt5::Test KF5::I18n ${extra_LIB}) - - -########### next target ############### -set(interactivesmtpserver_SRCS interactivesmtpserver.cpp ) - -add_executable( interactivesmtpserver ${interactivesmtpserver_SRCS} ) -ecm_mark_as_test(smtp-interactivesmtpserver) -target_link_libraries(interactivesmtpserver Qt5::Test Qt5::Widgets Qt5::Network) - - -########### next target ############### -set(test_capabilities_SRCS test_capabilities.cpp ../capabilities.cpp ) - -include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/../) - -add_executable( test_capabilities ${test_capabilities_SRCS} ) -ecm_mark_as_test(test-capabilities) -target_link_libraries(test_capabilities KF5::KIOCore) - - -########### next target ############### -set( test_request_source requesttest.cpp ../request.cpp ) -ecm_qt_declare_logging_category(test_request_source HEADER smtp_debug.h IDENTIFIER SMTP_LOG CATEGORY_NAME org.kde.pim.smtp) - -add_executable( requesttest ${test_request_source}) -add_test(requesttest requesttest) -ecm_mark_as_test(requesttest) -target_link_libraries( requesttest Qt5::Test Qt5::Gui) - diff --git a/kioslave/src/smtp/tests/fakesession.h b/kioslave/src/smtp/tests/fakesession.h deleted file mode 100644 --- a/kioslave/src/smtp/tests/fakesession.h +++ /dev/null @@ -1,131 +0,0 @@ -/* - Copyright (c) 2010 Volker Krause - - 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 KIOSMTP_FAKESESSION_H -#define KIOSMTP_FAKESESSION_H - -#include "smtpsessioninterface.h" - -#include -#include - -namespace KioSMTP { -class FakeSession : public SMTPSessionInterface -{ -public: - FakeSession() - { - clear(); - } - - // - // public members to control the API emulation below: - // - bool startTLSReturnCode; - bool usesTLS; // ### unused below, most likely something wrong in the tests... - int lastErrorCode; - QString lastErrorMessage; - QString lastMessageBoxText; - QByteArray nextData; - int nextDataReturnCode; - QStringList caps; - - bool eightBitMime; - bool lf2crlfAndDotStuff; - bool pipelining; - QString saslMethod; - - void clear() - { - startTLSReturnCode = true; - usesTLS = false; - lastErrorCode = 0; - lastErrorMessage.clear(); - lastMessageBoxText.clear(); - nextData.resize(0); - nextDataReturnCode = -1; - caps.clear(); - - lf2crlfAndDotStuff = false; - saslMethod.clear(); - } - - // - // emulated API: - // - bool startSsl() override - { - return startTLSReturnCode; - } - - bool haveCapability(const char *cap) const override - { - return caps.contains(QLatin1String(cap)); - } - - void error(int id, const QString &msg) override - { - lastErrorCode = id; - lastErrorMessage = msg; - qWarning() << id << msg; - } - - void informationMessageBox(const QString &msg, const QString &caption) override - { - Q_UNUSED(caption); - lastMessageBoxText = msg; - } - - bool openPasswordDialog(KIO::AuthInfo &) override - { - return true; - } - - void dataReq() override - { - /* noop */ - } - - int readData(QByteArray &ba) override - { - ba = nextData; - return nextDataReturnCode; - } - - bool lf2crlfAndDotStuffingRequested() const override - { - return lf2crlfAndDotStuff; - } - - QString requestedSaslMethod() const override - { - return saslMethod; - } - - TLSRequestState tlsRequested() const override - { - return SMTPSessionInterface::UseTLSIfAvailable; - } -}; -} - -#include "smtpsessioninterface.cpp" -#include "capabilities.cpp" - -#endif diff --git a/kioslave/src/smtp/tests/interactivesmtpserver.h b/kioslave/src/smtp/tests/interactivesmtpserver.h deleted file mode 100644 --- a/kioslave/src/smtp/tests/interactivesmtpserver.h +++ /dev/null @@ -1,85 +0,0 @@ -#ifndef INTERACTIVESMTPSERVER_H -#define INTERACTIVESMTPSERVER_H - -/* -*- c++ -*- - interactivesmtpserver.h - - Code based on the serverSocket example by Jesper Pedersen. - - This file is part of the testsuite of kio_smtp, the KDE SMTP kioslave. - Copyright (c) 2004 Marc Mutz - - 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 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 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. - - In addition, as a special exception, the copyright holders give - permission to link the code of this program with any edition of - the Qt library by Trolltech AS, Norway (or with modified versions - of Qt that use the same license as Qt), and distribute linked - combinations including the two. You must obey the GNU General - Public License in all respects for all of the code used other than - Qt. If you modify this file, you may extend this exception to - your version of the file, but you are not obligated to do so. If - you do not wish to do so, delete this exception statement from - your version. -*/ - -#include -#include - -class QLabel; -class QLineEdit; -class QTcpServer; -class QTextEdit; - -class InteractiveSMTPServerWindow : public QWidget -{ - Q_OBJECT -public: - InteractiveSMTPServerWindow(QTcpSocket *socket, QWidget *parent = nullptr); - ~InteractiveSMTPServerWindow(); - -public Q_SLOTS: - void slotSendResponse(); - void slotDisplayClient(const QString &s); - void slotDisplayServer(const QString &s); - void slotDisplayMeta(const QString &s); - void slotReadyRead(); - void slotError(QAbstractSocket::SocketError error); - void slotConnectionClosed(); - void slotCloseConnection(); - -private: - QTcpSocket *mSocket; - QTextEdit *mTextEdit; - QLineEdit *mLineEdit; - QLabel *mLabel; -}; - -class InteractiveSMTPServer : public QTcpServer -{ - Q_OBJECT - -public: - InteractiveSMTPServer(QObject *parent = nullptr); - ~InteractiveSMTPServer() - { - } - -private Q_SLOTS: - void newConnectionAvailable(); -}; - -#endif diff --git a/kioslave/src/smtp/tests/interactivesmtpserver.cpp b/kioslave/src/smtp/tests/interactivesmtpserver.cpp deleted file mode 100644 --- a/kioslave/src/smtp/tests/interactivesmtpserver.cpp +++ /dev/null @@ -1,211 +0,0 @@ -/* -*- c++ -*- - interactivesmtpserver.cc - - Code based on the serverSocket example by Jesper Pedersen. - - This file is part of the testsuite of kio_smtp, the KDE SMTP kioslave. - Copyright (c) 2004 Marc Mutz - - 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 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 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. - - In addition, as a special exception, the copyright holders give - permission to link the code of this program with any edition of - the Qt library by Trolltech AS, Norway (or with modified versions - of Qt that use the same license as Qt), and distribute linked - combinations including the two. You must obey the GNU General - Public License in all respects for all of the code used other than - Qt. If you modify this file, you may extend this exception to - your version of the file, but you are not obligated to do so. If - you do not wish to do so, delete this exception statement from - your version. -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "interactivesmtpserver.h" - -static const QHostAddress localhost(0x7f000001); // 127.0.0.1 - -static QString err2str(QAbstractSocket::SocketError error) -{ - switch (error) { - case QAbstractSocket::ConnectionRefusedError: - return QStringLiteral("Connection refused"); - case QAbstractSocket::HostNotFoundError: - return QStringLiteral("Host not found"); - default: - return QStringLiteral("Unknown error"); - } -} - -static QString escape(QString s) -{ - return s - .replace(QLatin1Char('&'), QLatin1String("&")) - .replace(QLatin1Char('>'), QLatin1String(">")) - .replace(QLatin1Char('<'), QLatin1String("<")) - .replace(QLatin1Char('"'), QLatin1String(""")) - ; -} - -static QString trim(const QString &s) -{ - if (s.endsWith(QLatin1String("\r\n"))) { - return s.left(s.length() - 2); - } - if (s.endsWith(QLatin1String("\r")) || s.endsWith(QLatin1String("\n"))) { - return s.left(s.length() - 1); - } - return s; -} - -InteractiveSMTPServerWindow::~InteractiveSMTPServerWindow() -{ - if (mSocket) { - mSocket->close(); - if (mSocket->state() == QAbstractSocket::ClosingState) { - connect(mSocket, SIGNAL(disconnected()), - mSocket, SLOT(deleteLater())); - } else { - mSocket->deleteLater(); - } - mSocket = nullptr; - } -} - -void InteractiveSMTPServerWindow::slotSendResponse() -{ - const QString line = mLineEdit->text(); - mLineEdit->clear(); - QTextStream s(mSocket); - s << line + QLatin1String("\r\n"); - slotDisplayServer(line); -} - -InteractiveSMTPServer::InteractiveSMTPServer(QObject *parent) - : QTcpServer(parent) -{ - listen(localhost, 2525); - setMaxPendingConnections(1); - - connect(this, SIGNAL(newConnection()), this, SLOT(newConnectionAvailable())); -} - -void InteractiveSMTPServer::newConnectionAvailable() -{ - InteractiveSMTPServerWindow *w = new InteractiveSMTPServerWindow(nextPendingConnection()); - w->show(); -} - -int main(int argc, char *argv[]) -{ - QApplication app(argc, argv); - - InteractiveSMTPServer server; - - qDebug("Server should now listen on localhost:2525"); - qDebug("Hit CTRL-C to quit."); - - return app.exec(); -} - -InteractiveSMTPServerWindow::InteractiveSMTPServerWindow(QTcpSocket *socket, QWidget *parent) - : QWidget(parent) - , mSocket(socket) -{ - QPushButton *but; - Q_ASSERT(socket); - - QVBoxLayout *vlay = new QVBoxLayout(this); - - mTextEdit = new QTextEdit(this); - vlay->addWidget(mTextEdit, 1); - QWidget *mLayoutWidget = new QWidget; - vlay->addWidget(mLayoutWidget); - - QHBoxLayout *hlay = new QHBoxLayout(mLayoutWidget); - - mLineEdit = new QLineEdit(this); - mLabel = new QLabel(QStringLiteral("&Response:"), this); - mLabel->setBuddy(mLineEdit); - but = new QPushButton(QStringLiteral("&Send"), this); - hlay->addWidget(mLabel); - hlay->addWidget(mLineEdit, 1); - hlay->addWidget(but); - - connect(mLineEdit, SIGNAL(returnPressed()), SLOT(slotSendResponse())); - connect(but, SIGNAL(clicked()), SLOT(slotSendResponse())); - - but = new QPushButton(QStringLiteral("&Close Connection"), this); - vlay->addWidget(but); - - connect(but, SIGNAL(clicked()), SLOT(slotConnectionClosed())); - - connect(socket, SIGNAL(disconnected()), SLOT(slotConnectionClosed())); - connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), - SLOT(slotError(QAbstractSocket::SocketError))); - connect(socket, SIGNAL(readyRead()), SLOT(slotReadyRead())); - - mLineEdit->setText(QStringLiteral("220 hi there")); - mLineEdit->setFocus(); -} - -void InteractiveSMTPServerWindow::slotDisplayClient(const QString &s) -{ - mTextEdit->append(QLatin1String("C:") + escape(s)); -} - -void InteractiveSMTPServerWindow::slotDisplayServer(const QString &s) -{ - mTextEdit->append(QLatin1String("S:") + escape(s)); -} - -void InteractiveSMTPServerWindow::slotDisplayMeta(const QString &s) -{ - mTextEdit->append(QLatin1String("") + escape(s) + QLatin1String("")); -} - -void InteractiveSMTPServerWindow::slotReadyRead() -{ - while (mSocket->canReadLine()) { - slotDisplayClient(trim(QString::fromLatin1(mSocket->readLine()))); - } -} - -void InteractiveSMTPServerWindow::slotError(QAbstractSocket::SocketError error) -{ - slotDisplayMeta(QString::fromLatin1("E: %1").arg(err2str(error))); -} - -void InteractiveSMTPServerWindow::slotConnectionClosed() -{ - slotDisplayMeta(QStringLiteral("Connection closed by peer")); -} - -void InteractiveSMTPServerWindow::slotCloseConnection() -{ - mSocket->close(); -} diff --git a/kioslave/src/smtp/tests/requesttest.h b/kioslave/src/smtp/tests/requesttest.h deleted file mode 100644 --- a/kioslave/src/smtp/tests/requesttest.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - Copyright (c) 2014-2017 Montel Laurent - - 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 REQUESTTEST_H -#define REQUESTTEST_H - -#include - -class RequestTest : public QObject -{ - Q_OBJECT -public: - explicit RequestTest(QObject *parent = nullptr); - ~RequestTest(); -private Q_SLOTS: - void shouldHaveDefaultValue(); - void shouldParseRequest_data(); - void shouldParseRequest(); -}; - -#endif // REQUESTTEST_H diff --git a/kioslave/src/smtp/tests/requesttest.cpp b/kioslave/src/smtp/tests/requesttest.cpp deleted file mode 100644 --- a/kioslave/src/smtp/tests/requesttest.cpp +++ /dev/null @@ -1,83 +0,0 @@ -/* - Copyright (c) 2014-2017 Montel Laurent - - 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 "requesttest.h" -#include "../request.h" -#include -#include -RequestTest::RequestTest(QObject *parent) - : QObject(parent) -{ -} - -RequestTest::~RequestTest() -{ -} - -void RequestTest::shouldHaveDefaultValue() -{ - KioSMTP::Request request; - QVERIFY(request.to().isEmpty()); - QVERIFY(request.cc().isEmpty()); - QVERIFY(request.bcc().isEmpty()); - QVERIFY(request.emitHeaders()); - QVERIFY(!request.is8BitBody()); - QVERIFY(request.profileName().isEmpty()); - QVERIFY(request.fromAddress().isEmpty()); - QVERIFY(request.heloHostname().isEmpty()); - QCOMPARE(request.size(), static_cast(0)); -} - -void RequestTest::shouldParseRequest_data() -{ - QTest::addColumn("smtpurl"); - QTest::addColumn("to"); - QTest::addColumn("from"); - QTest::addColumn("cc"); - QTest::addColumn("bcc"); - QTest::addColumn("emitheaders"); - QTest::addColumn("size"); - QTest::newRow("correct url") << QUrl(QStringLiteral("smtps://smtp.kde.org:465/send?headers=0&from=foo%40kde.org&to=foo%40kde.org&size=617")) - << QStringLiteral("foo@kde.org") - << QStringLiteral("foo@kde.org") - << QString() - << QString() - << false - << static_cast(617); -} - -void RequestTest::shouldParseRequest() -{ - QFETCH(QUrl, smtpurl); - QFETCH(QString, to); - QFETCH(QString, from); - QFETCH(QString, cc); - QFETCH(QString, bcc); - QFETCH(bool, emitheaders); - QFETCH(unsigned int, size); - - KioSMTP::Request request = KioSMTP::Request::fromURL(smtpurl); - QCOMPARE(request.to().join(QLatin1Char(',')), to); - QCOMPARE(request.cc().join(QLatin1Char(',')), cc); - QCOMPARE(request.fromAddress(), from); - QCOMPARE(request.bcc().join(QLatin1Char(',')), bcc); - QCOMPARE(request.size(), size); - QCOMPARE(request.emitHeaders(), emitheaders); -} - -QTEST_MAIN(RequestTest) diff --git a/kioslave/src/smtp/tests/test_capabilities.cpp b/kioslave/src/smtp/tests/test_capabilities.cpp deleted file mode 100644 --- a/kioslave/src/smtp/tests/test_capabilities.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include -#include "capabilities.h" -#include - -using namespace KioSMTP; - -int main() -{ - Capabilities c; - - const QString size_cap = QObject::tr("SIZE 12"); - c.add(size_cap); - // Capability was added - assert(c.have("SIZE")); - - const QString expected_response = QObject::tr("SIZE=12"); - const QString actual_response = c.createSpecialResponse(false); - // SIZE actually handled - assert(actual_response == expected_response); - - const QString auth_cap = QObject::tr("AUTH GSSAPI"); - c.add(auth_cap); - c.add(auth_cap); - // Duplicate methods was removed - assert(c.saslMethodsQSL().length() == 1); -} diff --git a/kioslave/src/smtp/tests/test_commands.cpp b/kioslave/src/smtp/tests/test_commands.cpp deleted file mode 100644 --- a/kioslave/src/smtp/tests/test_commands.cpp +++ /dev/null @@ -1,676 +0,0 @@ -#include -#include -#include - -#define KIOSMTP_COMPARATORS // for TransactionState::operator== -#include "fakesession.h" -#include "command.h" -#include "response.h" -#include "transactionstate.h" -#include "common.h" -#include "smtp_debug.h" -#include - -using namespace KioSMTP; - -static const char *foobarbaz = ".Foo bar baz"; -static const unsigned int foobarbaz_len = qstrlen(foobarbaz); - -static const char *foobarbaz_dotstuffed = "..Foo bar baz"; -static const unsigned int foobarbaz_dotstuffed_len = qstrlen(foobarbaz_dotstuffed); - -static const char *foobarbaz_lf = ".Foo bar baz\n"; -static const unsigned int foobarbaz_lf_len = qstrlen(foobarbaz_lf); - -static const char *foobarbaz_crlf = "..Foo bar baz\r\n"; -static const unsigned int foobarbaz_crlf_len = qstrlen(foobarbaz_crlf); - -static void checkSuccessfulTransferCommand(bool, bool, bool, bool, bool); - -int main(int, char **) -{ - if (!initSASL()) { - exit(-1); - } - - FakeSession smtp; - Response r; - TransactionState ts, ts2; - - // - // EHLO / HELO - // - - smtp.clear(); - EHLOCommand ehlo(&smtp, QStringLiteral("mail.example.com")); - // flags - assert(ehlo.closeConnectionOnError()); - assert(ehlo.mustBeLastInPipeline()); - assert(!ehlo.mustBeFirstInPipeline()); - - // initial state - assert(!ehlo.isComplete()); - assert(!ehlo.doNotExecute(nullptr)); - assert(!ehlo.needsResponse()); - - // dynamics 1: EHLO succeeds - assert(ehlo.nextCommandLine(nullptr) == "EHLO mail.example.com\r\n"); - assert(!ehlo.isComplete()); // EHLO may fail and we then try HELO - assert(ehlo.needsResponse()); - r.clear(); - r.parseLine("250-mail.example.net\r\n"); - r.parseLine("250-PIPELINING\r\n"); - r.parseLine("250 8BITMIME\r\n"); - assert(ehlo.processResponse(r, nullptr) == true); - assert(ehlo.isComplete()); - assert(!ehlo.needsResponse()); - assert(smtp.lastErrorCode == 0); - assert(smtp.lastErrorMessage.isNull()); - - // dynamics 2: EHLO fails with "unknown command" - smtp.clear(); - EHLOCommand ehlo2(&smtp, QStringLiteral("mail.example.com")); - ehlo2.nextCommandLine(nullptr); - r.clear(); - r.parseLine("500 unknown command\r\n"); - assert(ehlo2.processResponse(r, nullptr) == true); - assert(!ehlo2.isComplete()); - assert(!ehlo2.needsResponse()); - assert(ehlo2.nextCommandLine(nullptr) == "HELO mail.example.com\r\n"); - assert(ehlo2.isComplete()); - assert(ehlo2.needsResponse()); - r.clear(); - r.parseLine("250 mail.example.net\r\n"); - assert(ehlo2.processResponse(r, nullptr) == true); - assert(!ehlo2.needsResponse()); - assert(smtp.lastErrorCode == 0); - assert(smtp.lastErrorMessage.isNull()); - - // dynamics 3: EHLO fails with unknown response code - smtp.clear(); - EHLOCommand ehlo3(&smtp, QStringLiteral("mail.example.com")); - ehlo3.nextCommandLine(nullptr); - r.clear(); - r.parseLine("545 you don't know me\r\n"); - assert(ehlo3.processResponse(r, nullptr) == false); - assert(ehlo3.isComplete()); - assert(!ehlo3.needsResponse()); - assert(smtp.lastErrorCode == KIO::ERR_UNKNOWN); - - // dynamics 4: EHLO _and_ HELO fail with "command unknown" - smtp.clear(); - EHLOCommand ehlo4(&smtp, QStringLiteral("mail.example.com")); - ehlo4.nextCommandLine(nullptr); - r.clear(); - r.parseLine("500 unknown command\r\n"); - ehlo4.processResponse(r, nullptr); - ehlo4.nextCommandLine(nullptr); - r.clear(); - r.parseLine("500 unknown command\r\n"); - assert(ehlo4.processResponse(r, nullptr) == false); - assert(ehlo4.isComplete()); - assert(!ehlo4.needsResponse()); - assert(smtp.lastErrorCode == KIO::ERR_INTERNAL_SERVER); - - // - // STARTTLS - // - - smtp.clear(); - StartTLSCommand tls(&smtp); - // flags - assert(tls.closeConnectionOnError()); - assert(tls.mustBeLastInPipeline()); - assert(!tls.mustBeFirstInPipeline()); - - // initial state - assert(!tls.isComplete()); - assert(!tls.doNotExecute(nullptr)); - assert(!tls.needsResponse()); - - // dynamics 1: ok from server, TLS negotiation successful - ts.clear(); - ts2 = ts; - assert(tls.nextCommandLine(&ts) == "STARTTLS\r\n"); - assert(ts == ts2); - assert(tls.isComplete()); - assert(tls.needsResponse()); - r.clear(); - r.parseLine("220 Go ahead"); - smtp.startTLSReturnCode = true; - assert(tls.processResponse(r, &ts) == true); - assert(!tls.needsResponse()); - assert(smtp.lastErrorCode == 0); - - // dynamics 2: NAK from server - smtp.clear(); - StartTLSCommand tls2(&smtp); - ts.clear(); - tls2.nextCommandLine(&ts); - r.clear(); - r.parseLine("454 TLS temporarily disabled"); - smtp.startTLSReturnCode = true; - assert(tls2.processResponse(r, &ts) == false); - assert(!tls2.needsResponse()); - assert(smtp.lastErrorCode == KIO::ERR_SERVICE_NOT_AVAILABLE); - - // dynamics 3: ok from server, TLS negotiation unsuccessful - smtp.clear(); - StartTLSCommand tls3(&smtp); - ts.clear(); - tls3.nextCommandLine(&ts); - r.clear(); - r.parseLine("220 Go ahead"); - smtp.startTLSReturnCode = false; - assert(tls.processResponse(r, &ts) == false); - assert(!tls.needsResponse()); - - // - // AUTH - // - - smtp.clear(); - QStringList mechs; - mechs.append(QStringLiteral("PLAIN")); - smtp.saslMethod = QStringLiteral("PLAIN"); - KIO::AuthInfo authInfo; - authInfo.username = QStringLiteral("user"); - authInfo.password = QStringLiteral("pass"); - AuthCommand auth(&smtp, "PLAIN", QStringLiteral("mail.example.com"), authInfo); - // flags - assert(auth.closeConnectionOnError()); - assert(auth.mustBeLastInPipeline()); - assert(!auth.mustBeFirstInPipeline()); - - // initial state - assert(!auth.isComplete()); - assert(!auth.doNotExecute(nullptr)); - assert(!auth.needsResponse()); - - // dynamics 1: TLS, so AUTH should include initial-response: - smtp.usesTLS = true; - ts.clear(); - ts2 = ts; - assert(auth.nextCommandLine(&ts) == "AUTH PLAIN dXNlcgB1c2VyAHBhc3M=\r\n"); - assert(auth.isComplete()); - assert(auth.needsResponse()); - assert(ts == ts2); - r.clear(); - r.parseLine("250 OK"); - - // dynamics 2: No TLS, so AUTH should not include initial-response: - /* FIXME fails since nothing evaluates useTLS = false anywhere... - smtp.clear(); - smtp.saslMethod = "PLAIN"; - smtp.usesTLS = false; - authInfo = KIO::AuthInfo(); - authInfo.username = "user"; - authInfo.password = "pass"; - AuthCommand auth2( &smtp, "PLAIN", "mail.example.com", authInfo ); - ts.clear(); - assert( auth2.nextCommandLine( &ts ) == "AUTH PLAIN\r\n" ); - assert( !auth2.isComplete() ); - assert( auth2.needsResponse() ); - r.clear(); - r.parseLine( "334 Go on" ); - assert( auth2.processResponse( r, &ts ) == true ); - assert( auth2.nextCommandLine( &ts ) == "dXNlcgB1c2VyAHBhc3M=\r\n" ); - assert( auth2.isComplete() ); - assert( auth2.needsResponse() );*/ - - // dynamics 3: LOGIN - smtp.clear(); - smtp.saslMethod = QStringLiteral("LOGIN"); - mechs.clear(); - mechs.append(QStringLiteral("LOGIN")); - authInfo = KIO::AuthInfo(); - authInfo.username = QStringLiteral("user"); - authInfo.password = QStringLiteral("pass"); - AuthCommand auth3(&smtp, "LOGIN", QStringLiteral("mail.example.com"), authInfo); - ts.clear(); - ts2 = ts; - assert(auth3.nextCommandLine(&ts) == "AUTH LOGIN\r\n"); - assert(!auth3.isComplete()); - assert(auth3.needsResponse()); - r.clear(); - r.parseLine("334 VXNlcm5hbWU6"); - assert(auth3.processResponse(r, &ts) == true); - assert(!auth3.needsResponse()); - assert(auth3.nextCommandLine(&ts) == "dXNlcg==\r\n"); - assert(!auth3.isComplete()); - assert(auth3.needsResponse()); - r.clear(); - r.parseLine("334 go on"); - assert(auth3.processResponse(r, &ts) == true); - assert(!auth3.needsResponse()); - assert(auth3.nextCommandLine(&ts) == "cGFzcw==\r\n"); - assert(auth3.isComplete()); - assert(auth3.needsResponse()); - r.clear(); - r.parseLine("250 OK"); - assert(auth3.processResponse(r, &ts) == true); - assert(!auth3.needsResponse()); - assert(!smtp.lastErrorCode); - assert(ts == ts2); - - // - // MAIL FROM: - // - - smtp.clear(); - MailFromCommand mail(&smtp, "joe@user.org"); - // flags - assert(!mail.closeConnectionOnError()); - assert(!mail.mustBeLastInPipeline()); - assert(!mail.mustBeFirstInPipeline()); - - // initial state - assert(!mail.isComplete()); - assert(!mail.doNotExecute(nullptr)); - assert(!mail.needsResponse()); - - // dynamics: success, no size, no 8bit - ts.clear(); - ts2 = ts; - assert(mail.nextCommandLine(&ts) == "MAIL FROM:\r\n"); - assert(ts2 == ts); - assert(mail.isComplete()); - assert(mail.needsResponse()); - r.clear(); - r.parseLine("250 Ok"); - assert(mail.processResponse(r, &ts) == true); - assert(!mail.needsResponse()); - assert(ts == ts2); - assert(smtp.lastErrorCode == 0); - - // dynamics: success, size, 8bit, but no SIZE, 8BITMIME caps - smtp.clear(); - MailFromCommand mail2(&smtp, "joe@user.org", true, 500); - ts.clear(); - ts2 = ts; - assert(mail2.nextCommandLine(&ts) == "MAIL FROM:\r\n"); - assert(ts == ts2); - - // dynamics: success, size, 8bit, SIZE, 8BITMIME caps - smtp.clear(); - MailFromCommand mail3(&smtp, "joe@user.org", true, 500); - ts.clear(); - ts2 = ts; - smtp.caps << QStringLiteral("SIZE") << QStringLiteral("8BITMIME"); - assert(mail3.nextCommandLine(&ts) == "MAIL FROM: BODY=8BITMIME SIZE=500\r\n"); - assert(ts == ts2); - - // dynamics: failure - smtp.clear(); - MailFromCommand mail4(&smtp, "joe@user.org"); - ts.clear(); - mail4.nextCommandLine(&ts); - r.clear(); - r.parseLine("503 Bad sequence of commands"); - assert(mail4.processResponse(r, &ts) == false); - assert(mail4.isComplete()); - assert(!mail4.needsResponse()); - assert(ts.failed()); - assert(!ts.failedFatally()); - assert(smtp.lastErrorCode == 0); - - // - // RCPT TO: - // - - smtp.clear(); - RcptToCommand rcpt(&smtp, "joe@user.org"); - // flags - assert(!rcpt.closeConnectionOnError()); - assert(!rcpt.mustBeLastInPipeline()); - assert(!rcpt.mustBeFirstInPipeline()); - - // initial state - assert(!rcpt.isComplete()); - assert(!rcpt.doNotExecute(nullptr)); - assert(!rcpt.needsResponse()); - - // dynamics: success - ts.clear(); - ts2 = ts; - assert(rcpt.nextCommandLine(&ts) == "RCPT TO:\r\n"); - assert(ts == ts2); - assert(rcpt.isComplete()); - assert(rcpt.needsResponse()); - r.clear(); - r.parseLine("250 Ok"); - assert(rcpt.processResponse(r, &ts) == true); - assert(!rcpt.needsResponse()); - assert(ts.atLeastOneRecipientWasAccepted()); - assert(!ts.haveRejectedRecipients()); - assert(!ts.failed()); - assert(!ts.failedFatally()); - assert(smtp.lastErrorCode == 0); - - // dynamics: failure - smtp.clear(); - RcptToCommand rcpt2(&smtp, "joe@user.org"); - ts.clear(); - rcpt2.nextCommandLine(&ts); - r.clear(); - r.parseLine("530 5.7.1 Relaying not allowed!"); - assert(rcpt2.processResponse(r, &ts) == false); - assert(rcpt2.isComplete()); - assert(!rcpt2.needsResponse()); - assert(!ts.atLeastOneRecipientWasAccepted()); - assert(ts.haveRejectedRecipients()); - assert(ts.rejectedRecipients().count() == 1); - assert(ts.rejectedRecipients().front().recipient == QLatin1String("joe@user.org")); - assert(ts.failed()); - assert(!ts.failedFatally()); - assert(smtp.lastErrorCode == 0); - - // dynamics: success and failure combined - smtp.clear(); - RcptToCommand rcpt3(&smtp, "info@example.com"); - RcptToCommand rcpt4(&smtp, "halloween@microsoft.com"); - RcptToCommand rcpt5(&smtp, "joe@user.org"); - ts.clear(); - rcpt3.nextCommandLine(&ts); - r.clear(); - r.parseLine("530 5.7.1 Relaying not allowed!"); - rcpt3.processResponse(r, &ts); - - rcpt4.nextCommandLine(&ts); - r.clear(); - r.parseLine("250 Ok"); - rcpt4.processResponse(r, &ts); - - rcpt5.nextCommandLine(&ts); - r.clear(); - r.parseLine("250 Ok"); - assert(ts.failed()); - assert(!ts.failedFatally()); - assert(ts.haveRejectedRecipients()); - assert(ts.atLeastOneRecipientWasAccepted()); - assert(smtp.lastErrorCode == 0); - - // - // DATA (init) - // - - smtp.clear(); - DataCommand data(&smtp); - // flags - assert(!data.closeConnectionOnError()); - assert(data.mustBeLastInPipeline()); - assert(!data.mustBeFirstInPipeline()); - - // initial state - assert(!data.isComplete()); - assert(!data.doNotExecute(nullptr)); - assert(!data.needsResponse()); - - // dynamics: success - ts.clear(); - assert(data.nextCommandLine(&ts) == "DATA\r\n"); - assert(data.isComplete()); - assert(data.needsResponse()); - assert(ts.dataCommandIssued()); - assert(!ts.dataCommandSucceeded()); - r.clear(); - r.parseLine("354 Send data, end in ."); - assert(data.processResponse(r, &ts) == true); - assert(!data.needsResponse()); - assert(ts.dataCommandSucceeded()); - assert(ts.dataResponse() == r); - assert(smtp.lastErrorCode == 0); - - // dynamics: failure - smtp.clear(); - DataCommand data2(&smtp); - ts.clear(); - data2.nextCommandLine(&ts); - r.clear(); - r.parseLine("551 No valid recipients"); - assert(data2.processResponse(r, &ts) == false); - assert(!data2.needsResponse()); - assert(!ts.dataCommandSucceeded()); - assert(ts.dataResponse() == r); - assert(smtp.lastErrorCode == 0); - - // - // DATA (transfer) - // - - TransferCommand xfer(&smtp, nullptr); - // flags - assert(!xfer.closeConnectionOnError()); - assert(!xfer.mustBeLastInPipeline()); - assert(xfer.mustBeFirstInPipeline()); - - // initial state - assert(!xfer.isComplete()); - assert(!xfer.needsResponse()); - - // dynamics 1: DATA command failed - ts.clear(); - r.clear(); - r.parseLine("551 no valid recipients"); - ts.setDataCommandIssued(true); - ts.setDataCommandSucceeded(false, r); - assert(xfer.doNotExecute(&ts)); - - // dynamics 2: some recipients rejected, but not all - smtp.clear(); - TransferCommand xfer2(&smtp, nullptr); - ts.clear(); - ts.setRecipientAccepted(); - ts.addRejectedRecipient(QStringLiteral("joe@user.org"), QStringLiteral("No relaying allowed")); - ts.setDataCommandIssued(true); - r.clear(); - r.parseLine("354 go on"); - ts.setDataCommandSucceeded(true, r); - // ### will change with allow-partial-delivery option: - assert(xfer.doNotExecute(&ts)); - - // successful dynamics with all combinations of: - enum { - EndInLF = 1, - PerformDotStuff = 2, - UngetLast = 4, - Preloading = 8, - Error = 16, - EndOfOptions = 32 - }; - for (unsigned int i = 0; i < EndOfOptions; ++i) { - checkSuccessfulTransferCommand(i & Error, i & Preloading, i & UngetLast, - i & PerformDotStuff, i & EndInLF); - } - - // - // NOOP - // - - smtp.clear(); - NoopCommand noop(&smtp); - // flags - assert(!noop.closeConnectionOnError()); - assert(noop.mustBeLastInPipeline()); - assert(!noop.mustBeFirstInPipeline()); - - // initial state - assert(!noop.isComplete()); - assert(!noop.doNotExecute(&ts)); - assert(!noop.needsResponse()); - - // dynamics: success (failure is tested with RSET) - assert(noop.nextCommandLine(nullptr) == "NOOP\r\n"); - assert(noop.isComplete()); - assert(noop.needsResponse()); - r.clear(); - r.parseLine("250 Ok"); - assert(noop.processResponse(r, nullptr) == true); - assert(noop.isComplete()); - assert(!noop.needsResponse()); - assert(smtp.lastErrorCode == 0); - assert(smtp.lastErrorMessage.isNull()); - - // - // RSET - // - - smtp.clear(); - RsetCommand rset(&smtp); - // flags - assert(rset.closeConnectionOnError()); - assert(!rset.mustBeLastInPipeline()); - assert(!rset.mustBeFirstInPipeline()); - - // initial state - assert(!rset.isComplete()); - assert(!rset.doNotExecute(&ts)); - assert(!rset.needsResponse()); - - // dynamics: failure (success is tested with NOOP/QUIT) - assert(rset.nextCommandLine(nullptr) == "RSET\r\n"); - assert(rset.isComplete()); - assert(rset.needsResponse()); - r.clear(); - r.parseLine("502 command not implemented"); - assert(rset.processResponse(r, nullptr) == false); - assert(rset.isComplete()); - assert(!rset.needsResponse()); - assert(smtp.lastErrorCode == 0); // an RSET failure isn't worth it, is it? - assert(smtp.lastErrorMessage.isNull()); - - // - // QUIT - // - - smtp.clear(); - QuitCommand quit(&smtp); - // flags - assert(quit.closeConnectionOnError()); - assert(quit.mustBeLastInPipeline()); - assert(!quit.mustBeFirstInPipeline()); - - // initial state - assert(!quit.isComplete()); - assert(!quit.doNotExecute(nullptr)); - assert(!quit.needsResponse()); - - // dynamics 1: success - assert(quit.nextCommandLine(nullptr) == "QUIT\r\n"); - assert(quit.isComplete()); - assert(quit.needsResponse()); - r.clear(); - r.parseLine("221 Goodbye"); - assert(quit.processResponse(r, nullptr) == true); - assert(quit.isComplete()); - assert(!quit.needsResponse()); - assert(smtp.lastErrorCode == 0); - assert(smtp.lastErrorMessage.isNull()); - - // dynamics 2: success - smtp.clear(); - QuitCommand quit2(&smtp); - quit2.nextCommandLine(nullptr); - r.clear(); - r.parseLine("500 unknown command"); - assert(quit2.processResponse(r, nullptr) == false); - assert(quit2.isComplete()); - assert(!quit2.needsResponse()); - assert(smtp.lastErrorCode == 0); // an QUIT failure isn't worth it, is it? - assert(smtp.lastErrorMessage.isNull()); - - return 0; -} - -void checkSuccessfulTransferCommand(bool error, bool preload, bool ungetLast, bool slaveDotStuff, bool mailEndsInNewline) -{ - qDebug() << " ===== checkTransferCommand( " - << error << ", " - << preload << ", " - << ungetLast << ", " - << slaveDotStuff << ", " - << mailEndsInNewline << " ) =====" << endl; - - FakeSession smtp; - if (slaveDotStuff) { - smtp.lf2crlfAndDotStuff = true; - } - - Response r; - - const char *s_pre = slaveDotStuff - ? mailEndsInNewline ? foobarbaz_lf : foobarbaz - : - mailEndsInNewline ? foobarbaz_crlf : foobarbaz_dotstuffed; - const unsigned int s_pre_len = qstrlen(s_pre); - - const char *s_post = mailEndsInNewline ? foobarbaz_crlf : foobarbaz_dotstuffed; - //const unsigned int s_post_len = qstrlen( s_post ); - - TransferCommand xfer(&smtp, preload ? s_post : nullptr); - - TransactionState ts; - ts.setRecipientAccepted(); - ts.setDataCommandIssued(true); - r.clear(); - r.parseLine("354 ok"); - ts.setDataCommandSucceeded(true, r); - assert(!xfer.doNotExecute(&ts)); - if (preload) { - assert(xfer.nextCommandLine(&ts) == s_post); - assert(!xfer.isComplete()); - assert(!xfer.needsResponse()); - assert(!ts.failed()); - assert(smtp.lastErrorCode == 0); - } - smtp.nextData = QByteArray(s_pre, s_pre_len); - smtp.nextDataReturnCode = s_pre_len; - assert(xfer.nextCommandLine(&ts) == s_post); - assert(!xfer.isComplete()); - assert(!xfer.needsResponse()); - assert(!ts.failed()); - assert(smtp.lastErrorCode == 0); - smtp.nextData.resize(0); - smtp.nextDataReturnCode = 0; - if (ungetLast) { - xfer.ungetCommandLine(xfer.nextCommandLine(&ts), &ts); - assert(!xfer.isComplete()); - assert(!xfer.needsResponse()); - assert(!ts.complete()); - smtp.nextDataReturnCode = -1; // double read -> error - } - if (mailEndsInNewline) { - assert(xfer.nextCommandLine(&ts) == ".\r\n"); - } else { - assert(xfer.nextCommandLine(&ts) == "\r\n.\r\n"); - } - assert(xfer.isComplete()); - assert(xfer.needsResponse()); - assert(!ts.complete()); - assert(!ts.failed()); - assert(smtp.lastErrorCode == 0); - r.clear(); - if (error) { - r.parseLine("552 Exceeded storage allocation"); - assert(xfer.processResponse(r, &ts) == false); - assert(!xfer.needsResponse()); - assert(ts.complete()); - assert(ts.failed()); - assert(smtp.lastErrorCode == KIO::ERR_DISK_FULL); - } else { - r.parseLine("250 Message accepted"); - assert(xfer.processResponse(r, &ts) == true); - assert(!xfer.needsResponse()); - assert(ts.complete()); - assert(!ts.failed()); - assert(smtp.lastErrorCode == 0); - } -} - -#ifndef NDEBUG -# define NDEBUG -#endif - -#include "command.cpp" -#include "response.cpp" -#include "transactionstate.cpp" diff --git a/kioslave/src/smtp/tests/test_headergeneration.cpp b/kioslave/src/smtp/tests/test_headergeneration.cpp deleted file mode 100644 --- a/kioslave/src/smtp/tests/test_headergeneration.cpp +++ /dev/null @@ -1,89 +0,0 @@ -#include "../request.h" - -#include - -//using std::cout; -//using std::endl; - -int main(int, char **) -{ - static QByteArray expected - = "From: mutz@kde.org\r\n" - "Subject: missing subject\r\n" - "To: joe@user.org,\r\n" - "\tvalentine@14th.february.org\r\n" - "Cc: boss@example.com\r\n" - "\n" - "From: Marc Mutz \r\n" - "Subject: missing subject\r\n" - "To: joe@user.org,\r\n" - "\tvalentine@14th.february.org\r\n" - "Cc: boss@example.com\r\n" - "\n" - "From: \"Mutz, Marc\" \r\n" - "Subject: missing subject\r\n" - "To: joe@user.org,\r\n" - "\tvalentine@14th.february.org\r\n" - "Cc: boss@example.com\r\n" - "\n" - "From: =?utf-8?b?TWFyYyBNw7Z0eg==?= \r\n" - "Subject: missing subject\r\n" - "To: joe@user.org,\r\n" - "\tvalentine@14th.february.org\r\n" - "Cc: boss@example.com\r\n" - "\n" - "From: mutz@kde.org\r\n" - "Subject: =?utf-8?b?QmzDtmRlcyBTdWJqZWN0?=\r\n" - "To: joe@user.org,\r\n" - "\tvalentine@14th.february.org\r\n" - "Cc: boss@example.com\r\n" - "\n" - "From: Marc Mutz \r\n" - "Subject: =?utf-8?b?QmzDtmRlcyBTdWJqZWN0?=\r\n" - "To: joe@user.org,\r\n" - "\tvalentine@14th.february.org\r\n" - "Cc: boss@example.com\r\n" - "\n" - "From: \"Mutz, Marc\" \r\n" - "Subject: =?utf-8?b?QmzDtmRlcyBTdWJqZWN0?=\r\n" - "To: joe@user.org,\r\n" - "\tvalentine@14th.february.org\r\n" - "Cc: boss@example.com\r\n" - "\n" - "From: =?utf-8?b?TWFyYyBNw7Z0eg==?= \r\n" - "Subject: =?utf-8?b?QmzDtmRlcyBTdWJqZWN0?=\r\n" - "To: joe@user.org,\r\n" - "\tvalentine@14th.february.org\r\n" - "Cc: boss@example.com\r\n" - "\n"; - - KioSMTP::Request request; - QByteArray result; - - request.setEmitHeaders(true); - request.setFromAddress(QStringLiteral("mutz@kde.org")); - request.addTo(QStringLiteral("joe@user.org")); - request.addTo(QStringLiteral("valentine@14th.february.org")); - request.addCc(QStringLiteral("boss@example.com")); - - result += request.headerFields() + '\n'; - result += request.headerFields(QStringLiteral("Marc Mutz")) + '\n'; - result += request.headerFields(QStringLiteral("Mutz, Marc")) + '\n'; - result += request.headerFields(QString::fromUtf8("Marc Mötz")) + '\n'; - - request.setSubject(QString::fromUtf8("Blödes Subject")); - - result += request.headerFields() + '\n'; - result += request.headerFields(QStringLiteral("Marc Mutz")) + '\n'; - result += request.headerFields(QStringLiteral("Mutz, Marc")) + '\n'; - result += request.headerFields(QString::fromUtf8("Marc Mötz")) + '\n'; - - if (result != expected) { - std::cout << "Result:\n" << result.data() << std::endl; - std::cout << "Expected:\n" << expected.data() << std::endl; - } - - return result == expected ? 0 : 1; -} - -#include "../request.cpp" diff --git a/kioslave/src/smtp/tests/test_responseparser.h b/kioslave/src/smtp/tests/test_responseparser.h deleted file mode 100644 --- a/kioslave/src/smtp/tests/test_responseparser.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - Copyright (c) 2006 Volker Krause - - 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 RESPONSEPARSER_TEST_H -#define RESPONSEPARSER_TEST_H - -#include - -class ResponseParserTest : public QObject -{ - Q_OBJECT -private Q_SLOTS: - void testResponseParser(); -}; - -#endif diff --git a/kioslave/src/smtp/tests/test_responseparser.cpp b/kioslave/src/smtp/tests/test_responseparser.cpp deleted file mode 100644 --- a/kioslave/src/smtp/tests/test_responseparser.cpp +++ /dev/null @@ -1,106 +0,0 @@ -#include "test_responseparser.h" -#include "../response.h" - -#include -#include - -QTEST_GUILESS_MAIN(ResponseParserTest) - -static const QByteArray singleLineResponseCRLF = "250 OK\r\n"; -static const QByteArray singleLineResponse = "250 OK"; - -static const QByteArray multiLineResponse[] = { - "250-ktown.kde.org\r\n", - "250-STARTTLS\r\n", - "250-AUTH PLAIN DIGEST-MD5\r\n", - "250 PIPELINING\r\n" -}; -static const unsigned int numMultiLineLines = sizeof multiLineResponse / sizeof *multiLineResponse; - -void ResponseParserTest::testResponseParser() -{ - KioSMTP::Response r; - QVERIFY(r.isValid()); - QVERIFY(r.lines().empty()); - QVERIFY(r.isWellFormed()); - QCOMPARE(r.code(), 0u); - QVERIFY(r.isUnknown()); - QVERIFY(!r.isComplete()); - QVERIFY(!r.isOk()); - r.parseLine(singleLineResponseCRLF.data(), singleLineResponseCRLF.length()); - QVERIFY(r.isWellFormed()); - QVERIFY(r.isComplete()); - QVERIFY(r.isValid()); - QVERIFY(r.isPositive()); - QVERIFY(r.isOk()); - QCOMPARE(r.code(), 250u); - QCOMPARE(r.errorCode(), 0); - QCOMPARE(r.first(), 2u); - QCOMPARE(r.second(), 5u); - QCOMPARE(r.third(), 0u); - QCOMPARE(r.lines().count(), 1); - QCOMPARE(r.lines().front(), QByteArray("OK")); - r.parseLine(singleLineResponse.data(), singleLineResponse.length()); - QVERIFY(!r.isValid()); - r.clear(); - QVERIFY(r.isValid()); - QVERIFY(r.lines().empty()); - - r.parseLine(singleLineResponse.data(), singleLineResponse.length()); - QVERIFY(r.isWellFormed()); - QVERIFY(r.isComplete()); - QVERIFY(r.isValid()); - QVERIFY(r.isPositive()); - QVERIFY(r.isOk()); - QCOMPARE(r.code(), 250u); - QCOMPARE(r.first(), 2u); - QCOMPARE(r.second(), 5u); - QCOMPARE(r.third(), 0u); - QCOMPARE(r.lines().count(), 1); - QCOMPARE(r.lines().front(), QByteArray("OK")); - r.parseLine(singleLineResponse.data(), singleLineResponse.length()); - QVERIFY(!r.isValid()); - r.clear(); - QVERIFY(r.isValid()); - - for (unsigned int i = 0; i < numMultiLineLines; ++i) { - r.parseLine(multiLineResponse[i].data(), multiLineResponse[i].length()); - QVERIFY(r.isWellFormed()); - if (i < numMultiLineLines - 1) { - QVERIFY(!r.isComplete()); - } else { - QVERIFY(r.isComplete()); - } - QVERIFY(r.isValid()); - QVERIFY(r.isPositive()); - QCOMPARE(r.code(), 250u); - QCOMPARE(r.first(), 2u); - QCOMPARE(r.second(), 5u); - QCOMPARE(r.third(), 0u); - QCOMPARE(r.lines().count(), (int)i + 1); - } - QCOMPARE(r.lines().back(), QByteArray("PIPELINING")); - - r.clear(); - r.parseLine("230", 3); - QVERIFY(r.isValid()); - QVERIFY(r.isWellFormed()); // even though it isn't ;-) - QCOMPARE(r.code(), 230u); - QCOMPARE(r.lines().count(), 1); - QVERIFY(r.lines().front().isNull()); - - r.clear(); - r.parseLine("230\r\n", 5); - QVERIFY(r.isValid()); - QVERIFY(r.isWellFormed()); // even though it isn't ;-) - QCOMPARE(r.code(), 230u); - QCOMPARE(r.lines().count(), 1); - QVERIFY(r.lines().front().isNull()); - - r.clear(); - r.parseLine(" 23 ok", 6); - QVERIFY(!r.isValid()); - QVERIFY(!r.isWellFormed()); -} - -#include "../response.cpp" diff --git a/kioslave/src/smtp/transactionstate.h b/kioslave/src/smtp/transactionstate.h deleted file mode 100644 --- a/kioslave/src/smtp/transactionstate.h +++ /dev/null @@ -1,237 +0,0 @@ -/* -*- c++ -*- - transactionstate.h - - This file is part of kio_smtp, the KDE SMTP kioslave. - Copyright (c) 2003 Marc Mutz - - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - - In addition, as a special exception, the copyright holders give - permission to link the code of this program with any edition of - the Qt library by Trolltech AS, Norway (or with modified versions - of Qt that use the same license as Qt), and distribute linked - combinations including the two. You must obey the GNU General - Public License in all respects for all of the code used other than - Qt. If you modify this file, you may extend this exception to - your version of the file, but you are not obligated to do so. If - you do not wish to do so, delete this exception statement from - your version. -*/ - -#ifndef __KIOSMTP_TRANSACTIONSTATE_H__ -#define __KIOSMTP_TRANSACTIONSTATE_H__ - -#include "response.h" - -#include - -namespace KioSMTP { -/** - @short A class modelling an SMTP transaction's state - - This class models SMTP transaction state, ie. the collective - result of the MAIL FROM:, RCPT TO: and DATA commands. This is - needed since e.g. a single failed RCPT TO: command does not - necessarily fail the whole transaction (servers are free to - accept delivery for some recipients, but not for others). - - The class can operate in two modes, which differ in the way - failed recipients are handled. If @p rcptToDenyIsFailure is true - (the default), then any failing RCPT TO: will cause the - transaction to fail. Since at the point of RCPT TO: failure - detection, the DATA command may have already been sent - (pipelining), the only way to cancel the transaction is to take - down the connection hard (ie. without proper quit). - - Since that is not very nice behaviour, a second mode that is more - to the spirit of SMTP is provided that can cope with partially - failed RCPT TO: commands. -*/ -class TransactionState -{ -public: - struct RecipientRejection { - RecipientRejection(const QString &who = QString(), const QString &why = QString()) - : recipient(who) - , reason(why) - { - } - - QString recipient; - QString reason; -#ifdef KIOSMTP_COMPARATORS - bool operator==(const RecipientRejection &other) const - { - return recipient == other.recipient && reason == other.reason; - } - -#endif - }; - typedef QList RejectedRecipientList; - - TransactionState(bool rcptToDenyIsFailure = true) - : mErrorCode(0) - , mRcptToDenyIsFailure(rcptToDenyIsFailure) - , mAtLeastOneRecipientWasAccepted(false) - , mDataCommandIssued(false) - , mDataCommandSucceeded(false) - , mFailed(false) - , mFailedFatally(false) - , mComplete(false) - { - } - - /** - * @return whether the transaction failed (e.g. the server - * rejected all recipients. Graceful failure is handled after - * transaction ends. - */ - bool failed() const - { - return mFailed || mFailedFatally; - } - - void setFailed() - { - mFailed = true; - } - - /** - * @return whether the failure was so grave that an immediate - * untidy connection shutdown is in order (ie. @ref - * smtp_close(false)). Fatal failure is handled immediately - */ - bool failedFatally() const - { - return mFailedFatally; - } - - void setFailedFatally(int code = 0, const QString &msg = QString()); - - /** @return whether the transaction was completed successfully */ - bool complete() const - { - return mComplete; - } - - void setComplete() - { - mComplete = true; - } - - /** - * @return an appropriate KIO error code in case the transaction - * failed, or 0 otherwise - */ - int errorCode() const; - - /** - * @return an appropriate error message in case the transaction - * failed or QString() otherwise - */ - QString errorMessage() const; - - void setMailFromFailed(const QString &addr, const Response &r); - - bool dataCommandIssued() const - { - return mDataCommandIssued; - } - - void setDataCommandIssued(bool issued) - { - mDataCommandIssued = issued; - } - - bool dataCommandSucceeded() const - { - return mDataCommandIssued && mDataCommandSucceeded; - } - - void setDataCommandSucceeded(bool succeeded, const Response &r); - - Response dataResponse() const - { - return mDataResponse; - } - - bool atLeastOneRecipientWasAccepted() const - { - return mAtLeastOneRecipientWasAccepted; - } - - void setRecipientAccepted() - { - mAtLeastOneRecipientWasAccepted = true; - } - - bool haveRejectedRecipients() const - { - return !mRejectedRecipients.empty(); - } - - RejectedRecipientList rejectedRecipients() const - { - return mRejectedRecipients; - } - - void addRejectedRecipient(const RecipientRejection &r); - void addRejectedRecipient(const QString &who, const QString &why) - { - addRejectedRecipient(RecipientRejection(who, why)); - } - - void clear() - { - mRejectedRecipients.clear(); - mDataResponse.clear(); - mAtLeastOneRecipientWasAccepted - = mDataCommandIssued - = mDataCommandSucceeded - = mFailed = mFailedFatally - = mComplete = false; - } - -#ifdef KIOSMTP_COMPARATORS - bool operator==(const TransactionState &other) const - { - return - mAtLeastOneRecipientWasAccepted == other.mAtLeastOneRecipientWasAccepted - && mDataCommandIssued == other.mDataCommandIssued - && mDataCommandSucceeded == other.mDataCommandSucceeded - && mFailed == other.mFailed - && mFailedFatally == other.mFailedFatally - && mComplete == other.mComplete - && mDataResponse.code() == other.mDataResponse.code() - && mRejectedRecipients == other.mRejectedRecipients; - } - -#endif - -private: - RejectedRecipientList mRejectedRecipients; - Response mDataResponse; - QString mErrorMessage; - int mErrorCode; - bool mRcptToDenyIsFailure; - bool mAtLeastOneRecipientWasAccepted; - bool mDataCommandIssued; - bool mDataCommandSucceeded; - bool mFailed; - bool mFailedFatally; - bool mComplete; -}; -} // namespace KioSMTP - -#endif // __KIOSMTP_TRANSACTIONSTATE_H__ diff --git a/kioslave/src/smtp/transactionstate.cpp b/kioslave/src/smtp/transactionstate.cpp deleted file mode 100644 --- a/kioslave/src/smtp/transactionstate.cpp +++ /dev/null @@ -1,124 +0,0 @@ -/* -*- c++ -*- - transactionstate.cc - - This file is part of kio_smtp, the KDE SMTP kioslave. - Copyright (c) 2003 Marc Mutz - - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - - In addition, as a special exception, the copyright holders give - permission to link the code of this program with any edition of - the Qt library by Trolltech AS, Norway (or with modified versions - of Qt that use the same license as Qt), and distribute linked - combinations including the two. You must obey the GNU General - Public License in all respects for all of the code used other than - Qt. If you modify this file, you may extend this exception to - your version of the file, but you are not obligated to do so. If - you do not wish to do so, delete this exception statement from - your version. -*/ - -#include "transactionstate.h" - -#include -#include - -namespace KioSMTP { -void TransactionState::setFailedFatally(int code, const QString &msg) -{ - mFailed = mFailedFatally = true; - mErrorCode = code; - mErrorMessage = msg; -} - -void TransactionState::setMailFromFailed(const QString &addr, const Response &r) -{ - setFailed(); - mErrorCode = KIO::ERR_NO_CONTENT; - if (addr.isEmpty()) { - mErrorMessage = i18n("The server did not accept a blank sender address.\n" - "%1", r.errorMessage()); - } else { - mErrorMessage = i18n("The server did not accept the sender address \"%1\".\n" - "%2", addr, r.errorMessage()); - } -} - -void TransactionState::addRejectedRecipient(const RecipientRejection &r) -{ - mRejectedRecipients.push_back(r); - if (mRcptToDenyIsFailure) { - setFailed(); - } -} - -void TransactionState::setDataCommandSucceeded(bool succeeded, const Response &r) -{ - mDataCommandSucceeded = succeeded; - mDataResponse = r; - if (!succeeded) { - setFailed(); - } else if (failed()) { - // can happen with pipelining: the server accepts the DATA, but - // we don't want to send the data, so force a connection - // shutdown: - setFailedFatally(); - } -} - -int TransactionState::errorCode() const -{ - if (!failed()) { - return 0; - } - if (mErrorCode) { - return mErrorCode; - } - if (haveRejectedRecipients() || !dataCommandSucceeded()) { - return KIO::ERR_NO_CONTENT; - } - // ### what else? - return KIO::ERR_INTERNAL; -} - -QString TransactionState::errorMessage() const -{ - if (!failed()) { - return QString(); - } - - if (!mErrorMessage.isEmpty()) { - return mErrorMessage; - } - - if (haveRejectedRecipients()) { - QStringList recip; - recip.reserve(mRejectedRecipients.count()); - for (RejectedRecipientList::const_iterator it = mRejectedRecipients.begin(), end(mRejectedRecipients.end()); - it != end; ++it) { - recip.push_back((*it).recipient + QLatin1String(" (") + (*it).reason + QLatin1Char(')')); - } - return i18n("Message sending failed since the following recipients were rejected by the server:\n" - "%1", recip.join(QLatin1Char('\n'))); - } - - if (!dataCommandSucceeded()) { - return i18n("The attempt to start sending the message content failed.\n" - "%1", mDataResponse.errorMessage()); - } - - // ### what else? - return i18n("Unhandled error condition. Please send a bug report."); -} -} diff --git a/src/kmailtransport/plugins/smtp/CMakeLists.txt b/src/kmailtransport/plugins/smtp/CMakeLists.txt --- a/src/kmailtransport/plugins/smtp/CMakeLists.txt +++ b/src/kmailtransport/plugins/smtp/CMakeLists.txt @@ -1,3 +1,7 @@ +if (BUILD_TESTING) + add_subdirectory(autotests) +endif() + set(mailtransport_smtpplugin_SRCS smtpmailtransportplugin.cpp smtpconfigdialog.cpp @@ -18,6 +22,7 @@ KF5::MailTransport KF5::I18n KF5::ConfigWidgets - KF5::KIOCore + KF5::KIOWidgets KF5::Completion + KPim::SMTP ) diff --git a/src/kmailtransport/plugins/smtp/autotests/CMakeLists.txt b/src/kmailtransport/plugins/smtp/autotests/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/src/kmailtransport/plugins/smtp/autotests/CMakeLists.txt @@ -0,0 +1,19 @@ +include(ECMAddTests) + +find_package(Qt5Test ${QT_REQUIRED_VERSION} REQUIRED) + +include_directories(${CMAKE_CURRENT_BINARY_DIR}/..) + +ecm_add_test(smtpjobtest.cpp + ../smtpjob.cpp + ${CMAKE_CURRENT_BINARY_DIR}/../mailtransportplugin_smtp_debug.cpp + fakeserver.cpp + LINK_LIBRARIES Qt5::Network + Qt5::Test + KF5::MailTransport + KF5::I18n + KF5::ConfigWidgets + KF5::KIOWidgets + KPim::SMTP + TEST_NAME smtpjobtest +) diff --git a/src/kmailtransport/plugins/smtp/autotests/fakeserver.h b/src/kmailtransport/plugins/smtp/autotests/fakeserver.h new file mode 100644 --- /dev/null +++ b/src/kmailtransport/plugins/smtp/autotests/fakeserver.h @@ -0,0 +1,67 @@ +/* + Copyright 2010 BetterInbox + Author: Christophe Laveault + Gregory Schlomoff + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#ifndef KSMTP_FAKESERVER_H +#define KSMTP_FAKESERVER_H + +#include +#include +#include +#include +#include + +Q_DECLARE_METATYPE(QList) + +class FakeServer : public QThread +{ + Q_OBJECT + +public: + explicit FakeServer(QObject *parent = nullptr); + ~FakeServer() override; + + void startAndWait(); + void run() override; + + static QByteArray greeting(); + static QList greetingAndEhlo(bool multiline = true); + static QList bye(); + + void setScenario(const QList &scenario); + void addScenario(const QList &scenario); + void addScenarioFromFile(const QString &fileName); + bool isScenarioDone(int scenarioNumber) const; + bool isAllScenarioDone() const; + +private Q_SLOTS: + void newConnection(); + void dataAvailable(); + void started(); + +private: + void writeServerPart(int scenarioNumber); + void readClientPart(int scenarioNumber); + + QList< QList > m_scenarios; + QTcpServer *m_tcpServer; + mutable QMutex m_mutex; + QList m_clientSockets; +}; + +#endif // KSMTP_FAKESERVER_H diff --git a/src/kmailtransport/plugins/smtp/autotests/fakeserver.cpp b/src/kmailtransport/plugins/smtp/autotests/fakeserver.cpp new file mode 100644 --- /dev/null +++ b/src/kmailtransport/plugins/smtp/autotests/fakeserver.cpp @@ -0,0 +1,232 @@ +/* + Copyright 2010 BetterInbox + Author: Christophe Laveault + Gregory Schlomoff + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#include "fakeserver.h" + +#include +#include +#include + +FakeServer::FakeServer(QObject *parent) : + QThread(parent), + m_tcpServer(nullptr) +{ + moveToThread(this); +} + +QByteArray FakeServer::greeting() +{ + return "S: 220 localhost ESMTP xx777xx"; +} + +QList FakeServer::greetingAndEhlo(bool multiline) +{ + return QList() << greeting() + << "C: EHLO 127.0.0.1" + << QByteArray("S: 250") + (multiline ? '-' : ' ') + "Localhost ready to roll"; +} + +QList FakeServer::bye() +{ + return { "C: QUIT", + "S: 221 So long, and thanks for all the fish", + "X: " + }; +} + +FakeServer::~FakeServer() +{ + quit(); + wait(); +} + +void FakeServer::startAndWait() +{ + start(); + // this will block until the event queue starts + QMetaObject::invokeMethod(this, "started", Qt::BlockingQueuedConnection); +} + +void FakeServer::dataAvailable() +{ + QMutexLocker locker(&m_mutex); + + QTcpSocket *socket = qobject_cast(sender()); + Q_ASSERT(socket != nullptr); + + int scenarioNumber = m_clientSockets.indexOf(socket); + + QVERIFY(!m_scenarios[scenarioNumber].isEmpty()); + + readClientPart(scenarioNumber); + writeServerPart(scenarioNumber); +} + +void FakeServer::newConnection() +{ + QMutexLocker locker(&m_mutex); + + m_clientSockets << m_tcpServer->nextPendingConnection(); + connect(m_clientSockets.last(), SIGNAL(readyRead()), this, SLOT(dataAvailable())); + //m_clientParsers << new KIMAP::ImapStreamParser( m_clientSockets.last(), true ); + + QVERIFY(m_clientSockets.size() <= m_scenarios.size()); + + writeServerPart(m_clientSockets.size() - 1); +} + +void FakeServer::run() +{ + m_tcpServer = new QTcpServer(); + if (!m_tcpServer->listen(QHostAddress(QHostAddress::LocalHost), 5989)) { + qFatal("Unable to start the server"); + return; + } + + connect(m_tcpServer, SIGNAL(newConnection()), this, SLOT(newConnection())); + + exec(); + + qDeleteAll(m_clientSockets); + + delete m_tcpServer; +} + +void FakeServer::started() +{ + // do nothing: this is a dummy slot used by startAndWait() +} + +void FakeServer::setScenario(const QList &scenario) +{ + QMutexLocker locker(&m_mutex); + + m_scenarios.clear(); + m_scenarios << scenario; +} + +void FakeServer::addScenario(const QList &scenario) +{ + QMutexLocker locker(&m_mutex); + + m_scenarios << scenario; +} + +void FakeServer::addScenarioFromFile(const QString &fileName) +{ + QFile file(fileName); + file.open(QFile::ReadOnly); + + QList scenario; + + // When loading from files we never have the authentication phase + // force jumping directly to authenticated state. + //scenario << preauth(); + + while (!file.atEnd()) { + scenario << file.readLine().trimmed(); + } + + file.close(); + + addScenario(scenario); +} + +bool FakeServer::isScenarioDone(int scenarioNumber) const +{ + QMutexLocker locker(&m_mutex); + + if (scenarioNumber < m_scenarios.size()) { + return m_scenarios[scenarioNumber].isEmpty(); + } else { + return true; // Non existent hence empty, right? + } +} + +bool FakeServer::isAllScenarioDone() const +{ + QMutexLocker locker(&m_mutex); + + for (const auto &scenario : qAsConst(m_scenarios)) { + if (!scenario.isEmpty()) { + qDebug() << scenario; + return false; + } + } + + return true; +} + +void FakeServer::writeServerPart(int scenarioNumber) +{ + QList scenario = m_scenarios[scenarioNumber]; + QTcpSocket *clientSocket = m_clientSockets[scenarioNumber]; + + while (!scenario.isEmpty() + && (scenario.first().startsWith("S: ") || scenario.first().startsWith("W: "))) { + QByteArray rule = scenario.takeFirst(); + + if (rule.startsWith("S: ")) { + QByteArray payload = rule.mid(3); + clientSocket->write(payload + "\r\n"); + } else { + int timeout = rule.mid(3).toInt(); + QTest::qWait(timeout); + } + } + + if (!scenario.isEmpty() && scenario.first().startsWith('X')) { + scenario.takeFirst(); + clientSocket->close(); + } + + if (!scenario.isEmpty()) { + QVERIFY(scenario.first().startsWith("C: ")); + } + + m_scenarios[scenarioNumber] = scenario; +} + +void FakeServer::readClientPart(int scenarioNumber) +{ + QList scenario = m_scenarios[scenarioNumber]; + QTcpSocket *clientSocket = m_clientSockets[scenarioNumber]; + + while (!scenario.isEmpty() && scenario.first().startsWith("C: ")) { + QByteArray line = clientSocket->readLine(); + QByteArray received = "C: " + line.trimmed(); + QByteArray expected = scenario.takeFirst(); + + if (expected == "C: SKIP" && !scenario.isEmpty()) { + expected = scenario.takeFirst(); + while (received != expected) { + received = "C: " + clientSocket->readLine().trimmed(); + } + } + + QCOMPARE(QString::fromUtf8(received), QString::fromUtf8(expected)); + QCOMPARE(received, expected); + } + + if (!scenario.isEmpty()) { + QVERIFY(scenario.first().startsWith("S: ")); + } + + m_scenarios[scenarioNumber] = scenario; +} diff --git a/src/kmailtransport/plugins/smtp/autotests/smtpjobtest.cpp b/src/kmailtransport/plugins/smtp/autotests/smtpjobtest.cpp new file mode 100644 --- /dev/null +++ b/src/kmailtransport/plugins/smtp/autotests/smtpjobtest.cpp @@ -0,0 +1,132 @@ +/* + Copyright (c) 2017 Daniel Vrátil + + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + This library is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#include "../smtpjob.h" +#include "fakeserver.h" + +#include "transportbase.h" +#include "transportmanager.h" + +#include +#include +#include + +Q_DECLARE_METATYPE(MailTransport::TransportBase::EnumAuthenticationType::type) + +class SmtpJobTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase() + { + QStandardPaths::setTestModeEnabled(true); + } + + void smtpJobTest_data() + { + QTest::addColumn>("scenario"); + QTest::addColumn("authType"); + QTest::addColumn("from"); + QTest::addColumn("to"); + QTest::addColumn("cc"); + QTest::addColumn("data"); + QTest::addColumn("success"); + + QList scenario; + scenario << FakeServer::greetingAndEhlo() + << "S: 250 AUTH PLAIN LOGIN" + << "C: AUTH LOGIN" + << "S: 334 VXNlcm5hbWU6" + << "C: bG9naW4=" // "login".toBase64() + << "S: 334 UGFzc3dvcmQ6" + << "C: cGFzc3dvcmQ=" // "password".toBase64() + << "S: 235 Authenticated." + << "C: MAIL FROM:" + << "S: 250 ok" + << "C: RCPT TO:" + << "S: 250 ok" + << "C: RCPT TO:" + << "S: 250 ok" + << "C: DATA" + << "S: 354 Ok go ahead" + << "C: Hi Bob" + << "C: " + << "C: ." + << "S: 250 Ok transfer done" + << FakeServer::bye(); + QTest::newRow("simple") << scenario << MailTransport::TransportBase::EnumAuthenticationType::LOGIN + << QStringLiteral("Foo Bar ") + << QStringList{} + << QStringList{ QStringLiteral("bar@foo.com"), QStringLiteral("") } + << QByteArray("Hi Bob") + << true; + } + + void smtpJobTest() + { + QFETCH(QList, scenario); + QFETCH(MailTransport::TransportBase::EnumAuthenticationType::type, authType); + QFETCH(QString, from); + QFETCH(QStringList, to); + QFETCH(QStringList, cc); + QFETCH(QByteArray, data); + QFETCH(bool, success); + + FakeServer server; + server.setScenario(scenario); + server.startAndWait(); + + auto transport = MailTransport::TransportManager::self()->createTransport(); + transport->setHost(QStringLiteral("127.0.0.1")); + transport->setPort(5989); + transport->setRequiresAuthentication(true); + transport->setAuthenticationType(authType); + transport->setStorePassword(false); + transport->setUserName(QStringLiteral("login")); + transport->setPassword(QStringLiteral("password")); + + { + MailTransport::SmtpJob smtpJob(transport); + smtpJob.setSender(from); + smtpJob.setTo(to); + smtpJob.setCc(cc); + smtpJob.setData(data); + + QVERIFY(smtpJob.exec()); + if (success) { + QCOMPARE(smtpJob.error(), 0); + } else { + QVERIFY(smtpJob.error() > 0); + } + + // Make sure the smtpJob goes out-of-scope here and thus the + // internal session pool is destroyed + } + // KSMTP time to stop the session + QTest::qWait(10); + + QVERIFY(server.isAllScenarioDone()); + server.quit(); + } +}; + +QTEST_MAIN(SmtpJobTest) + +#include "smtpjobtest.moc" diff --git a/src/kmailtransport/plugins/smtp/sessionuiproxy.h b/src/kmailtransport/plugins/smtp/sessionuiproxy.h new file mode 100644 --- /dev/null +++ b/src/kmailtransport/plugins/smtp/sessionuiproxy.h @@ -0,0 +1,33 @@ +/* + Copyright (c) 2017 Daniel Vrátil + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef SESSIONUIPROXY_H_ +#define SESSIONUIPROXY_H_ + +#include +#include + +class SmtpSessionUiProxy : public KSmtp::SessionUiProxy +{ +public: + bool ignoreSslError(const KSslErrorUiData &errorData) override + { + return KIO::SslUi::askIgnoreSslErrors(errorData, KIO::SslUi::RecallAndStoreRules); + } +}; + +#endif diff --git a/src/kmailtransport/plugins/smtp/smtpjob.h b/src/kmailtransport/plugins/smtp/smtpjob.h --- a/src/kmailtransport/plugins/smtp/smtpjob.h +++ b/src/kmailtransport/plugins/smtp/smtpjob.h @@ -24,6 +24,7 @@ #define MAILTRANSPORT_SMTPJOB_H #include +#include namespace KIO { class Job; @@ -65,13 +66,12 @@ protected Q_SLOTS: void slotResult(KJob *job) override; - void slaveError(KIO::Slave *slave, int errorCode, const QString &errorMsg); + void sessionStateChanged(KSmtp::Session::State state); private: void startSmtpJob(); - -private Q_SLOTS: - void dataRequest(KIO::Job *job, QByteArray &data); + void startLoginJob(); + void startSendJob(); private: friend class ::SmtpJobPrivate; diff --git a/src/kmailtransport/plugins/smtp/smtpjob.cpp b/src/kmailtransport/plugins/smtp/smtpjob.cpp --- a/src/kmailtransport/plugins/smtp/smtpjob.cpp +++ b/src/kmailtransport/plugins/smtp/smtpjob.cpp @@ -24,6 +24,7 @@ #include "transport.h" #include "mailtransport_defs.h" #include "precommandjob.h" +#include "sessionuiproxy.h" #include "mailtransportplugin_smtp_debug.h" #include @@ -34,36 +35,42 @@ #include #include #include "mailtransport_debug.h" -#include -#include #include +#include +#include +#include + using namespace MailTransport; -class SlavePool +class SessionPool { public: - SlavePool() : ref(0) + SessionPool() : ref(0) { } int ref; - QHash slaves; + QHash sessions; - void removeSlave(KIO::Slave *slave, bool disconnect = false) + void removeSession(KSmtp::Session *session) { - qCDebug(MAILTRANSPORT_SMTP_LOG) << "Removing slave" << slave << "from pool"; - const int slaveKey = slaves.key(slave); - if (slaveKey > 0) { - slaves.remove(slaveKey); - if (disconnect) { - KIO::Scheduler::disconnectSlave(slave); - } + qCDebug(MAILTRANSPORT_SMTP_LOG) << "Removing session" << session << "from the pool"; + int key = sessions.key(session); + if (key > 0) { + QObject::connect(session, &KSmtp::Session::stateChanged, + [session](KSmtp::Session::State state) { + if (state == KSmtp::Session::Disconnected) { + session->deleteLater(); + } + }); + session->quit(); + sessions.remove(key); } } }; -Q_GLOBAL_STATIC(SlavePool, s_slavePool) +Q_GLOBAL_STATIC(SessionPool, s_sessionPool) /** * Private class that helps to provide binary compatibility between releases. @@ -77,7 +84,8 @@ } SmtpJob *q; - KIO::Slave *slave; + KSmtp::Session *session; + KSmtp::SessionUiProxy::Ptr uiProxy; enum State { Idle, Precommand, Smtp } currentState; @@ -89,39 +97,36 @@ , d(new SmtpJobPrivate(this)) { d->currentState = SmtpJobPrivate::Idle; - d->slave = nullptr; + d->session = nullptr; d->finished = false; - if (!s_slavePool.isDestroyed()) { - s_slavePool->ref++; + d->uiProxy = KSmtp::SessionUiProxy::Ptr(new SmtpSessionUiProxy); + if (!s_sessionPool.isDestroyed()) { + s_sessionPool->ref++; } - KIO::Scheduler::connect(SIGNAL(slaveError(KIO::Slave *,int,QString)), this, SLOT(slaveError(KIO::Slave *,int,QString))); } SmtpJob::~SmtpJob() { - if (!s_slavePool.isDestroyed()) { - s_slavePool->ref--; - if (s_slavePool->ref == 0) { - qCDebug(MAILTRANSPORT_SMTP_LOG) << "clearing SMTP slave pool" << s_slavePool->slaves.count(); - foreach (KIO::Slave *slave, s_slavePool->slaves) { - if (slave) { - KIO::Scheduler::disconnectSlave(slave); - } + if (!s_sessionPool.isDestroyed()) { + s_sessionPool->ref--; + if (s_sessionPool->ref == 0) { + qCDebug(MAILTRANSPORT_SMTP_LOG) << "clearing SMTP session pool" << s_sessionPool->sessions.count(); + while (!s_sessionPool->sessions.isEmpty()) { + s_sessionPool->removeSession(*(s_sessionPool->sessions.begin())); } - s_slavePool->slaves.clear(); } } delete d; } void SmtpJob::doStart() { - if (s_slavePool.isDestroyed()) { + if (s_sessionPool.isDestroyed()) { return; } - if ((!s_slavePool->slaves.isEmpty() - && s_slavePool->slaves.contains(transport()->id())) + if ((!s_sessionPool->sessions.isEmpty() + && s_sessionPool->sessions.contains(transport()->id())) || transport()->precommand().isEmpty()) { d->currentState = SmtpJobPrivate::Smtp; startSmtpJob(); @@ -135,118 +140,161 @@ void SmtpJob::startSmtpJob() { - if (s_slavePool.isDestroyed()) { + if (s_sessionPool.isDestroyed()) { return; } - QUrl destination; - destination.setScheme((transport()->encryption() == Transport::EnumEncryption::SSL) - ? SMTPS_PROTOCOL : SMTP_PROTOCOL); - destination.setHost(transport()->host().trimmed()); - destination.setPort(transport()->port()); + d->session = s_sessionPool->sessions.value(transport()->id()); + if (!d->session) { + d->session = new KSmtp::Session(transport()->host(), transport()->port()); + d->session->setUiProxy(d->uiProxy); + if (transport()->specifyHostname()) { + d->session->setCustomHostname(transport()->localHostname()); + } + s_sessionPool->sessions.insert(transport()->id(), d->session); + } - QUrlQuery destinationQuery(destination); - destinationQuery.addQueryItem(QStringLiteral("headers"), QStringLiteral("0")); - destinationQuery.addQueryItem(QStringLiteral("from"), sender()); + connect(d->session, &KSmtp::Session::stateChanged, + this, &SmtpJob::sessionStateChanged, Qt::UniqueConnection); + connect(d->session, &KSmtp::Session::connectionError, + this, [this](const QString &err) { + setError(KJob::UserDefinedError); + setErrorText(err); + s_sessionPool->removeSession(d->session); + emitResult(); + }); - for (const QString &str : to()) { - destinationQuery.addQueryItem(QStringLiteral("to"), str); - } - for (const QString &str : cc()) { - destinationQuery.addQueryItem(QStringLiteral("cc"), str); + if (d->session->state() == KSmtp::Session::Disconnected) { + d->session->open(); + } else { + if (d->session->state() != KSmtp::Session::Authenticated) { + startLoginJob(); + } + + startSendJob(); } - for (const QString &str : bcc()) { - destinationQuery.addQueryItem(QStringLiteral("bcc"), str); +} + +void SmtpJob::sessionStateChanged(KSmtp::Session::State state) +{ + if (state == KSmtp::Session::Ready) { + startLoginJob(); + } else if (state == KSmtp::Session::Authenticated) { + startSendJob(); } +} - if (transport()->specifyHostname()) { - destinationQuery.addQueryItem(QStringLiteral("hostname"), transport()->localHostname()); +void SmtpJob::startLoginJob() +{ + if (!transport()->requiresAuthentication()) { + startSendJob(); + return; } - if (transport()->requiresAuthentication()) { - QString user = transport()->userName(); - QString passwd = transport()->password(); - if ((user.isEmpty() || passwd.isEmpty()) - && transport()->authenticationType() != Transport::EnumAuthenticationType::GSSAPI) { - QPointer dlg - = new KPasswordDialog( - nullptr, - KPasswordDialog::ShowUsernameLine - |KPasswordDialog::ShowKeepPassword); - dlg->setPrompt(i18n("You need to supply a username and a password " - "to use this SMTP server.")); - dlg->setKeepPassword(transport()->storePassword()); - dlg->addCommentLine(QString(), transport()->name()); - dlg->setUsername(user); - dlg->setPassword(passwd); - - bool gotIt = false; - if (dlg->exec()) { - transport()->setUserName(dlg->username()); - transport()->setPassword(dlg->password()); - transport()->setStorePassword(dlg->keepPassword()); - transport()->save(); - gotIt = true; - } - delete dlg; + auto login = new KSmtp::LoginJob(d->session); + auto user = transport()->userName(); + auto passwd = transport()->password(); + if ((user.isEmpty() || passwd.isEmpty()) + && transport()->authenticationType() != Transport::EnumAuthenticationType::GSSAPI) { + QPointer dlg + = new KPasswordDialog( + nullptr, + KPasswordDialog::ShowUsernameLine + |KPasswordDialog::ShowKeepPassword); + dlg->setPrompt(i18n("You need to supply a username and a password " + "to use this SMTP server.")); + dlg->setKeepPassword(transport()->storePassword()); + dlg->addCommentLine(QString(), transport()->name()); + dlg->setUsername(user); + dlg->setPassword(passwd); + + bool gotIt = false; + if (dlg->exec()) { + transport()->setUserName(dlg->username()); + transport()->setPassword(dlg->password()); + transport()->setStorePassword(dlg->keepPassword()); + transport()->save(); + gotIt = true; + } + delete dlg; - if (!gotIt) { - setError(KilledJobError); - emitResult(); - return; - } + if (!gotIt) { + setError(KilledJobError); + emitResult(); + return; } - destination.setUserName(transport()->userName()); - destination.setPassword(transport()->password()); } - // dotstuffing is now done by the slave (see setting of metadata) - if (!data().isEmpty()) { - // allow +5% for subsequent LF->CRLF and dotstuffing (an average - // over 2G-lines gives an average line length of 42-43): - destinationQuery.addQueryItem(QStringLiteral("size"), - QString::number(qRound(data().length() * 1.05))); + login->setUserName(transport()->userName()); + login->setPassword(transport()->password()); + switch (transport()->authenticationType()) { + case TransportBase::EnumAuthenticationType::PLAIN: + login->setPreferedAuthMode(KSmtp::LoginJob::Plain); + break; + case TransportBase::EnumAuthenticationType::LOGIN: + login->setPreferedAuthMode(KSmtp::LoginJob::Login); + break; + case TransportBase::EnumAuthenticationType::CRAM_MD5: + login->setPreferedAuthMode(KSmtp::LoginJob::CramMD5); + break; + case TransportBase::EnumAuthenticationType::XOAUTH2: + login->setPreferedAuthMode(KSmtp::LoginJob::XOAuth); + break; + case TransportBase::EnumAuthenticationType::DIGEST_MD5: + login->setPreferedAuthMode(KSmtp::LoginJob::DigestMD5); + break; + case TransportBase::EnumAuthenticationType::NTLM: + login->setPreferedAuthMode(KSmtp::LoginJob::NTLM); + break; + case TransportBase::EnumAuthenticationType::GSSAPI: + login->setPreferedAuthMode(KSmtp::LoginJob::GSSAPI); + break; + default: + qCWarning(MAILTRANSPORT_SMTP_LOG) << "Unknown authentication mode" << transport()->authenticationTypeString(); + break; } - destination.setPath(QStringLiteral("/send")); - destination.setQuery(destinationQuery); - - d->slave = s_slavePool->slaves.value(transport()->id()); - if (!d->slave) { - KIO::MetaData slaveConfig; - slaveConfig.insert(QStringLiteral("tls"), - (transport()->encryption() == Transport::EnumEncryption::TLS) - ? QStringLiteral("on") : QStringLiteral("off")); - if (transport()->requiresAuthentication()) { - slaveConfig.insert(QStringLiteral("sasl"), transport()->authenticationTypeString()); - } - d->slave = KIO::Scheduler::getConnectedSlave(destination, slaveConfig); - qCDebug(MAILTRANSPORT_SMTP_LOG) << "Created new SMTP slave" << d->slave; - s_slavePool->slaves.insert(transport()->id(), d->slave); - } else { - qCDebug(MAILTRANSPORT_SMTP_LOG) << "Re-using existing slave" << d->slave; - } + switch (transport()->encryption()) { + case Transport::EnumEncryption::None: + login->setEncryptionMode(KSmtp::LoginJob::Unencrypted); + break; + case Transport::EnumEncryption::TLS: + login->setEncryptionMode(KSmtp::LoginJob::TlsV1); + break; + case Transport::EnumEncryption::SSL: + login->setEncryptionMode(KSmtp::LoginJob::AnySslVersion); + break; + default: + qCWarning(MAILTRANSPORT_SMTP_LOG) << "Unknown encryption mode" << transport()->encryption(); + break; - KIO::TransferJob *job = KIO::put(destination, -1, KIO::HideProgressInfo); - if (!d->slave || !job) { - setError(UserDefinedError); - setErrorText(i18n("Unable to create SMTP job.")); - emitResult(); - return; } - job->addMetaData(QStringLiteral("lf2crlf+dotstuff"), QStringLiteral("slave")); - connect(job, &KIO::TransferJob::dataReq, this, &SmtpJob::dataRequest); - - addSubjob(job); - KIO::Scheduler::assignJobToSlave(d->slave, job); + connect(login, &KJob::result, this, &SmtpJob::slotResult); + addSubjob(login); + login->start(); + qCDebug(MAILTRANSPORT_SMTP_LOG) << "Login started"; +} - setTotalAmount(KJob::Bytes, data().length()); +void SmtpJob::startSendJob() +{ + auto send = new KSmtp::SendJob(d->session); + send->setFrom(sender()); + send->setTo(to()); + send->setCc(cc()); + send->setBcc(bcc()); + send->setData(data()); + + connect(send, &KJob::result, this, &SmtpJob::slotResult); + addSubjob(send); + send->start(); + + qCDebug(MAILTRANSPORT_SMTP_LOG) << "Send started"; } bool SmtpJob::doKill() { - if (s_slavePool.isDestroyed()) { + if (s_sessionPool.isDestroyed()) { return false; } @@ -256,18 +304,16 @@ if (d->currentState == SmtpJobPrivate::Precommand) { return subjobs().first()->kill(); } else if (d->currentState == SmtpJobPrivate::Smtp) { - KIO::SimpleJob *job = static_cast(subjobs().first()); clearSubjobs(); - KIO::Scheduler::cancelJob(job); - s_slavePool->removeSlave(d->slave); + s_sessionPool->removeSession(d->session); return true; } return false; } void SmtpJob::slotResult(KJob *job) { - if (s_slavePool.isDestroyed()) { + if (s_sessionPool.isDestroyed()) { return; } @@ -298,7 +344,7 @@ } if (errorCode && d->currentState == SmtpJobPrivate::Smtp) { - s_slavePool->removeSlave(d->slave, errorCode != KIO::ERR_SLAVE_DIED); + s_sessionPool->removeSession(d->session); TransportJob::slotResult(job); return; } @@ -309,38 +355,7 @@ startSmtpJob(); return; } - if (!error()) { - emitResult(); - } -} - -void SmtpJob::dataRequest(KIO::Job *job, QByteArray &data) -{ - if (s_slavePool.isDestroyed()) { - return; - } - - Q_UNUSED(job); - Q_ASSERT(job); - if (buffer()->atEnd()) { - data.clear(); - } else { - Q_ASSERT(buffer()->isOpen()); - data = buffer()->read(32 * 1024); - } - setProcessedAmount(KJob::Bytes, buffer()->pos()); -} - -void SmtpJob::slaveError(KIO::Slave *slave, int errorCode, const QString &errorMsg) -{ - if (s_slavePool.isDestroyed()) { - return; - } - - s_slavePool->removeSlave(slave, errorCode != KIO::ERR_SLAVE_DIED); - if (d->slave == slave && !d->finished) { - setError(errorCode); - setErrorText(KIO::buildErrorString(errorCode, errorMsg)); + if (!error() && !hasSubjobs()) { emitResult(); } }