Commit b2198b44 authored by Daniel Vrátil's avatar Daniel Vrátil 🤖
Browse files

Replace SMTP KIO Slave with KSMTP library

Instead of using a KIO Slave for sending emails via SMTP, for which
KIO wasn't really designed, use a KSMTP library. This has no
functional or performance impacts, but improves maintainability
of our SMTP code and opens doors to further improvements (like
native Gmail authentication)

Differential Revision: https://phabricator.kde.org/D6879
parent ecab7a60
......@@ -30,6 +30,7 @@ set(KMAILTRANSPORT_LIB_VERSION ${PIM_VERSION})
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(KF5KIO ${KF5_VERSION} CONFIG REQUIRED)
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 @@ endif(BUILD_TESTING)
########### 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)
......
add_definitions("-DQT_NO_CAST_FROM_ASCII -DQT_NO_CAST_TO_ASCII")
add_subdirectory(src)
add_subdirectory(doc)
########### install files ###############
kdoctools_create_handbook(index.docbook INSTALL_DESTINATION ${KDE_INSTALL_DOCBUNDLEDIR}/en SUBDIR kioslave5/smtp)
<?xml version="1.0" ?>
<!DOCTYPE article PUBLIC "-//KDE//DTD DocBook XML V4.5-Based Variant V1.1//EN"
"dtd/kdedbx45.dtd" [
<!ENTITY % addindex "IGNORE">
<!ENTITY % English "INCLUDE" > <!-- change language only here -->
]>
<article lang="&language;" id="smtp">
<title>smtp</title>
<articleinfo>
<authorgroup>
<author>&Ferdinand.Gassauer; &Ferdinand.Gassauer.mail;</author>
<!-- TRANS:ROLES_OF_TRANSLATORS -->
</authorgroup>
</articleinfo>
<para>
A protocol to send mail from the client workstation to the mail server.
</para>
<para> See : <ulink url="http://cr.yp.to/smtp.html">Simple Mail Transfer Protocol </ulink>.
</para>
</article>
/* This file is part of the KDE project
Copyright (C) 2008 Jarosław Staniek <staniek@kde.org>
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 <stdio.h>
#include <QFile>
#include <QDir>
#include <QCoreApplication>
extern "C" {
#include <sasl/sasl.h>
}
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
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} )
#! /usr/bin/env bash
$XGETTEXT *.cpp -o $podir/kio_smtp.pot
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 ;-)
/* -*- c++ -*-
capabilities.cc
This file is part of kio_smtp, the KDE SMTP kioslave.
Copyright (c) 2003 Marc Mutz <mutz@kde.org>
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<QString, QStringList>::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
/* -*- c++ -*-
capabilities.h
This file is part of kio_smtp, the KDE SMTP kioslave.
Copyright (c) 2003 Marc Mutz <mutz@kde.org>
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 <QMap>
#include <QStringList>
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<QString, QStringList> mCapabilities;
};
} // namespace KioSMTP
#endif // __KIOSMTP_CAPABILITIES_H__
/* -*- c++ -*-
command.cc
This file is part of kio_smtp, the KDE SMTP kioslave.
Copyright (c) 2003 Marc Mutz <mutz@kde.org>
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 <KLocalizedString>
#include <kio/slavebase.h> // for test_commands, where SMTPProtocol is not derived from TCPSlaveBase
#include <QUrl>
#include <assert.h>
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;