MailTransport

transportmanager.cpp
1 /*
2  SPDX-FileCopyrightText: 2006-2007 Volker Krause <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "transportmanager.h"
8 #include "mailtransport_defs.h"
9 #include "plugins/transportabstractplugin.h"
10 #include "plugins/transportpluginmanager.h"
11 #include "transport.h"
12 #include "transport_p.h"
13 #include "transportjob.h"
14 #include "transporttype_p.h"
15 #include "widgets/addtransportdialogng.h"
16 #include <MailTransport/TransportAbstractPlugin>
17 
18 #include <QApplication>
19 #include <QDBusConnection>
20 #include <QDBusConnectionInterface>
21 #include <QDBusServiceWatcher>
22 #include <QPointer>
23 #include <QRandomGenerator>
24 #include <QRegularExpression>
25 #include <QStringList>
26 
27 #include "mailtransport_debug.h"
28 #include <KConfig>
29 #include <KConfigGroup>
30 #include <KEMailSettings>
31 #include <KLocalizedString>
32 #include <KMessageBox>
33 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
34 #include <Kdelibs4ConfigMigrator>
35 #include <qt5keychain/keychain.h>
36 #else
37 #include <qt6keychain/keychain.h>
38 
39 #endif
40 
41 using namespace QKeychain;
42 
43 using namespace MailTransport;
44 
45 namespace MailTransport
46 {
47 /**
48  * Private class that helps to provide binary compatibility between releases.
49  * @internal
50  */
51 class TransportManagerPrivate
52 {
53 public:
54  TransportManagerPrivate(TransportManager *parent)
55  : q(parent)
56  {
57  }
58 
59  ~TransportManagerPrivate()
60  {
61  delete config;
62  qDeleteAll(transports);
63  }
64 
65  KConfig *config = nullptr;
66  QList<Transport *> transports;
68  bool myOwnChange = false;
69  bool appliedChange = false;
70  bool walletAsyncOpen = false;
71  int defaultTransportId = -1;
72  bool isMainInstance = false;
73  QList<TransportJob *> walletQueue;
75  TransportManager *const q;
76 
77  void readConfig();
78  void writeConfig();
79  void fillTypes();
80  Q_REQUIRED_RESULT int createId() const;
81  void prepareWallet();
82  void validateDefault();
83  void migrateToWallet();
84  void updatePluginList();
85 
86  // Slots
87  void slotTransportsChanged();
88  void dbusServiceUnregistered();
89  void startQueuedJobs();
90  void jobResult(KJob *job);
91 };
92 }
93 
94 class StaticTransportManager : public TransportManager
95 {
96 public:
97  StaticTransportManager()
99  {
100  }
101 };
102 
103 StaticTransportManager *sSelf = nullptr;
104 
105 static void destroyStaticTransportManager()
106 {
107  delete sSelf;
108 }
109 
110 TransportManager::TransportManager()
111  : QObject()
112  , d(new TransportManagerPrivate(this))
113 {
114 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
115  Kdelibs4ConfigMigrator migrate(QStringLiteral("transportmanager"));
116  migrate.setConfigFiles(QStringList() << QStringLiteral("mailtransports"));
117  migrate.migrate();
118 #endif
119  qAddPostRoutine(destroyStaticTransportManager);
120  d->config = new KConfig(QStringLiteral("mailtransports"));
121 
123 
125  connect(watcher, &QDBusServiceWatcher::serviceUnregistered, this, [this]() {
126  d->dbusServiceUnregistered();
127  });
128 
129  QDBusConnection::sessionBus().connect(QString(), QString(), DBUS_INTERFACE_NAME, DBUS_CHANGE_SIGNAL, this, SLOT(slotTransportsChanged()));
130 
131  d->isMainInstance = QDBusConnection::sessionBus().registerService(DBUS_SERVICE_NAME);
132 
133  d->fillTypes();
134 }
135 
137 {
138  qRemovePostRoutine(destroyStaticTransportManager);
139 }
140 
142 {
143  if (!sSelf) {
144  sSelf = new StaticTransportManager;
145  sSelf->d->readConfig();
146  }
147  return sSelf;
148 }
149 
151 {
152  for (Transport *t : std::as_const(d->transports)) {
153  if (t->id() == id) {
154  return t;
155  }
156  }
157 
158  if (def || (id == 0 && d->defaultTransportId != id)) {
159  return transportById(d->defaultTransportId, false);
160  }
161  return nullptr;
162 }
163 
165 {
166  for (Transport *t : std::as_const(d->transports)) {
167  if (t->name() == name) {
168  return t;
169  }
170  }
171  if (def) {
172  return transportById(0, false);
173  }
174  return nullptr;
175 }
176 
178 {
179  return d->transports;
180 }
181 
183 {
184  return d->types;
185 }
186 
188 {
189  int id = d->createId();
190  auto t = new Transport(QString::number(id));
191  t->setId(id);
192  return t;
193 }
194 
196 {
197  if (d->transports.contains(transport)) {
198  qCDebug(MAILTRANSPORT_LOG) << "Already have this transport.";
199  return;
200  }
201 
202  qCDebug(MAILTRANSPORT_LOG) << "Added transport" << transport;
203  d->transports.append(transport);
204  d->validateDefault();
205  emitChangesCommitted();
206 }
207 
209 {
210  connect(job, &TransportJob::result, this, [this](KJob *job) {
211  d->jobResult(job);
212  });
213 
214  // check if the job is waiting for the wallet
215  if (!job->transport()->isComplete()) {
216  qCDebug(MAILTRANSPORT_LOG) << "job waits for wallet:" << job;
217  d->walletQueue << job;
219  return;
220  }
221 
222  job->start();
223 }
224 
226 {
227  KEMailSettings kes;
228  Transport *t = createTransport();
229  t->setName(i18n("Default Transport"));
230  t->setHost(kes.getSetting(KEMailSettings::OutServer));
231  if (t->isValid()) {
232  t->save();
233  addTransport(t);
234  } else {
235  qCWarning(MAILTRANSPORT_LOG) << "KEMailSettings does not contain a valid transport.";
236  }
237 }
238 
240 {
241  if (showCondition == IfNoTransportExists) {
242  if (!isEmpty()) {
243  return true;
244  }
245 
246  const int response = KMessageBox::warningContinueCancel(parent,
247  i18n("You must create an outgoing account before sending."),
248  i18n("Create Account Now?"),
249  KGuiItem(i18n("Create Account Now")),
251  if (response != KMessageBox::Continue) {
252  return false;
253  }
254  }
255 
257  const bool accepted = (dialog->exec() == QDialog::Accepted);
258  delete dialog;
259  return accepted;
260 }
261 
262 void TransportManager::initializeTransport(const QString &identifier, Transport *transport)
263 {
264  TransportAbstractPlugin *plugin = TransportPluginManager::self()->plugin(identifier);
265  if (plugin) {
266  plugin->initializeTransport(transport, identifier);
267  }
268 }
269 
270 bool TransportManager::configureTransport(const QString &identifier, Transport *transport, QWidget *parent)
271 {
272  TransportAbstractPlugin *plugin = TransportPluginManager::self()->plugin(identifier);
273  if (plugin) {
274  return plugin->configureTransport(identifier, transport, parent);
275  }
276  return false;
277 }
278 
280 {
281  Transport *t = transportById(transportId, false);
282  if (!t) {
283  return nullptr;
284  }
285  t = t->clone(); // Jobs delete their transports.
286  t->updatePasswordState();
287  TransportAbstractPlugin *plugin = TransportPluginManager::self()->plugin(t->identifier());
288  if (plugin) {
289  return plugin->createTransportJob(t, t->identifier());
290  }
291  Q_ASSERT(false);
292  return nullptr;
293 }
294 
296 {
297  bool ok = false;
298  Transport *t = nullptr;
299 
300  int transportId = transport.toInt(&ok);
301  if (ok) {
302  t = transportById(transportId);
303  }
304 
305  if (!t) {
306  t = transportByName(transport, false);
307  }
308 
309  if (t) {
310  return createTransportJob(t->id());
311  }
312 
313  return nullptr;
314 }
315 
317 {
318  return d->transports.isEmpty();
319 }
320 
322 {
323  QVector<int> rv;
324  rv.reserve(d->transports.count());
325  for (Transport *t : std::as_const(d->transports)) {
326  rv << t->id();
327  }
328  return rv;
329 }
330 
332 {
333  QStringList rv;
334  rv.reserve(d->transports.count());
335  for (Transport *t : std::as_const(d->transports)) {
336  rv << t->name();
337  }
338  return rv;
339 }
340 
342 {
343  Transport *t = transportById(d->defaultTransportId, false);
344  if (t) {
345  return t->name();
346  }
347  return {};
348 }
349 
351 {
352  return d->defaultTransportId;
353 }
354 
356 {
357  if (id == d->defaultTransportId || !transportById(id, false)) {
358  return;
359  }
360  d->defaultTransportId = id;
361  d->writeConfig();
362 }
363 
364 void TransportManager::removePasswordFromWallet(int id)
365 {
366  auto deleteJob = new DeletePasswordJob(WALLET_FOLDER);
367  deleteJob->setKey(QString::number(id));
368  deleteJob->start();
369 }
370 
372 {
373  Transport *t = transportById(id, false);
374  if (!t) {
375  return;
376  }
377  auto plugin = MailTransport::TransportPluginManager::self()->plugin(t->identifier());
378  if (plugin) {
379  plugin->cleanUp(t);
380  }
381  Q_EMIT transportRemoved(t->id(), t->name());
382 
383  d->transports.removeAll(t);
384  d->validateDefault();
385  const QString group = t->currentGroup();
386  if (t->storePassword()) {
387  auto deleteJob = new DeletePasswordJob(WALLET_FOLDER);
388  deleteJob->setKey(QString::number(t->id()));
389  deleteJob->start();
390  }
391  delete t;
392  d->config->deleteGroup(group);
393  d->writeConfig();
394 }
395 
396 void TransportManagerPrivate::readConfig()
397 {
398  QList<Transport *> oldTransports = transports;
399  transports.clear();
400 
401  static QRegularExpression re(QStringLiteral("^Transport (.+)$"));
402  const QStringList groups = config->groupList().filter(re);
403  for (const QString &s : groups) {
404  const QRegularExpressionMatch match = re.match(s);
405  if (!match.hasMatch()) {
406  continue;
407  }
408  Transport *t = nullptr;
409  // see if we happen to have that one already
410  const QString capturedString = match.captured(1);
411  const QString checkString = QLatin1String("Transport ") + capturedString;
412  for (Transport *old : oldTransports) {
413  if (old->currentGroup() == checkString) {
414  qCDebug(MAILTRANSPORT_LOG) << "reloading existing transport:" << s;
415  t = old;
416  t->load();
417  oldTransports.removeAll(old);
418  break;
419  }
420  }
421 
422  if (!t) {
423  t = new Transport(capturedString);
424  }
425  if (t->id() <= 0) {
426  t->setId(createId());
427  t->save();
428  }
429  transports.append(t);
430  }
431 
432  qDeleteAll(oldTransports);
433  oldTransports.clear();
434  // read default transport
435  KConfigGroup group(config, "General");
436  defaultTransportId = group.readEntry("default-transport", 0);
437  if (defaultTransportId == 0) {
438  // migrated default transport contains the name instead
439  QString name = group.readEntry("default-transport", QString());
440  if (!name.isEmpty()) {
441  Transport *t = q->transportByName(name, false);
442  if (t) {
443  defaultTransportId = t->id();
444  writeConfig();
445  }
446  }
447  }
448  validateDefault();
449  migrateToWallet();
450  q->loadPasswordsAsync();
451 }
452 
453 void TransportManagerPrivate::writeConfig()
454 {
455  KConfigGroup group(config, "General");
456  group.writeEntry("default-transport", defaultTransportId);
457  config->sync();
458  q->emitChangesCommitted();
459 }
460 
461 void TransportManagerPrivate::fillTypes()
462 {
463  Q_ASSERT(types.isEmpty());
464 
465  updatePluginList();
466  QObject::connect(MailTransport::TransportPluginManager::self(), &TransportPluginManager::updatePluginList, q, &TransportManager::updatePluginList);
467 }
468 
469 void TransportManagerPrivate::updatePluginList()
470 {
471  types.clear();
472  const QVector<MailTransport::TransportAbstractPlugin *> lstPlugins = MailTransport::TransportPluginManager::self()->pluginsList();
473  for (MailTransport::TransportAbstractPlugin *plugin : lstPlugins) {
474  if (plugin->names().isEmpty()) {
475  qCDebug(MAILTRANSPORT_LOG) << "Plugin " << plugin << " doesn't provide plugin";
476  }
477  const QVector<TransportAbstractPluginInfo> lstInfos = plugin->names();
478  for (const MailTransport::TransportAbstractPluginInfo &info : lstInfos) {
480  type.d->mName = info.name;
481  type.d->mDescription = info.description;
482  type.d->mIdentifier = info.identifier;
483  type.d->mIsAkonadiResource = info.isAkonadi;
484  types << type;
485  }
486  }
487 }
488 
489 void TransportManager::updatePluginList()
490 {
491  d->updatePluginList();
492 }
493 
494 void TransportManager::emitChangesCommitted()
495 {
496  d->myOwnChange = true; // prevent us from reading our changes again
497  d->appliedChange = false; // but we have to read them at least once
500 }
501 
502 void TransportManagerPrivate::slotTransportsChanged()
503 {
504  if (myOwnChange && appliedChange) {
505  myOwnChange = false;
506  appliedChange = false;
507  return;
508  }
509 
510  qCDebug(MAILTRANSPORT_LOG);
511  config->reparseConfiguration();
512  // FIXME: this deletes existing transport objects!
513  readConfig();
514  appliedChange = true; // to prevent recursion
515  Q_EMIT q->transportsChanged();
516 }
517 
518 int TransportManagerPrivate::createId() const
519 {
520  QVector<int> usedIds;
521  usedIds.reserve(transports.size());
522  for (Transport *t : std::as_const(transports)) {
523  usedIds << t->id();
524  }
525  int newId;
526  do {
527  // 0 is default for unknown, so use 1 as lower value accepted
528  newId = QRandomGenerator::global()->bounded(1, RAND_MAX);
529  } while (usedIds.contains(newId));
530  return newId;
531 }
532 
534 {
535  QEventLoop loop;
536  for (Transport *t : std::as_const(d->transports)) {
537  if (d->passwordConnections.contains(t)) {
538  continue;
539  }
540  auto conn = connect(t, &Transport::passwordLoaded, this, [&]() {
541  disconnect(d->passwordConnections[t]);
542  d->passwordConnections.remove(t);
543  if (d->passwordConnections.count() == 0) {
544  loop.exit();
545  }
546  });
547  d->passwordConnections[t] = conn;
548  t->readPassword();
549  }
550  loop.exec();
551 
552  d->startQueuedJobs();
554 }
555 
557 {
558  for (Transport *t : std::as_const(d->transports)) {
559  if (!t->isComplete()) {
560  if (d->passwordConnections.contains(t)) {
561  continue;
562  }
563  auto conn = connect(t, &Transport::passwordLoaded, this, [&]() {
564  disconnect(d->passwordConnections[t]);
565  d->passwordConnections.remove(t);
566  if (d->passwordConnections.count() == 0) {
567  d->startQueuedJobs();
569  }
570  });
571  d->passwordConnections[t] = conn;
572  t->readPassword();
573  }
574  }
575 }
576 
577 void TransportManagerPrivate::startQueuedJobs()
578 {
579  QList<TransportJob *> jobsToDel;
580  for (auto job : walletQueue) {
581  if (job->transport()->isComplete()) {
582  job->start();
583  jobsToDel << job;
584  }
585  }
586 
587  for (auto job : jobsToDel) {
588  walletQueue.removeAll(job);
589  }
590 }
591 
592 void TransportManagerPrivate::validateDefault()
593 {
594  if (!q->transportById(defaultTransportId, false)) {
595  if (q->isEmpty()) {
596  defaultTransportId = -1;
597  } else {
598  defaultTransportId = transports.constFirst()->id();
599  writeConfig();
600  }
601  }
602 }
603 
604 void TransportManagerPrivate::migrateToWallet()
605 {
606  // check if we tried this already
607  static bool firstRun = true;
608  if (!firstRun) {
609  return;
610  }
611  firstRun = false;
612 
613  // check if we are the main instance
614  if (!isMainInstance) {
615  return;
616  }
617 
618  // check if migration is needed
619  QStringList names;
620  for (Transport *t : std::as_const(transports)) {
621  if (t->needsWalletMigration()) {
622  names << t->name();
623  }
624  }
625  if (names.isEmpty()) {
626  return;
627  }
628 
629  // ask user if he wants to migrate
630  int result = KMessageBox::questionTwoActionsList(nullptr,
631  i18n("The following mail transports store their passwords in an "
632  "unencrypted configuration file.\nFor security reasons, "
633  "please consider migrating these passwords to KWallet, the "
634  "KDE Wallet management tool,\nwhich stores sensitive data "
635  "for you in a strongly encrypted file.\n"
636  "Do you want to migrate your passwords to KWallet?"),
637  names,
638  i18n("Question"),
639  KGuiItem(i18n("Migrate")),
640  KGuiItem(i18n("Keep")),
641  QStringLiteral("WalletMigrate"));
642  if (result != KMessageBox::ButtonCode::PrimaryAction) {
643  return;
644  }
645 
646  // perform migration
647  for (Transport *t : std::as_const(transports)) {
648  if (t->needsWalletMigration()) {
649  t->migrateToWallet();
650  }
651  }
652 }
653 
654 void TransportManagerPrivate::dbusServiceUnregistered()
655 {
656  QDBusConnection::sessionBus().registerService(DBUS_SERVICE_NAME);
657 }
658 
659 void TransportManagerPrivate::jobResult(KJob *job)
660 {
661  walletQueue.removeAll(static_cast<TransportJob *>(job));
662 }
663 
664 #include "moc_transportmanager.cpp"
double bounded(double highest)
Transport * createTransport() const
Creates a new, empty Transport object.
bool connect(const QString &service, const QString &path, const QString &interface, const QString &name, QObject *receiver, const char *slot)
Central transport management interface.
Transport * transportByName(const QString &name, bool def=true) const
Returns the transport object with the given name.
Q_SCRIPTABLE QString defaultTransportName() const
Returns the default transport name.
Abstract base class for all mail transport jobs.
Definition: transportjob.h:30
Q_SCRIPTABLE int defaultTransportId() const
Returns the default transport identifier.
Transport * transportById(int id, bool def=true) const
Returns the Transport object with the given id.
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
QString number(int n, int base)
Transport * clone() const
Returns a deep copy of this Transport object which will no longer be automatically updated.
Definition: transport.cpp:302
virtual Q_SCRIPTABLE void start()=0
ButtonCode warningContinueCancel(QWidget *parent, const QString &text, const QString &title=QString(), const KGuiItem &buttonContinue=KStandardGuiItem::cont(), const KGuiItem &buttonCancel=KStandardGuiItem::cancel(), const QString &dontAskAgainName=QString(), Options options=Notify)
Q_EMITQ_EMIT
void result(KJob *job)
int removeAll(const T &value)
Type type(const QSqlDatabase &db)
void addTransport(Transport *transport)
Adds the given transport.
QList< Transport * > transports() const
Returns a list of all available transports.
void readConfig()
Q_SCRIPTABLE void setDefaultTransport(int id)
Sets the default transport.
void passwordsChanged()
Emitted when passwords have been loaded from the wallet.
~TransportManager() override
Destructor.
QString getSetting(KEMailSettings::Setting s) const
bool registerObject(const QString &path, QObject *object, QDBusConnection::RegisterOptions options)
int exec(QEventLoop::ProcessEventsFlags flags)
Q_SCRIPTABLE void transportsChanged()
Emitted when transport settings have changed (by this or any other TransportManager instance).
void serviceUnregistered(const QString &serviceName)
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void exit(int returnCode)
bool isComplete() const
Returns true if all settings have been loaded.
Definition: transport.cpp:110
Q_SCRIPTABLE QVector< int > transportIds() const
Returns a list of transport identifiers.
ButtonCode questionTwoActionsList(QWidget *parent, const QString &text, const QStringList &strlist, const QString &title, const KGuiItem &primaryAction, const KGuiItem &secondaryAction, const QString &dontAskAgainName=QString(), Options options=Notify)
QStringList types(Mode mode=Writing)
MAILTRANSPORT_DEPRECATED TransportJob * createTransportJob(int transportId)
Creates a mail transport job for the given transport identifier.
void reserve(int alloc)
KGuiItem cancel()
Q_SCRIPTABLE QStringList transportNames() const
Returns a list of transport names.
QString i18n(const char *text, const TYPE &arg...)
The TransportAbstractPluginInfo struct.
QDBusConnection sessionBus()
bool registerService(const QString &serviceName)
A representation of a transport type.
Definition: transporttype.h:30
ShowCondition
Describes when to show the transport creation dialog.
The TransportAbstractPlugin class.
Q_SCRIPTABLE void changesCommitted()
Internal signal to synchronize all TransportManager instances.
static TransportManager * self()
Returns the TransportManager instance.
bool contains(const T &value) const const
int toInt(bool *ok, int base) const const
bool isEmpty() const const
KSharedConfigPtr config()
void createDefaultTransport()
Tries to create a transport based on KEMailSettings.
void reserve(int size)
void loadPasswords()
Loads all passwords synchronously.
Represents the settings of a specific mail transport.
Definition: transport.h:32
void migrateToWallet()
Try to migrate the password from the config file to the wallet.
Definition: transport.cpp:291
MAILTRANSPORT_DEPRECATED void schedule(TransportJob *job)
Executes the given transport job.
void passwordLoaded()
Emitted when passwords have been loaded from QKeyChain.
bool showTransportCreationDialog(QWidget *parent, ShowCondition showCondition=Always)
Shows a dialog for creating and configuring a new transport.
const char * name(StandardAction id)
bool needsWalletMigration() const
Returns true if the password was not stored in the wallet.
Definition: transport.cpp:286
void loadPasswordsAsync()
Tries to load passwords asynchronously from KWallet if needed.
void clear()
void updatePasswordState()
This function synchronizes the password of this transport with the password of the transport with the...
Definition: transport.cpp:93
bool configureTransport(const QString &identifier, Transport *transport, QWidget *parent)
Open a configuration dialog for an existing transport.
Q_INVOKABLE bool isValid() const
Returns true if this transport is valid, ie.
Definition: transport.cpp:43
@ IfNoTransportExists
Only show the transport creation dialog if no transport currently.
Q_SCRIPTABLE bool isEmpty() const
Returns true if there are no mail transports at all.
QObject * parent() const const
TransportType::List types() const
Returns a list of all available transport types.
void transportRemoved(int id, const QString &name)
Emitted when a transport is deleted.
Q_SCRIPTABLE void removeTransport(int id)
Deletes the specified transport.
QRandomGenerator * global()
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Thu Mar 23 2023 04:19:12 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.