File indexing completed on 2024-05-12 05:17:17

0001 /*
0002     SPDX-FileCopyrightText: 2009 Kevin Ottens <ervin@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "sessionthread_p.h"
0008 
0009 #include <KSslErrorUiData>
0010 
0011 #include "kimap_debug.h"
0012 #include <QDebug>
0013 #include <QNetworkProxy>
0014 #include <QSslCipher>
0015 #include <QThread>
0016 
0017 #include "imapstreamparser.h"
0018 #include "response_p.h"
0019 
0020 using namespace KIMAP;
0021 
0022 Q_DECLARE_METATYPE(KSslErrorUiData)
0023 
0024 namespace
0025 {
0026 static const int _kimap_abstractSocketError = qRegisterMetaType<QAbstractSocket::SocketError>();
0027 static const int _kimap_sslErrorUiData = qRegisterMetaType<KSslErrorUiData>();
0028 }
0029 
0030 SessionThread::SessionThread(const QString &hostName, quint16 port)
0031     : QObject()
0032     , m_hostName(hostName)
0033     , m_port(port)
0034 {
0035     // Just like the Qt docs now recommend, for event-driven threads:
0036     // don't derive from QThread, create one directly and move the object to it.
0037     auto thread = new QThread();
0038     moveToThread(thread);
0039     thread->start();
0040     QMetaObject::invokeMethod(this, &SessionThread::threadInit);
0041 }
0042 
0043 SessionThread::~SessionThread()
0044 {
0045     QMetaObject::invokeMethod(this, &SessionThread::threadQuit);
0046     if (!thread()->wait(10 * 1000)) {
0047         qCWarning(KIMAP_LOG) << "Session thread refuses to die, killing harder...";
0048         thread()->terminate();
0049         // Make sure to wait until it's done, otherwise it can crash when the pthread callback is called
0050         thread()->wait();
0051     }
0052     delete thread();
0053 }
0054 
0055 // Called in primary thread, passes setting to secondary thread
0056 void SessionThread::setUseNetworkProxy(bool useProxy)
0057 {
0058     QMetaObject::invokeMethod(
0059         this,
0060         [this, useProxy]() {
0061             setUseProxyInternal(useProxy);
0062         },
0063         Qt::QueuedConnection);
0064 }
0065 
0066 // Called in primary thread
0067 void SessionThread::sendData(const QByteArray &payload)
0068 {
0069     QMutexLocker locker(&m_mutex);
0070 
0071     m_dataQueue.enqueue(payload);
0072     QMetaObject::invokeMethod(this, &SessionThread::writeDataQueue);
0073 }
0074 
0075 // Called in secondary thread
0076 void SessionThread::writeDataQueue()
0077 {
0078     Q_ASSERT(QThread::currentThread() == thread());
0079     if (!m_socket) {
0080         return;
0081     }
0082     QMutexLocker locker(&m_mutex);
0083 
0084     while (!m_dataQueue.isEmpty()) {
0085         m_socket->write(m_dataQueue.dequeue());
0086     }
0087 }
0088 
0089 // Called in secondary thread
0090 void SessionThread::readMessage()
0091 {
0092     Q_ASSERT(QThread::currentThread() == thread());
0093     if (!m_stream || m_stream->availableDataSize() == 0) {
0094         return;
0095     }
0096 
0097     Response message;
0098     QList<Response::Part> *payload = &message.content;
0099 
0100     try {
0101         while (!m_stream->atCommandEnd()) {
0102             if (m_stream->hasString()) {
0103                 QByteArray string = m_stream->readString();
0104                 if (string == "NIL") {
0105                     *payload << Response::Part(QList<QByteArray>());
0106                 } else {
0107                     *payload << Response::Part(string);
0108                 }
0109             } else if (m_stream->hasList()) {
0110                 *payload << Response::Part(m_stream->readParenthesizedList());
0111             } else if (m_stream->hasResponseCode()) {
0112                 payload = &message.responseCode;
0113             } else if (m_stream->atResponseCodeEnd()) {
0114                 payload = &message.content;
0115             } else if (m_stream->hasLiteral()) {
0116                 QByteArray literal;
0117                 while (!m_stream->atLiteralEnd()) {
0118                     literal += m_stream->readLiteralPart();
0119                 }
0120                 *payload << Response::Part(literal);
0121             } else {
0122                 // Oops! Something really bad happened, we won't be able to recover
0123                 // so close the socket immediately
0124                 qWarning("Inconsistent state, probably due to some packet loss");
0125                 doCloseSocket();
0126                 return;
0127             }
0128         }
0129 
0130         Q_EMIT responseReceived(message);
0131 
0132     } catch (const KIMAP::ImapParserException &e) {
0133         qCWarning(KIMAP_LOG) << "The stream parser raised an exception:" << e.what();
0134     }
0135 
0136     if (m_stream->availableDataSize() > 1) {
0137         QMetaObject::invokeMethod(this, &SessionThread::readMessage, Qt::QueuedConnection);
0138     }
0139 }
0140 
0141 // Called in main thread
0142 void SessionThread::closeSocket()
0143 {
0144     QMetaObject::invokeMethod(this, &SessionThread::doCloseSocket, Qt::QueuedConnection);
0145 }
0146 
0147 // Called in secondary thread
0148 void SessionThread::doCloseSocket()
0149 {
0150     Q_ASSERT(QThread::currentThread() == thread());
0151     if (!m_socket) {
0152         return;
0153     }
0154     m_encryptedMode = false;
0155     qCDebug(KIMAP_LOG) << "close";
0156     m_socket->close();
0157 }
0158 
0159 // Called in secondary thread
0160 void SessionThread::reconnect()
0161 {
0162     Q_ASSERT(QThread::currentThread() == thread());
0163     if (m_socket == nullptr) { // threadQuit already called
0164         return;
0165     }
0166     if (m_socket->state() != QSslSocket::ConnectedState && m_socket->state() != QSslSocket::ConnectingState) {
0167         QNetworkProxy proxy;
0168         if (!m_useProxy) {
0169             qCDebug(KIMAP_LOG) << "Connecting to IMAP server with no proxy";
0170             proxy.setType(QNetworkProxy::NoProxy);
0171         } else {
0172             qCDebug(KIMAP_LOG) << "Connecting to IMAP server using default system proxy";
0173             proxy.setType(QNetworkProxy::DefaultProxy);
0174         }
0175         m_socket->setProxy(proxy);
0176 
0177         if (m_encryptedMode) {
0178             qCDebug(KIMAP_LOG) << "connectToHostEncrypted" << m_hostName << m_port;
0179             m_socket->connectToHostEncrypted(m_hostName, m_port);
0180         } else {
0181             qCDebug(KIMAP_LOG) << "connectToHost" << m_hostName << m_port;
0182             m_socket->connectToHost(m_hostName, m_port);
0183         }
0184     }
0185 }
0186 
0187 // Called in secondary thread
0188 void SessionThread::threadInit()
0189 {
0190     Q_ASSERT(QThread::currentThread() == thread());
0191     m_socket = std::make_unique<QSslSocket>();
0192     m_stream = std::make_unique<ImapStreamParser>(m_socket.get());
0193     connect(m_socket.get(), &QIODevice::readyRead, this, &SessionThread::readMessage, Qt::QueuedConnection);
0194 
0195     // Delay the call to slotSocketDisconnected so that it finishes disconnecting before we call reconnect()
0196     connect(m_socket.get(), &QSslSocket::disconnected, this, &SessionThread::slotSocketDisconnected, Qt::QueuedConnection);
0197     connect(m_socket.get(), &QSslSocket::connected, this, &SessionThread::socketConnected);
0198     connect(m_socket.get(), &QAbstractSocket::errorOccurred, this, &SessionThread::slotSocketError);
0199 
0200     connect(m_socket.get(), &QIODevice::bytesWritten, this, &SessionThread::socketActivity);
0201     connect(m_socket.get(), &QSslSocket::encryptedBytesWritten, this, &SessionThread::socketActivity);
0202     connect(m_socket.get(), &QIODevice::readyRead, this, &SessionThread::socketActivity);
0203     QMetaObject::invokeMethod(this, &SessionThread::reconnect, Qt::QueuedConnection);
0204 }
0205 
0206 // Called in secondary thread
0207 void SessionThread::threadQuit()
0208 {
0209     Q_ASSERT(QThread::currentThread() == thread());
0210     m_stream.reset();
0211     m_socket.reset();
0212     thread()->quit();
0213 }
0214 
0215 // Called in secondary thread
0216 void SessionThread::setUseProxyInternal(bool useProxy)
0217 {
0218     m_useProxy = useProxy;
0219     if (m_socket != nullptr) {
0220         if (m_socket->state() != QSslSocket::UnconnectedState) {
0221             m_socket->disconnectFromHost();
0222             QMetaObject::invokeMethod(this, &SessionThread::reconnect, Qt::QueuedConnection);
0223         }
0224     }
0225 }
0226 
0227 // Called in primary thread
0228 void SessionThread::startSsl(QSsl::SslProtocol protocol)
0229 {
0230     QMetaObject::invokeMethod(this, [this, protocol]() {
0231         doStartSsl(protocol);
0232     });
0233 }
0234 
0235 // Called in secondary thread (via invokeMethod)
0236 void SessionThread::doStartSsl(QSsl::SslProtocol protocol)
0237 {
0238     Q_ASSERT(QThread::currentThread() == thread());
0239     if (!m_socket) {
0240         return;
0241     }
0242 
0243     m_socket->setProtocol(protocol);
0244     m_socket->ignoreSslErrors(); // Don't worry, errors are handled manually below
0245     connect(m_socket.get(), &QSslSocket::encrypted, this, &SessionThread::sslConnected);
0246     m_socket->startClientEncryption();
0247 }
0248 
0249 // Called in secondary thread
0250 void SessionThread::slotSocketDisconnected()
0251 {
0252     Q_ASSERT(QThread::currentThread() == thread());
0253     Q_EMIT socketDisconnected();
0254 }
0255 
0256 // Called in secondary thread
0257 void SessionThread::slotSocketError(QAbstractSocket::SocketError error)
0258 {
0259     Q_ASSERT(QThread::currentThread() == thread());
0260     if (!m_socket) {
0261         return;
0262     }
0263     Q_EMIT socketError(error);
0264 }
0265 
0266 // Called in secondary thread
0267 void SessionThread::sslConnected()
0268 {
0269     Q_ASSERT(QThread::currentThread() == thread());
0270     if (!m_socket) {
0271         return;
0272     }
0273     QSslCipher cipher = m_socket->sessionCipher();
0274     if (!m_socket->sslHandshakeErrors().isEmpty() || !m_socket->isEncrypted() || cipher.isNull() || cipher.usedBits() == 0) {
0275         qCDebug(KIMAP_LOG) << "Initial SSL handshake failed. cipher.isNull() is" << cipher.isNull() << ", cipher.usedBits() is" << cipher.usedBits()
0276                            << ", the socket says:" << m_socket->errorString() << "and the list of SSL errors contains" << m_socket->sslHandshakeErrors().count()
0277                            << "items.";
0278         KSslErrorUiData errorData(m_socket.get());
0279         Q_EMIT sslError(errorData);
0280     } else {
0281         qCDebug(KIMAP_LOG) << "TLS negotiation done, the negotiated protocol is" << cipher.protocolString();
0282         m_encryptedMode = true;
0283         Q_EMIT encryptionNegotiationResult(true, m_socket->sessionProtocol());
0284     }
0285 }
0286 
0287 void SessionThread::sslErrorHandlerResponse(bool response)
0288 {
0289     QMetaObject::invokeMethod(this, [this, response]() {
0290         doSslErrorHandlerResponse(response);
0291     });
0292 }
0293 
0294 // Called in secondary thread (via invokeMethod)
0295 void SessionThread::doSslErrorHandlerResponse(bool response)
0296 {
0297     Q_ASSERT(QThread::currentThread() == thread());
0298     if (!m_socket) {
0299         return;
0300     }
0301     if (response) {
0302         m_encryptedMode = true;
0303         Q_EMIT encryptionNegotiationResult(true, m_socket->sessionProtocol());
0304     } else {
0305         m_encryptedMode = false;
0306         Q_EMIT socketError(QAbstractSocket::SslInvalidUserDataError);
0307         m_socket->disconnectFromHost();
0308     }
0309 }
0310 
0311 #include "moc_sessionthread_p.cpp"