KSMTP

sessionthread.cpp
1/*
2 SPDX-FileCopyrightText: 2010 BetterInbox <contact@betterinbox.com>
3 SPDX-FileContributor: Christophe Laveault <christophe@betterinbox.com>
4 SPDX-FileContributor: Gregory Schlomoff <gregory.schlomoff@gmail.com>
5
6 SPDX-License-Identifier: LGPL-2.1-or-later
7*/
8
9#include "ksmtp_debug.h"
10#include "serverresponse_p.h"
11#include "session.h"
12#include "session_p.h"
13#include "sessionthread_p.h"
14
15#include <QCoreApplication>
16#include <QFile>
17#include <QNetworkProxy>
18#include <QSslCipher>
19#include <QTimer>
20
21#include <memory>
22
23using namespace KSmtp;
24
25SessionThread::SessionThread(const QString &hostName, quint16 port, Session *session)
26 : m_parentSession(session)
27 , m_hostName(hostName)
28 , m_port(port)
29{
30 moveToThread(this);
31
32 const auto logfile = qgetenv("KSMTP_SESSION_LOG");
33 if (!logfile.isEmpty()) {
34 static uint sSessionCount = 0;
35 const QString filename = QStringLiteral("%1.%2.%3").arg(QString::fromUtf8(logfile)).arg(qApp->applicationPid()).arg(++sSessionCount);
36 m_logFile = std::make_unique<QFile>(filename);
37 if (!m_logFile->open(QIODevice::WriteOnly | QIODevice::Truncate)) {
38 qCWarning(KSMTP_LOG) << "Failed to open log file" << filename << ":" << m_logFile->errorString();
39 m_logFile.reset();
40 }
41 }
42}
43
44SessionThread::~SessionThread() = default;
45
46QString SessionThread::hostName() const
47{
48 return m_hostName;
49}
50
51quint16 SessionThread::port() const
52{
53 return m_port;
54}
55
56void SessionThread::sendData(const QByteArray &payload)
57{
58 QMutexLocker locker(&m_mutex);
59 // qCDebug(KSMTP_LOG) << "C:: " << payload;
60 if (m_logFile) {
61 m_logFile->write("C: " + payload + '\n');
62 m_logFile->flush();
63 }
64
65 m_dataQueue.enqueue(payload + "\r\n");
66 QTimer::singleShot(0, this, &SessionThread::writeDataQueue);
67}
68
69void SessionThread::writeDataQueue()
70{
71 QMutexLocker locker(&m_mutex);
72
73 while (!m_dataQueue.isEmpty()) {
74 m_socket->write(m_dataQueue.dequeue());
75 }
76}
77
78void SessionThread::readResponse()
79{
80 QMutexLocker locker(&m_mutex);
81
82 if (!m_socket->bytesAvailable()) {
83 return;
84 }
85
86 const QByteArray data = m_socket->readLine();
87 // qCDebug(KSMTP_LOG) << "S:" << data;
88 if (m_logFile) {
89 m_logFile->write("S: " + data);
90 m_logFile->flush();
91 }
92
93 const ServerResponse response = parseResponse(data);
94 Q_EMIT responseReceived(response);
95
96 if (m_socket->bytesAvailable()) {
97 QTimer::singleShot(0, this, &SessionThread::readResponse);
98 }
99}
100
101void SessionThread::closeSocket()
102{
103 QTimer::singleShot(0, this, &SessionThread::doCloseSocket);
104}
105
106void SessionThread::doCloseSocket()
107{
108 m_socket->close();
109}
110
111void SessionThread::reconnect()
112{
113 QMutexLocker locker(&m_mutex);
114
115 if (m_socket->state() != QAbstractSocket::ConnectedState && m_socket->state() != QAbstractSocket::ConnectingState) {
116 if (!m_useProxy) {
117 qCDebug(KSMTP_LOG) << "Not using any proxy to connect to the SMTP server.";
118
119 QNetworkProxy proxy;
121 m_socket->setProxy(proxy);
122 } else {
123 qCDebug(KSMTP_LOG) << "Using the default system proxy to connect to the SMTP server.";
124 }
125
126 if (m_useTls) {
127 m_socket->connectToHostEncrypted(hostName(), port());
128 } else {
129 m_socket->connectToHost(hostName(), port());
130 }
131 }
132}
133
134void SessionThread::run()
135{
136 m_socket = std::make_unique<QSslSocket>();
137
138 connect(m_socket.get(), &QSslSocket::readyRead, this, &SessionThread::readResponse, Qt::QueuedConnection);
139 connect(m_socket.get(), &QSslSocket::encrypted, this, &SessionThread::sslConnected);
140
141 connect(m_socket.get(), &QSslSocket::disconnected, m_parentSession->d, &SessionPrivate::socketDisconnected);
142 connect(m_socket.get(), &QSslSocket::connected, m_parentSession->d, &SessionPrivate::socketConnected);
143 connect(m_socket.get(), &QAbstractSocket::errorOccurred, this, [this](QAbstractSocket::SocketError err) {
144 qCWarning(KSMTP_LOG) << "SMTP Socket error:" << err << m_socket->errorString();
145 Q_EMIT m_parentSession->connectionError(m_socket->errorString());
146 });
147 connect(this, &SessionThread::encryptionNegotiationResult, m_parentSession->d, &SessionPrivate::encryptionNegotiationResult);
148
149 connect(this, &SessionThread::responseReceived, m_parentSession->d, &SessionPrivate::responseReceived);
150
151 exec();
152
153 m_socket.reset();
154}
155
156void SessionThread::setUseNetworkProxy(bool useProxy)
157{
158 m_useProxy = useProxy;
159}
160
161void SessionThread::setConnectWithTls(bool useTls)
162{
163 QMutexLocker locker(&m_mutex);
164 m_useTls = useTls;
165}
166
167ServerResponse SessionThread::parseResponse(const QByteArray &resp)
168{
169 QByteArray response(resp);
170
171 // Remove useless CRLF
172 const int indexOfCR = response.indexOf("\r");
173 const int indexOfLF = response.indexOf("\n");
174
175 if (indexOfCR > 0) {
176 response.truncate(indexOfCR);
177 }
178 if (indexOfLF > 0) {
179 response.truncate(indexOfLF);
180 }
181
182 // Server response code
183 const QByteArray code = response.left(3);
184 bool ok = false;
185 const int returnCode = code.toInt(&ok);
186 if (!ok) {
187 return ServerResponse();
188 }
189
190 // RFC821, Appendix E
191 const bool multiline = (response.at(3) == '-');
192
193 if (returnCode) {
194 response.remove(0, 4); // Keep the text part
195 }
196
197 return ServerResponse(returnCode, response, multiline);
198}
199
200void SessionThread::startSsl()
201{
202 QMutexLocker locker(&m_mutex);
203
204 m_socket->ignoreSslErrors(); // don't worry, we DO handle the errors ourselves below
205 m_socket->startClientEncryption();
206}
207
208void SessionThread::sslConnected()
209{
210 QMutexLocker locker(&m_mutex);
211 QSslCipher cipher = m_socket->sessionCipher();
212
213 if (!m_socket->sslHandshakeErrors().isEmpty() || !m_socket->isEncrypted() || cipher.isNull() || cipher.usedBits() == 0) {
214 qCDebug(KSMTP_LOG) << "Initial SSL handshake failed. cipher.isNull() is" << cipher.isNull() << ", cipher.usedBits() is" << cipher.usedBits()
215 << ", the socket says:" << m_socket->errorString() << "and the list of SSL errors contains" << m_socket->sslHandshakeErrors().count()
216 << "items.";
217 KSslErrorUiData errorData(m_socket.get());
218 Q_EMIT sslError(errorData);
219 } else {
220 qCDebug(KSMTP_LOG) << "TLS negotiation done, the negotiated protocol is" << m_socket->sessionCipher().protocolString();
221
222 Q_EMIT encryptionNegotiationResult(true, m_socket->sessionProtocol());
223 }
224}
225
226void SessionThread::handleSslErrorResponse(bool ignoreError)
227{
229 this,
230 [this, ignoreError] {
231 doHandleSslErrorResponse(ignoreError);
232 },
234}
235
236void SessionThread::doHandleSslErrorResponse(bool ignoreError)
237{
238 Q_ASSERT(QThread::currentThread() == thread());
239 if (!m_socket) {
240 return;
241 }
242 if (ignoreError) {
243 Q_EMIT encryptionNegotiationResult(true, m_socket->sessionProtocol());
244 } else {
245 const auto sslErrors = m_socket->sslHandshakeErrors();
246 QStringList errorMsgs;
247 errorMsgs.reserve(sslErrors.size());
248 std::transform(sslErrors.begin(), sslErrors.end(), std::back_inserter(errorMsgs), std::mem_fn(&QSslError::errorString));
249 Q_EMIT m_parentSession->connectionError(errorMsgs.join(QLatin1Char('\n')));
250 m_socket->disconnectFromHost();
251 }
252}
253
254#include "moc_sessionthread_p.cpp"
void errorOccurred(QAbstractSocket::SocketError socketError)
QByteArray left(qsizetype len) const const
int toInt(bool *ok, int base) const const
void reserve(qsizetype size)
bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret)
void setType(QNetworkProxy::ProxyType type)
QString arg(Args &&... args) const const
QString fromUtf8(QByteArrayView str)
QString join(QChar separator) const const
QueuedConnection
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QThread * currentThread()
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Apr 18 2025 12:07:23 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.