File indexing completed on 2024-06-23 05:15:12
0001 /* 0002 SPDX-FileCopyrightText: 2006-2007 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "transportmanager.h" 0008 #include "mailtransport_defs.h" 0009 #include "plugins/transportabstractplugin.h" 0010 #include "plugins/transportpluginmanager.h" 0011 #include "transport.h" 0012 #include "transport_p.h" 0013 #include "transportjob.h" 0014 #include "transporttype_p.h" 0015 #include "widgets/addtransportdialogng.h" 0016 #include <MailTransport/TransportAbstractPlugin> 0017 0018 #include <QApplication> 0019 #include <QDBusConnection> 0020 #include <QDBusConnectionInterface> 0021 #include <QDBusServiceWatcher> 0022 #include <QPointer> 0023 #include <QRandomGenerator> 0024 #include <QRegularExpression> 0025 #include <QStringList> 0026 0027 #include "mailtransport_debug.h" 0028 #include <KConfig> 0029 #include <KConfigGroup> 0030 #include <KLocalizedString> 0031 #include <KMessageBox> 0032 #include <qt6keychain/keychain.h> 0033 0034 using namespace QKeychain; 0035 0036 using namespace MailTransport; 0037 0038 namespace MailTransport 0039 { 0040 /** 0041 * Private class that helps to provide binary compatibility between releases. 0042 * @internal 0043 */ 0044 class TransportManagerPrivate 0045 { 0046 public: 0047 TransportManagerPrivate(TransportManager *parent) 0048 : q(parent) 0049 { 0050 } 0051 0052 ~TransportManagerPrivate() 0053 { 0054 delete config; 0055 qDeleteAll(transports); 0056 } 0057 0058 KConfig *config = nullptr; 0059 QList<Transport *> transports; 0060 TransportType::List types; 0061 bool myOwnChange = false; 0062 bool appliedChange = false; 0063 bool walletAsyncOpen = false; 0064 int defaultTransportId = -1; 0065 bool isMainInstance = false; 0066 QList<TransportJob *> walletQueue; 0067 QMap<Transport *, QMetaObject::Connection> passwordConnections; 0068 TransportManager *const q; 0069 0070 void readConfig(); 0071 void writeConfig(); 0072 void fillTypes(); 0073 [[nodiscard]] Transport::Id createId() const; 0074 void prepareWallet(); 0075 void validateDefault(); 0076 void migrateToWallet(); 0077 void updatePluginList(); 0078 0079 // Slots 0080 void slotTransportsChanged(); 0081 void dbusServiceUnregistered(); 0082 void startQueuedJobs(); 0083 void jobResult(KJob *job); 0084 }; 0085 } 0086 0087 class StaticTransportManager : public TransportManager 0088 { 0089 public: 0090 StaticTransportManager() 0091 : TransportManager() 0092 { 0093 } 0094 }; 0095 0096 StaticTransportManager *sSelf = nullptr; 0097 0098 static void destroyStaticTransportManager() 0099 { 0100 delete sSelf; 0101 } 0102 0103 TransportManager::TransportManager() 0104 : QObject() 0105 , d(new TransportManagerPrivate(this)) 0106 { 0107 qAddPostRoutine(destroyStaticTransportManager); 0108 d->config = new KConfig(QStringLiteral("mailtransports")); 0109 0110 QDBusConnection::sessionBus().registerObject(DBUS_OBJECT_PATH, this, QDBusConnection::ExportScriptableSlots | QDBusConnection::ExportScriptableSignals); 0111 0112 auto watcher = new QDBusServiceWatcher(DBUS_SERVICE_NAME, QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForUnregistration, this); 0113 connect(watcher, &QDBusServiceWatcher::serviceUnregistered, this, [this]() { 0114 d->dbusServiceUnregistered(); 0115 }); 0116 0117 QDBusConnection::sessionBus().connect(QString(), QString(), DBUS_INTERFACE_NAME, DBUS_CHANGE_SIGNAL, this, SLOT(slotTransportsChanged())); 0118 0119 d->isMainInstance = QDBusConnection::sessionBus().registerService(DBUS_SERVICE_NAME); 0120 0121 d->fillTypes(); 0122 } 0123 0124 TransportManager::~TransportManager() 0125 { 0126 qRemovePostRoutine(destroyStaticTransportManager); 0127 } 0128 0129 TransportManager *TransportManager::self() 0130 { 0131 if (!sSelf) { 0132 sSelf = new StaticTransportManager; 0133 sSelf->d->readConfig(); 0134 } 0135 return sSelf; 0136 } 0137 0138 Transport *TransportManager::transportById(Transport::Id id, bool def) const 0139 { 0140 for (Transport *t : std::as_const(d->transports)) { 0141 if (t->id() == id) { 0142 return t; 0143 } 0144 } 0145 0146 if (def || (id == 0 && d->defaultTransportId != id)) { 0147 return transportById(d->defaultTransportId, false); 0148 } 0149 return nullptr; 0150 } 0151 0152 Transport *TransportManager::transportByName(const QString &name, bool def) const 0153 { 0154 for (Transport *t : std::as_const(d->transports)) { 0155 if (t->name() == name) { 0156 return t; 0157 } 0158 } 0159 if (def) { 0160 return transportById(0, false); 0161 } 0162 return nullptr; 0163 } 0164 0165 QList<Transport *> TransportManager::transports() const 0166 { 0167 return d->transports; 0168 } 0169 0170 TransportType::List TransportManager::types() const 0171 { 0172 return d->types; 0173 } 0174 0175 Transport *TransportManager::createTransport() const 0176 { 0177 auto id = d->createId(); 0178 auto t = new Transport(QString::number(id)); 0179 t->setId(id); 0180 return t; 0181 } 0182 0183 void TransportManager::addTransport(Transport *transport) 0184 { 0185 if (d->transports.contains(transport)) { 0186 qCDebug(MAILTRANSPORT_LOG) << "Already have this transport."; 0187 return; 0188 } 0189 0190 qCDebug(MAILTRANSPORT_LOG) << "Added transport" << transport; 0191 d->transports.append(transport); 0192 d->validateDefault(); 0193 emitChangesCommitted(); 0194 } 0195 0196 void TransportManager::schedule(TransportJob *job) 0197 { 0198 connect(job, &TransportJob::result, this, [this](KJob *job) { 0199 d->jobResult(job); 0200 }); 0201 0202 // check if the job is waiting for the wallet 0203 if (!job->transport()->isComplete()) { 0204 qCDebug(MAILTRANSPORT_LOG) << "job waits for wallet:" << job; 0205 d->walletQueue << job; 0206 loadPasswordsAsync(); 0207 return; 0208 } 0209 0210 job->start(); 0211 } 0212 0213 bool TransportManager::showTransportCreationDialog(QWidget *parent, ShowCondition showCondition) 0214 { 0215 if (showCondition == IfNoTransportExists) { 0216 if (!isEmpty()) { 0217 return true; 0218 } 0219 0220 const int response = KMessageBox::warningContinueCancel(parent, 0221 i18n("You must create an outgoing account before sending."), 0222 i18n("Create Account Now?"), 0223 KGuiItem(i18n("Create Account Now")), 0224 KStandardGuiItem::cancel()); 0225 if (response != KMessageBox::Continue) { 0226 return false; 0227 } 0228 } 0229 0230 QPointer<AddTransportDialogNG> dialog = new AddTransportDialogNG(parent); 0231 const bool accepted = (dialog->exec() == QDialog::Accepted); 0232 delete dialog; 0233 return accepted; 0234 } 0235 0236 void TransportManager::initializeTransport(const QString &identifier, Transport *transport) 0237 { 0238 TransportAbstractPlugin *plugin = TransportPluginManager::self()->plugin(identifier); 0239 if (plugin) { 0240 plugin->initializeTransport(transport, identifier); 0241 } 0242 } 0243 0244 bool TransportManager::configureTransport(const QString &identifier, Transport *transport, QWidget *parent) 0245 { 0246 TransportAbstractPlugin *plugin = TransportPluginManager::self()->plugin(identifier); 0247 if (plugin) { 0248 return plugin->configureTransport(identifier, transport, parent); 0249 } 0250 return false; 0251 } 0252 0253 TransportJob *TransportManager::createTransportJob(int transportId) 0254 { 0255 Transport *t = transportById(transportId, false); 0256 if (!t) { 0257 return nullptr; 0258 } 0259 t = t->clone(); // Jobs delete their transports. 0260 t->updatePasswordState(); 0261 TransportAbstractPlugin *plugin = TransportPluginManager::self()->plugin(t->identifier()); 0262 if (plugin) { 0263 return plugin->createTransportJob(t, t->identifier()); 0264 } 0265 Q_ASSERT(false); 0266 return nullptr; 0267 } 0268 0269 TransportJob *TransportManager::createTransportJob(const QString &transport) 0270 { 0271 bool ok = false; 0272 Transport *t = nullptr; 0273 0274 int transportId = transport.toInt(&ok); 0275 if (ok) { 0276 t = transportById(transportId); 0277 } 0278 0279 if (!t) { 0280 t = transportByName(transport, false); 0281 } 0282 0283 if (t) { 0284 return createTransportJob(t->id()); 0285 } 0286 0287 return nullptr; 0288 } 0289 0290 bool TransportManager::isEmpty() const 0291 { 0292 return d->transports.isEmpty(); 0293 } 0294 0295 QList<int> TransportManager::transportIds() const 0296 { 0297 QList<int> rv; 0298 rv.reserve(d->transports.count()); 0299 for (Transport *t : std::as_const(d->transports)) { 0300 rv << t->id(); 0301 } 0302 return rv; 0303 } 0304 0305 QStringList TransportManager::transportNames() const 0306 { 0307 QStringList rv; 0308 rv.reserve(d->transports.count()); 0309 for (Transport *t : std::as_const(d->transports)) { 0310 rv << t->name(); 0311 } 0312 return rv; 0313 } 0314 0315 QString TransportManager::defaultTransportName() const 0316 { 0317 Transport *t = transportById(d->defaultTransportId, false); 0318 if (t) { 0319 return t->name(); 0320 } 0321 return {}; 0322 } 0323 0324 int TransportManager::defaultTransportId() const 0325 { 0326 return d->defaultTransportId; 0327 } 0328 0329 void TransportManager::setDefaultTransport(Transport::Id id) 0330 { 0331 if (id == d->defaultTransportId || !transportById(id, false)) { 0332 return; 0333 } 0334 d->defaultTransportId = id; 0335 d->writeConfig(); 0336 } 0337 0338 void TransportManager::removePasswordFromWallet(Transport::Id id) 0339 { 0340 auto deleteJob = new DeletePasswordJob(WALLET_FOLDER); 0341 deleteJob->setKey(QString::number(id)); 0342 deleteJob->start(); 0343 } 0344 0345 void TransportManager::removeTransport(Transport::Id id) 0346 { 0347 Transport *t = transportById(id, false); 0348 if (!t) { 0349 return; 0350 } 0351 auto plugin = MailTransport::TransportPluginManager::self()->plugin(t->identifier()); 0352 if (plugin) { 0353 plugin->cleanUp(t); 0354 } 0355 Q_EMIT transportRemoved(t->id(), t->name()); 0356 0357 d->transports.removeAll(t); 0358 d->validateDefault(); 0359 const QString group = t->currentGroup(); 0360 if (t->storePassword()) { 0361 auto deleteJob = new DeletePasswordJob(WALLET_FOLDER); 0362 deleteJob->setKey(QString::number(t->id())); 0363 deleteJob->start(); 0364 } 0365 delete t; 0366 d->config->deleteGroup(group); 0367 d->writeConfig(); 0368 } 0369 0370 void TransportManagerPrivate::readConfig() 0371 { 0372 QList<Transport *> oldTransports = transports; 0373 transports.clear(); 0374 0375 static QRegularExpression re(QStringLiteral("^Transport (.+)$")); 0376 const QStringList groups = config->groupList().filter(re); 0377 for (const QString &s : groups) { 0378 const QRegularExpressionMatch match = re.match(s); 0379 if (!match.hasMatch()) { 0380 continue; 0381 } 0382 Transport *t = nullptr; 0383 // see if we happen to have that one already 0384 const QString capturedString = match.captured(1); 0385 const QString checkString = QLatin1StringView("Transport ") + capturedString; 0386 for (Transport *old : oldTransports) { 0387 if (old->currentGroup() == checkString) { 0388 qCDebug(MAILTRANSPORT_LOG) << "reloading existing transport:" << s; 0389 t = old; 0390 t->load(); 0391 oldTransports.removeAll(old); 0392 break; 0393 } 0394 } 0395 0396 if (!t) { 0397 t = new Transport(capturedString); 0398 } 0399 if (t->id() <= 0) { 0400 t->setId(createId()); 0401 t->save(); 0402 } 0403 transports.append(t); 0404 } 0405 0406 qDeleteAll(oldTransports); 0407 oldTransports.clear(); 0408 // read default transport 0409 KConfigGroup group(config, QStringLiteral("General")); 0410 defaultTransportId = group.readEntry("default-transport", 0); 0411 if (defaultTransportId == 0) { 0412 // migrated default transport contains the name instead 0413 QString name = group.readEntry("default-transport", QString()); 0414 if (!name.isEmpty()) { 0415 Transport *t = q->transportByName(name, false); 0416 if (t) { 0417 defaultTransportId = t->id(); 0418 writeConfig(); 0419 } 0420 } 0421 } 0422 validateDefault(); 0423 migrateToWallet(); 0424 q->loadPasswordsAsync(); 0425 } 0426 0427 void TransportManagerPrivate::writeConfig() 0428 { 0429 KConfigGroup group(config, QStringLiteral("General")); 0430 group.writeEntry("default-transport", defaultTransportId); 0431 config->sync(); 0432 q->emitChangesCommitted(); 0433 } 0434 0435 void TransportManagerPrivate::fillTypes() 0436 { 0437 Q_ASSERT(types.isEmpty()); 0438 0439 updatePluginList(); 0440 QObject::connect(MailTransport::TransportPluginManager::self(), &TransportPluginManager::updatePluginList, q, &TransportManager::updatePluginList); 0441 } 0442 0443 void TransportManagerPrivate::updatePluginList() 0444 { 0445 types.clear(); 0446 const QList<MailTransport::TransportAbstractPlugin *> lstPlugins = MailTransport::TransportPluginManager::self()->pluginsList(); 0447 for (MailTransport::TransportAbstractPlugin *plugin : lstPlugins) { 0448 if (plugin->names().isEmpty()) { 0449 qCDebug(MAILTRANSPORT_LOG) << "Plugin " << plugin << " doesn't provide plugin"; 0450 } 0451 const QList<TransportAbstractPluginInfo> lstInfos = plugin->names(); 0452 for (const MailTransport::TransportAbstractPluginInfo &info : lstInfos) { 0453 TransportType type; 0454 type.d->mName = info.name; 0455 type.d->mDescription = info.description; 0456 type.d->mIdentifier = info.identifier; 0457 type.d->mIsAkonadiResource = info.isAkonadi; 0458 types << type; 0459 } 0460 } 0461 } 0462 0463 void TransportManager::updatePluginList() 0464 { 0465 d->updatePluginList(); 0466 } 0467 0468 void TransportManager::emitChangesCommitted() 0469 { 0470 d->myOwnChange = true; // prevent us from reading our changes again 0471 d->appliedChange = false; // but we have to read them at least once 0472 Q_EMIT transportsChanged(); 0473 Q_EMIT changesCommitted(); 0474 } 0475 0476 void TransportManagerPrivate::slotTransportsChanged() 0477 { 0478 if (myOwnChange && appliedChange) { 0479 myOwnChange = false; 0480 appliedChange = false; 0481 return; 0482 } 0483 0484 qCDebug(MAILTRANSPORT_LOG); 0485 config->reparseConfiguration(); 0486 // FIXME: this deletes existing transport objects! 0487 readConfig(); 0488 appliedChange = true; // to prevent recursion 0489 Q_EMIT q->transportsChanged(); 0490 } 0491 0492 Transport::Id TransportManagerPrivate::createId() const 0493 { 0494 QList<int> usedIds; 0495 usedIds.reserve(transports.size()); 0496 for (Transport *t : std::as_const(transports)) { 0497 usedIds << t->id(); 0498 } 0499 int newId; 0500 do { 0501 // 0 is default for unknown, so use 1 as lower value accepted 0502 newId = QRandomGenerator::global()->bounded(1, RAND_MAX); 0503 } while (usedIds.contains(newId)); 0504 return newId; 0505 } 0506 0507 void TransportManager::loadPasswords() 0508 { 0509 QEventLoop loop; 0510 for (Transport *t : std::as_const(d->transports)) { 0511 if (d->passwordConnections.contains(t)) { 0512 continue; 0513 } 0514 auto conn = connect(t, &Transport::passwordLoaded, this, [&]() { 0515 disconnect(d->passwordConnections[t]); 0516 d->passwordConnections.remove(t); 0517 if (d->passwordConnections.count() == 0) { 0518 loop.exit(); 0519 } 0520 }); 0521 d->passwordConnections[t] = conn; 0522 t->readPassword(); 0523 } 0524 loop.exec(); 0525 0526 d->startQueuedJobs(); 0527 Q_EMIT passwordsChanged(); 0528 } 0529 0530 void TransportManager::loadPasswordsAsync() 0531 { 0532 for (Transport *t : std::as_const(d->transports)) { 0533 if (!t->isComplete()) { 0534 if (d->passwordConnections.contains(t)) { 0535 continue; 0536 } 0537 auto conn = connect(t, &Transport::passwordLoaded, this, [&]() { 0538 disconnect(d->passwordConnections[t]); 0539 d->passwordConnections.remove(t); 0540 if (d->passwordConnections.count() == 0) { 0541 d->startQueuedJobs(); 0542 Q_EMIT passwordsChanged(); 0543 } 0544 }); 0545 d->passwordConnections[t] = conn; 0546 t->readPassword(); 0547 } 0548 } 0549 } 0550 0551 void TransportManagerPrivate::startQueuedJobs() 0552 { 0553 QList<TransportJob *> jobsToDel; 0554 for (auto job : walletQueue) { 0555 if (job->transport()->isComplete()) { 0556 job->start(); 0557 jobsToDel << job; 0558 } 0559 } 0560 0561 for (auto job : jobsToDel) { 0562 walletQueue.removeAll(job); 0563 } 0564 } 0565 0566 void TransportManagerPrivate::validateDefault() 0567 { 0568 if (!q->transportById(defaultTransportId, false)) { 0569 if (q->isEmpty()) { 0570 defaultTransportId = -1; 0571 } else { 0572 defaultTransportId = transports.constFirst()->id(); 0573 writeConfig(); 0574 } 0575 } 0576 } 0577 0578 void TransportManagerPrivate::migrateToWallet() 0579 { 0580 // check if we tried this already 0581 static bool firstRun = true; 0582 if (!firstRun) { 0583 return; 0584 } 0585 firstRun = false; 0586 0587 // check if we are the main instance 0588 if (!isMainInstance) { 0589 return; 0590 } 0591 0592 // check if migration is needed 0593 QStringList names; 0594 for (Transport *t : std::as_const(transports)) { 0595 if (t->needsWalletMigration()) { 0596 names << t->name(); 0597 } 0598 } 0599 if (names.isEmpty()) { 0600 return; 0601 } 0602 0603 // ask user if he wants to migrate 0604 int result = KMessageBox::questionTwoActionsList(nullptr, 0605 i18n("The following mail transports store their passwords in an " 0606 "unencrypted configuration file.\nFor security reasons, " 0607 "please consider migrating these passwords to KWallet, the " 0608 "KDE Wallet management tool,\nwhich stores sensitive data " 0609 "for you in a strongly encrypted file.\n" 0610 "Do you want to migrate your passwords to KWallet?"), 0611 names, 0612 i18n("Question"), 0613 KGuiItem(i18n("Migrate")), 0614 KGuiItem(i18n("Keep")), 0615 QStringLiteral("WalletMigrate")); 0616 if (result != KMessageBox::ButtonCode::PrimaryAction) { 0617 return; 0618 } 0619 0620 // perform migration 0621 for (Transport *t : std::as_const(transports)) { 0622 if (t->needsWalletMigration()) { 0623 t->migrateToWallet(); 0624 } 0625 } 0626 } 0627 0628 void TransportManagerPrivate::dbusServiceUnregistered() 0629 { 0630 QDBusConnection::sessionBus().registerService(DBUS_SERVICE_NAME); 0631 } 0632 0633 void TransportManagerPrivate::jobResult(KJob *job) 0634 { 0635 walletQueue.removeAll(static_cast<TransportJob *>(job)); 0636 } 0637 0638 #include "moc_transportmanager.cpp"