File indexing completed on 2024-05-12 05:21:36

0001 /*
0002   SPDX-FileCopyrightText: 2010 BetterInbox <contact@betterinbox.com>
0003   SPDX-FileContributor: Christophe Laveault <christophe@betterinbox.com>
0004   SPDX-FileContributor: Gregory Schlomoff <gregory.schlomoff@gmail.com>
0005 
0006   SPDX-License-Identifier: LGPL-2.1-or-later
0007 */
0008 
0009 #include "ksmtp_debug.h"
0010 #include "serverresponse_p.h"
0011 #include "session.h"
0012 #include "session_p.h"
0013 #include "sessionthread_p.h"
0014 
0015 #include <QCoreApplication>
0016 #include <QFile>
0017 #include <QNetworkProxy>
0018 #include <QSslCipher>
0019 #include <QTimer>
0020 
0021 #include <memory>
0022 
0023 using namespace KSmtp;
0024 
0025 SessionThread::SessionThread(const QString &hostName, quint16 port, Session *session)
0026     : QThread()
0027     , m_parentSession(session)
0028     , m_hostName(hostName)
0029     , m_port(port)
0030 {
0031     moveToThread(this);
0032 
0033     const auto logfile = qgetenv("KSMTP_SESSION_LOG");
0034     if (!logfile.isEmpty()) {
0035         static uint sSessionCount = 0;
0036         const QString filename = QStringLiteral("%1.%2.%3").arg(QString::fromUtf8(logfile)).arg(qApp->applicationPid()).arg(++sSessionCount);
0037         m_logFile = std::make_unique<QFile>(filename);
0038         if (!m_logFile->open(QIODevice::WriteOnly | QIODevice::Truncate)) {
0039             qCWarning(KSMTP_LOG) << "Failed to open log file" << filename << ":" << m_logFile->errorString();
0040             m_logFile.reset();
0041         }
0042     }
0043 }
0044 
0045 SessionThread::~SessionThread() = default;
0046 
0047 QString SessionThread::hostName() const
0048 {
0049     return m_hostName;
0050 }
0051 
0052 quint16 SessionThread::port() const
0053 {
0054     return m_port;
0055 }
0056 
0057 void SessionThread::sendData(const QByteArray &payload)
0058 {
0059     QMutexLocker locker(&m_mutex);
0060     // qCDebug(KSMTP_LOG) << "C:: " << payload;
0061     if (m_logFile) {
0062         m_logFile->write("C: " + payload + '\n');
0063         m_logFile->flush();
0064     }
0065 
0066     m_dataQueue.enqueue(payload + "\r\n");
0067     QTimer::singleShot(0, this, &SessionThread::writeDataQueue);
0068 }
0069 
0070 void SessionThread::writeDataQueue()
0071 {
0072     QMutexLocker locker(&m_mutex);
0073 
0074     while (!m_dataQueue.isEmpty()) {
0075         m_socket->write(m_dataQueue.dequeue());
0076     }
0077 }
0078 
0079 void SessionThread::readResponse()
0080 {
0081     QMutexLocker locker(&m_mutex);
0082 
0083     if (!m_socket->bytesAvailable()) {
0084         return;
0085     }
0086 
0087     const QByteArray data = m_socket->readLine();
0088     // qCDebug(KSMTP_LOG) << "S:" << data;
0089     if (m_logFile) {
0090         m_logFile->write("S: " + data);
0091         m_logFile->flush();
0092     }
0093 
0094     const ServerResponse response = parseResponse(data);
0095     Q_EMIT responseReceived(response);
0096 
0097     if (m_socket->bytesAvailable()) {
0098         QTimer::singleShot(0, this, &SessionThread::readResponse);
0099     }
0100 }
0101 
0102 void SessionThread::closeSocket()
0103 {
0104     QTimer::singleShot(0, this, &SessionThread::doCloseSocket);
0105 }
0106 
0107 void SessionThread::doCloseSocket()
0108 {
0109     m_socket->close();
0110 }
0111 
0112 void SessionThread::reconnect()
0113 {
0114     QMutexLocker locker(&m_mutex);
0115 
0116     if (m_socket->state() != QAbstractSocket::ConnectedState && m_socket->state() != QAbstractSocket::ConnectingState) {
0117         if (!m_useProxy) {
0118             qCDebug(KSMTP_LOG) << "Not using any proxy to connect to the SMTP server.";
0119 
0120             QNetworkProxy proxy;
0121             proxy.setType(QNetworkProxy::NoProxy);
0122             m_socket->setProxy(proxy);
0123         } else {
0124             qCDebug(KSMTP_LOG) << "Using the default system proxy to connect to the SMTP server.";
0125         }
0126 
0127         if (m_useTls) {
0128             m_socket->connectToHostEncrypted(hostName(), port());
0129         } else {
0130             m_socket->connectToHost(hostName(), port());
0131         }
0132     }
0133 }
0134 
0135 void SessionThread::run()
0136 {
0137     m_socket = std::make_unique<QSslSocket>();
0138 
0139     connect(m_socket.get(), &QSslSocket::readyRead, this, &SessionThread::readResponse, Qt::QueuedConnection);
0140     connect(m_socket.get(), &QSslSocket::encrypted, this, &SessionThread::sslConnected);
0141 
0142     connect(m_socket.get(), &QSslSocket::disconnected, m_parentSession->d, &SessionPrivate::socketDisconnected);
0143     connect(m_socket.get(), &QSslSocket::connected, m_parentSession->d, &SessionPrivate::socketConnected);
0144     connect(m_socket.get(), &QAbstractSocket::errorOccurred, this, [this](QAbstractSocket::SocketError err) {
0145         qCWarning(KSMTP_LOG) << "SMTP Socket error:" << err << m_socket->errorString();
0146         Q_EMIT m_parentSession->connectionError(m_socket->errorString());
0147     });
0148     connect(this, &SessionThread::encryptionNegotiationResult, m_parentSession->d, &SessionPrivate::encryptionNegotiationResult);
0149 
0150     connect(this, &SessionThread::responseReceived, m_parentSession->d, &SessionPrivate::responseReceived);
0151 
0152     exec();
0153 
0154     m_socket.reset();
0155 }
0156 
0157 void SessionThread::setUseNetworkProxy(bool useProxy)
0158 {
0159     m_useProxy = useProxy;
0160 }
0161 
0162 void SessionThread::setConnectWithTls(bool useTls)
0163 {
0164     QMutexLocker locker(&m_mutex);
0165     m_useTls = useTls;
0166 }
0167 
0168 ServerResponse SessionThread::parseResponse(const QByteArray &resp)
0169 {
0170     QByteArray response(resp);
0171 
0172     // Remove useless CRLF
0173     const int indexOfCR = response.indexOf("\r");
0174     const int indexOfLF = response.indexOf("\n");
0175 
0176     if (indexOfCR > 0) {
0177         response.truncate(indexOfCR);
0178     }
0179     if (indexOfLF > 0) {
0180         response.truncate(indexOfLF);
0181     }
0182 
0183     // Server response code
0184     const QByteArray code = response.left(3);
0185     bool ok = false;
0186     const int returnCode = code.toInt(&ok);
0187     if (!ok) {
0188         return ServerResponse();
0189     }
0190 
0191     // RFC821, Appendix E
0192     const bool multiline = (response.at(3) == '-');
0193 
0194     if (returnCode) {
0195         response.remove(0, 4); // Keep the text part
0196     }
0197 
0198     return ServerResponse(returnCode, response, multiline);
0199 }
0200 
0201 void SessionThread::startSsl()
0202 {
0203     QMutexLocker locker(&m_mutex);
0204 
0205     m_socket->ignoreSslErrors(); // don't worry, we DO handle the errors ourselves below
0206     m_socket->startClientEncryption();
0207 }
0208 
0209 void SessionThread::sslConnected()
0210 {
0211     QMutexLocker locker(&m_mutex);
0212     QSslCipher cipher = m_socket->sessionCipher();
0213 
0214     if (!m_socket->sslHandshakeErrors().isEmpty() || !m_socket->isEncrypted() || cipher.isNull() || cipher.usedBits() == 0) {
0215         qCDebug(KSMTP_LOG) << "Initial SSL handshake failed. cipher.isNull() is" << cipher.isNull() << ", cipher.usedBits() is" << cipher.usedBits()
0216                            << ", the socket says:" << m_socket->errorString() << "and the list of SSL errors contains" << m_socket->sslHandshakeErrors().count()
0217                            << "items.";
0218         KSslErrorUiData errorData(m_socket.get());
0219         Q_EMIT sslError(errorData);
0220     } else {
0221         qCDebug(KSMTP_LOG) << "TLS negotiation done, the negotiated protocol is" << m_socket->sessionCipher().protocolString();
0222 
0223         Q_EMIT encryptionNegotiationResult(true, m_socket->sessionProtocol());
0224     }
0225 }
0226 
0227 void SessionThread::handleSslErrorResponse(bool ignoreError)
0228 {
0229     QMetaObject::invokeMethod(
0230         this,
0231         [this, ignoreError] {
0232             doHandleSslErrorResponse(ignoreError);
0233         },
0234         Qt::QueuedConnection);
0235 }
0236 
0237 void SessionThread::doHandleSslErrorResponse(bool ignoreError)
0238 {
0239     Q_ASSERT(QThread::currentThread() == thread());
0240     if (!m_socket) {
0241         return;
0242     }
0243     if (ignoreError) {
0244         Q_EMIT encryptionNegotiationResult(true, m_socket->sessionProtocol());
0245     } else {
0246         const auto sslErrors = m_socket->sslHandshakeErrors();
0247         QStringList errorMsgs;
0248         errorMsgs.reserve(sslErrors.size());
0249         std::transform(sslErrors.begin(), sslErrors.end(), std::back_inserter(errorMsgs), std::mem_fn(&QSslError::errorString));
0250         Q_EMIT m_parentSession->connectionError(errorMsgs.join(QLatin1Char('\n')));
0251         m_socket->disconnectFromHost();
0252     }
0253 }
0254 
0255 #include "moc_sessionthread_p.cpp"