File indexing completed on 2024-12-22 03:47:15

0001 /*
0002     This file is part of the KDE project
0003 
0004     SPDX-FileCopyrightText: 2002-2004 George Staikos <staikos@kde.org>
0005     SPDX-FileCopyrightText: 2008 Michael Leupold <lemma@confuego.org>
0006     SPDX-FileCopyrightText: 2010 Frank Osterfeld <osterfeld@kde.org>
0007 
0008     SPDX-License-Identifier: LGPL-2.0-or-later
0009 */
0010 
0011 #include "kwallet.h"
0012 #include <KConfigGroup>
0013 #include <KSharedConfig>
0014 
0015 #include <QApplication>
0016 #include <QDebug>
0017 #include <QPointer>
0018 #include <QWidget>
0019 
0020 #include <Carbon/Carbon.h>
0021 #include <Security/SecKeychain.h>
0022 #include <Security/Security.h>
0023 
0024 // TODO: OSX_KEYCHAIN_PORT_DISABLED is never defined, all the enclosing code should be removed
0025 
0026 using namespace KWallet;
0027 
0028 typedef QMap<QString, QString> StringStringMap;
0029 Q_DECLARE_METATYPE(StringStringMap)
0030 typedef QMap<QString, StringStringMap> StringToStringStringMapMap;
0031 Q_DECLARE_METATYPE(StringToStringStringMapMap)
0032 typedef QMap<QString, QByteArray> StringByteArrayMap;
0033 Q_DECLARE_METATYPE(StringByteArrayMap)
0034 
0035 namespace
0036 {
0037 template<typename T>
0038 struct CFReleaser {
0039     explicit CFReleaser(const T &r)
0040         : ref(r)
0041     {
0042     }
0043     ~CFReleaser()
0044     {
0045         CFRelease(ref);
0046     }
0047     T ref;
0048 };
0049 }
0050 
0051 static QString asQString(CFStringRef sr)
0052 {
0053     return QString::fromLatin1(CFStringGetCStringPtr(sr, NULL)); // TODO Latin1 correct?
0054 }
0055 
0056 static QString errorString(OSStatus s)
0057 {
0058     const CFReleaser<CFStringRef> ref(SecCopyErrorMessageString(s, NULL));
0059     return asQString(ref.ref);
0060 }
0061 
0062 static bool isError(OSStatus s, QString *errMsg)
0063 {
0064     if (errMsg) {
0065         *errMsg = errorString(s);
0066     }
0067     return s != 0;
0068 }
0069 
0070 static QString appid()
0071 {
0072     return qApp->applicationName();
0073 }
0074 
0075 static OSStatus removeEntryImplementation(const QString &walletName, const QString &key)
0076 {
0077     const QByteArray serviceName(walletName.toUtf8());
0078     const QByteArray accountName(key.toUtf8());
0079     SecKeychainItemRef itemRef;
0080     QString errMsg;
0081     OSStatus result =
0082         SecKeychainFindGenericPassword(NULL, serviceName.size(), serviceName.constData(), accountName.size(), accountName.constData(), NULL, NULL, &itemRef);
0083     if (isError(result, &errMsg)) {
0084         qWarning() << "Could not retrieve password:" << qPrintable(errMsg);
0085         return result;
0086     }
0087     const CFReleaser<SecKeychainItemRef> itemReleaser(itemRef);
0088     result = SecKeychainItemDelete(itemRef);
0089     if (isError(result, &errMsg)) {
0090         qWarning() << "Could not delete password:" << qPrintable(errMsg);
0091         return result;
0092     }
0093     return result;
0094 }
0095 
0096 const QString Wallet::LocalWallet()
0097 {
0098     KConfigGroup cfg(KSharedConfig::openConfig(QStringLiteral("kwalletrc"))->group("Wallet"));
0099     if (!cfg.readEntry("Use One Wallet", true)) {
0100         QString tmp = cfg.readEntry("Local Wallet", "localwallet");
0101         if (tmp.isEmpty()) {
0102             return QStringLiteral("localwallet");
0103         }
0104         return tmp;
0105     }
0106 
0107     QString tmp = cfg.readEntry("Default Wallet", "kdewallet");
0108     if (tmp.isEmpty()) {
0109         return QStringLiteral("kdewallet");
0110     }
0111     return tmp;
0112 }
0113 
0114 const QString Wallet::NetworkWallet()
0115 {
0116     KConfigGroup cfg(KSharedConfig::openConfig(QStringLiteral("kwalletrc"))->group("Wallet"));
0117 
0118     QString tmp = cfg.readEntry("Default Wallet", "kdewallet");
0119     if (tmp.isEmpty()) {
0120         return QStringLiteral("kdewallet");
0121     }
0122     return tmp;
0123 }
0124 
0125 const QString Wallet::PasswordFolder()
0126 {
0127     return QStringLiteral("Passwords");
0128 }
0129 
0130 const QString Wallet::FormDataFolder()
0131 {
0132     return QStringLiteral("Form Data");
0133 }
0134 
0135 class Q_DECL_HIDDEN Wallet::WalletPrivate
0136 {
0137 public:
0138     WalletPrivate(Wallet *wallet, int h, const QString &n)
0139         : q(wallet)
0140         , name(n)
0141         , handle(h)
0142     {
0143     }
0144 
0145     void walletServiceUnregistered();
0146 
0147     Wallet *q;
0148     QString name;
0149     QString folder;
0150     int handle;
0151     int transactionId;
0152 };
0153 
0154 void Wallet::WalletPrivate::walletServiceUnregistered()
0155 {
0156     if (handle >= 0) {
0157         q->slotWalletClosed(handle);
0158     }
0159 }
0160 
0161 Wallet::Wallet(int handle, const QString &name)
0162     : QObject(0L)
0163     , d(new WalletPrivate(this, handle, name))
0164 {
0165     Q_UNUSED(handle);
0166 }
0167 
0168 Wallet::~Wallet()
0169 {
0170     delete d;
0171 }
0172 
0173 QStringList Wallet::walletList()
0174 {
0175 #ifdef OSX_KEYCHAIN_PORT_DISABLED
0176     return walletLauncher->getInterface().wallets();
0177 #else
0178     return QStringList();
0179 #endif
0180 }
0181 
0182 void Wallet::changePassword(const QString &name, WId w)
0183 {
0184 #ifdef OSX_KEYCHAIN_PORT_DISABLED
0185     if (w == 0) {
0186         qDebug() << "Pass a valid window to KWallet::Wallet::changePassword().";
0187     }
0188     walletLauncher->getInterface().changePassword(name, (qlonglong)w, appid());
0189 #endif
0190 }
0191 
0192 bool Wallet::isEnabled()
0193 {
0194     // PENDING(frank) check
0195     return true;
0196 }
0197 
0198 bool Wallet::isOpen(const QString &name)
0199 {
0200 #ifdef OSX_KEYCHAIN_PORT_DISABLED
0201     return walletLauncher->getInterface().isOpen(name); // default is false
0202 #else
0203     return true;
0204 #endif
0205 }
0206 
0207 int Wallet::closeWallet(const QString &name, bool force)
0208 {
0209 #ifdef OSX_KEYCHAIN_PORT_DISABLED
0210     QDBusReply<int> r = walletLauncher->getInterface().close(name, force);
0211     return r.isValid() ? r : -1;
0212 #else
0213     return 0;
0214 #endif
0215 }
0216 
0217 int Wallet::deleteWallet(const QString &name)
0218 {
0219 #ifdef OSX_KEYCHAIN_PORT_DISABLED
0220     QDBusReply<int> r = walletLauncher->getInterface().deleteWallet(name);
0221     return r.isValid() ? r : -1;
0222 #else
0223     return -1;
0224 #endif
0225 }
0226 
0227 Wallet *Wallet::openWallet(const QString &name, WId w, OpenType ot)
0228 {
0229     Q_UNUSED(w);
0230     Q_UNUSED(ot);
0231     Wallet *wallet = new Wallet(-1, name);
0232     QMetaObject::invokeMethod(wallet, "emitWalletOpened", Qt::QueuedConnection);
0233     return wallet;
0234 }
0235 
0236 bool Wallet::disconnectApplication(const QString &wallet, const QString &app)
0237 {
0238 #ifdef OSX_KEYCHAIN_PORT_DISABLED
0239     return walletLauncher->getInterface().disconnectApplication(wallet, app); // default is false
0240 #else
0241     return true;
0242 #endif
0243 }
0244 
0245 QStringList Wallet::users(const QString &name)
0246 {
0247 #ifdef OSX_KEYCHAIN_PORT_DISABLED
0248     return walletLauncher->getInterface().users(name); // default is QStringList()
0249 #else
0250     return QStringList();
0251 #endif
0252 }
0253 
0254 int Wallet::sync()
0255 {
0256 #ifdef OSX_KEYCHAIN_PORT_DISABLED
0257     if (d->handle == -1) {
0258         return -1;
0259     }
0260 
0261     walletLauncher->getInterface().sync(d->handle, appid());
0262 #endif
0263     return 0;
0264 }
0265 
0266 int Wallet::lockWallet()
0267 {
0268 #ifdef OSX_KEYCHAIN_PORT_DISABLED
0269     if (d->handle == -1) {
0270         return -1;
0271     }
0272 
0273     QDBusReply<int> r = walletLauncher->getInterface().close(d->handle, true, appid());
0274     d->handle = -1;
0275     d->folder.clear();
0276     d->name.clear();
0277     if (r.isValid()) {
0278         return r;
0279     }
0280 #endif
0281     return -1;
0282 }
0283 
0284 const QString &Wallet::walletName() const
0285 {
0286     return d->name;
0287 }
0288 
0289 bool Wallet::isOpen() const
0290 {
0291 #ifdef OSX_KEYCHAIN_PORT_DISABLED
0292     return d->handle != -1;
0293 #else
0294     return true;
0295 #endif
0296 }
0297 
0298 void Wallet::requestChangePassword(WId w)
0299 {
0300 #ifdef OSX_KEYCHAIN_PORT_DISABLED
0301     if (w == 0) {
0302         qDebug() << "Pass a valid window to KWallet::Wallet::requestChangePassword().";
0303     }
0304     if (d->handle == -1) {
0305         return;
0306     }
0307 
0308     walletLauncher->getInterface().changePassword(d->name, (qlonglong)w, appid());
0309 #endif
0310 }
0311 
0312 void Wallet::slotWalletClosed(int handle)
0313 {
0314 #ifdef OSX_KEYCHAIN_PORT_DISABLED
0315     if (d->handle == handle) {
0316         d->handle = -1;
0317         d->folder.clear();
0318         d->name.clear();
0319         Q_EMIT walletClosed();
0320     }
0321 #endif
0322 }
0323 
0324 QStringList Wallet::folderList()
0325 {
0326 #ifdef OSX_KEYCHAIN_PORT_DISABLED
0327     if (d->handle == -1) {
0328         return QStringList();
0329     }
0330 
0331     QDBusReply<QStringList> r = walletLauncher->getInterface().folderList(d->handle, appid());
0332     return r;
0333 #else
0334     return QStringList();
0335 #endif
0336 }
0337 
0338 QStringList Wallet::entryList()
0339 {
0340 #ifdef OSX_KEYCHAIN_PORT_DISABLED
0341     if (d->handle == -1) {
0342         return QStringList();
0343     }
0344 
0345     QDBusReply<QStringList> r = walletLauncher->getInterface().entryList(d->handle, d->folder, appid());
0346     return r;
0347 #else
0348     return QStringList();
0349 #endif
0350 }
0351 
0352 bool Wallet::hasFolder(const QString &f)
0353 {
0354 #ifdef OSX_KEYCHAIN_PORT_DISABLED
0355     if (d->handle == -1) {
0356         return false;
0357     }
0358 
0359     QDBusReply<bool> r = walletLauncher->getInterface().hasFolder(d->handle, f, appid());
0360     return r; // default is false
0361 #else
0362     return true;
0363 #endif
0364 }
0365 
0366 bool Wallet::createFolder(const QString &f)
0367 {
0368 #ifdef OSX_KEYCHAIN_PORT_DISABLED
0369     if (d->handle == -1) {
0370         return false;
0371     }
0372 
0373     if (!hasFolder(f)) {
0374         QDBusReply<bool> r = walletLauncher->getInterface().createFolder(d->handle, f, appid());
0375         return r;
0376     }
0377 
0378     return true; // folder already exists
0379 #else
0380     return true;
0381 #endif
0382 }
0383 
0384 bool Wallet::setFolder(const QString &f)
0385 {
0386 #ifdef OSX_KEYCHAIN_PORT_DISABLED
0387     bool rc = false;
0388 
0389     if (d->handle == -1) {
0390         return rc;
0391     }
0392 
0393     // Don't do this - the folder could have disappeared?
0394 #if 0
0395     if (f == d->folder) {
0396         return true;
0397     }
0398 #endif
0399 
0400     if (hasFolder(f)) {
0401         d->folder = f;
0402         rc = true;
0403     }
0404 
0405     return rc;
0406 #else
0407     return true;
0408 #endif
0409 }
0410 
0411 bool Wallet::removeFolder(const QString &f)
0412 {
0413 #ifdef OSX_KEYCHAIN_PORT_DISABLED
0414     if (d->handle == -1) {
0415         return false;
0416     }
0417 
0418     QDBusReply<bool> r = walletLauncher->getInterface().removeFolder(d->handle, f, appid());
0419     if (d->folder == f) {
0420         setFolder(QString());
0421     }
0422 
0423     return r; // default is false
0424 #else
0425     return true;
0426 #endif
0427 }
0428 
0429 const QString &Wallet::currentFolder() const
0430 {
0431     return d->folder;
0432 }
0433 
0434 int Wallet::readEntry(const QString &key, QByteArray &value)
0435 {
0436     const QByteArray serviceName(walletName().toUtf8());
0437     const QByteArray accountName(key.toUtf8());
0438     UInt32 passwordSize = 0;
0439     void *passwordData = 0;
0440     QString errMsg;
0441     if (isError(SecKeychainFindGenericPassword(NULL,
0442                                                serviceName.size(),
0443                                                serviceName.constData(),
0444                                                accountName.size(),
0445                                                accountName.constData(),
0446                                                &passwordSize,
0447                                                &passwordData,
0448                                                NULL),
0449                 &errMsg)) {
0450         qWarning() << "Could not retrieve password:" << qPrintable(errMsg);
0451         return -1;
0452     }
0453 
0454     value = QByteArray(reinterpret_cast<const char *>(passwordData), passwordSize);
0455     SecKeychainItemFreeContent(NULL, passwordData);
0456     return 0;
0457 }
0458 
0459 int Wallet::readEntryList(const QString &key, QMap<QString, QByteArray> &value)
0460 {
0461 #ifdef OSX_KEYCHAIN_PORT_DISABLED
0462     registerTypes();
0463 
0464     int rc = -1;
0465 
0466     if (d->handle == -1) {
0467         return rc;
0468     }
0469 
0470     QDBusReply<QVariantMap> r = walletLauncher->getInterface().readEntryList(d->handle, d->folder, key, appid());
0471     if (r.isValid()) {
0472         rc = 0;
0473         // convert <QString, QVariant> to <QString, QByteArray>
0474         const QVariantMap val = r.value();
0475         for (QVariantMap::const_iterator it = val.begin(); it != val.end(); ++it) {
0476             value.insert(it.key(), it.value().toByteArray());
0477         }
0478     }
0479 
0480     return rc;
0481 #else
0482     return -1;
0483 #endif
0484 }
0485 
0486 int Wallet::renameEntry(const QString &oldName, const QString &newName)
0487 {
0488 #ifdef OSX_KEYCHAIN_PORT_DISABLED
0489     int rc = -1;
0490 
0491     if (d->handle == -1) {
0492         return rc;
0493     }
0494 
0495     QDBusReply<int> r = walletLauncher->getInterface().renameEntry(d->handle, d->folder, oldName, newName, appid());
0496     if (r.isValid()) {
0497         rc = r;
0498     }
0499 
0500     return rc;
0501 #else
0502     return -1;
0503 #endif
0504 }
0505 
0506 int Wallet::readMap(const QString &key, QMap<QString, QString> &value)
0507 {
0508     QByteArray v;
0509     const int ret = readEntry(key, v);
0510     if (ret != 0) {
0511         return ret;
0512     }
0513     if (!v.isEmpty()) {
0514         QDataStream ds(&v, QIODevice::ReadOnly);
0515         ds >> value;
0516     }
0517     return 0;
0518 }
0519 
0520 int Wallet::readMapList(const QString &key, QMap<QString, QMap<QString, QString>> &value)
0521 {
0522 #ifdef OSX_KEYCHAIN_PORT_DISABLED
0523     registerTypes();
0524 
0525     int rc = -1;
0526 
0527     if (d->handle == -1) {
0528         return rc;
0529     }
0530 
0531     QDBusReply<QVariantMap> r = walletLauncher->getInterface().readMapList(d->handle, d->folder, key, appid());
0532     if (r.isValid()) {
0533         rc = 0;
0534         const QVariantMap val = r.value();
0535         for (QVariantMap::const_iterator it = val.begin(); it != val.end(); ++it) {
0536             QByteArray mapData = it.value().toByteArray();
0537             if (!mapData.isEmpty()) {
0538                 QDataStream ds(&mapData, QIODevice::ReadOnly);
0539                 QMap<QString, QString> v;
0540                 ds >> v;
0541                 value.insert(it.key(), v);
0542             }
0543         }
0544     }
0545 
0546     return rc;
0547 #else
0548     return -1;
0549 #endif
0550 }
0551 
0552 int Wallet::readPassword(const QString &key, QString &value)
0553 {
0554     QByteArray ba;
0555     const int ret = readEntry(key, ba);
0556     if (ret == 0) {
0557         value = QString::fromUtf8(ba.constData());
0558     }
0559     return ret;
0560 }
0561 
0562 int Wallet::readPasswordList(const QString &key, QMap<QString, QString> &value)
0563 {
0564     return -1;
0565 }
0566 
0567 static OSStatus writeEntryImplementation(const QString &walletName, const QString &key, const QByteArray &value)
0568 {
0569     const QByteArray serviceName(walletName.toUtf8());
0570     const QByteArray accountName(key.toUtf8());
0571     QString errMsg;
0572     OSStatus err = SecKeychainAddGenericPassword(NULL,
0573                                                  serviceName.size(),
0574                                                  serviceName.constData(),
0575                                                  accountName.size(),
0576                                                  accountName.constData(),
0577                                                  value.size(),
0578                                                  value.constData(),
0579                                                  NULL);
0580     if (err == errSecDuplicateItem) {
0581         err = removeEntryImplementation(walletName, key);
0582         if (isError(err, &errMsg)) {
0583             qWarning() << "Could not delete old key in keychain for replacing: " << qPrintable(errMsg);
0584             return err;
0585         }
0586     }
0587     if (isError(err, &errMsg)) {
0588         qWarning() << "Could not store password in keychain: " << qPrintable(errMsg);
0589         return err;
0590     }
0591     // qDebug() << "Successfully written out key:" << key;
0592     return err;
0593 }
0594 
0595 int Wallet::writeEntry(const QString &key, const QByteArray &password, EntryType entryType)
0596 {
0597     Q_UNUSED(entryType)
0598     return writeEntryImplementation(walletName(), key, password);
0599 }
0600 
0601 int Wallet::writeEntry(const QString &key, const QByteArray &value)
0602 {
0603     return writeEntryImplementation(walletName(), key, value);
0604 }
0605 
0606 int Wallet::writeMap(const QString &key, const QMap<QString, QString> &value)
0607 {
0608     QByteArray mapData;
0609     QDataStream ds(&mapData, QIODevice::WriteOnly);
0610     ds << value;
0611     return writeEntry(key, mapData);
0612 }
0613 
0614 int Wallet::writePassword(const QString &key, const QString &value)
0615 {
0616     return writeEntry(key, value.toUtf8());
0617 }
0618 
0619 bool Wallet::hasEntry(const QString &key)
0620 {
0621     const QByteArray serviceName(walletName().toUtf8());
0622     const QByteArray accountName(key.toUtf8());
0623     return !isError(
0624         SecKeychainFindGenericPassword(NULL, serviceName.size(), serviceName.constData(), accountName.size(), accountName.constData(), NULL, NULL, NULL),
0625         0);
0626 }
0627 
0628 int Wallet::removeEntry(const QString &key)
0629 {
0630     return removeEntryImplementation(walletName(), key);
0631 }
0632 
0633 Wallet::EntryType Wallet::entryType(const QString &key)
0634 {
0635 #ifdef OSX_KEYCHAIN_PORT_DISABLED
0636     int rc = 0;
0637 
0638     if (d->handle == -1) {
0639         return Wallet::Unknown;
0640     }
0641 
0642     QDBusReply<int> r = walletLauncher->getInterface().entryType(d->handle, d->folder, key, appid());
0643     if (r.isValid()) {
0644         rc = r;
0645     }
0646 
0647     return static_cast<EntryType>(rc);
0648 #else
0649     return Wallet::Unknown;
0650 #endif
0651 }
0652 
0653 void Wallet::slotFolderUpdated(const QString &wallet, const QString &folder)
0654 {
0655     if (d->name == wallet) {
0656         Q_EMIT folderUpdated(folder);
0657     }
0658 }
0659 
0660 void Wallet::slotFolderListUpdated(const QString &wallet)
0661 {
0662     if (d->name == wallet) {
0663         Q_EMIT folderListUpdated();
0664     }
0665 }
0666 
0667 void Wallet::slotApplicationDisconnected(const QString &wallet, const QString &application)
0668 {
0669 #ifdef OSX_KEYCHAIN_PORT_DISABLED
0670     if (d->handle >= 0 && d->name == wallet && application == appid()) {
0671         slotWalletClosed(d->handle);
0672     }
0673 #endif
0674 }
0675 
0676 void Wallet::walletAsyncOpened(int tId, int handle)
0677 {
0678 #ifdef OSX_KEYCHAIN_PORT_DISABLED
0679     // ignore responses to calls other than ours
0680     if (d->transactionId != tId || d->handle != -1) {
0681         return;
0682     }
0683 
0684     // disconnect the async signal
0685     disconnect(this, SLOT(walletAsyncOpened(int, int)));
0686 
0687     d->handle = handle;
0688     Q_EMIT walletOpened(handle > 0);
0689 #endif
0690 }
0691 
0692 void Wallet::emitWalletAsyncOpenError()
0693 {
0694     Q_EMIT walletOpened(false);
0695 }
0696 
0697 void Wallet::emitWalletOpened()
0698 {
0699     Q_EMIT walletOpened(true);
0700 }
0701 
0702 bool Wallet::folderDoesNotExist(const QString &wallet, const QString &folder)
0703 {
0704 #ifdef OSX_KEYCHAIN_PORT_DISABLED
0705     QDBusReply<bool> r = walletLauncher->getInterface().folderDoesNotExist(wallet, folder);
0706     return r;
0707 #else
0708     return false;
0709 #endif
0710 }
0711 
0712 bool Wallet::keyDoesNotExist(const QString &wallet, const QString &folder, const QString &key)
0713 {
0714 #ifdef OSX_KEYCHAIN_PORT_DISABLED
0715     QDBusReply<bool> r = walletLauncher->getInterface().keyDoesNotExist(wallet, folder, key);
0716     return r;
0717 #else
0718     return false;
0719 #endif
0720 }
0721 
0722 void Wallet::slotCollectionStatusChanged(int status)
0723 {
0724 }
0725 
0726 void Wallet::slotCollectionDeleted()
0727 {
0728     d->folder.clear();
0729     d->name.clear();
0730     Q_EMIT walletClosed();
0731 }
0732 
0733 void Wallet::virtual_hook(int, void *)
0734 {
0735     // BASE::virtual_hook( id, data );
0736 }
0737 
0738 #include "moc_kwallet.cpp"