MailTransport

transport.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 "transport.h"
8 #include "mailtransport_defs.h"
9 #include "transport_p.h"
10 #include "transportmanager.h"
11 #include "transporttype_p.h"
12 
13 #include "mailtransport_debug.h"
14 #include <KConfigGroup>
15 #include <KLocalizedString>
16 #include <KMessageBox>
17 #include <KStringHandler>
18 #include <KWallet>
19 
20 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
21 #include <qt5keychain/keychain.h>
22 #else
23 #include <qt6keychain/keychain.h>
24 #endif
25 using namespace QKeychain;
26 using namespace MailTransport;
27 
28 Transport::Transport(const QString &cfgGroup)
29  : TransportBase(cfgGroup)
30  , d(new TransportPrivate)
31 {
32  qCDebug(MAILTRANSPORT_LOG) << cfgGroup;
33  d->passwordLoaded = false;
34  d->passwordDirty = false;
35  d->storePasswordInFile = false;
36  d->needsWalletMigration = false;
37  load();
38  loadPassword();
39 }
40 
41 Transport::~Transport() = default;
42 
43 bool Transport::isValid() const
44 {
45  return (id() > 0) && !host().isEmpty() && port() <= 65536;
46 }
47 
48 void Transport::loadPassword()
49 {
50  if (!d->passwordLoaded && requiresAuthentication() && storePassword() && d->password.isEmpty()) {
51  readPassword();
52  }
53 }
54 
55 QString Transport::password() const
56 {
57  return d->password;
58 }
59 
60 void Transport::setPassword(const QString &passwd)
61 {
62  d->passwordLoaded = true;
63  if (d->password == passwd) {
64  return;
65  }
66  d->passwordDirty = true;
67  d->password = passwd;
68  Q_EMIT passwordChanged();
69 }
70 
72 {
73  QStringList existingNames;
74  const auto lstTransports = TransportManager::self()->transports();
75  for (Transport *t : lstTransports) {
76  if (t->id() != id()) {
77  existingNames << t->name();
78  }
79  }
80  int suffix = 1;
81  QString origName = name();
82  while (existingNames.contains(name())) {
83  setName(
84  i18nc("%1: name; %2: number appended to it to make "
85  "it unique among a list of names",
86  "%1 #%2",
87  origName,
88  suffix));
89  ++suffix;
90  }
91 }
92 
94 {
95  Transport *original = TransportManager::self()->transportById(id(), false);
96  if (original == this) {
97  qCWarning(MAILTRANSPORT_LOG) << "Tried to update password state of non-cloned transport.";
98  return;
99  }
100  if (original) {
101  d->password = original->d->password;
102  d->passwordLoaded = original->d->passwordLoaded;
103  d->passwordDirty = original->d->passwordDirty;
104  Q_EMIT passwordChanged();
105  } else {
106  qCWarning(MAILTRANSPORT_LOG) << "Transport with this ID not managed by transport manager.";
107  }
108 }
109 
111 {
112  return !requiresAuthentication() || !storePassword() || d->passwordLoaded;
113 }
114 
116 {
117  return Transport::authenticationTypeString(authenticationType());
118 }
119 
121 {
122  switch (type) {
123  case EnumAuthenticationType::LOGIN:
124  return QStringLiteral("LOGIN");
125  case EnumAuthenticationType::PLAIN:
126  return QStringLiteral("PLAIN");
127  case EnumAuthenticationType::CRAM_MD5:
128  return QStringLiteral("CRAM-MD5");
129  case EnumAuthenticationType::DIGEST_MD5:
130  return QStringLiteral("DIGEST-MD5");
131  case EnumAuthenticationType::NTLM:
132  return QStringLiteral("NTLM");
133  case EnumAuthenticationType::GSSAPI:
134  return QStringLiteral("GSSAPI");
135  case EnumAuthenticationType::CLEAR:
136  return i18nc("Authentication method", "Clear text");
137  case EnumAuthenticationType::APOP:
138  return QStringLiteral("APOP");
139  case EnumAuthenticationType::ANONYMOUS:
140  return i18nc("Authentication method", "Anonymous");
141  case EnumAuthenticationType::XOAUTH2:
142  return QStringLiteral("XOAUTH2");
143  }
144  Q_ASSERT(false);
145  return {};
146 }
147 
148 void Transport::usrRead()
149 {
150  TransportBase::usrRead();
151 
152  setHost(host().trimmed());
153 
154  if (d->oldName.isEmpty()) {
155  d->oldName = name();
156  }
157 
158  // Set TransportType.
159  {
160  d->transportType = TransportType();
161  d->transportType.d->mIdentifier = identifier();
162  // qCDebug(MAILTRANSPORT_LOG) << "type" << identifier();
163  // Now we have the type and possibly agentType. Get the name, description
164  // etc. from TransportManager.
166  int index = types.indexOf(d->transportType);
167  if (index != -1) {
168  d->transportType = types[index];
169  } else {
170  qCWarning(MAILTRANSPORT_LOG) << "Type unknown to manager.";
171  d->transportType.d->mName = i18nc("An unknown transport type", "Unknown");
172  }
173  Q_EMIT transportTypeChanged();
174  }
175 
176  // we have everything we need
177  if (!storePassword()) {
178  return;
179  }
180 
181  if (d->passwordLoaded) {
182  return;
183  }
184 
185  // try to find a password in the config file otherwise
186  KConfigGroup group(config(), currentGroup());
187  if (group.hasKey("password")) {
188  d->password = KStringHandler::obscure(group.readEntry("password"));
189  }
190 
191  if (!d->password.isEmpty()) {
192  d->passwordLoaded = true;
194  // TODO: Needs to replaced with a check, if a backend is available.
195  // 2022-10-12: QtKeyChain has no method to request, if there is any backend.
196  // see https://github.com/frankosterfeld/qtkeychain/issues/224
197  d->needsWalletMigration = true;
198  } else {
199  d->storePasswordInFile = true;
200  }
201  }
202 }
203 
204 bool Transport::usrSave()
205 {
206  if (requiresAuthentication() && storePassword() && d->passwordDirty) {
207  const QString storePassword = d->password;
208  auto writeJob = new WritePasswordJob(WALLET_FOLDER, this);
209  connect(writeJob, &Job::finished, this, [=] {
210  if (writeJob->error()) {
211  qWarning(MAILTRANSPORT_LOG()) << "WritePasswordJob failed with: " << writeJob->errorString();
212  // wallet saving failed, ask if we should store in the config file instead
213  if (d->storePasswordInFile
214  || KMessageBox::warningTwoActions(nullptr,
215  i18n("QKeychain not found a backend for storing your password. "
216  "It is strongly recommended to use strong backend for managing your passwords.\n"
217  "However, the password can be stored in the configuration "
218  "file instead. The password is stored in an obfuscated format, "
219  "but should not be considered secure from decryption efforts "
220  "if access to the configuration file is obtained.\n"
221  "Do you want to store the password for server '%1' in the "
222  "configuration file?",
223  name()),
224  i18n("KWallet Not Available"),
225  KGuiItem(i18n("Store Password")),
226  KGuiItem(i18n("Do Not Store Password")))
227  == KMessageBox::ButtonCode::PrimaryAction) {
228  // write to config file
229  KConfigGroup group(config(), currentGroup());
230  group.writeEntry("password", KStringHandler::obscure(storePassword));
231  d->storePasswordInFile = true;
232  }
233  }
234  });
235 
236  writeJob->setKey(QString::number(id()));
237  writeJob->setTextData(storePassword);
238  QEventLoop loop;
239  connect(writeJob, &Job::finished, &loop, &QEventLoop::quit);
240  writeJob->start();
241  loop.exec();
242  d->passwordDirty = false;
243  }
244 
245  if (!TransportBase::usrSave()) {
246  return false;
247  }
248  TransportManager::self()->emitChangesCommitted();
249  if (name() != d->oldName) {
250  Q_EMIT TransportManager::self()->transportRenamed(id(), d->oldName, name());
251  d->oldName = name();
252  }
253 
254  return true;
255 }
256 
257 void Transport::readPassword()
258 {
259  // no need to load a password if the account doesn't require auth
260  if (!requiresAuthentication()) {
261  return;
262  }
263  d->passwordLoaded = true;
264 
265  auto readJob = new ReadPasswordJob(WALLET_FOLDER, this);
266  connect(readJob, &Job::finished, this, &Transport::readTransportPasswordFinished);
267  readJob->setKey(QString::number(id()));
268  readJob->start();
269 }
270 
271 void Transport::readTransportPasswordFinished(QKeychain::Job *baseJob)
272 {
273  auto job = qobject_cast<ReadPasswordJob *>(baseJob);
274  Q_ASSERT(job);
275  if (job->error()) {
276  d->password.clear();
277  d->passwordLoaded = false;
278  qWarning() << "We have an error during reading password " << job->errorString();
279  Q_EMIT passwordChanged();
280  } else {
281  setPassword(job->textData());
282  }
283  Q_EMIT passwordLoaded();
284 }
285 
287 {
288  return d->needsWalletMigration;
289 }
290 
292 {
293  qCDebug(MAILTRANSPORT_LOG) << "migrating" << id() << "to wallet";
294  d->needsWalletMigration = false;
295  KConfigGroup group(config(), currentGroup());
296  group.deleteEntry("password");
297  d->passwordDirty = true;
298  d->storePasswordInFile = false;
299  save();
300 }
301 
303 {
304  const QString id = currentGroup().mid(10);
305  return new Transport(id);
306 }
307 
308 TransportType Transport::transportType() const
309 {
310  if (!d->transportType.isValid()) {
311  qCWarning(MAILTRANSPORT_LOG) << "Invalid transport type.";
312  }
313  return d->transportType;
314 }
Transport * transportById(int id, bool def=true) const
Returns the Transport object with the given id.
QString number(int n, int base)
void deleteEntry(const char *key, WriteConfigFlags pFlags=Normal)
Transport * clone() const
Returns a deep copy of this Transport object which will no longer be automatically updated.
Definition: transport.cpp:302
bool contains(const QString &str, Qt::CaseSensitivity cs) const const
QList< Transport * > transports() const
Returns a list of all available transports.
KCOREADDONS_EXPORT QString obscure(const QString &str)
int exec(QEventLoop::ProcessEventsFlags flags)
void setHost(ScriptableExtension *host)
bool isComplete() const
Returns true if all settings have been loaded.
Definition: transport.cpp:110
void setPassword(const QString &passwd)
Sets the password of this transport.
Definition: transport.cpp:60
static bool isEnabled()
A representation of a transport type.
Definition: transporttype.h:30
void quit()
Transport(const QString &cfgGroup)
Creates a Transport object.
Definition: transport.cpp:28
static TransportManager * self()
Returns the TransportManager instance.
KSharedConfigPtr config()
int indexOf(QStringView str, int from) const const
void passwordChanged()
Emitted when the password is changed.
Represents the settings of a specific mail transport.
Definition: transport.h:32
ScriptableExtension * host() const
void migrateToWallet()
Try to migrate the password from the config file to the wallet.
Definition: transport.cpp:291
~Transport() override
Destructor.
void passwordLoaded()
Emitted when passwords have been loaded from QKeyChain.
QString authenticationTypeString() const
Returns a string representation of the authentication type.
Definition: transport.cpp:115
const char * name(StandardAction id)
bool needsWalletMigration() const
Returns true if the password was not stored in the wallet.
Definition: transport.cpp:286
QString i18nc(const char *context, const char *text, const TYPE &arg...)
void forceUniqueName()
Makes sure the transport has a unique name.
Definition: transport.cpp:71
void updatePasswordState()
This function synchronizes the password of this transport with the password of the transport with the...
Definition: transport.cpp:93
Q_INVOKABLE bool isValid() const
Returns true if this transport is valid, ie.
Definition: transport.cpp:43
QString mid(int position, int n) const const
void transportRenamed(int id, const QString &oldName, const QString &newName)
Emitted when a transport has been renamed.
void transportTypeChanged()
Emitted when the transport type is changed.
TransportType::List types() const
Returns a list of all available transport types.
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.