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"