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

KDE's Doxygen guidelines are available online.