Libksieve

session.cpp
1 /*
2  SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
3  SPDX-FileContributor: Volker Krause <[email protected]>
4 
5  SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7 
8 #include "session.h"
9 #include "sessionthread_p.h"
10 #include "sievejob_p.h"
11 
12 #include "kmanagersieve_debug.h"
13 #include <KLocalizedString>
14 #include <KMessageBox>
15 #include <KPasswordDialog>
16 #include <QRegularExpression>
17 #include <QUrlQuery>
18 #include <kio/authinfo.h>
19 #include <kio/job.h>
20 #include <kio/sslui.h>
21 
22 using namespace KManageSieve;
23 
24 Q_DECLARE_METATYPE(KManageSieve::AuthDetails)
25 Q_DECLARE_METATYPE(KManageSieve::Response)
26 Q_DECLARE_METATYPE(KSslErrorUiData)
27 
28 Session::Session(QObject *parent)
29  : QObject(parent)
30  , m_thread(new SessionThread(this))
31 {
32  qRegisterMetaType<KManageSieve::AuthDetails>();
33  qRegisterMetaType<KManageSieve::Response>();
34  qRegisterMetaType<KSslErrorUiData>();
35 
36  static int counter = 0;
37  setObjectName(QStringLiteral("session") + QString::number(++counter));
38 
39  connect(m_thread, &SessionThread::responseReceived, this, &Session::processResponse);
40  connect(m_thread, &SessionThread::error, this, &Session::setErrorMessage);
41  connect(m_thread, &SessionThread::authenticationDone, this, &Session::authenticationDone);
42  connect(m_thread, &SessionThread::sslError, this, &Session::sslError);
43  connect(m_thread, &SessionThread::sslDone, this, &Session::sslDone);
44  connect(m_thread, &SessionThread::socketDisconnected, [=]() {
45  m_connected = false;
46  m_disconnected = true;
47  });
48 }
49 
50 Session::~Session()
51 {
52  qCDebug(KMANAGERSIEVE_LOG) << objectName() << Q_FUNC_INFO;
53  delete m_thread;
54 }
55 
56 void Session::connectToHost(const QUrl &url)
57 {
58  qCDebug(KMANAGERSIEVE_LOG) << objectName() << "connect to host url: " << url;
59  m_url = url;
60  m_disconnected = false;
61  m_thread->connectToHost(url);
62  m_state = PreTlsCapabilities;
63 }
64 
65 void Session::disconnectFromHost(bool sendLogout)
66 {
67  qCDebug(KMANAGERSIEVE_LOG) << objectName() << "sendLogout=" << sendLogout;
68  m_thread->disconnectFromHost(sendLogout);
69  if (m_currentJob) {
70  killJob(m_currentJob, KJob::EmitResult);
71  }
72  for (SieveJob *job : qAsConst(m_jobs)) {
73  killJob(job, KJob::EmitResult);
74  }
75  deleteLater();
76 }
77 
78 void Session::processResponse(const KManageSieve::Response &response, const QByteArray &data)
79 {
80  switch (m_state) {
81  // should probably be refactored into a capability job
82  case PreTlsCapabilities:
83  case PostTlsCapabilities:
84  if (response.type() == Response::Action) {
85  if (response.operationSuccessful()) {
86  qCDebug(KMANAGERSIEVE_LOG) << objectName() << "Sieve server ready & awaiting authentication.";
87  if (m_state == PreTlsCapabilities) {
88  if (!allowUnencrypted() && !QSslSocket::supportsSsl()) {
89  setErrorMessage(QAbstractSocket::UnknownSocketError, i18n("Cannot use TLS since the underlying Qt library does not support it."));
90  disconnectFromHost();
91  return;
92  }
93  if (!allowUnencrypted() && QSslSocket::supportsSsl() && !m_supportsStartTls
95  nullptr,
96  i18n("TLS encryption was requested, but your Sieve server does not advertise TLS in its capabilities.\n"
97  "You can choose to try to initiate TLS negotiations nonetheless, or cancel the operation."),
98  i18n("Sieve Server Does Not Advertise TLS"),
99  KGuiItem(i18n("&Start TLS nonetheless")),
101  QStringLiteral("ask_starttls_%1").arg(m_url.host()))
102  != KMessageBox::Continue) {
103  setErrorMessage(QAbstractSocket::UnknownSocketError, i18n("TLS encryption requested, but not supported by server."));
104  disconnectFromHost();
105  return;
106  }
107 
108  if (m_supportsStartTls && QSslSocket::supportsSsl()) {
109  m_state = StartTls;
110  sendData("STARTTLS");
111  } else {
112  m_state = Authenticating;
113  m_thread->startAuthentication();
114  }
115  } else {
116  m_state = Authenticating;
117  m_thread->startAuthentication();
118  }
119  } else {
120  qCDebug(KMANAGERSIEVE_LOG) << objectName() << "Unknown action " << response.action() << ".";
121  }
122  } else if (response.key() == "IMPLEMENTATION") {
123  m_implementation = QString::fromLatin1(response.value());
124  qCDebug(KMANAGERSIEVE_LOG) << objectName() << "Connected to Sieve server: " << response.value();
125  } else if (response.key() == "SASL") {
126  m_saslMethods = QString::fromLatin1(response.value()).split(QLatin1Char(' '), Qt::SkipEmptyParts);
127  qCDebug(KMANAGERSIEVE_LOG) << objectName() << "Server SASL authentication methods: " << m_saslMethods;
128  } else if (response.key() == "SIEVE") {
129  // Save script capabilities
130  m_sieveExtensions = QString::fromLatin1(response.value()).split(QLatin1Char(' '), Qt::SkipEmptyParts);
131  qCDebug(KMANAGERSIEVE_LOG) << objectName() << "Server script capabilities: " << m_sieveExtensions;
132  } else if (response.key() == "STARTTLS") {
133  qCDebug(KMANAGERSIEVE_LOG) << objectName() << "Server supports TLS";
134  m_supportsStartTls = true;
135  } else {
136  qCDebug(KMANAGERSIEVE_LOG) << objectName() << "Unrecognised key " << response.key();
137  }
138  break;
139  case StartTls:
140  if (response.operationSuccessful()) {
141  m_thread->startSsl();
142  m_state = None;
143  } else {
144  setErrorMessage(QAbstractSocket::UnknownSocketError,
145  i18n("The server does not seem to support TLS. Disable TLS if you want to connect without encryption."));
146  disconnectFromHost();
147  }
148  break;
149  case Authenticating:
150  m_thread->continueAuthentication(response, data);
151  break;
152  default:
153  if (m_currentJob) {
154  if (m_currentJob->d->handleResponse(response, data)) {
155  m_currentJob = nullptr;
156  QMetaObject::invokeMethod(this, &Session::executeNextJob, Qt::QueuedConnection);
157  }
158  break;
159  } else {
160  // we can get here in the kill current job case
161  if (response.operationResult() != Response::Other) {
162  QMetaObject::invokeMethod(this, &Session::executeNextJob, Qt::QueuedConnection);
163  return;
164  }
165  }
166  qCDebug(KMANAGERSIEVE_LOG) << objectName() << "Unhandled response! state=" << m_state << "response=" << response.key() << response.value()
167  << response.extra() << data;
168  }
169 }
170 
171 void Session::scheduleJob(SieveJob *job)
172 {
173  qCDebug(KMANAGERSIEVE_LOG) << objectName() << Q_FUNC_INFO << job;
174  m_jobs.enqueue(job);
175  QMetaObject::invokeMethod(this, &Session::executeNextJob, Qt::QueuedConnection);
176 }
177 
178 void Session::killJob(SieveJob *job, KJob::KillVerbosity verbosity)
179 {
180  qCDebug(KMANAGERSIEVE_LOG) << objectName() << Q_FUNC_INFO << "job " << job << " m_currentJob " << m_currentJob << " verbosity " << verbosity;
181  if (m_currentJob == job) {
182  if (verbosity == KJob::EmitResult) {
183  m_currentJob->d->killed();
184  }
185  m_currentJob = nullptr;
186  } else {
187  m_jobs.removeAll(job);
188  if (verbosity == KJob::EmitResult) {
189  job->d->killed();
190  } else {
191  job->deleteLater();
192  }
193  }
194 }
195 
196 void Session::executeNextJob()
197 {
198  if (!m_connected || m_state != None || m_currentJob || m_jobs.isEmpty()) {
199  return;
200  }
201  m_currentJob = m_jobs.dequeue();
202  qCDebug(KMANAGERSIEVE_LOG) << objectName() << Q_FUNC_INFO << "running job" << m_currentJob;
203  m_currentJob->d->run(this);
204 }
205 
207 {
208  return m_disconnected;
209 }
210 
211 QStringList Session::sieveExtensions() const
212 {
213  return m_sieveExtensions;
214 }
215 
216 bool Session::requestCapabilitiesAfterStartTls() const
217 {
218  // Cyrus didn't send CAPABILITIES after STARTTLS until 2.3.11, which is
219  // not standard conform, but we need to support that anyway.
220  // m_implementation looks like this 'Cyrus timsieved v2.2.12' for Cyrus btw.
221  QRegularExpression regExp(QStringLiteral("Cyrus\\stimsieved\\sv(\\d+)\\.(\\d+)\\.(\\d+)([-\\w]*)"), QRegularExpression::CaseInsensitiveOption);
222  QRegularExpressionMatch matchExpression = regExp.match(m_implementation);
223  if (matchExpression.hasMatch()) {
224  const int major = matchExpression.captured(1).toInt();
225  const int minor = matchExpression.captured(2).toInt();
226  const int patch = matchExpression.captured(3).toInt();
227  const QString vendor = matchExpression.captured(4);
228  if (major < 2 || (major == 2 && (minor < 3 || (minor == 3 && patch < 11))) || (vendor == QLatin1String("-kolab-nocaps"))) {
229  qCDebug(KMANAGERSIEVE_LOG) << objectName() << "Enabling compat mode for Cyrus < 2.3.11 or Cyrus marked as \"kolab-nocaps\"";
230  return true;
231  }
232  }
233  return false;
234 }
235 
236 void Session::sendData(const QByteArray &data)
237 {
238  m_thread->sendData(data);
239 }
240 
241 QStringList Session::requestedSaslMethod() const
242 {
243  const QString m = QUrlQuery(m_url).queryItemValue(QStringLiteral("x-mech"));
244  if (!m.isEmpty()) {
245  return QStringList(m);
246  }
247  return m_saslMethods;
248 }
249 
250 KManageSieve::AuthDetails Session::requestAuthDetails(const QUrl &url)
251 {
252  KIO::AuthInfo ai;
253  ai.url = url;
254  ai.username = url.userName();
255  ai.password = url.password();
256  ai.keepPassword = true;
257  ai.caption = i18n("Sieve Authentication Details");
258  ai.comment = i18n(
259  "Please enter your authentication details for your sieve account "
260  "(usually the same as your email password):");
261 
263  dlg->setUsername(ai.username);
264  dlg->setPassword(ai.password);
265  dlg->setKeepPassword(ai.keepPassword);
266  dlg->setPrompt(ai.prompt);
267  dlg->setUsernameReadOnly(ai.readOnly);
268  dlg->setWindowTitle(ai.caption);
269  dlg->addCommentLine(ai.commentLabel, ai.comment);
270 
271  AuthDetails ad;
272  ad.valid = false;
273  if (dlg->exec()) {
274  ad.username = dlg->password();
275  ad.password = dlg->password();
276  ad.valid = true;
277  }
278  delete dlg;
279  return ad;
280 }
281 
282 void Session::authenticationDone()
283 {
284  m_state = None;
285  m_connected = true;
286  qCDebug(KMANAGERSIEVE_LOG) << objectName() << "authentication done, ready to execute jobs";
287  QMetaObject::invokeMethod(this, &Session::executeNextJob, Qt::QueuedConnection);
288 }
289 
290 void Session::sslError(const KSslErrorUiData &data)
291 {
292  const bool ignore = KIO::SslUi::askIgnoreSslErrors(data);
293  if (ignore) {
294  sslDone();
295  } else {
296  m_thread->disconnectFromHost(true);
297  }
298 }
299 
300 void Session::sslDone()
301 {
302  qCDebug(KMANAGERSIEVE_LOG) << objectName() << "TLS negotiation done.";
303  if (requestCapabilitiesAfterStartTls()) {
304  sendData("CAPABILITY");
305  }
306  m_state = PostTlsCapabilities;
307  qCDebug(KMANAGERSIEVE_LOG) << objectName() << "TLS negotiation done, m_state=" << m_state;
308 }
309 
310 void Session::setErrorMessage(int error, const QString &msg)
311 {
312  if (m_currentJob) {
313  m_currentJob->setErrorMessage(msg);
314  } else {
315  // Don't bother the user about idle timeout
317  qCWarning(KMANAGERSIEVE_LOG) << objectName() << "No job for reporting this error message!" << msg << "host" << m_url.host() << "error" << error;
318  KMessageBox::error(nullptr, i18n("The Sieve server on %1 has reported an error:\n%2", m_url.host(), msg), i18n("Sieve Manager"));
319  }
320  }
321 }
322 
323 bool Session::allowUnencrypted() const
324 {
325  return QUrlQuery(m_url).queryItemValue(QStringLiteral("x-allow-unencrypted")) == QLatin1String("true");
326 }
QString comment
QString captured(int nth) const const
QRegularExpressionMatch match(const QString &subject, int offset, QRegularExpression::MatchType matchType, QRegularExpression::MatchOptions matchOptions) const const
QString queryItemValue(const QString &key, QUrl::ComponentFormattingOptions encoding) const const
QString userName(QUrl::ComponentFormattingOptions options) const const
QString host(QUrl::ComponentFormattingOptions options) const const
KGuiItem cancel()
bool supportsSsl()
bool disconnected() const
Definition: session.cpp:206
QString number(int n, int base)
ButtonCode warningContinueCancel(QWidget *parent, const QString &text, const QString &caption=QString(), const KGuiItem &buttonContinue=KStandardGuiItem::cont(), const KGuiItem &buttonCancel=KStandardGuiItem::cancel(), const QString &dontAskAgainName=QString(), Options options=Notify)
bool KIOWIDGETS_EXPORT askIgnoreSslErrors(const KTcpSocket *socket, RulesStorage storedRules=RecallAndStoreRules)
int toInt(bool *ok, int base) const const
QString objectName() const const
bool isEmpty() const const
void error(QWidget *parent, const QString &text, const QString &caption=QString(), Options options=Notify)
bool hasMatch() const const
void deleteLater()
QString password(QUrl::ComponentFormattingOptions options) const const
SkipEmptyParts
bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9)
QString i18n(const char *text, const TYPE &arg...)
QString password
QString username
QString fromLatin1(const char *str, int size)
A response from a managesieve server.
Definition: response.h:17
QueuedConnection
A job to manage sieve scripts.
Definition: sievejob.h:37
QString caption
QString commentLabel
QString prompt
A network session with a manage sieve server.
Definition: session.h:34
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Fri Apr 9 2021 23:09:28 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.