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"