diff --git a/src/api/KWallet/kwallet.cpp b/src/api/KWallet/kwallet.cpp index 8e4e3c3..f610fc3 100644 --- a/src/api/KWallet/kwallet.cpp +++ b/src/api/KWallet/kwallet.cpp @@ -1,1672 +1,1679 @@ /* This file is part of the KDE project * * Copyright (C) 2002-2004 George Staikos * Copyright (C) 2008 Michael Leupold * Copyright (C) 2011 Valentin Rusu * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kwallet.h" #include #include "kwallet_api_debug.h" #include #include +#include #include #include #include #if HAVE_KSECRETSSERVICE #include "ksecretsservice/ksecretsservicecollection.h" #endif #include "kwallet_interface.h" #if HAVE_KSECRETSSERVICE typedef QMap StringToStringStringMapMap; Q_DECLARE_METATYPE(StringToStringStringMapMap) #endif typedef QMap StringByteArrayMap; Q_DECLARE_METATYPE(StringByteArrayMap) namespace KWallet { class KWalletDLauncher { public: KWalletDLauncher(); ~KWalletDLauncher(); KWalletDLauncher(const KWalletDLauncher &) = delete; KWalletDLauncher& operator=(const KWalletDLauncher &) = delete; org::kde::KWallet &getInterface(); // this static variable is used below to switch between old KWallet // infrastructure and the new one which is built on top of the new // KSecretsService infrastructure. It's value can be changed via the // the Wallet configuration module in System Settings bool m_useKSecretsService; org::kde::KWallet *m_wallet_deamon; KConfigGroup m_cgroup; bool m_walletEnabled; }; Q_GLOBAL_STATIC(KWalletDLauncher, walletLauncher) static QString appid() { return qApp->applicationName(); } static void registerTypes() { static bool registered = false; if (!registered) { #if HAVE_KSECRETSSERVICE qDBusRegisterMetaType(); qDBusRegisterMetaType(); #endif qDBusRegisterMetaType(); registered = true; } } bool Wallet::isUsingKSecretsService() { return walletLauncher()->m_useKSecretsService; } const QString Wallet::LocalWallet() { // NOTE: This method stays unchanged for KSecretsService KConfigGroup cfg(KSharedConfig::openConfig(QStringLiteral("kwalletrc"))->group("Wallet")); if (!cfg.readEntry("Use One Wallet", true)) { QString tmp = cfg.readEntry("Local Wallet", "localwallet"); if (tmp.isEmpty()) { return QStringLiteral("localwallet"); } return tmp; } QString tmp = cfg.readEntry("Default Wallet", "kdewallet"); if (tmp.isEmpty()) { return QStringLiteral("kdewallet"); } return tmp; } const QString Wallet::NetworkWallet() { // NOTE: This method stays unchanged for KSecretsService KConfigGroup cfg(KSharedConfig::openConfig(QStringLiteral("kwalletrc"))->group("Wallet")); QString tmp = cfg.readEntry("Default Wallet", "kdewallet"); if (tmp.isEmpty()) { return QStringLiteral("kdewallet"); } return tmp; } const QString Wallet::PasswordFolder() { return QStringLiteral("Passwords"); } const QString Wallet::FormDataFolder() { return QStringLiteral("Form Data"); } class Q_DECL_HIDDEN Wallet::WalletPrivate { public: WalletPrivate(Wallet *wallet, int h, const QString &n) : q(wallet), name(n), handle(h) #if HAVE_KSECRETSSERVICE , secretsCollection(0) #endif {} void walletServiceUnregistered(); #if HAVE_KSECRETSSERVICE template int writeEntry(const QString &key, const T &value, Wallet::EntryType entryType) { int rc = -1; KSecretsService::Secret secret; secret.setValue(QVariant::fromValue(value)); KSecretsService::StringStringMap attrs; attrs[KSS_ATTR_ENTRYFOLDER] = folder; attrs[KSS_ATTR_WALLETTYPE] = QString("%1").arg((int)entryType); KSecretsService::CreateCollectionItemJob *createItemJob = secretsCollection->createItem(key, attrs, secret); if (!createItemJob->exec()) { qCDebug(KWALLET_API_LOG) << "Cannot execute CreateCollectionItemJob : " << createItemJob->errorString(); } rc = createItemJob->error(); return rc; } QExplicitlySharedDataPointer findItem(const QString &key) const; template int readEntry(const QString &key, T &value) const; bool readSecret(const QString &key, KSecretsService::Secret &value) const; template int forEachItemThatMatches(const QString &key, V verb) { int rc = -1; KSecretsService::StringStringMap attrs; attrs[KSS_ATTR_ENTRYFOLDER] = folder; KSecretsService::SearchCollectionItemsJob *searchItemsJob = secretsCollection->searchItems(attrs); if (searchItemsJob->exec()) { - const QRegExp re(key, Qt::CaseSensitive, QRegExp::Wildcard); + // HACK: QRegularExpression::wildcardToRegularExpression() mainly handles file pattern + // globbing (e.g. "*.txt") which means it doesn't allow "/" in the file name (which is + // technically correct); we have to subvert it because the keys in kwallet are in the + // form e.g. "foo.com/" which does have a "/" in it + const QString pattern = QRegularExpression::wildcardToRegularExpression(key).replace( + QLatin1String("[^/]"), QLatin1String(".")); + const QRegularExpression re(pattern); const auto list = searchItemsJob->items(); for (KSecretsService::SearchCollectionItemsJob::Item item : list) { KSecretsService::ReadItemPropertyJob *readLabelJob = item->label(); if (readLabelJob->exec()) { QString label = readLabelJob->propertyValue().toString(); - if (re.exactMatch(label)) { + if (re.match(label).hasMatch()) { if (verb(this, label, item.data())) { rc = 0; // one successful iteration already produced results, so success return } } } else { qCDebug(KWALLET_API_LOG) << "Cannot execute ReadItemPropertyJob " << readLabelJob->errorString(); } } } else { qCDebug(KWALLET_API_LOG) << "Cannot execute KSecretsService::SearchCollectionItemsJob " << searchItemsJob->errorString(); } return rc; } void createDefaultFolders(); struct InsertIntoEntryList; struct InsertIntoMapList; struct InsertIntoPasswordList; KSecretsService::Collection *secretsCollection; #endif // HAVE_KSECRETSSERVICE Wallet *q; QString name; QString folder; int handle; int transactionId; }; #if HAVE_KSECRETSSERVICE void Wallet::WalletPrivate::createDefaultFolders() { // NOTE: KWalletManager expects newly created wallets to have two default folders // b->createFolder(KWallet::Wallet::PasswordFolder()); // b->createFolder(KWallet::Wallet::FormDataFolder()); QString strDummy(""); folder = PasswordFolder(); writeEntry(PasswordFolder(), strDummy, KWallet::Wallet::Unknown); folder = FormDataFolder(); writeEntry(FormDataFolder(), strDummy, KWallet::Wallet::Unknown); } #endif // HAVE_KSECRETSSERVICE static const char s_kwalletdServiceName[] = "org.kde.kwalletd5"; Wallet::Wallet(int handle, const QString &name) : QObject(nullptr), d(new WalletPrivate(this, handle, name)) { if (walletLauncher()->m_useKSecretsService) { // see openWallet for initialization code; this constructor does not have any code } else { QDBusServiceWatcher *watcher = new QDBusServiceWatcher(QString::fromLatin1(s_kwalletdServiceName), QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForUnregistration, this); connect(watcher, SIGNAL(serviceUnregistered(QString)), this, SLOT(walletServiceUnregistered())); connect(&walletLauncher()->getInterface(), SIGNAL(walletClosed(int)), SLOT(slotWalletClosed(int))); connect(&walletLauncher()->getInterface(), SIGNAL(folderListUpdated(QString)), SLOT(slotFolderListUpdated(QString))); connect(&walletLauncher()->getInterface(), SIGNAL(folderUpdated(QString,QString)), SLOT(slotFolderUpdated(QString,QString))); connect(&walletLauncher()->getInterface(), SIGNAL(applicationDisconnected(QString,QString)), SLOT(slotApplicationDisconnected(QString,QString))); // Verify that the wallet is still open if (d->handle != -1) { QDBusReply r = walletLauncher()->getInterface().isOpen(d->handle); if (r.isValid() && !r) { d->handle = -1; d->name.clear(); } } } } Wallet::~Wallet() { #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { d->folder.clear(); d->name.clear(); delete d->secretsCollection; } else { #endif if (d->handle != -1) { if (!walletLauncher.isDestroyed()) { walletLauncher()->getInterface().close(d->handle, false, appid()); } else { qCDebug(KWALLET_API_LOG) << "Problem with static destruction sequence." "Destroy any static Wallet before the event-loop exits."; } d->handle = -1; d->folder.clear(); d->name.clear(); } #if HAVE_KSECRETSSERVICE } #endif delete d; } QStringList Wallet::walletList() { QStringList result; #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { KSecretsService::ListCollectionsJob *listJob = KSecretsService::Collection::listCollections(); if (listJob->exec()) { result = listJob->collections(); } else { qCDebug(KWALLET_API_LOG) << "Cannot execute ListCollectionsJob: " << listJob->errorString(); } } else { #endif if (walletLauncher()->m_walletEnabled) { QDBusReply r = walletLauncher()->getInterface().wallets(); if (!r.isValid()) { qCDebug(KWALLET_API_LOG) << "Invalid DBus reply: " << r.error(); } else { result = r; } } #if HAVE_KSECRETSSERVICE } #endif return result; } void Wallet::changePassword(const QString &name, WId w) { if (w == 0) { qCDebug(KWALLET_API_LOG) << "Pass a valid window to KWallet::Wallet::changePassword()."; } // Make sure the password prompt window will be visible and activated KWindowSystem::allowExternalProcessWindowActivation(); #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { KSecretsService::Collection *coll = KSecretsService::Collection::findCollection(name); KSecretsService::ChangeCollectionPasswordJob *changePwdJob = coll->changePassword(); if (!changePwdJob->exec()) { qCDebug(KWALLET_API_LOG) << "Cannot execute change password job: " << changePwdJob->errorString(); } coll->deleteLater(); } else { #endif if (walletLauncher()->m_walletEnabled) { walletLauncher()->getInterface().changePassword(name, (qlonglong)w, appid()); } #if HAVE_KSECRETSSERVICE } #endif } bool Wallet::isEnabled() { #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { return walletLauncher()->m_cgroup.readEntry("Enabled", true); } else { #endif return walletLauncher()->m_walletEnabled; #if HAVE_KSECRETSSERVICE } #endif } bool Wallet::isOpen(const QString &name) { #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { KSecretsService::Collection *coll = KSecretsService::Collection::findCollection(name, KSecretsService::Collection::OpenOnly); KSecretsService::ReadCollectionPropertyJob *readLocked = coll->isLocked(); if (readLocked->exec()) { return !readLocked->propertyValue().toBool(); } else { qCDebug(KWALLET_API_LOG) << "ReadLocked job failed"; return false; } } else { #endif if (walletLauncher()->m_walletEnabled) { QDBusReply r = walletLauncher()->getInterface().isOpen(name); if (!r.isValid()) { qCDebug(KWALLET_API_LOG) << "Invalid DBus reply: " << r.error(); return false; } else { return r; } } else return false; #if HAVE_KSECRETSSERVICE } #endif } int Wallet::closeWallet(const QString &name, bool force) { #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { qCDebug(KWALLET_API_LOG) << "Wallet::closeWallet NOOP"; return 0; } else { #endif if (walletLauncher()->m_walletEnabled) { QDBusReply r = walletLauncher()->getInterface().close(name, force); if (!r.isValid()) { qCDebug(KWALLET_API_LOG) << "Invalid DBus reply: " << r.error(); return -1; } else { return r; } } else return -1; #if HAVE_KSECRETSSERVICE } #endif } int Wallet::deleteWallet(const QString &name) { #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { KSecretsService::Collection *coll = KSecretsService::Collection::findCollection(name, KSecretsService::Collection::OpenOnly); KJob *deleteJob = coll->deleteCollection(); if (!deleteJob->exec()) { qCDebug(KWALLET_API_LOG) << "Cannot execute delete job " << deleteJob->errorString(); } return deleteJob->error(); } else { #endif if (walletLauncher->m_walletEnabled) { QDBusReply r = walletLauncher()->getInterface().deleteWallet(name); if (!r.isValid()) { qCDebug(KWALLET_API_LOG) << "Invalid DBus reply: " << r.error(); return -1; } else { return r; } } else return -1; #if HAVE_KSECRETSSERVICE } #endif } Wallet *Wallet::openWallet(const QString &name, WId w, OpenType ot) { if (w == 0) { qCDebug(KWALLET_API_LOG) << "Pass a valid window to KWallet::Wallet::openWallet()."; } if (!walletLauncher()->m_walletEnabled) { qCDebug(KWALLET_API_LOG) << "User disabled the wallet system so returning 0 here."; return nullptr; } #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { Wallet *wallet = new Wallet(-1, name); // FIXME: should we specify CreateCollection or OpenOnly here? wallet->d->secretsCollection = KSecretsService::Collection::findCollection(name, KSecretsService::Collection::CreateCollection, QVariantMap(), w); connect(wallet->d->secretsCollection, SIGNAL(statusChanged(int)), wallet, SLOT(slotCollectionStatusChanged(int))); connect(wallet->d->secretsCollection, SIGNAL(deleted()), wallet, SLOT(slotCollectionDeleted())); if (ot == Synchronous) { qCDebug(KWALLET_API_LOG) << "WARNING openWallet OpenType=Synchronous requested"; // TODO: not sure what to do with in this case; however, all other KSecretsService API methods are already // async and will perform sync inside this API because of it's design } return wallet; } else { #endif Wallet *wallet = new Wallet(-1, name); // connect the daemon's opened signal to the slot filtering the // signals we need connect(&walletLauncher()->getInterface(), SIGNAL(walletAsyncOpened(int,int)), wallet, SLOT(walletAsyncOpened(int,int))); // Make sure the password prompt window will be visible and activated KWindowSystem::allowExternalProcessWindowActivation(); org::kde::KWallet &interface = walletLauncher->getInterface(); // do the call QDBusReply r; if (ot == Synchronous) { interface.setTimeout(0x7FFFFFFF); // Don't timeout after 25s, but 24 days r = interface.open(name, (qlonglong)w, appid()); interface.setTimeout(-1); // Back to the default 25s // after this call, r would contain a transaction id >0 if OK or -1 if NOK // if OK, the slot walletAsyncOpened should have been received, but the transaction id // will not match. We'll get that handle from the reply - see below } else if (ot == Asynchronous) { r = interface.openAsync(name, (qlonglong)w, appid(), true); } else if (ot == Path) { r = interface.openPathAsync(name, (qlonglong)w, appid(), true); } else { delete wallet; return nullptr; } // error communicating with the daemon (maybe not running) if (!r.isValid()) { qCDebug(KWALLET_API_LOG) << "Invalid DBus reply: " << r.error(); delete wallet; return nullptr; } wallet->d->transactionId = r.value(); if (ot == Synchronous || ot == Path) { // check for an immediate error if (wallet->d->transactionId < 0) { delete wallet; wallet = nullptr; } else { wallet->d->handle = r.value(); } } else if (ot == Asynchronous) { if (wallet->d->transactionId < 0) { QTimer::singleShot(0, wallet, SLOT(emitWalletAsyncOpenError())); // client code is responsible for deleting the wallet } } return wallet; #if HAVE_KSECRETSSERVICE } #endif } void Wallet::slotCollectionStatusChanged(int status) { #if HAVE_KSECRETSSERVICE KSecretsService::Collection::Status collStatus = (KSecretsService::Collection::Status)status; switch (collStatus) { case KSecretsService::Collection::NewlyCreated: d->createDefaultFolders(); // fall through case KSecretsService::Collection::FoundExisting: emitWalletOpened(); break; case KSecretsService::Collection::Deleted: case KSecretsService::Collection::Invalid: case KSecretsService::Collection::Pending: // nothing to do break; case KSecretsService::Collection::NotFound: emitWalletAsyncOpenError(); break; } #else Q_UNUSED(status) #endif } void Wallet::slotCollectionDeleted() { d->folder.clear(); d->name.clear(); emit walletClosed(); } bool Wallet::disconnectApplication(const QString &wallet, const QString &app) { #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { qCDebug(KWALLET_API_LOG) << "Wallet::disconnectApplication NOOP"; return true; } else { #endif if (walletLauncher()->m_walletEnabled) { QDBusReply r = walletLauncher()->getInterface().disconnectApplication(wallet, app); if (!r.isValid()) { qCDebug(KWALLET_API_LOG) << "Invalid DBus reply: " << r.error(); return false; } else { return r; } } else return -1; #if HAVE_KSECRETSSERVICE } #endif } QStringList Wallet::users(const QString &name) { #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { qCDebug(KWALLET_API_LOG) << "KSecretsService does not handle users list"; return QStringList(); } else { #endif if (walletLauncher()->m_walletEnabled) { QDBusReply r = walletLauncher()->getInterface().users(name); if (!r.isValid()) { qCDebug(KWALLET_API_LOG) << "Invalid DBus reply: " << r.error(); return QStringList(); } else { return r; } } else return QStringList(); #if HAVE_KSECRETSSERVICE } #endif } int Wallet::sync() { #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { // NOOP with KSecretsService } else { #endif if (d->handle == -1) { return -1; } walletLauncher()->getInterface().sync(d->handle, appid()); #if HAVE_KSECRETSSERVICE } #endif return 0; } int Wallet::lockWallet() { #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { KSecretsService::CollectionLockJob *lockJob = d->secretsCollection->lock(); if (lockJob->exec()) { d->folder.clear(); d->name.clear(); } else { qCDebug(KWALLET_API_LOG) << "Cannot execute KSecretsService::CollectionLockJob : " << lockJob->errorString(); return -1; } return lockJob->error(); } else { #endif if (d->handle == -1) { return -1; } QDBusReply r = walletLauncher()->getInterface().close(d->handle, true, appid()); d->handle = -1; d->folder.clear(); d->name.clear(); if (r.isValid()) { return r; } else { qCDebug(KWALLET_API_LOG) << "Invalid DBus reply: " << r.error(); return -1; } #if HAVE_KSECRETSSERVICE } #endif } const QString &Wallet::walletName() const { return d->name; } bool Wallet::isOpen() const { #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { return !d->secretsCollection->isLocked(); } else { #endif return d->handle != -1; #if HAVE_KSECRETSSERVICE } #endif } void Wallet::requestChangePassword(WId w) { if (w == 0) { qCDebug(KWALLET_API_LOG) << "Pass a valid window to KWallet::Wallet::requestChangePassword()."; } #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { KSecretsService::ChangeCollectionPasswordJob *changePwdJob = d->secretsCollection->changePassword(); if (!changePwdJob->exec()) { qCDebug(KWALLET_API_LOG) << "Cannot execute ChangeCollectionPasswordJob : " << changePwdJob->errorString(); } } else { #endif if (d->handle == -1) { return; } // Make sure the password prompt window will be visible and activated KWindowSystem::allowExternalProcessWindowActivation(); walletLauncher()->getInterface().changePassword(d->name, (qlonglong)w, appid()); #if HAVE_KSECRETSSERVICE } #endif } void Wallet::slotWalletClosed(int handle) { #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { // TODO: implement this Q_ASSERT(0); } else { #endif if (d->handle == handle) { d->handle = -1; d->folder.clear(); d->name.clear(); emit walletClosed(); } #if HAVE_KSECRETSSERVICE } #endif } QStringList Wallet::folderList() { #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { QStringList result; KSecretsService::StringStringMap attrs; attrs[KSS_ATTR_ENTRYFOLDER] = ""; // search for items having this attribute no matter what value it has KSecretsService::SearchCollectionItemsJob *searchJob = d->secretsCollection->searchItems(attrs); if (searchJob->exec()) { const KSecretsService::ReadCollectionItemsJob::ItemList itemList = searchJob->items(); for (const KSecretsService::ReadCollectionItemsJob::Item &item : itemList) { KSecretsService::ReadItemPropertyJob *readAttrsJob = item->attributes(); if (readAttrsJob->exec()) { KSecretsService::StringStringMap attrs = readAttrsJob->propertyValue().value(); const QString folder = attrs[KSS_ATTR_ENTRYFOLDER]; if (!folder.isEmpty() && !result.contains(folder)) { result.append(folder); } } else { qCDebug(KWALLET_API_LOG) << "Cannot read item attributes : " << readAttrsJob->errorString(); } } } else { qCDebug(KWALLET_API_LOG) << "Cannot execute ReadCollectionItemsJob : " << searchJob->errorString(); } return result; } else { #endif if (d->handle == -1) { return QStringList(); } QDBusReply r = walletLauncher()->getInterface().folderList(d->handle, appid()); if (!r.isValid()) { qCDebug(KWALLET_API_LOG) << "Invalid DBus reply: " << r.error(); return QStringList(); } else { return r; } #if HAVE_KSECRETSSERVICE } #endif } QStringList Wallet::entryList() { #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { QStringList result; KSecretsService::StringStringMap attrs; attrs[KSS_ATTR_ENTRYFOLDER] = d->folder; KSecretsService::SearchCollectionItemsJob *readItemsJob = d->secretsCollection->searchItems(attrs); if (readItemsJob->exec()) { const auto list = readItemsJob->items(); for (KSecretsService::SearchCollectionItemsJob::Item item : list) { KSecretsService::ReadItemPropertyJob *readLabelJob = item->label(); if (readLabelJob->exec()) { result.append(readLabelJob->propertyValue().toString()); } else { qCDebug(KWALLET_API_LOG) << "Cannot execute readLabelJob" << readItemsJob->errorString(); } } } else { qCDebug(KWALLET_API_LOG) << "Cannot execute readItemsJob" << readItemsJob->errorString(); } return result; } else { #endif if (d->handle == -1) { return QStringList(); } QDBusReply r = walletLauncher()->getInterface().entryList(d->handle, d->folder, appid()); if (!r.isValid()) { qCDebug(KWALLET_API_LOG) << "Invalid DBus reply: " << r.error(); return QStringList(); } else { return r; } #if HAVE_KSECRETSSERVICE } #endif } bool Wallet::hasFolder(const QString &f) { #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { // FIXME: well, this is not the best implementation, but it's done quickly :) // the best way would be to searchItems with the attribute label having the value f // doing that would reduce DBus traffic. But KWallet API wille not last. QStringList folders = folderList(); return folders.contains(f); } else { #endif if (d->handle == -1) { return false; } QDBusReply r = walletLauncher()->getInterface().hasFolder(d->handle, f, appid()); if (!r.isValid()) { qCDebug(KWALLET_API_LOG) << "Invalid DBus reply: " << r.error(); return false; } else { return r; } #if HAVE_KSECRETSSERVICE } #endif } bool Wallet::createFolder(const QString &f) { #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { QString strDummy(""); d->folder = f; d->writeEntry(f, strDummy, KWallet::Wallet::Unknown); return true; } else { #endif if (d->handle == -1) { return false; } if (!hasFolder(f)) { QDBusReply r = walletLauncher()->getInterface().createFolder(d->handle, f, appid()); if (!r.isValid()) { qCDebug(KWALLET_API_LOG) << "Invalid DBus reply: " << r.error(); return false; } else { return r; } } return true; // folder already exists #if HAVE_KSECRETSSERVICE } #endif } bool Wallet::setFolder(const QString &f) { bool rc = false; #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { if (hasFolder(f)) { d->folder = f; rc = true; } } else { #endif if (d->handle == -1) { return rc; } // Don't do this - the folder could have disappeared? #if 0 if (f == d->folder) { return true; } #endif if (hasFolder(f)) { d->folder = f; rc = true; } #if HAVE_KSECRETSSERVICE } #endif return rc; } bool Wallet::removeFolder(const QString &f) { #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { bool result = false; // search for all items having the folder f then delete them KSecretsService::StringStringMap attrs; attrs[KSS_ATTR_ENTRYFOLDER] = f; KSecretsService::SearchCollectionItemsJob *searchJob = d->secretsCollection->searchItems(attrs); if (searchJob->exec()) { const KSecretsService::SearchCollectionItemsJob::ItemList itemList = searchJob->items(); if (!itemList.isEmpty()) { result = true; for (const KSecretsService::SearchCollectionItemsJob::Item &item : itemList) { KSecretsService::SecretItemDeleteJob *deleteJob = item->deleteItem(); if (!deleteJob->exec()) { qCDebug(KWALLET_API_LOG) << "Cannot delete item : " << deleteJob->errorString(); result = false; } result &= true; } } } else { qCDebug(KWALLET_API_LOG) << "Cannot execute KSecretsService::SearchCollectionItemsJob : " << searchJob->errorString(); } return result; } else { #endif if (d->handle == -1) { return false; } QDBusReply r = walletLauncher()->getInterface().removeFolder(d->handle, f, appid()); if (d->folder == f) { setFolder(QString()); } if (!r.isValid()) { qCDebug(KWALLET_API_LOG) << "Invalid DBus reply: " << r.error(); return false; } else { return r; } #if HAVE_KSECRETSSERVICE } #endif } const QString &Wallet::currentFolder() const { return d->folder; } #if HAVE_KSECRETSSERVICE QExplicitlySharedDataPointer Wallet::WalletPrivate::findItem(const QString &key) const { QExplicitlySharedDataPointer result; KSecretsService::StringStringMap attrs; attrs[KSS_ATTR_ENTRYFOLDER] = folder; attrs["Label"] = key; KSecretsService::SearchCollectionItemsJob *searchJob = secretsCollection->searchItems(attrs); if (searchJob->exec()) { KSecretsService::SearchCollectionItemsJob::ItemList itemList = searchJob->items(); if (!itemList.isEmpty()) { result = itemList.first(); } else { qCDebug(KWALLET_API_LOG) << "entry named " << key << " not found in folder " << folder; } } else { qCDebug(KWALLET_API_LOG) << "Cannot exec KSecretsService::SearchCollectionItemsJob : " << searchJob->errorString(); } return result; } template int Wallet::WalletPrivate::readEntry(const QString &key, T &value) const { int rc = -1; QExplicitlySharedDataPointer item = findItem(key); if (item) { KSecretsService::GetSecretItemSecretJob *readJob = item->getSecret(); if (readJob->exec()) { KSecretsService::Secret theSecret = readJob->secret(); qCDebug(KWALLET_API_LOG) << "Secret contentType is " << theSecret.contentType(); value = theSecret.value().value(); rc = 0; } else { qCDebug(KWALLET_API_LOG) << "Cannot exec GetSecretItemSecretJob : " << readJob->errorString(); } } return rc; } bool Wallet::WalletPrivate::readSecret(const QString &key, KSecretsService::Secret &value) const { bool result = false; QExplicitlySharedDataPointer item = findItem(key); if (item) { KSecretsService::GetSecretItemSecretJob *readJob = item->getSecret(); if (readJob->exec()) { value = readJob->secret(); result = true; } else { qCDebug(KWALLET_API_LOG) << "Cannot exec GetSecretItemSecretJob : " << readJob->errorString(); } } return result; } #endif int Wallet::readEntry(const QString &key, QByteArray &value) { int rc = -1; #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { return d->readEntry(key, value); } else { #endif if (d->handle == -1) { return rc; } QDBusReply r = walletLauncher()->getInterface().readEntry(d->handle, d->folder, key, appid()); if (r.isValid()) { value = r; rc = 0; } #if HAVE_KSECRETSSERVICE } #endif return rc; } #if HAVE_KSECRETSSERVICE struct Wallet::WalletPrivate::InsertIntoEntryList { InsertIntoEntryList(QMap< QString, QByteArray> &value) : _value(value) {} bool operator()(Wallet::WalletPrivate *, const QString &label, KSecretsService::SecretItem *item) { bool result = false; KSecretsService::GetSecretItemSecretJob *readSecretJob = item->getSecret(); if (readSecretJob->exec()) { _value.insert(label, readSecretJob->secret().value().toByteArray()); result = true; } else { qCDebug(KWALLET_API_LOG) << "Cannot execute GetSecretItemSecretJob " << readSecretJob->errorString(); } return result; } QMap< QString, QByteArray > _value; }; #endif int Wallet::readEntryList(const QString &key, QMap &value) { int rc = -1; #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { rc = d->forEachItemThatMatches(key, WalletPrivate::InsertIntoEntryList(value)); } else { #endif registerTypes(); if (d->handle == -1) { return rc; } QDBusReply r = walletLauncher()->getInterface().readEntryList(d->handle, d->folder, key, appid()); if (r.isValid()) { rc = 0; // convert to const QVariantMap val = r.value(); for (QVariantMap::const_iterator it = val.begin(); it != val.end(); ++it) { value.insert(it.key(), it.value().toByteArray()); } } #if HAVE_KSECRETSSERVICE } #endif return rc; } int Wallet::renameEntry(const QString &oldName, const QString &newName) { int rc = -1; #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { QExplicitlySharedDataPointer item = d->findItem(oldName); if (item) { KSecretsService::WriteItemPropertyJob *writeJob = item->setLabel(newName); if (!writeJob->exec()) { qCDebug(KWALLET_API_LOG) << "Cannot exec WriteItemPropertyJob : " << writeJob->errorString(); } rc = writeJob->error(); } else { qCDebug(KWALLET_API_LOG) << "Cannot locate item " << oldName << " in folder " << d->folder; } } else { #endif if (d->handle == -1) { return rc; } QDBusReply r = walletLauncher()->getInterface().renameEntry(d->handle, d->folder, oldName, newName, appid()); if (r.isValid()) { rc = r; } #if HAVE_KSECRETSSERVICE } #endif return rc; } int Wallet::readMap(const QString &key, QMap &value) { int rc = -1; #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { QByteArray ba; rc = d->readEntry< QByteArray >(key, ba); if (rc == 0 && !ba.isEmpty()) { QDataStream ds(&ba, QIODevice::ReadOnly); ds >> value; } } else { #endif registerTypes(); if (d->handle == -1) { return rc; } QDBusReply r = walletLauncher()->getInterface().readMap(d->handle, d->folder, key, appid()); if (r.isValid()) { rc = 0; QByteArray v = r; if (!v.isEmpty()) { QDataStream ds(&v, QIODevice::ReadOnly); ds >> value; } } #if HAVE_KSECRETSSERVICE } #endif return rc; } #if HAVE_KSECRETSSERVICE struct Wallet::WalletPrivate::InsertIntoMapList { InsertIntoMapList(QMap< QString, QMap< QString, QString > > &value) : _value(value) {} bool operator()(Wallet::WalletPrivate *d, const QString &label, KSecretsService::SecretItem *) { bool result = false; QMap map; if (d->readEntry< QMap< QString, QString> >(label, map)) { _value.insert(label, map); result = true; } return result; } QMap< QString, QMap< QString, QString> > &_value; }; #endif int Wallet::readMapList(const QString &key, QMap > &value) { int rc = -1; #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { rc = d->forEachItemThatMatches(key, WalletPrivate::InsertIntoMapList(value)); } else { #endif registerTypes(); if (d->handle == -1) { return rc; } QDBusReply r = walletLauncher()->getInterface().readMapList(d->handle, d->folder, key, appid()); if (r.isValid()) { rc = 0; const QVariantMap val = r.value(); for (QVariantMap::const_iterator it = val.begin(); it != val.end(); ++it) { QByteArray mapData = it.value().toByteArray(); if (!mapData.isEmpty()) { QDataStream ds(&mapData, QIODevice::ReadOnly); QMap v; ds >> v; value.insert(it.key(), v); } } } #if HAVE_KSECRETSSERVICE } #endif return rc; } int Wallet::readPassword(const QString &key, QString &value) { int rc = -1; #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { rc = d->readEntry(key, value); } else { #endif if (d->handle == -1) { return rc; } QDBusReply r = walletLauncher()->getInterface().readPassword(d->handle, d->folder, key, appid()); if (r.isValid()) { value = r; rc = 0; } #if HAVE_KSECRETSSERVICE } #endif return rc; } #if HAVE_KSECRETSSERVICE struct Wallet::WalletPrivate::InsertIntoPasswordList { InsertIntoPasswordList(QMap< QString, QString> &value) : _value(value) {} bool operator()(Wallet::WalletPrivate *d, const QString &label, KSecretsService::SecretItem *) { bool result = false; QString pwd; if (d->readEntry(label, pwd) == 0) { _value.insert(label, pwd); result = true; } return result; } QMap< QString, QString > &_value; }; #endif int Wallet::readPasswordList(const QString &key, QMap &value) { int rc = -1; #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { rc = d->forEachItemThatMatches(key, WalletPrivate::InsertIntoPasswordList(value)); } else { #endif registerTypes(); if (d->handle == -1) { return rc; } QDBusReply r = walletLauncher()->getInterface().readPasswordList(d->handle, d->folder, key, appid()); if (r.isValid()) { rc = 0; const QVariantMap val = r.value(); for (QVariantMap::const_iterator it = val.begin(); it != val.end(); ++it) { value.insert(it.key(), it.value().toString()); } } #if HAVE_KSECRETSSERVICE } #endif return rc; } int Wallet::writeEntry(const QString &key, const QByteArray &value, EntryType entryType) { int rc = -1; #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { rc = d->writeEntry(key, value, entryType); } else { #endif if (d->handle == -1) { return rc; } QDBusReply r = walletLauncher()->getInterface().writeEntry(d->handle, d->folder, key, value, int(entryType), appid()); if (r.isValid()) { rc = r; } #if HAVE_KSECRETSSERVICE } #endif return rc; } int Wallet::writeEntry(const QString &key, const QByteArray &value) { int rc = -1; #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { rc = writeEntry(key, value, Stream); } else { #endif if (d->handle == -1) { return rc; } QDBusReply r = walletLauncher()->getInterface().writeEntry(d->handle, d->folder, key, value, appid()); if (r.isValid()) { rc = r; } #if HAVE_KSECRETSSERVICE } #endif return rc; } int Wallet::writeMap(const QString &key, const QMap &value) { int rc = -1; #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { d->writeEntry(key, value, Map); } else { #endif registerTypes(); if (d->handle == -1) { return rc; } QByteArray mapData; QDataStream ds(&mapData, QIODevice::WriteOnly); ds << value; QDBusReply r = walletLauncher()->getInterface().writeMap(d->handle, d->folder, key, mapData, appid()); if (r.isValid()) { rc = r; } #if HAVE_KSECRETSSERVICE } #endif return rc; } int Wallet::writePassword(const QString &key, const QString &value) { int rc = -1; #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { rc = d->writeEntry(key, value, Password); } else { #endif if (d->handle == -1) { return rc; } QDBusReply r = walletLauncher()->getInterface().writePassword(d->handle, d->folder, key, value, appid()); if (r.isValid()) { rc = r; } #if HAVE_KSECRETSSERVICE } #endif return rc; } bool Wallet::hasEntry(const QString &key) { #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { QExplicitlySharedDataPointer item = d->findItem(key); return item; } else { #endif if (d->handle == -1) { return false; } QDBusReply r = walletLauncher()->getInterface().hasEntry(d->handle, d->folder, key, appid()); if (!r.isValid()) { qCDebug(KWALLET_API_LOG) << "Invalid DBus reply: " << r.error(); return false; } else { return r; } #if HAVE_KSECRETSSERVICE } #endif } int Wallet::removeEntry(const QString &key) { int rc = -1; #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { QExplicitlySharedDataPointer item = d->findItem(key); if (item) { KSecretsService::SecretItemDeleteJob *deleteJob = item->deleteItem(); if (!deleteJob->exec()) { qCDebug(KWALLET_API_LOG) << "Cannot execute SecretItemDeleteJob " << deleteJob->errorString(); } rc = deleteJob->error(); } } else { #endif if (d->handle == -1) { return rc; } QDBusReply r = walletLauncher()->getInterface().removeEntry(d->handle, d->folder, key, appid()); if (r.isValid()) { rc = r; } #if HAVE_KSECRETSSERVICE } #endif return rc; } Wallet::EntryType Wallet::entryType(const QString &key) { int rc = 0; #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { QExplicitlySharedDataPointer item = d->findItem(key); if (item) { KSecretsService::ReadItemPropertyJob *readAttrsJob = item->attributes(); if (readAttrsJob->exec()) { KSecretsService::StringStringMap attrs = readAttrsJob->propertyValue().value(); if (attrs.contains(KSS_ATTR_WALLETTYPE)) { QString entryType = attrs[KSS_ATTR_WALLETTYPE]; bool ok = false; rc = entryType.toInt(&ok); if (!ok) { rc = 0; qCDebug(KWALLET_API_LOG) << KSS_ATTR_WALLETTYPE << " attribute holds non int value " << attrs[KSS_ATTR_WALLETTYPE]; } } } else { qCDebug(KWALLET_API_LOG) << "Cannot execute GetSecretItemSecretJob " << readAttrsJob->errorString(); } } } else { #endif if (d->handle == -1) { return Wallet::Unknown; } QDBusReply r = walletLauncher()->getInterface().entryType(d->handle, d->folder, key, appid()); if (r.isValid()) { rc = r; } #if HAVE_KSECRETSSERVICE } #endif return static_cast(rc); } void Wallet::WalletPrivate::walletServiceUnregistered() { if (handle >= 0) { q->slotWalletClosed(handle); } } void Wallet::slotFolderUpdated(const QString &wallet, const QString &folder) { #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { // TODO: implement this Q_ASSERT(0); } else { #endif if (d->name == wallet) { emit folderUpdated(folder); } #if HAVE_KSECRETSSERVICE } #endif } void Wallet::slotFolderListUpdated(const QString &wallet) { #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { // TODO: implement this Q_ASSERT(0); } else { #endif if (d->name == wallet) { emit folderListUpdated(); } #if HAVE_KSECRETSSERVICE } #endif } void Wallet::slotApplicationDisconnected(const QString &wallet, const QString &application) { #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { // TODO: implement this Q_ASSERT(0); } else { #endif if (d->handle >= 0 && d->name == wallet && application == appid()) { slotWalletClosed(d->handle); } #if HAVE_KSECRETSSERVICE } #endif } void Wallet::walletAsyncOpened(int tId, int handle) { #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { // TODO: implement this Q_ASSERT(0); } else { #endif // ignore responses to calls other than ours if (d->transactionId != tId || d->handle != -1) { return; } // disconnect the async signal disconnect(this, SLOT(walletAsyncOpened(int,int))); d->handle = handle; emit walletOpened(handle > 0); #if HAVE_KSECRETSSERVICE } #endif } void Wallet::emitWalletAsyncOpenError() { emit walletOpened(false); } void Wallet::emitWalletOpened() { emit walletOpened(true); } bool Wallet::folderDoesNotExist(const QString &wallet, const QString &folder) { #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { qCDebug(KWALLET_API_LOG) << "WARNING: changing semantics of folderDoesNotExist with KSS: will prompt for the password"; Wallet *w = openWallet(wallet, 0, Synchronous); if (w) { return !w->hasFolder(folder); } else { return true; } } else { #endif if (walletLauncher()->m_walletEnabled) { QDBusReply r = walletLauncher()->getInterface().folderDoesNotExist(wallet, folder); if (!r.isValid()) { qCDebug(KWALLET_API_LOG) << "Invalid DBus reply: " << r.error(); return false; } else { return r; } } else return false; #if HAVE_KSECRETSSERVICE } #endif } bool Wallet::keyDoesNotExist(const QString &wallet, const QString &folder, const QString &key) { #if HAVE_KSECRETSSERVICE if (walletLauncher()->m_useKSecretsService) { qCDebug(KWALLET_API_LOG) << "WARNING: changing semantics of keyDoesNotExist with KSS: will prompt for the password"; Wallet *w = openWallet(wallet, 0, Synchronous); if (w) { return !w->hasEntry(key); } return false; } else { #endif if (walletLauncher()->m_walletEnabled) { QDBusReply r = walletLauncher()->getInterface().keyDoesNotExist(wallet, folder, key); if (!r.isValid()) { qCDebug(KWALLET_API_LOG) << "Invalid DBus reply: " << r.error(); return false; } else { return r; } } else return false; #if HAVE_KSECRETSSERVICE } #endif } void Wallet::virtual_hook(int, void *) { //BASE::virtual_hook( id, data ); } KWalletDLauncher::KWalletDLauncher() : m_wallet_deamon(nullptr) , m_cgroup(KSharedConfig::openConfig(QStringLiteral("kwalletrc") , KConfig::NoGlobals)->group("Wallet")) , m_walletEnabled(false) { m_useKSecretsService = m_cgroup.readEntry("UseKSecretsService", false); m_walletEnabled = m_cgroup.readEntry("Enabled", true); if (!m_walletEnabled) { qCDebug(KWALLET_API_LOG) << "The wallet service was disabled by the user"; return; } #if HAVE_KSECRETSSERVICE if (m_useKSecretsService) { // NOOP } else { #endif m_wallet_deamon = new org::kde::KWallet(QString::fromLatin1(s_kwalletdServiceName), QStringLiteral("/modules/kwalletd5"), QDBusConnection::sessionBus()); #if HAVE_KSECRETSSERVICE } #endif } KWalletDLauncher::~KWalletDLauncher() { delete m_wallet_deamon; } org::kde::KWallet &KWalletDLauncher::getInterface() { // Q_ASSERT(!m_useKSecretsService); Q_ASSERT(m_wallet_deamon != nullptr); // check if kwalletd is already running QDBusConnectionInterface *bus = QDBusConnection::sessionBus().interface(); if (!bus->isServiceRegistered(QString::fromLatin1(s_kwalletdServiceName))) { // not running! check if it is enabled. if (m_walletEnabled) { // wallet is enabled! try launching it QDBusReply reply = bus->startService(QString::fromLatin1(s_kwalletdServiceName)); if (!reply.isValid()) { qCritical() << "Couldn't start kwalletd: " << reply.error(); } if (!bus->isServiceRegistered(QString::fromLatin1(s_kwalletdServiceName))) { qCDebug(KWALLET_API_LOG) << "The kwalletd service is still not registered"; } else { qCDebug(KWALLET_API_LOG) << "The kwalletd service has been registered"; } } else { qCritical() << "The kwalletd service has been disabled"; } } return *m_wallet_deamon; } } // namespace KWallet #include "moc_kwallet.cpp" diff --git a/src/runtime/kwalletd/backend/kwalletbackend.cc b/src/runtime/kwalletd/backend/kwalletbackend.cc index 5e2aa07..a6c4adb 100644 --- a/src/runtime/kwalletd/backend/kwalletbackend.cc +++ b/src/runtime/kwalletd/backend/kwalletbackend.cc @@ -1,723 +1,727 @@ /* This file is part of the KDE project * * Copyright (C) 2001-2004 George Staikos * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kwalletbackend.h" #include "kwalletbackend_debug.h" #include #include #ifdef HAVE_GPGMEPP #include #endif #include #include #include #include #include #include #include #include #include +#include #include #include "blowfish.h" #include "sha1.h" #include "cbc.h" #include // quick fix to get random numbers on win32 #ifdef Q_OS_WIN //krazy:exclude=cpp #include #include #endif #define KWALLET_VERSION_MAJOR 0 #define KWALLET_VERSION_MINOR 1 using namespace KWallet; #define KWMAGIC "KWALLET\n\r\0\r\n" class Backend::BackendPrivate { }; // static void initKWalletDir() // { // KGlobal::dirs()->addResourceType("kwallet", 0, "share/apps/kwallet"); // } Backend::Backend(const QString &name, bool isPath) : d(nullptr), _name(name), _useNewHash(false), _ref(0), _cipherType(KWallet::BACKEND_CIPHER_UNKNOWN) { // initKWalletDir(); if (isPath) { _path = name; } else { _path = getSaveLocation() + QDir::separator() + _name + ".kwl"; } _open = false; } Backend::~Backend() { if (_open) { close(); } delete d; } QString Backend::getSaveLocation() { QString writeLocation = QStandardPaths::writableLocation(QStandardPaths::DataLocation); if (writeLocation.right(1) == QLatin1String("5")) { // HACK // setApplicationName("kwalletd5") yields the path ~/.local/share/kwalletd5 for the location where to store wallets // that is not desirable, as the 5 is present in the data folder's name // this workaround getts the right ~/.local/share/kwalletd location writeLocation = writeLocation.left(writeLocation.length() -1); } QDir writeDir(writeLocation); if (!writeDir.exists()) { if (!writeDir.mkpath(writeLocation)) { qFatal("Cannot create wallet save location!"); } } // qCDebug(KWALLETBACKEND_LOG) << "Using saveLocation " + writeLocation; return writeLocation; } void Backend::setCipherType(BackendCipherType ct) { // changing cipher type on already initialed wallets is not permitted assert(_cipherType == KWallet::BACKEND_CIPHER_UNKNOWN); _cipherType = ct; } static int password2PBKDF2_SHA512(const QByteArray &password, QByteArray &hash, const QByteArray &salt) { if (!gcry_check_version("1.5.0")) { printf("libcrypt version is too old \n"); return GPG_ERR_USER_2; } gcry_error_t error; bool static gcry_secmem_init = false; if (!gcry_secmem_init) { error = gcry_control(GCRYCTL_INIT_SECMEM, 32768, 0); if (error != 0) { qWarning() << "Can't get secure memory:" << error; return error; } gcry_secmem_init = true; } gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); error = gcry_kdf_derive(password.constData(), password.size(), GCRY_KDF_PBKDF2, GCRY_MD_SHA512, salt.data(), salt.size(), PBKDF2_SHA512_ITERATIONS, PBKDF2_SHA512_KEYSIZE, hash.data()); return error; } // this should be SHA-512 for release probably static int password2hash(const QByteArray &password, QByteArray &hash) { SHA1 sha; int shasz = sha.size() / 8; assert(shasz >= 20); QByteArray block1(shasz, 0); sha.process(password.data(), qMin(password.size(), 16)); // To make brute force take longer for (int i = 0; i < 2000; i++) { memcpy(block1.data(), sha.hash(), shasz); sha.reset(); sha.process(block1.data(), shasz); } sha.reset(); if (password.size() > 16) { sha.process(password.data() + 16, qMin(password.size() - 16, 16)); QByteArray block2(shasz, 0); // To make brute force take longer for (int i = 0; i < 2000; i++) { memcpy(block2.data(), sha.hash(), shasz); sha.reset(); sha.process(block2.data(), shasz); } sha.reset(); if (password.size() > 32) { sha.process(password.data() + 32, qMin(password.size() - 32, 16)); QByteArray block3(shasz, 0); // To make brute force take longer for (int i = 0; i < 2000; i++) { memcpy(block3.data(), sha.hash(), shasz); sha.reset(); sha.process(block3.data(), shasz); } sha.reset(); if (password.size() > 48) { sha.process(password.data() + 48, password.size() - 48); QByteArray block4(shasz, 0); // To make brute force take longer for (int i = 0; i < 2000; i++) { memcpy(block4.data(), sha.hash(), shasz); sha.reset(); sha.process(block4.data(), shasz); } sha.reset(); // split 14/14/14/14 hash.resize(56); memcpy(hash.data(), block1.data(), 14); memcpy(hash.data() + 14, block2.data(), 14); memcpy(hash.data() + 28, block3.data(), 14); memcpy(hash.data() + 42, block4.data(), 14); block4.fill(0); } else { // split 20/20/16 hash.resize(56); memcpy(hash.data(), block1.data(), 20); memcpy(hash.data() + 20, block2.data(), 20); memcpy(hash.data() + 40, block3.data(), 16); } block3.fill(0); } else { // split 20/20 hash.resize(40); memcpy(hash.data(), block1.data(), 20); memcpy(hash.data() + 20, block2.data(), 20); } block2.fill(0); } else { // entirely block1 hash.resize(20); memcpy(hash.data(), block1.data(), 20); } block1.fill(0); return 0; } int Backend::deref() { if (--_ref < 0) { qCDebug(KWALLETBACKEND_LOG) << "refCount negative!"; _ref = 0; } return _ref; } bool Backend::exists(const QString &wallet) { QString saveLocation = getSaveLocation(); QString path = saveLocation + '/' + wallet + QLatin1String(".kwl"); // Note: 60 bytes is presently the minimum size of a wallet file. // Anything smaller is junk. return QFile::exists(path) && QFileInfo(path).size() >= 60; } QString Backend::openRCToString(int rc) { switch (rc) { case -255: return i18n("Already open."); case -2: return i18n("Error opening file."); case -3: return i18n("Not a wallet file."); case -4: return i18n("Unsupported file format revision."); case -41: return QStringLiteral("Unknown cipher or hash"); //FIXME: use i18n after string freeze case -42: return i18n("Unknown encryption scheme."); case -43: return i18n("Corrupt file?"); case -8: return i18n("Error validating wallet integrity. Possibly corrupted."); case -5: case -7: case -9: return i18n("Read error - possibly incorrect password."); case -6: return i18n("Decryption error."); default: return QString(); } } int Backend::open(const QByteArray &password, WId w) { if (_open) { return -255; // already open } setPassword(password); return openInternal(w); } #ifdef HAVE_GPGMEPP int Backend::open(const GpgME::Key &key) { if (_open) { return -255; // already open } _gpgKey = key; return openInternal(); } #endif // HAVE_GPGMEPP int Backend::openPreHashed(const QByteArray &passwordHash) { if (_open) { return -255; // already open } // check the password hash for correct size (currently fixed) if (passwordHash.size() != 20 && passwordHash.size() != 40 && passwordHash.size() != 56) { return -42; // unsupported encryption scheme } _passhash = passwordHash; _newPassHash = passwordHash; _useNewHash = true;//Only new hash is supported return openInternal(); } int Backend::openInternal(WId w) { // No wallet existed. Let's create it. // Note: 60 bytes is presently the minimum size of a wallet file. // Anything smaller is junk and should be deleted. if (!QFile::exists(_path) || QFileInfo(_path).size() < 60) { QFile newfile(_path); if (!newfile.open(QIODevice::ReadWrite)) { return -2; // error opening file } newfile.close(); _open = true; if (sync(w) != 0) { return -2; } } QFile db(_path); if (!db.open(QIODevice::ReadOnly)) { return -2; // error opening file } char magicBuf[KWMAGIC_LEN]; db.read(magicBuf, KWMAGIC_LEN); if (memcmp(magicBuf, KWMAGIC, KWMAGIC_LEN) != 0) { return -3; // bad magic } db.read(magicBuf, 4); // First byte is major version, second byte is minor version if (magicBuf[0] != KWALLET_VERSION_MAJOR) { return -4; // unknown version } //0 has been the MINOR version until 4.13, from that point we use it to upgrade the hash if (magicBuf[1] == 1) { qCDebug(KWALLETBACKEND_LOG) << "Wallet new enough, using new hash"; swapToNewHash(); } else if (magicBuf[1] != 0) { qCDebug(KWALLETBACKEND_LOG) << "Wallet is old, sad panda :("; return -4; // unknown version } BackendPersistHandler *phandler = BackendPersistHandler::getPersistHandler(magicBuf); if (nullptr == phandler) { return -41; // unknown cipher or hash } int result = phandler->read(this, db, w); delete phandler; return result; } void Backend::swapToNewHash() { //Runtime error happened and we can't use the new hash if (!_useNewHash) { qCDebug(KWALLETBACKEND_LOG) << "Runtime error on the new hash"; return; } _passhash.fill(0);//Making sure the old passhash is not around in memory _passhash = _newPassHash;//Use the new hash, means the wallet is modern enough } QByteArray Backend::createAndSaveSalt(const QString &path) const { QFile saltFile(path); saltFile.remove(); if (!saltFile.open(QIODevice::WriteOnly)) { return QByteArray(); } saltFile.setPermissions(QFile::ReadUser | QFile::WriteUser); char *randomData = (char *) gcry_random_bytes(PBKDF2_SHA512_SALTSIZE, GCRY_STRONG_RANDOM); QByteArray salt(randomData, PBKDF2_SHA512_SALTSIZE); free(randomData); if (saltFile.write(salt) != PBKDF2_SHA512_SALTSIZE) { return QByteArray(); } saltFile.close(); return salt; } int Backend::sync(WId w) { if (!_open) { return -255; // not open yet } if (!QFile::exists(_path)) { return -3; // File does not exist } QSaveFile sf(_path); if (!sf.open(QIODevice::WriteOnly | QIODevice::Unbuffered)) { return -1; // error opening file } sf.setPermissions(QFile::ReadUser | QFile::WriteUser); if (sf.write(KWMAGIC, KWMAGIC_LEN) != KWMAGIC_LEN) { sf.cancelWriting(); return -4; // write error } // Write the version number QByteArray version(4, 0); version[0] = KWALLET_VERSION_MAJOR; if (_useNewHash) { version[1] = KWALLET_VERSION_MINOR; //Use the sync to update the hash to PBKDF2_SHA512 swapToNewHash(); } else { version[1] = 0; //was KWALLET_VERSION_MINOR before the new hash } BackendPersistHandler *phandler = BackendPersistHandler::getPersistHandler(_cipherType); if (nullptr == phandler) { return -4; // write error } int rc = phandler->write(this, sf, version, w); if (rc < 0) { // Oops! wallet file sync filed! Display a notification about that // TODO: change kwalletd status flags, when status flags will be implemented KNotification *notification = new KNotification(QStringLiteral("syncFailed")); notification->setText(i18n("Failed to sync wallet %1 to disk. Error codes are:\nRC %2\nSF %3. Please file a BUG report using this information to bugs.kde.org").arg(_name).arg(rc).arg(sf.errorString())); notification->sendEvent(); } delete phandler; return rc; } int Backend::close(bool save) { // save if requested if (save) { int rc = sync(0); if (rc != 0) { return rc; } } // do the actual close for (FolderMap::ConstIterator i = _entries.constBegin(); i != _entries.constEnd(); ++i) { for (EntryMap::ConstIterator j = i.value().constBegin(); j != i.value().constEnd(); ++j) { delete j.value(); } } _entries.clear(); // empty the password hash _passhash.fill(0); _newPassHash.fill(0); _open = false; return 0; } const QString &Backend::walletName() const { return _name; } bool Backend::isOpen() const { return _open; } QStringList Backend::folderList() const { return _entries.keys(); } QStringList Backend::entryList() const { return _entries[_folder].keys(); } Entry *Backend::readEntry(const QString &key) { Entry *rc = nullptr; if (_open && hasEntry(key)) { rc = _entries[_folder][key]; } return rc; } QList Backend::readEntryList(const QString &key) { QList rc; if (!_open) { return rc; } - const QRegExp re(key, Qt::CaseSensitive, QRegExp::Wildcard); + // HACK: see Wallet::WalletPrivate::forEachItemThatMatches() + const QString pattern = QRegularExpression::wildcardToRegularExpression(key).replace( + QLatin1String("[^/]"), QLatin1String(".")); + const QRegularExpression re(pattern); const EntryMap &map = _entries[_folder]; for (EntryMap::ConstIterator i = map.begin(); i != map.end(); ++i) { - if (re.exactMatch(i.key())) { + if (re.match(i.key()).hasMatch()) { rc.append(i.value()); } } return rc; } bool Backend::createFolder(const QString &f) { if (_entries.contains(f)) { return false; } _entries.insert(f, EntryMap()); QCryptographicHash folderMd5(QCryptographicHash::Md5); folderMd5.addData(f.toUtf8()); _hashes.insert(MD5Digest(folderMd5.result()), QList()); return true; } int Backend::renameEntry(const QString &oldName, const QString &newName) { EntryMap &emap = _entries[_folder]; EntryMap::Iterator oi = emap.find(oldName); EntryMap::Iterator ni = emap.find(newName); if (oi != emap.end() && ni == emap.end()) { Entry *e = oi.value(); emap.erase(oi); emap[newName] = e; QCryptographicHash folderMd5(QCryptographicHash::Md5); folderMd5.addData(_folder.toUtf8()); HashMap::iterator i = _hashes.find(MD5Digest(folderMd5.result())); if (i != _hashes.end()) { QCryptographicHash oldMd5(QCryptographicHash::Md5), newMd5(QCryptographicHash::Md5); oldMd5.addData(oldName.toUtf8()); newMd5.addData(newName.toUtf8()); i.value().removeAll(MD5Digest(oldMd5.result())); i.value().append(MD5Digest(newMd5.result())); } return 0; } return -1; } void Backend::writeEntry(Entry *e) { if (!_open) { return; } if (!hasEntry(e->key())) { _entries[_folder][e->key()] = new Entry; } _entries[_folder][e->key()]->copy(e); QCryptographicHash folderMd5(QCryptographicHash::Md5); folderMd5.addData(_folder.toUtf8()); HashMap::iterator i = _hashes.find(MD5Digest(folderMd5.result())); if (i != _hashes.end()) { QCryptographicHash md5(QCryptographicHash::Md5); md5.addData(e->key().toUtf8()); i.value().append(MD5Digest(md5.result())); } } bool Backend::hasEntry(const QString &key) const { return _entries.contains(_folder) && _entries[_folder].contains(key); } bool Backend::removeEntry(const QString &key) { if (!_open) { return false; } FolderMap::Iterator fi = _entries.find(_folder); EntryMap::Iterator ei = fi.value().find(key); if (fi != _entries.end() && ei != fi.value().end()) { delete ei.value(); fi.value().erase(ei); QCryptographicHash folderMd5(QCryptographicHash::Md5); folderMd5.addData(_folder.toUtf8()); HashMap::iterator i = _hashes.find(MD5Digest(folderMd5.result())); if (i != _hashes.end()) { QCryptographicHash md5(QCryptographicHash::Md5); md5.addData(key.toUtf8()); i.value().removeAll(MD5Digest(md5.result())); } return true; } return false; } bool Backend::removeFolder(const QString &f) { if (!_open) { return false; } FolderMap::Iterator fi = _entries.find(f); if (fi != _entries.end()) { if (_folder == f) { _folder.clear(); } for (EntryMap::Iterator ei = fi.value().begin(); ei != fi.value().end(); ++ei) { delete ei.value(); } _entries.erase(fi); QCryptographicHash folderMd5(QCryptographicHash::Md5); folderMd5.addData(f.toUtf8()); _hashes.remove(MD5Digest(folderMd5.result())); return true; } return false; } bool Backend::folderDoesNotExist(const QString &folder) const { QCryptographicHash md5(QCryptographicHash::Md5); md5.addData(folder.toUtf8()); return !_hashes.contains(MD5Digest(md5.result())); } bool Backend::entryDoesNotExist(const QString &folder, const QString &entry) const { QCryptographicHash md5(QCryptographicHash::Md5); md5.addData(folder.toUtf8()); HashMap::const_iterator i = _hashes.find(MD5Digest(md5.result())); if (i != _hashes.end()) { md5.reset(); md5.addData(entry.toUtf8()); return !i.value().contains(MD5Digest(md5.result())); } return true; } void Backend::setPassword(const QByteArray &password) { _passhash.fill(0); // empty just in case BlowFish _bf; CipherBlockChain bf(&_bf); _passhash.resize(bf.keyLen() / 8); _newPassHash.resize(bf.keyLen() / 8); _newPassHash.fill(0); password2hash(password, _passhash); QByteArray salt; QFile saltFile(getSaveLocation() + QDir::separator() + _name + ".salt"); if (!saltFile.exists() || saltFile.size() == 0) { salt = createAndSaveSalt(saltFile.fileName()); } else { if (!saltFile.open(QIODevice::ReadOnly)) { salt = createAndSaveSalt(saltFile.fileName()); } else { salt = saltFile.readAll(); } } if (!salt.isEmpty() && password2PBKDF2_SHA512(password, _newPassHash, salt) == 0) { qCDebug(KWALLETBACKEND_LOG) << "Setting useNewHash to true"; _useNewHash = true; } } #ifdef HAVE_GPGMEPP const GpgME::Key &Backend::gpgKey() const { return _gpgKey; } #endif