/**************************************************************************** ** ** 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 #ifndef QT_NO_SSL #include #endif #include #include #include #include #include #include // 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)) , mailItr(mailList.end()) , messageLength(0) , fetchingCapabilities(false) , transport(0) , temporaryFile(0) , 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() { qMailLog(SMTP) << "fetchCapabilities"; capabilities.clear(); if (transport && transport->inUse()) { qWarning() << "Cannot fetch capabilities; transport in use"; emit fetchCapabilitiesFinished(); return; } if (!account().isValid()) { qWarning() << "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()) { qWarning() << "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, SIGNAL(readyRead()), this, SLOT(readyRead())); connect(transport, SIGNAL(connected(QMailTransport::EncryptType)), this, SLOT(connected(QMailTransport::EncryptType))); connect(transport, SIGNAL(bytesWritten(qint64)), this, SLOT(sent(qint64))); connect(transport, SIGNAL(updateStatus(QString)), this, SIGNAL(updateStatus(QString))); connect(transport, SIGNAL(errorOccurred(int,QString)), this, SLOT(transportError(int,QString))); #ifndef QT_NO_SSL connect(transport, SIGNAL(sslErrorOccured(QMailServiceAction::Status::ErrorCode,QString)), this, SIGNAL(connectionError(QMailServiceAction::Status::ErrorCode,QString))); #endif } status = Init; outstandingResponses = 0; qMailLog(SMTP) << "Open SMTP connection"; SmtpConfiguration smtpConfig(config); transport->setAcceptUntrustedCertificates(smtpConfig.acceptUntrustedCertificates()); transport->open(smtpConfig.smtpServer(), smtpConfig.smtpPort(), static_cast(smtpConfig.smtpEncryption())); } void SmtpClient::newConnection() { qMailLog(SMTP) << "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, sendSize.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 (mailList.isEmpty() && !sendSize.isEmpty()) { foreach (const QMailMessageId& id, sendSize.keys()) qMailLog(Messaging) << "Message" << id << "still in send map..."; sendSize.clear(); } if (mail.status() & QMailMessage::HasUnresolvedReferences) { qMailLog(Messaging) << "Cannot send SMTP message with unresolved references!"; return QMailServiceAction::Status::ErrInvalidData; } if (mail.from().address().isEmpty()) { qMailLog(Messaging) << "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()) { qMailLog(Messaging) << "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; mailList.append(rawmail); mailItr = mailList.end(); sendSize.insert(mail.id(), mail.indicativeSize()); return QMailServiceAction::Status::ErrNoError; } void SmtpClient::connected(QMailTransport::EncryptType encryptType) { delete authTimeout; authTimeout = new QTimer; authTimeout->setSingleShot(true); const int twentySeconds = 40 * 1000; connect(authTimeout, SIGNAL(timeout()), this, SLOT(authExpired())); authTimeout->setInterval(twentySeconds); authTimeout->start(); SmtpConfiguration smtpCfg(config); if (smtpCfg.smtpEncryption() == encryptType) { qMailLog(SMTP) << "Connected"; emit updateStatus(tr("Connected")); } #ifndef QT_NO_SSL 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; } #endif } void SmtpClient::transportError(int errorCode, QString msg) { if (status == Done) return; //Ignore errors after QUIT is sent operationFailed(errorCode, msg); } void SmtpClient::sent(qint64 size) { if (sendingId.isValid() && messageLength) { SendMap::const_iterator it = sendSize.find(sendingId); if (it != sendSize.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) { qMailLog(SMTP) << "SEND: