KWallet

ksecretd.cpp
1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2002-2004 George Staikos <staikos@kde.org>
4 SPDX-FileCopyrightText: 2008 Michael Leupold <lemma@confuego.org>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8
9#include "ksecretd.h"
10#include "ksecretd_debug.h"
11
12#include "kbetterthankdialog.h"
13#include "kwalletfreedesktopcollection.h"
14#include "kwalletfreedesktopitem.h"
15#include "kwalletfreedesktopprompt.h"
16#include "kwalletfreedesktopservice.h"
17#include "kwalletfreedesktopsession.h"
18#include "kwalletportalsecrets.h"
19#include "kwalletwizard.h"
20
21#ifdef HAVE_GPGMEPP
22#include "knewwalletdialog.h"
23#endif
24
25#include <KColorScheme>
26#include <KConfig>
27#include <KConfigGroup>
28#include <KDirWatch>
29#include <KLocalizedString>
30#include <KMessageBox>
31#include <KNewPasswordDialog>
32#include <KNotification>
33#include <KPasswordDialog>
34#include <KPluginFactory>
35#include <KSharedConfig>
36#include <kwalletentry.h>
37#include <kwindowsystem.h>
38
39#include <config-ksecretd.h>
40#if WITH_X11
41#include <KX11Extras>
42#endif
43
44#ifdef HAVE_GPGMEPP
45#include <gpgme++/key.h>
46#endif
47
48#include <QApplication>
49#include <QDir>
50#include <QIcon>
51#include <QTimer>
52
53#include <assert.h>
54
55#include "kwalletadaptor.h"
56
57static void startManagerForKSecretD()
58{
59 if (!QStandardPaths::findExecutable(QStringLiteral("kstart")).isEmpty()) {
60 QProcess::startDetached(QStringLiteral("kstart"), {QStringLiteral("kwalletmanager5"), QStringLiteral("--"), QStringLiteral("--KSecretD")});
61 } else if (!QStandardPaths::findExecutable(QStringLiteral("kstart5")).isEmpty()) {
62 QProcess::startDetached(QStringLiteral("kstart"), {QStringLiteral("kwalletmanager5"), QStringLiteral("--"), QStringLiteral("--KSecretD")});
63 } else {
64 QProcess::startDetached(QStringLiteral("kwalletmanager5"), QStringList{QStringLiteral("--KSecretD")});
65 }
66}
67
68class KWalletTransaction
69{
70public:
71 explicit KWalletTransaction(QDBusConnection conn)
72 : tId(nextTransactionId)
73 , res(-1)
74 , connection(conn)
75 {
76 nextTransactionId++;
77 // make sure the id is never < 0 as that's used for the
78 // error conditions.
79 if (nextTransactionId < 0) {
80 nextTransactionId = 0;
81 }
82 }
83
84 static int getTransactionId()
85 {
86 return nextTransactionId;
87 }
88
89 ~KWalletTransaction()
90 {
91 }
92
93 enum Type {
94 Unknown,
95 Open,
96 ChangePassword,
97 OpenFail,
98 CloseCancelled,
99 };
100 Type tType = Unknown;
101 QString appid;
102 qlonglong wId;
103 QString wallet;
104 QString service;
105 bool cancelled = false; // set true if the client dies before open
106 bool modal;
107 bool isPath;
108 int tId; // transaction id
109 int res;
110 QDBusMessage message;
111 QDBusConnection connection;
112
113private:
114 static int nextTransactionId;
115};
116
117int KWalletTransaction::nextTransactionId = 0;
118
119KSecretD::KSecretD()
120 : QObject(nullptr)
121 , _failed(0)
122 , _syncTime(5000)
123 , _curtrans(nullptr)
124 , _useGpg(false)
125{
126#ifdef HAVE_GPGMEPP
127 _useGpg = true;
128#endif
129
130 srand(time(nullptr));
131 _showingFailureNotify = false;
132 _closeIdle = false;
133 _idleTime = 0;
134 connect(&_closeTimers, &KTimeout::timedOut, this, &KSecretD::timedOutClose);
135 connect(&_syncTimers, &KTimeout::timedOut, this, &KSecretD::timedOutSync);
136
137 KConfig kwalletrc(QStringLiteral("kwalletrc"));
138 KConfigGroup cfgWallet(&kwalletrc, "Wallet");
139
140 if (cfgWallet.readEntry<bool>("apiEnabled", true)) {
141 (void)new KWalletAdaptor(this);
142 // register legacy services
143 QDBusConnection::sessionBus().registerObject(QStringLiteral("/ksecretd"), this);
144 QDBusConnection::sessionBus().registerObject(QStringLiteral("ksecretd"), this);
145 QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.ksecretd"));
147
148 new KWalletPortalSecrets(this);
149 }
150
151#ifdef Q_WS_X11
152 screensaver = 0;
153#endif
154
155 reconfigure();
156 // KGlobal::dirs()->addResourceType("kwallet", 0, "share/apps/kwallet");
157 _dw = new KDirWatch(this);
158 _dw->setObjectName(QStringLiteral("KWallet Directory Watcher"));
159 // _dw->addDir(KGlobal::dirs()->saveLocation("kwallet"));
160 _dw->addDir(KWallet::Backend::getSaveLocation());
161
162 _dw->startScan(true);
163 connect(_dw, &KDirWatch::dirty, this, &KSecretD::emitWalletListDirty);
164 connect(_dw, &KDirWatch::deleted, this, &KSecretD::emitWalletListDirty);
165
166 _serviceWatcher.setWatchMode(QDBusServiceWatcher::WatchForOwnerChange);
167 connect(&_serviceWatcher, &QDBusServiceWatcher::serviceOwnerChanged, this, &KSecretD::slotServiceOwnerChanged);
168
169 _fdoService.reset(new KWalletFreedesktopService(this));
170}
171
172KSecretD::~KSecretD()
173{
174#ifdef Q_WS_X11
175 delete screensaver;
176 screensaver = 0;
177#endif
178 closeAllWallets();
179 qDeleteAll(_transactions);
180}
181
182QString KSecretD::encodeWalletName(const QString &name)
183{
184 return KWallet::Backend::encodeWalletName(name);
185}
186
187QString KSecretD::decodeWalletName(const QString &mangledName)
188{
189 return KWallet::Backend::decodeWalletName(mangledName);
190}
191
192#ifdef Q_WS_X11
193void KSecretD::connectToScreenSaver()
194{
195 screensaver = new QDBusInterface("org.freedesktop.ScreenSaver", "/ScreenSaver", "org.freedesktop.ScreenSaver");
196 if (!screensaver->isValid()) {
197 qCDebug(KSECRETD_LOG) << "Service org.freedesktop.ScreenSaver not found. Retrying in 10 seconds...";
198 // keep attempting every 10 seconds
199 QTimer::singleShot(10000, this, SLOT(connectToScreenSaver()));
200 } else {
201 connect(screensaver, SIGNAL(ActiveChanged(bool)), SLOT(screenSaverChanged(bool)));
202 qCDebug(KSECRETD_LOG) << "connected to screen saver service.";
203 }
204}
205#endif
206
207int KSecretD::generateHandle()
208{
209 int rc;
210
211 // ASSUMPTION: RAND_MAX is fairly large.
212 do {
213 rc = rand();
214 } while (_wallets.contains(rc) || rc == 0);
215
216 return rc;
217}
218
219QPair<int, KWallet::Backend *> KSecretD::findWallet(const QString &walletName) const
220{
221 Wallets::const_iterator it = _wallets.constBegin();
222 const Wallets::const_iterator end = _wallets.constEnd();
223 for (; it != end; ++it) {
224 if (it.value()->walletName() == walletName) {
225 return qMakePair(it.key(), it.value());
226 }
227 }
228 return qMakePair(-1, static_cast<KWallet::Backend *>(nullptr));
229}
230
231bool KSecretD::_processing = false;
232
233void KSecretD::processTransactions()
234{
235 if (_processing) {
236 return;
237 }
238
239 _processing = true;
240
241 // Process remaining transactions
242 while (!_transactions.isEmpty()) {
243 _curtrans = _transactions.takeFirst();
244 int res;
245
246 assert(_curtrans->tType != KWalletTransaction::Unknown);
247
248 switch (_curtrans->tType) {
249 case KWalletTransaction::Open:
250 res = doTransactionOpen(_curtrans->appid, _curtrans->wallet, _curtrans->isPath, _curtrans->wId, _curtrans->modal, _curtrans->service);
251
252 // multiple requests from the same client
253 // should not produce multiple password
254 // dialogs on a failure
255 if (res < 0) {
256 QList<KWalletTransaction *>::iterator it;
257 for (it = _transactions.begin(); it != _transactions.end(); ++it) {
258 KWalletTransaction *x = *it;
259 if (_curtrans->appid == x->appid && x->tType == KWalletTransaction::Open && x->wallet == _curtrans->wallet && x->wId == _curtrans->wId) {
260 x->tType = KWalletTransaction::OpenFail;
261 }
262 }
263 } else if (_curtrans->cancelled) {
264 // the wallet opened successfully but the application
265 // opening exited/crashed while the dialog was still shown.
266 KWalletTransaction *_xact = new KWalletTransaction(_curtrans->connection);
267 _xact->tType = KWalletTransaction::CloseCancelled;
268 _xact->appid = _curtrans->appid;
269 _xact->wallet = _curtrans->wallet;
270 _xact->service = _curtrans->service;
271 _transactions.append(_xact);
272 }
273
274 // emit the AsyncOpened signal as a reply
275 _curtrans->res = res;
276 Q_EMIT walletAsyncOpened(_curtrans->tId, res);
277 break;
278
279 case KWalletTransaction::OpenFail:
280 // emit the AsyncOpened signal with an invalid handle
281 _curtrans->res = -1;
282 Q_EMIT walletAsyncOpened(_curtrans->tId, -1);
283 break;
284
285 case KWalletTransaction::ChangePassword:
286 doTransactionChangePassword(_curtrans->appid, _curtrans->wallet, _curtrans->wId);
287 break;
288
289 case KWalletTransaction::CloseCancelled:
290 doTransactionOpenCancelled(_curtrans->appid, _curtrans->wallet, _curtrans->service);
291 break;
292
293 case KWalletTransaction::Unknown:
294 break;
295 default:
296 break;
297 }
298
299 // send delayed dbus message reply to the caller
300 if (_curtrans->message.type() != QDBusMessage::InvalidMessage) {
301 if (_curtrans->connection.isConnected()) {
302 QDBusMessage reply = _curtrans->message.createReply();
303 reply << _curtrans->res;
304 _curtrans->connection.send(reply);
305 }
306 }
307
308 delete _curtrans;
309 _curtrans = nullptr;
310 }
311
312 _processing = false;
313}
314
315int KSecretD::openPath(const QString &path, qlonglong wId, const QString &appid)
316{
317 int tId = openPathAsync(path, wId, appid, false);
318 if (tId < 0) {
319 return tId;
320 }
321
322 // NOTE the real return value will be sent by the dbusmessage delayed
323 // reply
324 return 0;
325 // wait for the open-transaction to be processed
326 // KWalletOpenLoop loop(this);
327 // return loop.waitForAsyncOpen(tId);
328}
329
330int KSecretD::open(const QString &wallet, qlonglong wId, const QString &appid)
331{
332 if (!isEnabled()) { // guard
333 return -1;
334 }
335
336 KWalletTransaction *xact = new KWalletTransaction(connection());
337 _transactions.append(xact);
338
339 message().setDelayedReply(true);
340 xact->message = message();
341
342 xact->appid = appid;
343 xact->wallet = wallet;
344 xact->wId = wId;
345 xact->modal = true; // mark dialogs as modal, the app has blocking wait
346 xact->tType = KWalletTransaction::Open;
347 xact->isPath = false;
348
349 QTimer::singleShot(0, this, SLOT(processTransactions()));
350 checkActiveDialog();
351 // NOTE the real return value will be sent by the dbusmessage delayed
352 // reply
353 return 0;
354}
355
356int KSecretD::nextTransactionId() const
357{
358 return KWalletTransaction::getTransactionId();
359}
360
361int KSecretD::openAsync(const QString &wallet,
362 qlonglong wId,
363 const QString &appid,
364 bool handleSession,
365 const QDBusConnection &connection,
366 const QDBusMessage &message)
367{
368 if (!isEnabled()) { // guard
369 return -1;
370 }
371
372 KWalletTransaction *xact = new KWalletTransaction(connection);
373 _transactions.append(xact);
374
375 xact->appid = appid;
376 xact->wallet = wallet;
377 xact->wId = wId;
378 xact->modal = true; // mark dialogs as modal, the app has blocking wait
379 xact->tType = KWalletTransaction::Open;
380 xact->isPath = false;
381 if (handleSession) {
382 qCDebug(KSECRETD_LOG) << "openAsync for " << message.service();
383 _serviceWatcher.setConnection(connection);
384 _serviceWatcher.addWatchedService(message.service());
385 xact->service = message.service();
386 }
387 QTimer::singleShot(0, this, SLOT(processTransactions()));
388 checkActiveDialog();
389 // opening is in progress. return the transaction number
390 return xact->tId;
391}
392
393int KSecretD::openAsync(const QString &wallet, qlonglong wId, const QString &appid, bool handleSession)
394{
395 return openAsync(wallet, wId, appid, handleSession, connection(), message());
396}
397
398int KSecretD::openPathAsync(const QString &path, qlonglong wId, const QString &appid, bool handleSession)
399{
400 if (!isEnabled()) { // guard
401 return -1;
402 }
403
404 KWalletTransaction *xact = new KWalletTransaction(connection());
405 _transactions.append(xact);
406
407 xact->appid = appid;
408 xact->wallet = path;
409 xact->wId = wId;
410 xact->modal = true;
411 xact->tType = KWalletTransaction::Open;
412 xact->isPath = true;
413 if (handleSession) {
414 qCDebug(KSECRETD_LOG) << "openPathAsync " << message().service();
415 _serviceWatcher.setConnection(connection());
416 _serviceWatcher.addWatchedService(message().service());
417 xact->service = message().service();
418 }
419 QTimer::singleShot(0, this, SLOT(processTransactions()));
420 checkActiveDialog();
421 // opening is in progress. return the transaction number
422 return xact->tId;
423}
424
425// Sets up a dialog that will be shown by kwallet.
426void KSecretD::setupDialog(QWidget *dialog, WId wId, const QString &appid, bool modal)
427{
428 if (wId != 0) {
429 // correct, set dialog parent
430 dialog->setAttribute(Qt::WA_NativeWindow, true);
432 } else {
433 if (appid.isEmpty()) {
434 qWarning() << "Using kwallet without parent window!";
435 } else {
436 qWarning() << "Application" << appid << "using kwallet without parent window!";
437 }
438 // allow dialog activation even if it interrupts, better than trying
439 // hacks
440 // with keeping the dialog on top or on all desktops
441 // KF5 FIXME what should we use now instead of this:
442 // kapp->updateUserTimestamp();
443 }
444
445#if WITH_X11
447 if (modal) {
449 } else {
451 }
452 }
453#endif
454 activeDialog = dialog;
455}
456
457// If there's a dialog already open and another application tries some
458// operation that'd lead to
459// opening a dialog, that application will be blocked by this dialog. A proper
460// solution would
461// be to set the second application's window also as a parent for the active
462// dialog, so that
463// KWin properly handles focus changes and so on, but there's currently no
464// support for multiple
465// dialog parents. In the absence of this support, we use all kinds of bad
466// hacks to make
467// sure the user doesn't overlook the active dialog.
468void KSecretD::checkActiveDialog()
469{
470 if (!activeDialog) {
471 return;
472 }
473
474 // KF5 FIXME what should we use now instead of this:
475 // kapp->updateUserTimestamp();
476
477 activeDialog->show();
478
479#if WITH_X11
481 WId window = activeDialog->winId();
483 KX11Extras::setOnAllDesktops(window, true);
485 }
486#endif
487}
488
489int KSecretD::doTransactionOpen(const QString &appid, const QString &wallet, bool isPath, qlonglong wId, bool modal, const QString &service)
490{
491 if (_firstUse && !isPath) {
492 // if the user specifies a wallet name, the use it as the default
493 // wallet name
494 if (wallet != KWallet::Wallet::LocalWallet()) {
495 KConfig kwalletrc(QStringLiteral("kwalletrc"));
496 KConfigGroup cfg(&kwalletrc, "Wallet");
497 cfg.writeEntry("Default Wallet", wallet);
498 }
499 if (wallets().contains(KWallet::Wallet::LocalWallet())) {
500 KConfig kwalletrc(QStringLiteral("kwalletrc"));
501 KConfigGroup cfg(&kwalletrc, "Wallet");
502 _firstUse = false;
503 cfg.writeEntry("First Use", false);
504 }
505 // else {
506 // // First use wizard
507 // // TODO GPG adjust new smartcard options gathered by
508 // the wizard
509 // QPointer<KWalletWizard> wiz = new KWalletWizard(0);
510 // wiz->setWindowTitle(i18n("KDE Wallet Service"));
511 // setupDialog(wiz, (WId)wId, appid, modal);
512 // int rc = wiz->exec();
513 // if (rc == QDialog::Accepted && wiz) {
514 // bool useWallet = wiz->field("useWallet").toBool();
515 // KConfig kwalletrc("kwalletrc");
516 // KConfigGroup cfg(&kwalletrc, "Wallet");
517 // cfg.writeEntry("First Use", false);
518 // cfg.writeEntry("Enabled", useWallet);
519 // cfg.writeEntry("Close When Idle",
520 // wiz->field("closeWhenIdle").toBool());
521 // cfg.writeEntry("Use One Wallet",
522 // !wiz->field("networkWallet").toBool());
523 // cfg.sync();
524 // reconfigure();
525 //
526 // if (!useWallet) {
527 // delete wiz;
528 // return -1;
529 // }
530 //
531 // // Create the wallet
532 // // TODO GPG select the correct wallet type upon
533 // cretion (GPG or blowfish based)
534 // KWallet::Backend *b = new
535 // KWallet::Backend(KWallet::Wallet::LocalWallet());
536 // #ifdef HAVE_GPGMEPP
537 // if (wiz->field("useBlowfish").toBool()) {
538 // b->setCipherType(KWallet::BACKEND_CIPHER_BLOWFISH);
539 // #endif
540 // QString pass = wiz->field("pass1").toString();
541 // QByteArray p(pass.toUtf8(), pass.length());
542 // b->open(p);
543 // p.fill(0);
544 // #ifdef HAVE_GPGMEPP
545 // } else {
546 // assert(wiz->field("useGpg").toBool());
547 // b->setCipherType(KWallet::BACKEND_CIPHER_GPG);
548 // b->open(wiz->gpgKey());
549 // }
550 // #endif
551 // b->createFolder(KWallet::Wallet::PasswordFolder());
552 // b->createFolder(KWallet::Wallet::FormDataFolder());
553 // b->close(true);
554 // delete b;
555 // delete wiz;
556 // } else {
557 // delete wiz;
558 // return -1;
559 // }
560 // }
561 }
562
563 int rc = internalOpen(appid, wallet, isPath, WId(wId), modal, service);
564 return rc;
565}
566
567int KSecretD::internalOpen(const QString &appid, const QString &wallet, bool isPath, WId w, bool modal, const QString &service)
568{
569 bool brandNew = false;
570
571 QString thisApp;
572 if (appid.isEmpty()) {
573 thisApp = QStringLiteral("KDE System");
574 } else {
575 thisApp = appid;
576 }
577
578 if (implicitDeny(wallet, thisApp)) {
579 return -1;
580 }
581
582 QPair<int, KWallet::Backend *> walletInfo = findWallet(wallet);
583 int rc = walletInfo.first;
584 if (rc == -1) {
585 if (_wallets.count() > 20) {
586 qCDebug(KSECRETD_LOG) << "Too many wallets open.";
587 return -1;
588 }
589
590 KWallet::Backend *b = new KWallet::Backend(wallet, isPath);
591 QString password;
592 bool emptyPass = false;
593 if ((isPath && QFile::exists(wallet)) || (!isPath && KWallet::Backend::exists(wallet))) {
594 // this open attempt will set wallet type from the file header,
595 // even if password is needed
596 int pwless = b->open(QByteArray(), w);
597#ifdef HAVE_GPGMEPP
598 assert(b->cipherType() != KWallet::BACKEND_CIPHER_UNKNOWN);
599 if (b->cipherType() == KWallet::BACKEND_CIPHER_GPG) {
600 // GPG based wallets do not prompt for password here. Instead,
601 // GPG should already have popped pinentry utility for wallet
602 // decryption
603 if (!b->isOpen()) {
604 // for some reason, GPG operation failed
605 delete b;
606 return -1;
607 }
608 emptyPass = true;
609 } else {
610#endif
611 if (0 != pwless || !b->isOpen()) {
612 if (pwless == 0) {
613 // release, start anew
614 delete b;
615 b = new KWallet::Backend(wallet, isPath);
616 }
617 KPasswordDialog *kpd = new KPasswordDialog();
618 if (appid.isEmpty()) {
619 kpd->setPrompt(i18n("<qt>KDE has requested to open the wallet '<b>%1</b>'. Please enter the password for this wallet below.</qt>",
620 wallet.toHtmlEscaped()));
621 } else {
622 kpd->setPrompt(
623 i18n("<qt>The application '<b>%1</b>' has requested to open the wallet '<b>%2</b>'. Please enter the password for this wallet "
624 "below.</qt>",
625 appid.toHtmlEscaped(),
626 wallet.toHtmlEscaped()));
627 }
628 brandNew = false;
629 // don't use KStdGuiItem::open() here which has trailing
630 // ellipsis!
631 // KF5 FIXME what should we use now instead of this:
632 // kpd->setButtonGuiItem(KDialog::Ok,KGuiItem(
633 // i18n( "&Open" ), "wallet-open"));
634 kpd->setWindowTitle(i18n("KDE Wallet Service"));
635 kpd->setIcon(QIcon::fromTheme(QStringLiteral("kwalletmanager")));
636
637#if WITH_X11
638 if (KWindowSystem::isPlatformX11() && w != KX11Extras::activeWindow() && w != 0L) {
639 // If the dialog is modal to a minimized window it
640 // might not be visible
641 // (but still blocking the calling application).
642 // Notify the user about
643 // the request to open the wallet.
644 KNotification *notification =
645 new KNotification(QStringLiteral("needsPassword"), KNotification::Persistent | KNotification::CloseWhenWindowActivated);
646 notification->setWindow(kpd->windowHandle());
647 QString actionText;
648 if (appid.isEmpty()) {
649 notification->setText(i18n("An application has requested to open a wallet (%1).", wallet.toHtmlEscaped()));
650 actionText = i18nc("Text of a button for switching to the (unnamed) application requesting a password", "Switch there");
651 } else {
652 notification->setText(i18n("<b>%1</b> has requested to open a wallet (%2).", appid.toHtmlEscaped(), wallet.toHtmlEscaped()));
653 actionText =
654 i18nc("Text of a button for switching to the application requesting a password", "Switch to %1", appid.toHtmlEscaped());
655 }
656
657 KNotificationAction *action = notification->addAction(actionText);
658 connect(action, &KNotificationAction::activated, this, &KSecretD::activatePasswordDialog);
659 notification->sendEvent();
660 }
661#endif
662
663 while (!b->isOpen()) {
664 setupDialog(kpd, w, appid, modal);
665 if (kpd->exec() == QDialog::Accepted) {
666 password = kpd->password();
667 int rc = b->open(password.toUtf8());
668 if (!b->isOpen()) {
669 const auto errorStr = KWallet::Backend::openRCToString(rc);
670 qCWarning(KSECRETD_LOG) << "Failed to open wallet" << wallet << errorStr;
671 kpd->setPrompt(i18n("<qt>Error opening the wallet '<b>%1</b>'. Please try again.<br />(Error code %2: %3)</qt>",
672 wallet.toHtmlEscaped(),
673 rc,
674 errorStr));
675 kpd->setPassword(QLatin1String(""));
676 }
677 } else {
678 break;
679 }
680 }
681 delete kpd;
682 } else {
683 emptyPass = true;
684 }
685#ifdef HAVE_GPGMEPP
686 }
687#endif
688 } else {
689 brandNew = true;
690#ifdef HAVE_GPGMEPP
691 // prompt the user for the new wallet format here
692 KWallet::BackendCipherType newWalletType = KWallet::BACKEND_CIPHER_UNKNOWN;
693
694 std::shared_ptr<KWallet::KNewWalletDialog> newWalletDlg(new KWallet::KNewWalletDialog(appid, wallet, QWidget::find(w)));
695 GpgME::Key gpgKey;
696 setupDialog(newWalletDlg.get(), (WId)w, appid, true);
697 if (newWalletDlg->exec() == QDialog::Accepted) {
698 newWalletType = newWalletDlg->isBlowfish() ? KWallet::BACKEND_CIPHER_BLOWFISH : KWallet::BACKEND_CIPHER_GPG;
699 gpgKey = newWalletDlg->gpgKey();
700 } else {
701 // user cancelled the dialog box
702 delete b;
703 return -1;
704 }
705
706 if (newWalletType == KWallet::BACKEND_CIPHER_GPG) {
707 b->setCipherType(newWalletType);
708 b->open(gpgKey);
709 } else if (newWalletType == KWallet::BACKEND_CIPHER_BLOWFISH) {
710#endif // HAVE_GPGMEPP
711 b->setCipherType(KWallet::BACKEND_CIPHER_BLOWFISH);
712 KNewPasswordDialog *kpd = new KNewPasswordDialog();
713 KColorScheme colorScheme(QPalette::Active, KColorScheme::View);
714 kpd->setBackgroundWarningColor(colorScheme.background(KColorScheme::NegativeBackground).color());
715 if (wallet == KWallet::Wallet::LocalWallet() || wallet == KWallet::Wallet::NetworkWallet()) {
716 // Auto create these wallets.
717 if (appid.isEmpty()) {
718 kpd->setPrompt(
719 i18n("KDE has requested to open the wallet. This is used to store sensitive data in a "
720 "secure fashion. Please enter a password to use with this wallet or click cancel to "
721 "deny the application's request."));
722 } else {
723 kpd->setPrompt(
724 i18n("<qt>The application '<b>%1</b>' has requested to open the KDE wallet. This is "
725 "used to store sensitive data in a secure fashion. Please enter a password to use "
726 "with this wallet or click cancel to deny the application's request.</qt>",
727 appid.toHtmlEscaped()));
728 }
729 } else {
730 if (appid.length() == 0) {
731 kpd->setPrompt(
732 i18n("<qt>KDE has requested to create a new wallet named '<b>%1</b>'. Please choose a "
733 "password for this wallet, or cancel to deny the application's request.</qt>",
734 wallet.toHtmlEscaped()));
735 } else {
736 kpd->setPrompt(
737 i18n("<qt>The application '<b>%1</b>' has requested to create a new wallet named '<b>%2</b>'. "
738 "Please choose a password for this wallet, or cancel to deny the application's request.</qt>",
739 appid.toHtmlEscaped(),
740 wallet.toHtmlEscaped()));
741 }
742 }
743 kpd->setWindowTitle(i18n("KDE Wallet Service"));
744 // KF5 FIXME what should we use now instead of this:
745 // kpd->setButtonGuiItem(KDialog::Ok,KGuiItem(i18n("C&reate"),"document-new"));
746 kpd->setIcon(QIcon::fromTheme(QStringLiteral("kwalletmanager")));
747 while (!b->isOpen()) {
748 setupDialog(kpd, w, appid, modal);
749 if (kpd->exec() == QDialog::Accepted) {
750 password = kpd->password();
751 int rc = b->open(password.toUtf8());
752 if (!b->isOpen()) {
753 kpd->setPrompt(i18n("<qt>Error opening the wallet '<b>%1</b>'. Please try again.<br />(Error code %2: %3)</qt>",
754 wallet.toHtmlEscaped(),
755 rc,
756 KWallet::Backend::openRCToString(rc)));
757 }
758 } else {
759 break;
760 }
761 }
762 delete kpd;
763#ifdef HAVE_GPGMEPP
764 }
765#endif
766 }
767
768 if ((b->cipherType() == KWallet::BACKEND_CIPHER_BLOWFISH) && !emptyPass && (password.isNull() || !b->isOpen())) {
769 delete b;
770 return -1;
771 }
772
773 if (emptyPass && !isAuthorizedApp(appid, wallet, w)) {
774 delete b;
775 return -1;
776 }
777
778 _wallets.insert(rc = generateHandle(), b);
779 _sessions.addSession(appid, service, rc);
780 _syncTimers.addTimer(rc, _syncTime);
781
782 if (brandNew) {
783 createFolder(rc, KWallet::Wallet::PasswordFolder(), appid);
784 createFolder(rc, KWallet::Wallet::FormDataFolder(), appid);
785 }
786
787 b->ref();
788 if (_closeIdle) {
789 _closeTimers.addTimer(rc, _idleTime);
790 }
791 if (brandNew) {
792 Q_EMIT walletCreated(wallet);
793 }
794 Q_EMIT walletOpened(wallet);
795 if (_wallets.count() == 1 && _launchManager) {
796 startManagerForKSecretD();
797 }
798 } else {
799 // prematurely add a reference so that the wallet does not close while
800 // the
801 // authorization dialog is being shown.
802 walletInfo.second->ref();
803 bool isAuthorized = _sessions.hasSession(appid, rc) || isAuthorizedApp(appid, wallet, w);
804 // as the wallet might have been forcefully closed, find it again to
805 // make sure it's
806 // still available (isAuthorizedApp might show a dialog).
807 walletInfo = findWallet(wallet);
808 if (!isAuthorized) {
809 if (walletInfo.first != -1) {
810 walletInfo.second->deref();
811 // check if the wallet should be closed now.
812 internalClose(walletInfo.second, walletInfo.first, false);
813 }
814 return -1;
815 } else {
816 if (walletInfo.first != -1) {
817 _sessions.addSession(appid, service, rc);
818 } else {
819 // wallet was forcefully closed.
820 return -1;
821 }
822 }
823 }
824
825 return rc;
826}
827
828bool KSecretD::isAuthorizedApp(const QString &appid, const QString &wallet, WId w)
829{
830 if (!_openPrompt) {
831 return true;
832 }
833
834 int response = 0;
835
836 QString thisApp;
837 if (appid.isEmpty()) {
838 thisApp = QStringLiteral("KDE System");
839 } else {
840 thisApp = appid;
841 }
842
843 if (!implicitAllow(wallet, thisApp)) {
844 KConfigGroup cfg = KSharedConfig::openConfig(QStringLiteral("kwalletrc"))->group("Auto Allow");
845 if (!cfg.isEntryImmutable(wallet)) {
846 KBetterThanKDialog *dialog = new KBetterThanKDialog;
847 dialog->setWindowTitle(i18n("KDE Wallet Service"));
848 if (appid.isEmpty()) {
849 dialog->setLabel(i18n("<qt>KDE has requested access to the open wallet '<b>%1</b>'.</qt>", wallet.toHtmlEscaped()));
850 } else {
851 dialog->setLabel(i18n("<qt>The application '<b>%1</b>' has requested access to the open wallet '<b>%2</b>'.</qt>",
852 appid.toHtmlEscaped(),
853 wallet.toHtmlEscaped()));
854 }
855 setupDialog(dialog, w, appid, false);
856 response = dialog->exec();
857 delete dialog;
858 }
859 }
860
861 if (response == 0 || response == 1) {
862 if (response == 1) {
863 KConfigGroup cfg = KSharedConfig::openConfig(QStringLiteral("kwalletrc"))->group("Auto Allow");
864 QStringList apps = cfg.readEntry(wallet, QStringList());
865 if (!apps.contains(thisApp)) {
866 if (cfg.isEntryImmutable(wallet)) {
867 return false;
868 }
869 apps += thisApp;
870 _implicitAllowMap[wallet] += thisApp;
871 cfg.writeEntry(wallet, apps);
872 cfg.sync();
873 }
874 }
875 } else if (response == 3) {
876 KConfigGroup cfg = KSharedConfig::openConfig(QStringLiteral("kwalletrc"))->group("Auto Deny");
877 QStringList apps = cfg.readEntry(wallet, QStringList());
878 if (!apps.contains(thisApp)) {
879 apps += thisApp;
880 _implicitDenyMap[wallet] += thisApp;
881 cfg.writeEntry(wallet, apps);
882 cfg.sync();
883 }
884 return false;
885 } else {
886 return false;
887 }
888 return true;
889}
890
891int KSecretD::deleteWallet(const QString &wallet)
892{
893 int result = -1;
894 QString path = KWallet::Backend::getSaveLocation() + "/" + encodeWalletName(wallet) + ".kwl";
895 QString pathSalt = KWallet::Backend::getSaveLocation() + "/" + encodeWalletName(wallet) + ".salt";
896
897 if (QFile::exists(path)) {
898 const QPair<int, KWallet::Backend *> walletInfo = findWallet(wallet);
899 internalClose(walletInfo.second, walletInfo.first, true);
900 QFile::remove(path);
901 Q_EMIT walletDeleted(wallet);
902 // also delete access control entries
903 KConfigGroup cfgAllow = KSharedConfig::openConfig(QStringLiteral("kwalletrc"))->group("Auto Allow");
904 cfgAllow.deleteEntry(wallet);
905
906 KConfigGroup cfgDeny = KSharedConfig::openConfig(QStringLiteral("kwalletrc"))->group("Auto Deny");
907 cfgDeny.deleteEntry(wallet);
908
909 if (QFile::exists(pathSalt)) {
910 QFile::remove(pathSalt);
911 }
912
913 result = 0;
914 }
915
916 return result;
917}
918
919void KSecretD::changePassword(const QString &wallet, qlonglong wId, const QString &appid)
920{
921 KWalletTransaction *xact = new KWalletTransaction(connection());
922
923 message().setDelayedReply(true);
924 xact->message = message();
925 // TODO GPG this shouldn't be allowed on a GPG managed wallet; a warning
926 // should be displayed about this
927
928 xact->appid = appid;
929 xact->wallet = wallet;
930 xact->wId = wId;
931 xact->modal = false;
932 xact->tType = KWalletTransaction::ChangePassword;
933
934 _transactions.append(xact);
935
936 QTimer::singleShot(0, this, SLOT(processTransactions()));
937 checkActiveDialog();
938 checkActiveDialog();
939}
940
941void KSecretD::initiateSync(int handle)
942{
943 // add a timer and reset it right away
944 _syncTimers.addTimer(handle, _syncTime);
945 _syncTimers.resetTimer(handle, _syncTime);
946}
947
948void KSecretD::doTransactionChangePassword(const QString &appid, const QString &wallet, qlonglong wId)
949{
950 const QPair<int, KWallet::Backend *> walletInfo = findWallet(wallet);
951 int handle = walletInfo.first;
952 KWallet::Backend *w = walletInfo.second;
953
954 bool reclose = false;
955 if (!w) {
956 handle = doTransactionOpen(appid, wallet, false, wId, false, QLatin1String(""));
957 if (-1 == handle) {
958 KMessageBox::errorWId((WId)wId,
959 i18n("Unable to open wallet. The wallet must be opened in order to change the password."),
960 i18n("KDE Wallet Service"));
961 return;
962 }
963
964 w = _wallets.value(handle);
965 reclose = true;
966 }
967
968 assert(w);
969
970#ifdef HAVE_GPGMEPP
971 if (w->cipherType() == KWallet::BACKEND_CIPHER_GPG) {
972 QString keyID = w->gpgKey().shortKeyID();
973 assert(!keyID.isNull());
974 KMessageBox::errorWId((WId)wId,
975 i18n("<qt>The <b>%1</b> wallet is encrypted using GPG key <b>%2</b>. Please use <b>GPG</b> tools (such "
976 "as <b>kleopatra</b>) to change the passphrase associated to that key.</qt>",
977 wallet.toHtmlEscaped(),
978 keyID));
979 } else {
980#endif
981 QPointer<KNewPasswordDialog> kpd = new KNewPasswordDialog();
982 kpd->setPrompt(i18n("<qt>Please choose a new password for the wallet '<b>%1</b>'.</qt>", wallet.toHtmlEscaped()));
983 kpd->setWindowTitle(i18n("KDE Wallet Service"));
984 kpd->setAllowEmptyPasswords(true);
985 KColorScheme colorScheme(QPalette::Active, KColorScheme::View);
986 kpd->setBackgroundWarningColor(colorScheme.background(KColorScheme::NegativeBackground).color());
987 setupDialog(kpd, (WId)wId, appid, false);
988 if (kpd->exec() == QDialog::Accepted && kpd) {
989 QString p = kpd->password();
990 if (!p.isNull()) {
991 w->setPassword(p.toUtf8());
992 int rc = w->close(true);
993 if (rc < 0) {
994 KMessageBox::errorWId((WId)wId, i18n("Error re-encrypting the wallet. Password was not changed."), i18n("KDE Wallet Service"));
995 reclose = true;
996 } else {
997 rc = w->open(p.toUtf8());
998 if (rc < 0) {
999 KMessageBox::errorWId((WId)wId, i18n("Error reopening the wallet. Data may be lost."), i18n("KDE Wallet Service"));
1000 reclose = true;
1001 }
1002 }
1003 }
1004 }
1005
1006 delete kpd;
1007#ifdef HAVE_GPGMEPP
1008 }
1009#endif
1010
1011 if (reclose) {
1012 internalClose(w, handle, true);
1013 }
1014}
1015
1016int KSecretD::close(const QString &wallet, bool force)
1017{
1018 const QPair<int, KWallet::Backend *> walletInfo = findWallet(wallet);
1019 int handle = walletInfo.first;
1020 KWallet::Backend *w = walletInfo.second;
1021
1022 return internalClose(w, handle, force);
1023}
1024
1025int KSecretD::internalClose(KWallet::Backend *const w, const int handle, const bool force, const bool saveBeforeClose)
1026{
1027 if (w) {
1028 const QString &wallet = w->walletName();
1029 if ((w->refCount() == 0 && !_leaveOpen) || force) {
1030 // this is only a safety measure. sessions should be gone already.
1031 _sessions.removeAllSessions(handle);
1032 if (_closeIdle) {
1033 _closeTimers.removeTimer(handle);
1034 }
1035 _syncTimers.removeTimer(handle);
1036 _wallets.remove(handle);
1037 w->close(saveBeforeClose);
1038 doCloseSignals(handle, wallet);
1039 delete w;
1040 return 0;
1041 }
1042 return 1;
1043 }
1044
1045 return -1;
1046}
1047
1048int KSecretD::close(int handle, bool force, const QString &appid, const QDBusMessage &message)
1049{
1050 KWallet::Backend *w = _wallets.value(handle);
1051
1052 if (w) {
1053 if (_sessions.hasSession(appid, handle)) {
1054 // remove one handle for the application
1055 bool removed = _sessions.removeSession(appid, message.service(), handle);
1056 // alternatively try sessionless
1057 if (removed || _sessions.removeSession(appid, QLatin1String(""), handle)) {
1058 w->deref();
1059 }
1060 return internalClose(w, handle, force);
1061 }
1062 return 1; // not closed, handle unknown
1063 }
1064 return -1; // not open to begin with, or other error
1065}
1066
1067int KSecretD::close(int handle, bool force, const QString &appid)
1068{
1069 return close(handle, force, appid, message());
1070}
1071
1072bool KSecretD::isOpen(const QString &wallet)
1073{
1074 const QPair<int, KWallet::Backend *> walletInfo = findWallet(wallet);
1075 return walletInfo.second != nullptr;
1076}
1077
1078bool KSecretD::isOpen(int handle)
1079{
1080 if (handle == 0) {
1081 return false;
1082 }
1083
1084 KWallet::Backend *rc = _wallets.value(handle);
1085
1086 if (rc == nullptr && ++_failed > 5) {
1087 _failed = 0;
1088 QTimer::singleShot(0, this, SLOT(notifyFailures()));
1089 } else if (rc != nullptr) {
1090 _failed = 0;
1091 }
1092
1093 return rc != nullptr;
1094}
1095
1096QStringList KSecretD::wallets() const
1097{
1098 QString path = KWallet::Backend::getSaveLocation();
1099 QDir dir(path, QStringLiteral("*.kwl"));
1100 QStringList rc;
1101
1102 dir.setFilter(QDir::Files | QDir::Hidden);
1103
1104 const auto list = dir.entryInfoList();
1105 for (const QFileInfo &fi : list) {
1106 QString fn = fi.fileName();
1107 if (fn.endsWith(QLatin1String(".kwl"))) {
1108 fn.truncate(fn.length() - 4);
1109 }
1110 rc += decodeWalletName(fn);
1111 }
1112 return rc;
1113}
1114
1115void KSecretD::sync(int handle, const QString &appid)
1116{
1117 KWallet::Backend *b;
1118
1119 // get the wallet and check if we have a password for it (safety measure)
1120 if ((b = getWallet(appid, handle))) {
1121 QString wallet = b->walletName();
1122 b->sync(0);
1123 }
1124}
1125
1126void KSecretD::timedOutSync(int handle)
1127{
1128 _syncTimers.removeTimer(handle);
1129 if (_wallets.contains(handle) && _wallets[handle]) {
1130 _wallets[handle]->sync(0);
1131 } else {
1132 qDebug("wallet not found for sync!");
1133 }
1134}
1135
1136void KSecretD::doTransactionOpenCancelled(const QString &appid, const QString &wallet, const QString &service)
1137{
1138 // there will only be one session left to remove - all others
1139 // have already been removed in slotServiceOwnerChanged and all
1140 // transactions for opening new sessions have been deleted.
1141 if (!_sessions.hasSession(appid)) {
1142 return;
1143 }
1144
1145 const QPair<int, KWallet::Backend *> walletInfo = findWallet(wallet);
1146 int handle = walletInfo.first;
1147 KWallet::Backend *b = walletInfo.second;
1148 if (handle != -1 && b) {
1149 b->deref();
1150 internalClose(b, handle, false);
1151 }
1152
1153 // close the session in case the wallet hasn't been closed yet
1154 _sessions.removeSession(appid, service, handle);
1155}
1156
1157QStringList KSecretD::folderList(int handle, const QString &appid)
1158{
1159 KWallet::Backend *b;
1160
1161 if ((b = getWallet(appid, handle))) {
1162 return b->folderList();
1163 }
1164
1165 return QStringList();
1166}
1167
1168bool KSecretD::hasFolder(int handle, const QString &f, const QString &appid)
1169{
1170 KWallet::Backend *b;
1171
1172 if ((b = getWallet(appid, handle))) {
1173 return b->hasFolder(f);
1174 }
1175
1176 return false;
1177}
1178
1179bool KSecretD::removeFolder(int handle, const QString &f, const QString &appid)
1180{
1181 KWallet::Backend *b;
1182
1183 if ((b = getWallet(appid, handle))) {
1184 bool rc = b->removeFolder(f);
1185 initiateSync(handle);
1186 Q_EMIT folderListUpdated(b->walletName());
1187 return rc;
1188 }
1189
1190 return false;
1191}
1192
1193bool KSecretD::createFolder(int handle, const QString &f, const QString &appid)
1194{
1195 KWallet::Backend *b;
1196
1197 if ((b = getWallet(appid, handle))) {
1198 bool rc = b->createFolder(f);
1199 initiateSync(handle);
1200 Q_EMIT folderListUpdated(b->walletName());
1201 return rc;
1202 }
1203
1204 return false;
1205}
1206
1207QByteArray KSecretD::readMap(int handle, const QString &folder, const QString &key, const QString &appid)
1208{
1209 KWallet::Backend *b;
1210
1211 if ((b = getWallet(appid, handle))) {
1212 b->setFolder(folder);
1213 KWallet::Entry *e = b->readEntry(key);
1214 if (e && e->type() == KWallet::Wallet::Map) {
1215 return e->map();
1216 }
1217 }
1218
1219 return QByteArray();
1220}
1221
1222#if KWALLET_BUILD_DEPRECATED_SINCE(5, 72)
1223QVariantMap KSecretD::readMapList(int handle, const QString &folder, const QString &key, const QString &appid)
1224{
1225 KWallet::Backend *b;
1226
1227 if ((b = getWallet(appid, handle))) {
1228 b->setFolder(folder);
1229 QVariantMap rc;
1230 const auto lst = b->readEntryList(key);
1231 for (KWallet::Entry *entry : lst) {
1232 if (entry->type() == KWallet::Wallet::Map) {
1233 rc.insert(entry->key(), entry->map());
1234 }
1235 }
1236 return rc;
1237 }
1238
1239 return QVariantMap();
1240}
1241#endif
1242
1243QVariantMap KSecretD::mapList(int handle, const QString &folder, const QString &appid)
1244{
1245 QVariantMap rc;
1246
1247 KWallet::Backend *backend = getWallet(appid, handle);
1248 if (backend) {
1249 backend->setFolder(folder);
1250 const QList<KWallet::Entry *> lst = backend->entriesList();
1251 for (KWallet::Entry *entry : lst) {
1252 if (entry->type() == KWallet::Wallet::Map) {
1253 rc.insert(entry->key(), entry->map());
1254 }
1255 }
1256 }
1257
1258 return rc;
1259}
1260
1261QByteArray KSecretD::readEntry(int handle, const QString &folder, const QString &key, const QString &appid)
1262{
1263 KWallet::Backend *b;
1264
1265 if ((b = getWallet(appid, handle))) {
1266 b->setFolder(folder);
1267 KWallet::Entry *e = b->readEntry(key);
1268 if (e) {
1269 return e->value();
1270 }
1271 }
1272
1273 return QByteArray();
1274}
1275
1276#if KWALLET_BUILD_DEPRECATED_SINCE(5, 72)
1277QVariantMap KSecretD::readEntryList(int handle, const QString &folder, const QString &key, const QString &appid)
1278{
1279 KWallet::Backend *b;
1280
1281 if ((b = getWallet(appid, handle))) {
1282 b->setFolder(folder);
1283 QVariantMap rc;
1284 const auto lst = b->readEntryList(key);
1285 for (KWallet::Entry *entry : lst) {
1286 rc.insert(entry->key(), entry->value());
1287 }
1288 return rc;
1289 }
1290
1291 return QVariantMap();
1292}
1293#endif
1294
1295QVariantMap KSecretD::entriesList(int handle, const QString &folder, const QString &appid)
1296{
1297 QVariantMap rc;
1298
1299 KWallet::Backend *backend = getWallet(appid, handle);
1300 if (backend) {
1301 backend->setFolder(folder);
1302 const QList<KWallet::Entry *> lst = backend->entriesList();
1303 for (KWallet::Entry *entry : lst) {
1304 rc.insert(entry->key(), entry->value());
1305 }
1306 }
1307
1308 return rc;
1309}
1310
1311QStringList KSecretD::entryList(int handle, const QString &folder, const QString &appid)
1312{
1313 KWallet::Backend *b;
1314
1315 if ((b = getWallet(appid, handle))) {
1316 b->setFolder(folder);
1317 return b->entryList();
1318 }
1319
1320 return QStringList();
1321}
1322
1323QString KSecretD::readPassword(int handle, const QString &folder, const QString &key, const QString &appid)
1324{
1325 KWallet::Backend *b;
1326
1327 if ((b = getWallet(appid, handle))) {
1328 b->setFolder(folder);
1329 KWallet::Entry *e = b->readEntry(key);
1330 if (e && e->type() == KWallet::Wallet::Password) {
1331 return e->password();
1332 }
1333 }
1334
1335 return QString();
1336}
1337
1338#if KWALLET_BUILD_DEPRECATED_SINCE(5, 72)
1339QVariantMap KSecretD::readPasswordList(int handle, const QString &folder, const QString &key, const QString &appid)
1340{
1341 KWallet::Backend *b;
1342
1343 if ((b = getWallet(appid, handle))) {
1344 b->setFolder(folder);
1345 QVariantMap rc;
1346 const auto lst = b->readEntryList(key);
1347 for (KWallet::Entry *entry : lst) {
1348 if (entry->type() == KWallet::Wallet::Password) {
1349 rc.insert(entry->key(), entry->password());
1350 }
1351 }
1352 return rc;
1353 }
1354
1355 return QVariantMap();
1356}
1357#endif
1358
1359QVariantMap KSecretD::passwordList(int handle, const QString &folder, const QString &appid)
1360{
1361 QVariantMap rc;
1362
1363 KWallet::Backend *backend = getWallet(appid, handle);
1364 if (backend) {
1365 backend->setFolder(folder);
1366 const QList<KWallet::Entry *> lst = backend->entriesList();
1367 for (KWallet::Entry *entry : lst) {
1368 if (entry->type() == KWallet::Wallet::Password) {
1369 rc.insert(entry->key(), entry->password());
1370 }
1371 }
1372 }
1373
1374 return rc;
1375}
1376
1377int KSecretD::writeMap(int handle, const QString &folder, const QString &key, const QByteArray &value, const QString &appid)
1378{
1379 KWallet::Backend *b;
1380
1381 if ((b = getWallet(appid, handle))) {
1382 b->setFolder(folder);
1383 KWallet::Entry e;
1384 e.setKey(key);
1385 e.setValue(value);
1386 e.setType(KWallet::Wallet::Map);
1387 b->writeEntry(&e);
1388 initiateSync(handle);
1389 emitFolderUpdated(b->walletName(), folder);
1390 emitEntryUpdated(b->walletName(), folder, key);
1391 return 0;
1392 }
1393
1394 return -1;
1395}
1396
1397int KSecretD::writeEntry(int handle, const QString &folder, const QString &key, const QByteArray &value, const QString &appid)
1398{
1399 KWallet::Backend *b;
1400
1401 if ((b = getWallet(appid, handle))) {
1402 b->setFolder(folder);
1403 KWallet::Entry e;
1404 e.setKey(key);
1405 e.setValue(value);
1406 e.setType(KWallet::Wallet::Stream);
1407 b->writeEntry(&e);
1408 initiateSync(handle);
1409 emitFolderUpdated(b->walletName(), folder);
1410 emitEntryUpdated(b->walletName(), folder, key);
1411 return 0;
1412 }
1413
1414 return -1;
1415}
1416
1417int KSecretD::writeEntry(int handle, const QString &folder, const QString &key, const QByteArray &value, int entryType, const QString &appid)
1418{
1419 KWallet::Backend *b;
1420
1421 if ((b = getWallet(appid, handle))) {
1422 b->setFolder(folder);
1423 KWallet::Entry e;
1424 e.setKey(key);
1425 e.setValue(value);
1426 e.setType(KWallet::Wallet::EntryType(entryType));
1427 b->writeEntry(&e);
1428 initiateSync(handle);
1429 emitFolderUpdated(b->walletName(), folder);
1430 return 0;
1431 }
1432
1433 return -1;
1434}
1435
1436int KSecretD::writePassword(int handle, const QString &folder, const QString &key, const QString &value, const QString &appid)
1437{
1438 KWallet::Backend *b;
1439
1440 if ((b = getWallet(appid, handle))) {
1441 b->setFolder(folder);
1442 KWallet::Entry e;
1443 e.setKey(key);
1444 e.setValue(value);
1445 e.setType(KWallet::Wallet::Password);
1446 b->writeEntry(&e);
1447 initiateSync(handle);
1448 emitFolderUpdated(b->walletName(), folder);
1449 emitEntryUpdated(b->walletName(), folder, key);
1450 return 0;
1451 }
1452
1453 return -1;
1454}
1455
1456int KSecretD::entryType(int handle, const QString &folder, const QString &key, const QString &appid)
1457{
1458 KWallet::Backend *b;
1459
1460 if ((b = getWallet(appid, handle))) {
1461 if (!b->hasFolder(folder)) {
1462 return KWallet::Wallet::Unknown;
1463 }
1464 b->setFolder(folder);
1465 if (b->hasEntry(key)) {
1466 return b->readEntry(key)->type();
1467 }
1468 }
1469
1470 return KWallet::Wallet::Unknown;
1471}
1472
1473bool KSecretD::hasEntry(int handle, const QString &folder, const QString &key, const QString &appid)
1474{
1475 KWallet::Backend *b;
1476
1477 if ((b = getWallet(appid, handle))) {
1478 if (!b->hasFolder(folder)) {
1479 return false;
1480 }
1481 b->setFolder(folder);
1482 return b->hasEntry(key);
1483 }
1484
1485 return false;
1486}
1487
1488int KSecretD::removeEntry(int handle, const QString &folder, const QString &key, const QString &appid)
1489{
1490 KWallet::Backend *b;
1491
1492 if ((b = getWallet(appid, handle))) {
1493 if (!b->hasFolder(folder)) {
1494 return 0;
1495 }
1496 b->setFolder(folder);
1497 bool rc = b->removeEntry(key);
1498 initiateSync(handle);
1499 emitFolderUpdated(b->walletName(), folder);
1500 emitEntryDeleted(b->walletName(), folder, key);
1501 return rc ? 0 : -3;
1502 }
1503
1504 return -1;
1505}
1506
1507void KSecretD::slotServiceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner)
1508{
1509 Q_UNUSED(name);
1510 qCDebug(KSECRETD_LOG) << "slotServiceOwnerChanged " << name << ", " << oldOwner << ", " << newOwner;
1511
1512 if (!newOwner.isEmpty()) {
1513 return; // no application exit, don't care.
1514 }
1515
1516 // as we don't have the application id we have to cycle
1517 // all sessions. As an application can basically open wallets
1518 // with several appids, we can't stop if we found one.
1519 QString service(oldOwner);
1520 const QList<KWalletAppHandlePair> sessremove(_sessions.findSessions(service));
1521 KWallet::Backend *b = nullptr;
1522
1523 // check all sessions for wallets to close
1524 for (const KWalletAppHandlePair &s : sessremove) {
1525 b = getWallet(s.first, s.second);
1526 if (b) {
1527 b->deref();
1528 internalClose(b, s.second, false);
1529 }
1530 }
1531
1532 // remove all the sessions in case they aren't gone yet
1533 for (const KWalletAppHandlePair &s : sessremove) {
1534 _sessions.removeSession(s.first, service, s.second);
1535 }
1536
1537 // cancel all open-transactions still running for the service
1538 QList<KWalletTransaction *>::iterator tit;
1539 for (tit = _transactions.begin(); tit != _transactions.end(); ++tit) {
1540 if ((*tit)->tType == KWalletTransaction::Open && (*tit)->service == oldOwner) {
1541 delete (*tit);
1542 *tit = nullptr;
1543 }
1544 }
1545 _transactions.removeAll(nullptr);
1546
1547 // if there's currently an open-transaction being handled,
1548 // mark it as cancelled.
1549 if (_curtrans && _curtrans->tType == KWalletTransaction::Open && _curtrans->service == oldOwner) {
1550 qCDebug(KSECRETD_LOG) << "Cancelling current transaction!";
1551 _curtrans->cancelled = true;
1552 }
1553 _serviceWatcher.removeWatchedService(oldOwner);
1554}
1555
1556KWallet::Backend *KSecretD::getWallet(const QString &appid, int handle)
1557{
1558 if (handle == 0) {
1559 return nullptr;
1560 }
1561
1562 KWallet::Backend *w = _wallets.value(handle);
1563
1564 if (w) { // the handle is valid
1565 if (_sessions.hasSession(appid, handle)) {
1566 // the app owns this handle
1567 _failed = 0;
1568 if (_closeIdle) {
1569 _closeTimers.resetTimer(handle, _idleTime);
1570 }
1571 return w;
1572 }
1573 }
1574
1575 if (++_failed > 5) {
1576 _failed = 0;
1577 QTimer::singleShot(0, this, SLOT(notifyFailures()));
1578 }
1579
1580 return nullptr;
1581}
1582
1583void KSecretD::notifyFailures()
1584{
1585 if (!_showingFailureNotify) {
1586 _showingFailureNotify = true;
1588 i18n("There have been repeated failed attempts to gain access to a wallet. An application may be misbehaving."),
1589 i18n("KDE Wallet Service"));
1590 _showingFailureNotify = false;
1591 }
1592}
1593
1594void KSecretD::doCloseSignals(int handle, const QString &wallet)
1595{
1596 Q_EMIT walletClosed(handle);
1597 Q_EMIT walletClosedId(handle);
1598
1599 Q_EMIT walletClosed(wallet);
1600 if (_wallets.isEmpty()) {
1601 Q_EMIT allWalletsClosed();
1602 }
1603}
1604
1605int KSecretD::renameEntry(int handle, const QString &folder, const QString &oldName, const QString &newName, const QString &appid)
1606{
1607 KWallet::Backend *b;
1608
1609 if ((b = getWallet(appid, handle))) {
1610 b->setFolder(folder);
1611 int rc = b->renameEntry(oldName, newName);
1612 initiateSync(handle);
1613 emitFolderUpdated(b->walletName(), folder);
1614 emitEntryRenamed(b->walletName(), folder, oldName, newName);
1615 return rc;
1616 }
1617
1618 return -1;
1619}
1620
1621int KSecretD::renameWallet(const QString &oldName, const QString &newName)
1622{
1623 const QPair<int, KWallet::Backend *> walletInfo = findWallet(oldName);
1624 return walletInfo.second->renameWallet(newName);
1625}
1626
1627QStringList KSecretD::users(const QString &wallet) const
1628{
1629 const QPair<int, KWallet::Backend *> walletInfo = findWallet(wallet);
1630 return _sessions.getApplications(walletInfo.first);
1631}
1632
1633bool KSecretD::disconnectApplication(const QString &wallet, const QString &application)
1634{
1635 const QPair<int, KWallet::Backend *> walletInfo = findWallet(wallet);
1636 int handle = walletInfo.first;
1637 KWallet::Backend *backend = walletInfo.second;
1638
1639 if (handle != -1 && _sessions.hasSession(application, handle)) {
1640 int removed = _sessions.removeAllSessions(application, handle);
1641
1642 for (int i = 0; i < removed; ++i) {
1643 backend->deref();
1644 }
1645 internalClose(backend, handle, false);
1646
1647 Q_EMIT applicationDisconnected(wallet, application);
1648 return true;
1649 }
1650
1651 return false;
1652}
1653
1654void KSecretD::emitFolderUpdated(const QString &wallet, const QString &folder)
1655{
1656 Q_EMIT folderUpdated(wallet, folder);
1657}
1658
1659void KSecretD::emitEntryUpdated(const QString &wallet, const QString &folder, const QString &key)
1660{
1661 Q_EMIT entryUpdated(wallet, folder, key);
1662}
1663
1664void KSecretD::emitEntryRenamed(const QString &wallet, const QString &folder, const QString &oldName, const QString &newName)
1665{
1666 Q_EMIT entryRenamed(wallet, folder, oldName, newName);
1667}
1668
1669void KSecretD::emitEntryDeleted(const QString &wallet, const QString &folder, const QString &key)
1670{
1671 Q_EMIT entryDeleted(wallet, folder, key);
1672}
1673
1674void KSecretD::emitWalletListDirty()
1675{
1676 const QStringList walletsInDisk = wallets();
1677 const auto lst = _wallets.values();
1678 for (auto i : lst) {
1679 if (!walletsInDisk.contains(i->walletName())) {
1680 internalClose(i, _wallets.key(i), true, false);
1681 }
1682 }
1683 Q_EMIT walletListDirty();
1684}
1685
1686void KSecretD::reconfigure()
1687{
1688 KConfig cfg(QStringLiteral("kwalletrc"));
1689 KConfigGroup walletGroup(&cfg, "Wallet");
1690 _firstUse = walletGroup.readEntry("First Use", true);
1691 _launchManager = walletGroup.readEntry("Launch Manager", false);
1692 _leaveOpen = walletGroup.readEntry("Leave Open", true);
1693 bool idleSave = _closeIdle;
1694 _closeIdle = walletGroup.readEntry("Close When Idle", false);
1695 _openPrompt = walletGroup.readEntry("Prompt on Open", false);
1696 int timeSave = _idleTime;
1697 // in minutes!
1698 _idleTime = walletGroup.readEntry("Idle Timeout", 10) * 60 * 1000;
1699#ifdef Q_WS_X11
1700 if (walletGroup.readEntry("Close on Screensaver", false)) {
1701 // BUG 254273 : if KSecretD starts before the screen saver, then the
1702 // connection fails and KSecretD never receives it's notifications
1703 // To fix this, we use a timer and perform periodic connection
1704 // attempts until connection succeeds
1705 QTimer::singleShot(0, this, SLOT(connectToScreenSaver()));
1706 } else {
1707 if (screensaver && screensaver->isValid()) {
1708 screensaver->disconnect(SIGNAL(ActiveChanged(bool)), this, SLOT(screenSaverChanged(bool)));
1709 delete screensaver;
1710 screensaver = 0;
1711 }
1712 }
1713#endif
1714 // Handle idle changes
1715 if (_closeIdle) {
1716 if (_idleTime != timeSave) { // Timer length changed
1717 Wallets::const_iterator it = _wallets.constBegin();
1718 const Wallets::const_iterator end = _wallets.constEnd();
1719 for (; it != end; ++it) {
1720 _closeTimers.resetTimer(it.key(), _idleTime);
1721 }
1722 }
1723
1724 if (!idleSave) { // add timers for all the wallets
1725 Wallets::const_iterator it = _wallets.constBegin();
1726 const Wallets::const_iterator end = _wallets.constEnd();
1727 for (; it != end; ++it) {
1728 _closeTimers.addTimer(it.key(), _idleTime);
1729 }
1730 }
1731 } else {
1732 _closeTimers.clear();
1733 }
1734
1735 // Update the implicit allow stuff
1736 _implicitAllowMap.clear();
1737 const KConfigGroup autoAllowGroup(&cfg, "Auto Allow");
1738 QStringList entries = autoAllowGroup.entryMap().keys();
1739 for (QStringList::const_iterator i = entries.constBegin(); i != entries.constEnd(); ++i) {
1740 _implicitAllowMap[*i] = autoAllowGroup.readEntry(*i, QStringList());
1741 }
1742
1743 // Update the implicit allow stuff
1744 _implicitDenyMap.clear();
1745 const KConfigGroup autoDenyGroup(&cfg, "Auto Deny");
1746 entries = autoDenyGroup.entryMap().keys();
1747 for (QStringList::const_iterator i = entries.constBegin(); i != entries.constEnd(); ++i) {
1748 _implicitDenyMap[*i] = autoDenyGroup.readEntry(*i, QStringList());
1749 }
1750
1751 // Update if wallet was enabled/disabled
1752 if (!isEnabled()) { // close all wallets
1753 while (!_wallets.isEmpty()) {
1754 Wallets::const_iterator it = _wallets.constBegin();
1755 internalClose(it.value(), it.key(), true);
1756 }
1758 }
1759}
1760
1761bool KSecretD::isEnabled()
1762{
1763 KConfig cfg(QStringLiteral("kwalletrc"));
1764 KConfigGroup walletGroup(&cfg, "Wallet");
1765 KConfigGroup ksecretdGroup(&cfg, "KSecretD");
1766 // For KSecredD to be enabled, it needs both global kwallet enabled and ksecretd enabled
1767 // values, as it does not make sense without kwallet, but is possible kwallet without
1768 // ksecretd
1769 return ksecretdGroup.readEntry("Enabled", true) && walletGroup.readEntry("Enabled", true);
1770}
1771
1772bool KSecretD::folderDoesNotExist(const QString &wallet, const QString &folder)
1773{
1774 if (!wallets().contains(wallet)) {
1775 return true;
1776 }
1777
1778 const QPair<int, KWallet::Backend *> walletInfo = findWallet(wallet);
1779 if (walletInfo.second) {
1780 return walletInfo.second->folderDoesNotExist(folder);
1781 }
1782
1783 KWallet::Backend *b = new KWallet::Backend(wallet);
1784 b->open(QByteArray());
1785 bool rc = b->folderDoesNotExist(folder);
1786 delete b;
1787 return rc;
1788}
1789
1790bool KSecretD::keyDoesNotExist(const QString &wallet, const QString &folder, const QString &key)
1791{
1792 if (!wallets().contains(wallet)) {
1793 return true;
1794 }
1795
1796 const QPair<int, KWallet::Backend *> walletInfo = findWallet(wallet);
1797 if (walletInfo.second) {
1798 return walletInfo.second->entryDoesNotExist(folder, key);
1799 }
1800
1801 KWallet::Backend *b = new KWallet::Backend(wallet);
1802 b->open(QByteArray());
1803 bool rc = b->entryDoesNotExist(folder, key);
1804 delete b;
1805 return rc;
1806}
1807
1808bool KSecretD::implicitAllow(const QString &wallet, const QString &app)
1809{
1810 return _implicitAllowMap[wallet].contains(app);
1811}
1812
1813bool KSecretD::implicitDeny(const QString &wallet, const QString &app)
1814{
1815 return _implicitDenyMap[wallet].contains(app);
1816}
1817
1818void KSecretD::timedOutClose(int id)
1819{
1820 KWallet::Backend *w = _wallets.value(id);
1821 if (w) {
1822 internalClose(w, id, true);
1823 }
1824}
1825
1826void KSecretD::closeAllWallets()
1827{
1828 Wallets walletsCopy = _wallets;
1829
1830 Wallets::const_iterator it = walletsCopy.constBegin();
1831 const Wallets::const_iterator end = walletsCopy.constEnd();
1832 for (; it != end; ++it) {
1833 internalClose(it.value(), it.key(), true);
1834 }
1835
1836 walletsCopy.clear();
1837
1838 // All of this should be basically noop. Let's just be safe.
1839 _wallets.clear();
1840}
1841
1842QString KSecretD::networkWallet()
1843{
1845}
1846
1847QString KSecretD::localWallet()
1848{
1850}
1851
1852void KSecretD::screenSaverChanged(bool s)
1853{
1854 if (s) {
1855 closeAllWallets();
1856 }
1857}
1858
1859void KSecretD::activatePasswordDialog()
1860{
1861 checkActiveDialog();
1862}
1863
1864int KSecretD::pamOpen(const QString &wallet, const QByteArray &passwordHash, int sessionTimeout)
1865{
1866 if (_processing) {
1867 return -1;
1868 }
1869
1870 bool brandNew = false;
1871
1872 // check if the wallet is already open
1873 QPair<int, KWallet::Backend *> walletInfo = findWallet(wallet);
1874 int rc = walletInfo.first;
1875 if (rc != -1) {
1876 return rc; // Wallet already opened, return handle
1877 }
1878 if (_wallets.count() > 20) {
1879 return -1;
1880 }
1881
1882 KWallet::Backend *b = nullptr;
1883 // If the wallet we want to open does not exists. create it and set pam
1884 // hash
1885 if (!wallets().contains(wallet)) {
1886 b = new KWallet::Backend(wallet);
1887 b->setCipherType(KWallet::BACKEND_CIPHER_BLOWFISH);
1888 brandNew = true;
1889 } else {
1890 b = new KWallet::Backend(wallet);
1891 }
1892
1893 int openrc = b->openPreHashed(passwordHash);
1894 if (openrc != 0 || !b->isOpen()) {
1895 delete b;
1896 return openrc;
1897 }
1898
1899 // opening the wallet was successful
1900 int handle = generateHandle();
1901 _wallets.insert(handle, b);
1902 _syncTimers.addTimer(handle, _syncTime);
1903
1904 // don't reference the wallet or add a session so it
1905 // can be reclosed easily.
1906
1907 if (sessionTimeout > 0) {
1908 _closeTimers.addTimer(handle, sessionTimeout);
1909 } else if (_closeIdle) {
1910 _closeTimers.addTimer(handle, _idleTime);
1911 }
1912 if (brandNew) {
1913 Q_EMIT walletCreated(wallet);
1914 }
1915 Q_EMIT walletOpened(wallet);
1916
1917 if (_wallets.count() == 1 && _launchManager) {
1918 startManagerForKSecretD();
1919 }
1920
1921 return handle;
1922}
1923
1924// vim: tw=220:ts=4
1925
1926#include "moc_ksecretd.cpp"
KConfigGroup group(const QString &group)
void deleteEntry(const char *key, WriteConfigFlags pFlags=Normal)
void writeEntry(const char *key, const char *value, WriteConfigFlags pFlags=Normal)
bool isEntryImmutable(const char *key) const
QString readEntry(const char *key, const char *aDefault=nullptr) const
bool sync() override
void deleted(const QString &path)
void dirty(const QString &path)
void setBackgroundWarningColor(const QColor &color)
void setIcon(const QIcon &icon)
void setPrompt(const QString &prompt)
QString password() const
void sendEvent()
KNotificationAction * addAction(const QString &label)
void setWindow(QWindow *window)
void setText(const QString &text)
QString password() const
void setPrompt(const QString &prompt)
void setIcon(const QIcon &icon)
void setPassword(const QString &password)
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
static const QString PasswordFolder()
The standardized name of the password folder.
Definition kwallet.cpp:87
static const QString NetworkWallet()
The name of the wallet used to store network passwords.
Definition kwallet.cpp:76
static const QString LocalWallet()
The name of the wallet used to store local passwords.
Definition kwallet.cpp:58
static const QString FormDataFolder()
The standardized name of the form data folder.
Definition kwallet.cpp:92
static bool isPlatformX11()
static void setMainWindow(QWindow *subwindow, const QString &mainwindow)
static void setOnAllDesktops(WId win, bool b)
static void setState(WId win, NET::States state)
static void clearState(WId win, NET::States state)
static WId activeWindow()
static Q_INVOKABLE void forceActiveWindow(QWindow *window, long time=0)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
QWidget * window(QObject *job)
QString path(const QString &relativePath)
void errorWId(WId parent_id, const QString &text, const QString &title=QString(), Options options=Notify)
void information(QWidget *parent, const QString &text, const QString &title=QString(), const QString &dontShowAgainName=QString(), Options options=Notify)
KIOCORE_EXPORT QString dir(const QString &fileClass)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
QString name(StandardAction id)
const QList< QKeySequence > & end()
void exit(int returnCode)
QDBusConnectionInterface * interface() const const
bool registerObject(const QString &path, QObject *object, RegisterOptions options)
bool registerService(const QString &serviceName)
QDBusConnection sessionBus()
QDBusReply< QDBusConnectionInterface::RegisterServiceReply > registerService(const QString &serviceName, ServiceQueueOptions qoption, ServiceReplacementOptions roption)
QDBusConnection connection() const const
const QDBusMessage & message() const const
QString service() const const
void setDelayedReply(bool enable) const const
void serviceOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner)
virtual int exec()
bool exists() const const
bool remove()
QIcon fromTheme(const QString &name)
const_iterator constBegin() const const
const_iterator constEnd() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool startDetached(const QString &program, const QStringList &arguments, const QString &workingDirectory, qint64 *pid)
QString findExecutable(const QString &executableName, const QStringList &paths)
QString & append(QChar ch)
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
bool isNull() const const
qsizetype length() const const
QString toHtmlEscaped() const const
QByteArray toUtf8() const const
void truncate(qsizetype position)
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
WA_NativeWindow
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QWidget * find(WId id)
void setAttribute(Qt::WidgetAttribute attribute, bool on)
void show()
WId winId() const const
QWindow * windowHandle() const const
void setWindowTitle(const QString &)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Apr 25 2025 11:53:00 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.