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"