Libksieve

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

KDE's Doxygen guidelines are available online.