File indexing completed on 2024-06-23 05:15:11

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 "transport.h"
0008 #include "mailtransport_defs.h"
0009 #include "transport_p.h"
0010 #include "transportmanager.h"
0011 #include "transporttype_p.h"
0012 
0013 #include "mailtransport_debug.h"
0014 #include <KConfigGroup>
0015 #include <KLocalizedString>
0016 #include <KMessageBox>
0017 #include <KStringHandler>
0018 #if USE_LEGACY_WALLET_CODE
0019 #include <KWallet>
0020 #endif
0021 
0022 #include <qt6keychain/keychain.h>
0023 using namespace QKeychain;
0024 using namespace MailTransport;
0025 
0026 Transport::Transport(const QString &cfgGroup)
0027     : TransportBase(cfgGroup)
0028     , d(new TransportPrivate)
0029 {
0030     qCDebug(MAILTRANSPORT_LOG) << cfgGroup;
0031     d->passwordLoaded = false;
0032     d->passwordDirty = false;
0033     d->storePasswordInFile = false;
0034     d->needsWalletMigration = false;
0035     load();
0036     loadPassword();
0037 }
0038 
0039 Transport::~Transport() = default;
0040 
0041 bool Transport::isValid() const
0042 {
0043     return (id() > 0) && !host().isEmpty() && port() <= 65536;
0044 }
0045 
0046 void Transport::loadPassword()
0047 {
0048     if (!d->passwordLoaded && requiresAuthentication() && storePassword() && d->password.isEmpty()) {
0049         readPassword();
0050     }
0051 }
0052 
0053 QString Transport::password() const
0054 {
0055     return d->password;
0056 }
0057 
0058 void Transport::setPassword(const QString &passwd)
0059 {
0060     d->passwordLoaded = true;
0061     if (d->password == passwd) {
0062         return;
0063     }
0064     d->passwordDirty = true;
0065     d->password = passwd;
0066     Q_EMIT passwordChanged();
0067 }
0068 
0069 void Transport::forceUniqueName()
0070 {
0071     QStringList existingNames;
0072     const auto lstTransports = TransportManager::self()->transports();
0073     for (Transport *t : lstTransports) {
0074         if (t->id() != id()) {
0075             existingNames << t->name();
0076         }
0077     }
0078     int suffix = 1;
0079     QString origName = name();
0080     while (existingNames.contains(name())) {
0081         setName(
0082             i18nc("%1: name; %2: number appended to it to make "
0083                   "it unique among a list of names",
0084                   "%1 #%2",
0085                   origName,
0086                   suffix));
0087         ++suffix;
0088     }
0089 }
0090 
0091 void Transport::updatePasswordState()
0092 {
0093     Transport *original = TransportManager::self()->transportById(id(), false);
0094     if (original == this) {
0095         qCWarning(MAILTRANSPORT_LOG) << "Tried to update password state of non-cloned transport.";
0096         return;
0097     }
0098     if (original) {
0099         d->password = original->d->password;
0100         d->passwordLoaded = original->d->passwordLoaded;
0101         d->passwordDirty = original->d->passwordDirty;
0102         Q_EMIT passwordChanged();
0103     } else {
0104         qCWarning(MAILTRANSPORT_LOG) << "Transport with this ID not managed by transport manager.";
0105     }
0106 }
0107 
0108 bool Transport::isComplete() const
0109 {
0110     return !requiresAuthentication() || !storePassword() || d->passwordLoaded;
0111 }
0112 
0113 QString Transport::authenticationTypeString() const
0114 {
0115     return Transport::authenticationTypeString(authenticationType());
0116 }
0117 
0118 QString Transport::authenticationTypeString(int type)
0119 {
0120     switch (type) {
0121     case EnumAuthenticationType::LOGIN:
0122         return QStringLiteral("LOGIN");
0123     case EnumAuthenticationType::PLAIN:
0124         return QStringLiteral("PLAIN");
0125     case EnumAuthenticationType::CRAM_MD5:
0126         return QStringLiteral("CRAM-MD5");
0127     case EnumAuthenticationType::DIGEST_MD5:
0128         return QStringLiteral("DIGEST-MD5");
0129     case EnumAuthenticationType::NTLM:
0130         return QStringLiteral("NTLM");
0131     case EnumAuthenticationType::GSSAPI:
0132         return QStringLiteral("GSSAPI");
0133     case EnumAuthenticationType::CLEAR:
0134         return i18nc("Authentication method", "Clear text");
0135     case EnumAuthenticationType::APOP:
0136         return QStringLiteral("APOP");
0137     case EnumAuthenticationType::ANONYMOUS:
0138         return i18nc("Authentication method", "Anonymous");
0139     case EnumAuthenticationType::XOAUTH2:
0140         return QStringLiteral("XOAUTH2");
0141     }
0142     Q_ASSERT(false);
0143     return {};
0144 }
0145 
0146 void Transport::usrRead()
0147 {
0148     TransportBase::usrRead();
0149 
0150     setHost(host().trimmed());
0151 
0152     if (d->oldName.isEmpty()) {
0153         d->oldName = name();
0154     }
0155 
0156     // Set TransportType.
0157     {
0158         d->transportType = TransportType();
0159         d->transportType.d->mIdentifier = identifier();
0160         // qCDebug(MAILTRANSPORT_LOG) << "type" << identifier();
0161         // Now we have the type and possibly agentType.  Get the name, description
0162         // etc. from TransportManager.
0163         const TransportType::List &types = TransportManager::self()->types();
0164         int index = types.indexOf(d->transportType);
0165         if (index != -1) {
0166             d->transportType = types[index];
0167         } else {
0168             qCWarning(MAILTRANSPORT_LOG) << "Type unknown to manager.";
0169             d->transportType.d->mName = i18nc("An unknown transport type", "Unknown");
0170         }
0171         Q_EMIT transportTypeChanged();
0172     }
0173 
0174     // we have everything we need
0175     if (!storePassword()) {
0176         return;
0177     }
0178 
0179     if (d->passwordLoaded) {
0180         return;
0181     }
0182 
0183     // try to find a password in the config file otherwise
0184     KConfigGroup group(config(), currentGroup());
0185     if (group.hasKey("password")) {
0186         d->password = KStringHandler::obscure(group.readEntry("password"));
0187     }
0188 
0189     if (!d->password.isEmpty()) {
0190         d->passwordLoaded = true;
0191 #if USE_LEGACY_WALLET_CODE
0192         if (KWallet::Wallet::isEnabled()) {
0193 #else
0194         if (QKeychain::isAvailable()) {
0195 #endif
0196             // TODO: Needs to replaced with a check, if a backend is available.
0197             //  2022-10-12: QtKeyChain has no method to request, if there is any backend.
0198             //  see https://github.com/frankosterfeld/qtkeychain/issues/224
0199             d->needsWalletMigration = true;
0200         } else {
0201             d->storePasswordInFile = true;
0202         }
0203     }
0204 }
0205 
0206 bool Transport::usrSave()
0207 {
0208     if (requiresAuthentication() && storePassword() && d->passwordDirty) {
0209         const QString storePassword = d->password;
0210         auto writeJob = new WritePasswordJob(WALLET_FOLDER, this);
0211         connect(writeJob, &Job::finished, this, [=] {
0212             if (writeJob->error()) {
0213                 qWarning(MAILTRANSPORT_LOG()) << "WritePasswordJob failed with: " << writeJob->errorString();
0214                 // wallet saving failed, ask if we should store in the config file instead
0215                 if (d->storePasswordInFile
0216                     || KMessageBox::warningTwoActions(nullptr,
0217                                                       i18n("QKeychain not found a backend for storing your password. "
0218                                                            "It is strongly recommended to use strong backend for managing your passwords.\n"
0219                                                            "However, the password can be stored in the configuration "
0220                                                            "file instead. The password is stored in an obfuscated format, "
0221                                                            "but should not be considered secure from decryption efforts "
0222                                                            "if access to the configuration file is obtained.\n"
0223                                                            "Do you want to store the password for server '%1' in the "
0224                                                            "configuration file?",
0225                                                            name()),
0226                                                       i18n("KWallet Not Available"),
0227                                                       KGuiItem(i18n("Store Password")),
0228                                                       KGuiItem(i18n("Do Not Store Password")))
0229                         == KMessageBox::ButtonCode::PrimaryAction) {
0230                     // write to config file
0231                     KConfigGroup group(config(), currentGroup());
0232                     group.writeEntry("password", KStringHandler::obscure(storePassword));
0233                     d->storePasswordInFile = true;
0234                 }
0235             }
0236         });
0237 
0238         writeJob->setKey(QString::number(id()));
0239         writeJob->setTextData(storePassword);
0240         QEventLoop loop;
0241         connect(writeJob, &Job::finished, &loop, &QEventLoop::quit);
0242         writeJob->start();
0243         loop.exec();
0244         d->passwordDirty = false;
0245     }
0246 
0247     if (!TransportBase::usrSave()) {
0248         return false;
0249     }
0250     TransportManager::self()->emitChangesCommitted();
0251     if (name() != d->oldName) {
0252         Q_EMIT TransportManager::self()->transportRenamed(id(), d->oldName, name());
0253         d->oldName = name();
0254     }
0255 
0256     return true;
0257 }
0258 
0259 void Transport::readPassword()
0260 {
0261     // no need to load a password if the account doesn't require auth
0262     if (!requiresAuthentication()) {
0263         return;
0264     }
0265     d->passwordLoaded = true;
0266 
0267     auto readJob = new ReadPasswordJob(WALLET_FOLDER, this);
0268     connect(readJob, &Job::finished, this, &Transport::readTransportPasswordFinished);
0269     readJob->setKey(QString::number(id()));
0270     readJob->start();
0271 }
0272 
0273 void Transport::readTransportPasswordFinished(QKeychain::Job *baseJob)
0274 {
0275     auto job = qobject_cast<ReadPasswordJob *>(baseJob);
0276     Q_ASSERT(job);
0277     if (job->error()) {
0278         d->password.clear();
0279         d->passwordLoaded = false;
0280         qWarning() << "We have an error during reading password " << job->errorString();
0281         Q_EMIT passwordChanged();
0282     } else {
0283         setPassword(job->textData());
0284     }
0285     Q_EMIT passwordLoaded();
0286 }
0287 
0288 bool Transport::needsWalletMigration() const
0289 {
0290     return d->needsWalletMigration;
0291 }
0292 
0293 void Transport::migrateToWallet()
0294 {
0295     qCDebug(MAILTRANSPORT_LOG) << "migrating" << id() << "to wallet";
0296     d->needsWalletMigration = false;
0297     KConfigGroup group(config(), currentGroup());
0298     group.deleteEntry("password");
0299     d->passwordDirty = true;
0300     d->storePasswordInFile = false;
0301     save();
0302 }
0303 
0304 Transport *Transport::clone() const
0305 {
0306     const QString id = currentGroup().mid(10);
0307     return new Transport(id);
0308 }
0309 
0310 TransportType Transport::transportType() const
0311 {
0312     if (!d->transportType.isValid()) {
0313         qCWarning(MAILTRANSPORT_LOG) << "Invalid transport type.";
0314     }
0315     return d->transportType;
0316 }
0317 
0318 #include "moc_transport.cpp"