/**************************************************************************** ** ** Copyright (C) 2015 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the Qt Messaging Framework. ** ** $QT_BEGIN_LICENSE:LGPL21$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see http://www.qt.io/terms-conditions. For further ** information use the contact form at http://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 or version 3 as published by the Free ** Software Foundation and appearing in the file LICENSE.LGPLv21 and ** LICENSE.LGPLv3 included in the packaging of this file. Please review the ** following information to ensure the GNU Lesser General Public License ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** As a special exception, The Qt Company gives you certain additional ** rights. These rights are described in The Qt Company LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "smtpclient.h" #include "smtpauthenticator.h" #include "smtpconfiguration.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_LOGGING_CATEGORY(lcSMTP, "org.qt.messageserver.smtp", QtWarningMsg) // The size of the buffer used when sending messages. // Only this many bytes is queued to be sent at a time. #define SENDING_BUFFER_SIZE 5000 static QByteArray messageId(const QByteArray& domainName, quint32 addressComponent) { quint32 randomComponent(QRandomGenerator::global()->generate()); quint32 timeComponent(QDateTime::currentDateTimeUtc().toMSecsSinceEpoch() / 1000); return ('<' + QString::number(randomComponent, 36) + '.' + QString::number(timeComponent, 36) + '.' + QString::number(addressComponent, 36) + "-qmf@" + domainName + '>').toLatin1(); } static QByteArray localName(const QHostAddress &hostAddress) { QByteArray result(QHostInfo::localDomainName().toLatin1()); if (!result.isEmpty()) return result; if (hostAddress.protocol() == QAbstractSocket::IPv6Protocol) return "[IPv6:" + hostAddress.toString().toLatin1() + "]"; else if (!hostAddress.isNull()) return "[" + hostAddress.toString().toLatin1() + "]"; QList addresses(QNetworkInterface::allAddresses()); if (addresses.isEmpty()) return "localhost.localdomain"; QHostAddress addr; // try to find a non-loopback address foreach (const QHostAddress &a, addresses) { if (!a.isLoopback() && !a.isNull()) { addr = a; break; } } if (addr.isNull()) addr = addresses.first(); return "[" + addr.toString().toLatin1() + "]"; } SmtpClient::SmtpClient(const QMailAccountId &id, QObject* parent) : QObject(parent) , config(QMailAccountConfiguration(id)) , messageLength(0) , fetchingCapabilities(false) , transport(nullptr) , temporaryFile(nullptr) , waitingForBytes(0) , notUsingAuth(false) , authReset(false) , authTimeout(0) , credentials(QMailCredentialsFactory::getCredentialsHandlerForAccount(config)) { } SmtpClient::~SmtpClient() { delete transport; delete temporaryFile; delete authTimeout; delete credentials; } QMailAccountId SmtpClient::account() const { return config.id(); } void SmtpClient::fetchCapabilities() { qCDebug(lcSMTP) << "fetchCapabilities"; capabilities.clear(); if (transport && transport->inUse()) { qCWarning(lcSMTP) << "Cannot fetch capabilities; transport in use"; emit fetchCapabilitiesFinished(); return; } if (!account().isValid()) { qCWarning(lcSMTP) << "Cannot fetch capabilities; invalid account"; emit fetchCapabilitiesFinished(); return; } // Reload the account configuration whenever a new SMTP // connection is created, in order to ensure the changes // in the account settings are being managed properly. config = QMailAccountConfiguration(account()); SmtpConfiguration smtpConfig(config); if (smtpConfig.smtpServer().isEmpty()) { qCWarning(lcSMTP) << "Cannot fetch capabilities without SMTP server configuration"; emit fetchCapabilitiesFinished(); return; } fetchingCapabilities = true; openTransport(); } void SmtpClient::openTransport() { if (!transport) { // Set up the transport transport = new QMailTransport("SMTP"); connect(transport, &QMailTransport::readyRead, this, &SmtpClient::readyRead); connect(transport, &QMailTransport::connected, this, &SmtpClient::handleConnected); connect(transport, &QMailTransport::bytesWritten, this, &SmtpClient::handleSent); connect(transport, &QMailTransport::statusChanged, this, &SmtpClient::statusChanged); connect(transport, &QMailTransport::socketErrorOccurred, this, &SmtpClient::transportError); connect(transport, &QMailTransport::sslErrorOccurred, [](QMailServiceAction::Status::ErrorCode errorCode) { // at least report this qCWarning(lcSMTP) << "SMTP encountered SSL error" << errorCode; }); } status = Init; outstandingResponses = 0; qCDebug(lcSMTP) << "Open SMTP connection"; SmtpConfiguration smtpConfig(config); transport->setAcceptUntrustedCertificates(smtpConfig.acceptUntrustedCertificates()); transport->open(smtpConfig.smtpServer(), smtpConfig.smtpPort(), static_cast(smtpConfig.smtpEncryption())); } void SmtpClient::newConnection() { qCDebug(lcSMTP) << "newConnection"; if (transport && transport->inUse()) { operationFailed(QMailServiceAction::Status::ErrConnectionInUse, tr("Cannot send message; transport in use")); return; } if (!account().isValid()) { status = Done; operationFailed(QMailServiceAction::Status::ErrConfiguration, tr("Cannot send message without account configuration")); return; } // Reload the account configuration whenever a new SMTP // connection is created, in order to ensure the changes // in the account settings are being managed properly. config = QMailAccountConfiguration(account()); SmtpConfiguration smtpConfig(config); if (smtpConfig.smtpServer().isEmpty()) { status = Done; operationFailed(QMailServiceAction::Status::ErrConfiguration, tr("Cannot send message without SMTP server configuration")); return; } if (credentials) { credentials->init(smtpConfig); } // Calculate the total indicative size of the messages we're sending totalSendSize = 0; foreach (uint size, m_sendUnits.values()) totalSendSize += size; progressSendSize = 0; emit progressChanged(progressSendSize, totalSendSize); fetchingCapabilities = false; domainName = QByteArray(); authReset = false; openTransport(); } QMailServiceAction::Status::ErrorCode SmtpClient::addMail(const QMailMessage& mail) { // We shouldn't have anything left in our send list... if (sendList.isEmpty() && !m_sendUnits.isEmpty()) { foreach (const QMailMessageId& id, m_sendUnits.keys()) qCWarning(lcSMTP) << "Message" << id << "still in send map..."; m_sendUnits.clear(); } if (mail.status() & QMailMessage::HasUnresolvedReferences) { qCWarning(lcSMTP) << "Cannot send SMTP message with unresolved references!"; return QMailServiceAction::Status::ErrInvalidData; } if (mail.from().address().isEmpty()) { qCWarning(lcSMTP) << "Cannot send SMTP message with empty from address!"; return QMailServiceAction::Status::ErrInvalidAddress; } QStringList sendTo; foreach (const QMailAddress& address, mail.recipients()) { // Only send to mail addresses if (address.isEmailAddress()) sendTo.append(address.address()); } if (sendTo.isEmpty()) { qCWarning(lcSMTP) << "Cannot send SMTP message with empty recipient address!"; return QMailServiceAction::Status::ErrInvalidAddress; } RawEmail rawmail; rawmail.from = "<" + mail.from().address() + ">"; rawmail.to = sendTo; rawmail.mail = mail; sendList.append(rawmail); m_sendUnits.insert(mail.id(), mail.indicativeSize()); return QMailServiceAction::Status::ErrNoError; } void SmtpClient::handleConnected(QMailTransport::EncryptType encryptType) { delete authTimeout; authTimeout = new QTimer; authTimeout->setSingleShot(true); connect(authTimeout, &QTimer::timeout, this, &SmtpClient::authExpired); const int timeout = 40 * 1000; authTimeout->setInterval(timeout); authTimeout->start(); SmtpConfiguration smtpCfg(config); if (smtpCfg.smtpEncryption() == encryptType) { qCDebug(lcSMTP) << "Connected"; emit statusChanged(tr("Connected")); } if ((smtpCfg.smtpEncryption() == QMailTransport::Encrypt_TLS) && (status == TLS)) { // We have entered TLS mode - restart the SMTP dialog QByteArray ehlo("EHLO " + localName(transport->socket().localAddress())); sendCommand(ehlo); status = Helo; } } void SmtpClient::transportError(int errorCode, QString msg) { if (status == Done) return; //Ignore errors after QUIT is sent operationFailed(errorCode, msg); } void SmtpClient::handleSent(qint64 size) { if (sendingId.isValid() && messageLength) { SendMap::const_iterator it = m_sendUnits.find(sendingId); if (it != m_sendUnits.end()) { sentLength += size; uint percentage = qMin(sentLength * 100 / messageLength, 100); // Update the progress figure to count the sent portion of this message uint partialSize = (*it) * percentage / 100; emit progressChanged(progressSendSize + partialSize, totalSendSize); } } } void SmtpClient::readyRead() { incomingData(); } void SmtpClient::sendCommand(const char *data, int len, bool maskDebug) { if (len == -1) len = ::strlen(data); QDataStream &out(transport->stream()); out.writeRawData(data, len); out.writeRawData("\r\n", 2); ++outstandingResponses; if (maskDebug) { qCDebug(lcSMTP) << "SEND: