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