File indexing completed on 2024-12-01 12:36:28
0001 /* 0002 This file is part of the KDE Password Server 0003 SPDX-FileCopyrightText: 2002 Waldo Bastian <bastian@kde.org> 0004 SPDX-FileCopyrightText: 2005 David Faure <faure@kde.org> 0005 SPDX-FileCopyrightText: 2012 Dawit Alemayehu <adawit@kde.org> 0006 SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org> 0007 0008 SPDX-License-Identifier: GPL-2.0-only 0009 */ 0010 0011 // KDE Password Server 0012 0013 #include "kpasswdserver.h" 0014 0015 #include "kpasswdserveradaptor.h" 0016 0017 #include <KLocalizedString> 0018 #include <KMessageDialog> 0019 #include <KPasswordDialog> 0020 #include <KUserTimestamp> 0021 #include <kwindowsystem.h> 0022 0023 #ifdef HAVE_KF5WALLET 0024 #include <KWallet> 0025 #endif 0026 0027 #include <QPushButton> 0028 #include <QTimer> 0029 #include <ctime> 0030 0031 #include "../gui/config-kiogui.h" 0032 0033 #if HAVE_X11 0034 #include <KX11Extras> 0035 #endif 0036 0037 Q_LOGGING_CATEGORY(category, "kf.kio.kpasswdserver", QtInfoMsg) 0038 0039 static const char s_domain[] = "domain"; 0040 static const char s_anonymous[] = "anonymous"; 0041 static const char s_bypassCacheAndKwallet[] = "bypass-cache-and-kwallet"; 0042 static const char s_skipCachingOnQuery[] = "skip-caching-on-query"; 0043 static const char s_hideUsernameInput[] = "hide-username-line"; 0044 static const char s_usernameContextHelp[] = "username-context-help"; 0045 0046 static qlonglong getRequestId() 0047 { 0048 static qlonglong nextRequestId = 0; 0049 return nextRequestId++; 0050 } 0051 0052 bool KPasswdServer::AuthInfoContainer::Sorter::operator()(const AuthInfoContainer &n1, const AuthInfoContainer &n2) const 0053 { 0054 const int l1 = n1.directory.length(); 0055 const int l2 = n2.directory.length(); 0056 return l1 < l2; 0057 } 0058 0059 KPasswdServer::KPasswdServer(QObject *parent, const QList<QVariant> &) 0060 : KDEDModule(parent) 0061 { 0062 KIO::AuthInfo::registerMetaTypes(); 0063 0064 m_seqNr = 0; 0065 m_wallet = nullptr; 0066 m_walletDisabled = false; 0067 0068 KPasswdServerAdaptor *adaptor = new KPasswdServerAdaptor(this); 0069 // connect signals to the adaptor 0070 connect(this, &KPasswdServer::checkAuthInfoAsyncResult, adaptor, &KPasswdServerAdaptor::checkAuthInfoAsyncResult); 0071 connect(this, &KPasswdServer::queryAuthInfoAsyncResult, adaptor, &KPasswdServerAdaptor::queryAuthInfoAsyncResult); 0072 0073 connect(this, &KDEDModule::windowUnregistered, this, &KPasswdServer::removeAuthForWindowId); 0074 0075 #if HAVE_X11 0076 connect(KX11Extras::self(), &KX11Extras::windowRemoved, this, &KPasswdServer::windowRemoved); 0077 #endif 0078 } 0079 0080 KPasswdServer::~KPasswdServer() 0081 { 0082 // TODO: what about clients waiting for requests? will they just 0083 // notice kpasswdserver is gone from the dbus? 0084 qDeleteAll(m_authPending); 0085 qDeleteAll(m_authWait); 0086 qDeleteAll(m_authDict); 0087 qDeleteAll(m_authInProgress); 0088 qDeleteAll(m_authRetryInProgress); 0089 0090 #ifdef HAVE_KF5WALLET 0091 delete m_wallet; 0092 #endif 0093 } 0094 0095 #ifdef HAVE_KF5WALLET 0096 0097 // Helper - returns the wallet key to use for read/store/checking for existence. 0098 static QString makeWalletKey(const QString &key, const QString &realm) 0099 { 0100 return realm.isEmpty() ? key : key + QLatin1Char('-') + realm; 0101 } 0102 0103 // Helper for storeInWallet/readFromWallet 0104 static QString makeMapKey(const char *key, int entryNumber) 0105 { 0106 QString str = QLatin1String(key); 0107 if (entryNumber > 1) { 0108 str += QLatin1Char('-') + QString::number(entryNumber); 0109 } 0110 return str; 0111 } 0112 0113 static bool storeInWallet(KWallet::Wallet *wallet, const QString &key, const KIO::AuthInfo &info) 0114 { 0115 if (!wallet->hasFolder(KWallet::Wallet::PasswordFolder())) { 0116 if (!wallet->createFolder(KWallet::Wallet::PasswordFolder())) { 0117 return false; 0118 } 0119 } 0120 wallet->setFolder(KWallet::Wallet::PasswordFolder()); 0121 // Before saving, check if there's already an entry with this login. 0122 // If so, replace it (with the new password). Otherwise, add a new entry. 0123 typedef QMap<QString, QString> Map; 0124 int entryNumber = 1; 0125 Map map; 0126 QString walletKey = makeWalletKey(key, info.realmValue); 0127 qCDebug(category) << "walletKey =" << walletKey << " reading existing map"; 0128 if (wallet->readMap(walletKey, map) == 0) { 0129 Map::ConstIterator end = map.constEnd(); 0130 Map::ConstIterator it = map.constFind(QStringLiteral("login")); 0131 while (it != end) { 0132 if (it.value() == info.username) { 0133 break; // OK, overwrite this entry 0134 } 0135 it = map.constFind(QStringLiteral("login-") + QString::number(++entryNumber)); 0136 } 0137 // If no entry was found, create a new entry - entryNumber is set already. 0138 } 0139 const QString loginKey = makeMapKey("login", entryNumber); 0140 const QString passwordKey = makeMapKey("password", entryNumber); 0141 qCDebug(category) << "writing to " << loginKey << "," << passwordKey; 0142 // note the overwrite=true by default 0143 map.insert(loginKey, info.username); 0144 map.insert(passwordKey, info.password); 0145 wallet->writeMap(walletKey, map); 0146 return true; 0147 } 0148 0149 static bool readFromWallet(KWallet::Wallet *wallet, 0150 const QString &key, 0151 const QString &realm, 0152 QString &username, 0153 QString &password, 0154 bool userReadOnly, 0155 QMap<QString, QString> &knownLogins) 0156 { 0157 // qCDebug(category) << "key =" << key << " username =" << username << " password =" /*<< password*/ 0158 // << " userReadOnly =" << userReadOnly << " realm =" << realm; 0159 if (wallet->hasFolder(KWallet::Wallet::PasswordFolder())) { 0160 wallet->setFolder(KWallet::Wallet::PasswordFolder()); 0161 0162 QMap<QString, QString> map; 0163 if (wallet->readMap(makeWalletKey(key, realm), map) == 0) { 0164 typedef QMap<QString, QString> Map; 0165 int entryNumber = 1; 0166 Map::ConstIterator end = map.constEnd(); 0167 Map::ConstIterator it = map.constFind(QStringLiteral("login")); 0168 while (it != end) { 0169 // qCDebug(category) << "found " << it.key() << "=" << it.value(); 0170 Map::ConstIterator pwdIter = map.constFind(makeMapKey("password", entryNumber)); 0171 if (pwdIter != end) { 0172 if (it.value() == username) { 0173 password = pwdIter.value(); 0174 } 0175 knownLogins.insert(it.value(), pwdIter.value()); 0176 } 0177 0178 it = map.constFind(QStringLiteral("login-") + QString::number(++entryNumber)); 0179 } 0180 // qCDebug(category) << knownLogins.count() << " known logins"; 0181 0182 if (!userReadOnly && !knownLogins.isEmpty() && username.isEmpty()) { 0183 // Pick one, any one... 0184 username = knownLogins.begin().key(); 0185 password = knownLogins.begin().value(); 0186 // qCDebug(category) << "picked the first one:" << username; 0187 } 0188 0189 return true; 0190 } 0191 } 0192 return false; 0193 } 0194 0195 #endif 0196 0197 bool KPasswdServer::hasPendingQuery(const QString &key, const KIO::AuthInfo &info) 0198 { 0199 const QString path2(info.url.path().left(info.url.path().indexOf(QLatin1Char('/')) + 1)); 0200 for (const Request *request : std::as_const(m_authPending)) { 0201 if (request->key != key) { 0202 continue; 0203 } 0204 0205 if (info.verifyPath) { 0206 const QString path1(request->info.url.path().left(info.url.path().indexOf(QLatin1Char('/')) + 1)); 0207 if (!path2.startsWith(path1)) { 0208 continue; 0209 } 0210 } 0211 0212 return true; 0213 } 0214 0215 return false; 0216 } 0217 0218 // deprecated method, not used anymore. TODO KF6: REMOVE 0219 QByteArray KPasswdServer::checkAuthInfo(const QByteArray &data, qlonglong windowId, qlonglong usertime) 0220 { 0221 KIO::AuthInfo info; 0222 QDataStream stream(data); 0223 stream >> info; 0224 if (usertime != 0) { 0225 KUserTimestamp::updateUserTimestamp(usertime); 0226 } 0227 0228 // if the check depends on a pending query, delay it 0229 // until that query is finished. 0230 const QString key(createCacheKey(info)); 0231 if (hasPendingQuery(key, info)) { 0232 setDelayedReply(true); 0233 Request *pendingCheck = new Request; 0234 pendingCheck->isAsync = false; 0235 if (calledFromDBus()) { 0236 pendingCheck->transaction = message(); 0237 } 0238 pendingCheck->key = key; 0239 pendingCheck->info = info; 0240 m_authWait.append(pendingCheck); 0241 return data; // return value will be ignored 0242 } 0243 0244 // qCDebug(category) << "key =" << key << "user =" << info.username << "windowId =" << windowId; 0245 const AuthInfoContainer *result = findAuthInfoItem(key, info); 0246 if (!result || result->isCanceled) { 0247 #ifdef HAVE_KF5WALLET 0248 if (!result && !m_walletDisabled && (info.username.isEmpty() || info.password.isEmpty()) 0249 && !KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::NetworkWallet(), KWallet::Wallet::PasswordFolder(), makeWalletKey(key, info.realmValue))) { 0250 QMap<QString, QString> knownLogins; 0251 if (openWallet(windowId)) { 0252 if (readFromWallet(m_wallet, key, info.realmValue, info.username, info.password, info.readOnly, knownLogins)) { 0253 info.setModified(true); 0254 // fall through 0255 } 0256 } 0257 } else { 0258 info.setModified(false); 0259 } 0260 #else 0261 info.setModified(false); 0262 #endif 0263 } else { 0264 qCDebug(category) << "Found cached authentication for" << key; 0265 updateAuthExpire(key, result, windowId, false); 0266 copyAuthInfo(result, info); 0267 } 0268 0269 QByteArray data2; 0270 QDataStream stream2(&data2, QIODevice::WriteOnly); 0271 stream2 << info; 0272 return data2; 0273 } 0274 0275 qlonglong KPasswdServer::checkAuthInfoAsync(KIO::AuthInfo info, qlonglong windowId, qlonglong usertime) 0276 { 0277 if (usertime != 0) { 0278 KUserTimestamp::updateUserTimestamp(usertime); 0279 } 0280 0281 // send the request id back to the client 0282 qlonglong requestId = getRequestId(); 0283 qCDebug(category) << "User =" << info.username << ", WindowId =" << windowId; 0284 if (calledFromDBus()) { 0285 QDBusMessage reply(message().createReply(requestId)); 0286 QDBusConnection::sessionBus().send(reply); 0287 } 0288 0289 // if the check depends on a pending query, delay it 0290 // until that query is finished. 0291 const QString key(createCacheKey(info)); 0292 if (hasPendingQuery(key, info)) { 0293 Request *pendingCheck = new Request; 0294 pendingCheck->isAsync = true; 0295 pendingCheck->requestId = requestId; 0296 pendingCheck->key = key; 0297 pendingCheck->info = info; 0298 m_authWait.append(pendingCheck); 0299 return 0; // ignored as we already sent a reply 0300 } 0301 0302 const AuthInfoContainer *result = findAuthInfoItem(key, info); 0303 if (!result || result->isCanceled) { 0304 #ifdef HAVE_KF5WALLET 0305 if (!result && !m_walletDisabled && (info.username.isEmpty() || info.password.isEmpty()) 0306 && !KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::NetworkWallet(), KWallet::Wallet::PasswordFolder(), makeWalletKey(key, info.realmValue))) { 0307 QMap<QString, QString> knownLogins; 0308 if (openWallet(windowId)) { 0309 if (readFromWallet(m_wallet, key, info.realmValue, info.username, info.password, info.readOnly, knownLogins)) { 0310 info.setModified(true); 0311 // fall through 0312 } 0313 } 0314 } else { 0315 info.setModified(false); 0316 } 0317 #else 0318 info.setModified(false); 0319 #endif 0320 } else { 0321 // qCDebug(category) << "Found cached authentication for" << key; 0322 updateAuthExpire(key, result, windowId, false); 0323 copyAuthInfo(result, info); 0324 } 0325 0326 Q_EMIT checkAuthInfoAsyncResult(requestId, m_seqNr, info); 0327 return 0; // ignored 0328 } 0329 0330 // deprecated method, not used anymore. TODO KF6: REMOVE 0331 QByteArray KPasswdServer::queryAuthInfo(const QByteArray &data, const QString &errorMsg, qlonglong windowId, qlonglong seqNr, qlonglong usertime) 0332 { 0333 KIO::AuthInfo info; 0334 QDataStream stream(data); 0335 stream >> info; 0336 0337 qCDebug(category) << "User =" << info.username << ", WindowId =" << windowId << "seqNr =" << seqNr << ", errorMsg =" << errorMsg; 0338 0339 if (!info.password.isEmpty()) { // should we really allow the caller to pre-fill the password? 0340 qCDebug(category) << "password was set by caller"; 0341 } 0342 if (usertime != 0) { 0343 KUserTimestamp::updateUserTimestamp(usertime); 0344 } 0345 0346 const QString key(createCacheKey(info)); 0347 Request *request = new Request; 0348 setDelayedReply(true); 0349 request->isAsync = false; 0350 request->transaction = message(); 0351 request->key = key; 0352 request->info = info; 0353 request->windowId = windowId; 0354 request->seqNr = seqNr; 0355 if (errorMsg == QLatin1String("<NoAuthPrompt>")) { 0356 request->errorMsg.clear(); 0357 request->prompt = false; 0358 } else { 0359 request->errorMsg = errorMsg; 0360 request->prompt = true; 0361 } 0362 m_authPending.append(request); 0363 0364 if (m_authPending.count() == 1) { 0365 QTimer::singleShot(0, this, &KPasswdServer::processRequest); 0366 } 0367 0368 return QByteArray(); // return value is going to be ignored 0369 } 0370 0371 qlonglong KPasswdServer::queryAuthInfoAsync(const KIO::AuthInfo &info, const QString &errorMsg, qlonglong windowId, qlonglong seqNr, qlonglong usertime) 0372 { 0373 qCDebug(category) << "User =" << info.username << ", WindowId =" << windowId << "seqNr =" << seqNr << ", errorMsg =" << errorMsg; 0374 0375 if (!info.password.isEmpty()) { 0376 qCDebug(category) << "password was set by caller"; 0377 } 0378 if (usertime != 0) { 0379 KUserTimestamp::updateUserTimestamp(usertime); 0380 } 0381 0382 const QString key(createCacheKey(info)); 0383 Request *request = new Request; 0384 request->isAsync = true; 0385 request->requestId = getRequestId(); 0386 request->key = key; 0387 request->info = info; 0388 request->windowId = windowId; 0389 request->seqNr = seqNr; 0390 if (errorMsg == QLatin1String("<NoAuthPrompt>")) { 0391 request->errorMsg.clear(); 0392 request->prompt = false; 0393 } else { 0394 request->errorMsg = errorMsg; 0395 request->prompt = true; 0396 } 0397 m_authPending.append(request); 0398 0399 if (m_authPending.count() == 1) { 0400 QTimer::singleShot(0, this, &KPasswdServer::processRequest); 0401 } 0402 0403 return request->requestId; 0404 } 0405 0406 void KPasswdServer::addAuthInfo(const KIO::AuthInfo &info, qlonglong windowId) 0407 { 0408 qCDebug(category) << "User =" << info.username << ", Realm =" << info.realmValue << ", WindowId =" << windowId; 0409 if (!info.keepPassword) { 0410 qWarning() << "This KIO worker is caching a password in KWallet even though the user didn't ask for it!"; 0411 } 0412 const QString key(createCacheKey(info)); 0413 0414 m_seqNr++; 0415 0416 #ifdef HAVE_KF5WALLET 0417 if (!m_walletDisabled && openWallet(windowId) && storeInWallet(m_wallet, key, info)) { 0418 // Since storing the password in the wallet succeeded, make sure the 0419 // password information is stored in memory only for the duration the 0420 // windows associated with it are still around. 0421 KIO::AuthInfo authToken(info); 0422 authToken.keepPassword = false; 0423 addAuthInfoItem(key, authToken, windowId, m_seqNr, false); 0424 return; 0425 } 0426 #endif 0427 0428 addAuthInfoItem(key, info, windowId, m_seqNr, false); 0429 } 0430 0431 // deprecated method, not used anymore. TODO KF6: REMOVE 0432 void KPasswdServer::addAuthInfo(const QByteArray &data, qlonglong windowId) 0433 { 0434 KIO::AuthInfo info; 0435 QDataStream stream(data); 0436 stream >> info; 0437 addAuthInfo(info, windowId); 0438 } 0439 0440 void KPasswdServer::removeAuthInfo(const QString &host, const QString &protocol, const QString &user) 0441 { 0442 qCDebug(category) << protocol << host << user; 0443 0444 QHashIterator<QString, AuthInfoContainerList *> dictIterator(m_authDict); 0445 while (dictIterator.hasNext()) { 0446 dictIterator.next(); 0447 0448 const AuthInfoContainerList *authList = dictIterator.value(); 0449 if (!authList) { 0450 continue; 0451 } 0452 0453 for (const AuthInfoContainer ¤t : *authList) { 0454 qCDebug(category) << "Evaluating: " << current.info.url.scheme() << current.info.url.host() << current.info.username; 0455 if (current.info.url.scheme() == protocol && current.info.url.host() == host && (current.info.username == user || user.isEmpty())) { 0456 qCDebug(category) << "Removing this entry"; 0457 removeAuthInfoItem(dictIterator.key(), current.info); // warning, this can modify m_authDict! 0458 } 0459 } 0460 } 0461 } 0462 0463 #ifdef HAVE_KF5WALLET 0464 bool KPasswdServer::openWallet(qlonglong windowId) 0465 { 0466 if (m_wallet && !m_wallet->isOpen()) { // forced closed 0467 delete m_wallet; 0468 m_wallet = nullptr; 0469 } 0470 if (!m_wallet) { 0471 m_wallet = KWallet::Wallet::openWallet(KWallet::Wallet::NetworkWallet(), static_cast<WId>(windowId)); 0472 } 0473 return m_wallet != nullptr; 0474 } 0475 #endif 0476 0477 void KPasswdServer::processRequest() 0478 { 0479 if (m_authPending.isEmpty()) { 0480 return; 0481 } 0482 0483 std::unique_ptr<Request> request(m_authPending.takeFirst()); 0484 0485 // Prevent multiple prompts originating from the same window or the same 0486 // key (server address). 0487 const QString windowIdStr = QString::number(request->windowId); 0488 if (m_authPrompted.contains(windowIdStr) || m_authPrompted.contains(request->key)) { 0489 m_authPending.prepend(request.release()); // put it back. 0490 return; 0491 } 0492 0493 m_authPrompted.append(windowIdStr); 0494 m_authPrompted.append(request->key); 0495 0496 KIO::AuthInfo &info = request->info; 0497 0498 // NOTE: If info.username is empty and info.url.userName() is not, set 0499 // info.username to info.url.userName() to ensure proper caching. See 0500 // note passwordDialogDone. 0501 if (info.username.isEmpty() && !info.url.userName().isEmpty()) { 0502 info.username = info.url.userName(); 0503 } 0504 const bool bypassCacheAndKWallet = info.getExtraField(QString::fromLatin1(s_bypassCacheAndKwallet)).toBool(); 0505 0506 const AuthInfoContainer *result = findAuthInfoItem(request->key, request->info); 0507 qCDebug(category) << "key=" << request->key << ", user=" << info.username << "seqNr: request=" << request->seqNr 0508 << ", result=" << (result ? result->seqNr : -1); 0509 0510 if (!bypassCacheAndKWallet && result && (request->seqNr < result->seqNr)) { 0511 qCDebug(category) << "auto retry!"; 0512 if (result->isCanceled) { 0513 info.setModified(false); 0514 } else { 0515 updateAuthExpire(request->key, result, request->windowId, false); 0516 copyAuthInfo(result, info); 0517 } 0518 } else { 0519 m_seqNr++; 0520 if (result && !request->errorMsg.isEmpty()) { 0521 const QString prompt = request->errorMsg.trimmed() + QLatin1Char('\n') + i18n("Do you want to retry?"); 0522 0523 KMessageDialog *dlg = new KMessageDialog(KMessageDialog::WarningContinueCancel, prompt, nullptr); 0524 dlg->setAttribute(Qt::WA_DeleteOnClose); 0525 dlg->setWindowTitle(i18n("Retry Authentication")); 0526 dlg->setWindowIcon(QIcon::fromTheme(QStringLiteral("dialog-password"))); 0527 dlg->setObjectName(QStringLiteral("warningOKCancel")); 0528 KGuiItem retryButton(i18nc("@action:button filter-continue", "Retry")); 0529 0530 dlg->setButtons(retryButton); 0531 0532 connect(dlg, &QDialog::finished, this, [this, dlg](int result) { 0533 retryDialogDone(result, dlg); 0534 }); 0535 0536 dlg->setAttribute(Qt::WA_NativeWindow, true); 0537 KWindowSystem::setMainWindow(dlg->windowHandle(), request->windowId); 0538 0539 qCDebug(category) << "Calling open on retry dialog" << dlg; 0540 m_authRetryInProgress.insert(dlg, request.release()); 0541 dlg->open(); 0542 return; 0543 } 0544 0545 if (request->prompt) { 0546 showPasswordDialog(request.release()); 0547 return; 0548 } else { 0549 if (!bypassCacheAndKWallet && request->prompt) { 0550 addAuthInfoItem(request->key, info, 0, m_seqNr, true); 0551 } 0552 info.setModified(false); 0553 } 0554 } 0555 0556 sendResponse(request.get()); 0557 } 0558 0559 QString KPasswdServer::createCacheKey(const KIO::AuthInfo &info) 0560 { 0561 if (!info.url.isValid()) { 0562 // Note that a null key will break findAuthInfoItem later on... 0563 qCWarning(category) << "createCacheKey: invalid URL " << info.url; 0564 return QString(); 0565 } 0566 0567 // Generate the basic key sequence. 0568 QString key = info.url.scheme(); 0569 key += QLatin1Char('-'); 0570 if (!info.url.userName().isEmpty()) { 0571 key += info.url.userName() + QLatin1Char('@'); 0572 } 0573 key += info.url.host(); 0574 int port = info.url.port(); 0575 if (port) { 0576 key += QLatin1Char(':') + QString::number(port); 0577 } 0578 0579 return key; 0580 } 0581 0582 void KPasswdServer::copyAuthInfo(const AuthInfoContainer *i, KIO::AuthInfo &info) 0583 { 0584 info = i->info; 0585 info.setModified(true); 0586 } 0587 0588 const KPasswdServer::AuthInfoContainer *KPasswdServer::findAuthInfoItem(const QString &key, const KIO::AuthInfo &info) 0589 { 0590 // qCDebug(category) << "key=" << key << ", user=" << info.username; 0591 0592 AuthInfoContainerList *authList = m_authDict.value(key); 0593 if (authList) { 0594 QString path2 = info.url.path().left(info.url.path().indexOf(QLatin1Char('/')) + 1); 0595 auto it = authList->begin(); 0596 while (it != authList->end()) { 0597 const AuthInfoContainer ¤t = (*it); 0598 if (current.expire == AuthInfoContainer::expTime && static_cast<qulonglong>(time(nullptr)) > current.expireTime) { 0599 it = authList->erase(it); 0600 continue; 0601 } 0602 0603 if (info.verifyPath) { 0604 const QString path1 = current.directory; 0605 if (path2.startsWith(path1) && (info.username.isEmpty() || info.username == current.info.username)) { 0606 return ¤t; 0607 } 0608 } else { 0609 if (current.info.realmValue == info.realmValue && (info.username.isEmpty() || info.username == current.info.username)) { 0610 return ¤t; // TODO: Update directory info 0611 } 0612 } 0613 0614 ++it; 0615 } 0616 } 0617 return nullptr; 0618 } 0619 0620 void KPasswdServer::removeAuthInfoItem(const QString &key, const KIO::AuthInfo &info) 0621 { 0622 AuthInfoContainerList *authList = m_authDict.value(key); 0623 if (!authList) { 0624 return; 0625 } 0626 0627 auto it = authList->begin(); 0628 while (it != authList->end()) { 0629 if ((*it).info.realmValue == info.realmValue) { 0630 it = authList->erase(it); 0631 } else { 0632 ++it; 0633 } 0634 } 0635 if (authList->isEmpty()) { 0636 delete m_authDict.take(key); 0637 } 0638 } 0639 0640 void KPasswdServer::addAuthInfoItem(const QString &key, const KIO::AuthInfo &info, qlonglong windowId, qlonglong seqNr, bool canceled) 0641 { 0642 qCDebug(category) << "key=" << key << "window-id=" << windowId << "username=" << info.username << "realm=" << info.realmValue << "seqNr=" << seqNr 0643 << "keepPassword?" << info.keepPassword << "canceled?" << canceled; 0644 AuthInfoContainerList *authList = m_authDict.value(key); 0645 if (!authList) { 0646 authList = new AuthInfoContainerList; 0647 m_authDict.insert(key, authList); 0648 } 0649 bool found = false; 0650 AuthInfoContainer authItem; 0651 auto it = authList->begin(); 0652 while (it != authList->end()) { 0653 if ((*it).info.realmValue == info.realmValue) { 0654 authItem = (*it); 0655 it = authList->erase(it); 0656 found = true; 0657 break; 0658 } else { 0659 ++it; 0660 } 0661 } 0662 0663 if (!found) { 0664 qCDebug(category) << "Creating AuthInfoContainer"; 0665 authItem.expire = AuthInfoContainer::expTime; 0666 } 0667 0668 authItem.info = info; 0669 authItem.directory = info.url.path().left(info.url.path().indexOf(QLatin1Char('/')) + 1); 0670 authItem.seqNr = seqNr; 0671 authItem.isCanceled = canceled; 0672 0673 updateAuthExpire(key, &authItem, windowId, (info.keepPassword && !canceled)); 0674 0675 // Insert into list, keep the list sorted "longest path" first. 0676 authList->append(authItem); 0677 std::sort(authList->begin(), authList->end(), AuthInfoContainer::Sorter()); 0678 } 0679 0680 void KPasswdServer::updateAuthExpire(const QString &key, const AuthInfoContainer *auth, qlonglong windowId, bool keep) 0681 { 0682 AuthInfoContainer *current = const_cast<AuthInfoContainer *>(auth); 0683 Q_ASSERT(current); 0684 0685 qCDebug(category) << "key=" << key << "expire=" << current->expire << "window-id=" << windowId << "keep=" << keep; 0686 0687 if (keep && !windowId) { 0688 current->expire = AuthInfoContainer::expNever; 0689 } else if (windowId && (current->expire != AuthInfoContainer::expNever)) { 0690 current->expire = AuthInfoContainer::expWindowClose; 0691 if (!current->windowList.contains(windowId)) { 0692 current->windowList.append(windowId); 0693 } 0694 } else if (current->expire == AuthInfoContainer::expTime) { 0695 current->expireTime = time(nullptr) + 10; 0696 } 0697 0698 // Update mWindowIdList 0699 if (windowId) { 0700 QStringList &keysChanged = mWindowIdList[windowId]; // find or insert 0701 if (!keysChanged.contains(key)) { 0702 keysChanged.append(key); 0703 } 0704 } 0705 } 0706 0707 void KPasswdServer::removeAuthForWindowId(qlonglong windowId) 0708 { 0709 const QStringList keysChanged = mWindowIdList.value(windowId); 0710 for (const QString &key : keysChanged) { 0711 AuthInfoContainerList *authList = m_authDict.value(key); 0712 if (!authList) { 0713 continue; 0714 } 0715 0716 QMutableVectorIterator<AuthInfoContainer> it(*authList); 0717 while (it.hasNext()) { 0718 AuthInfoContainer ¤t = it.next(); 0719 if (current.expire == AuthInfoContainer::expWindowClose) { 0720 if (current.windowList.removeAll(windowId) && current.windowList.isEmpty()) { 0721 it.remove(); 0722 } 0723 } 0724 } 0725 } 0726 } 0727 0728 void KPasswdServer::showPasswordDialog(KPasswdServer::Request *request) 0729 { 0730 KIO::AuthInfo &info = request->info; 0731 QString username = info.username; 0732 QString password = info.password; 0733 bool hasWalletData = false; 0734 QMap<QString, QString> knownLogins; 0735 0736 #ifdef HAVE_KF5WALLET 0737 const bool bypassCacheAndKWallet = info.getExtraField(QString::fromLatin1(s_bypassCacheAndKwallet)).toBool(); 0738 if (!bypassCacheAndKWallet && (username.isEmpty() || password.isEmpty()) && !m_walletDisabled 0739 && !KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::NetworkWallet(), 0740 KWallet::Wallet::PasswordFolder(), 0741 makeWalletKey(request->key, info.realmValue))) { 0742 // no login+pass provided, check if kwallet has one 0743 if (openWallet(request->windowId)) { 0744 hasWalletData = readFromWallet(m_wallet, request->key, info.realmValue, username, password, info.readOnly, knownLogins); 0745 } 0746 } 0747 #endif 0748 0749 // assemble dialog-flags 0750 KPasswordDialog::KPasswordDialogFlags dialogFlags; 0751 0752 if (info.getExtraField(QString::fromLatin1(s_domain)).isValid()) { 0753 dialogFlags |= KPasswordDialog::ShowDomainLine; 0754 if (info.getExtraFieldFlags(QString::fromLatin1(s_domain)) & KIO::AuthInfo::ExtraFieldReadOnly) { 0755 dialogFlags |= KPasswordDialog::DomainReadOnly; 0756 } 0757 } 0758 0759 if (info.getExtraField(QString::fromLatin1(s_anonymous)).isValid()) { 0760 dialogFlags |= KPasswordDialog::ShowAnonymousLoginCheckBox; 0761 } 0762 0763 if (!info.getExtraField(QString::fromLatin1(s_hideUsernameInput)).toBool()) { 0764 dialogFlags |= KPasswordDialog::ShowUsernameLine; 0765 } 0766 0767 #ifdef HAVE_KF5WALLET 0768 // If wallet is not enabled and the caller explicitly requested for it, 0769 // do not show the keep password checkbox. 0770 if (info.keepPassword && KWallet::Wallet::isEnabled()) { 0771 dialogFlags |= KPasswordDialog::ShowKeepPassword; 0772 } 0773 #endif 0774 0775 // instantiate dialog 0776 qCDebug(category) << "Widget for" << request->windowId << QWidget::find(request->windowId); 0777 0778 KPasswordDialog *dlg = new KPasswordDialog(nullptr, dialogFlags); 0779 dlg->setAttribute(Qt::WA_DeleteOnClose); 0780 0781 connect(dlg, &QDialog::finished, this, [this, dlg](int result) { 0782 passwordDialogDone(result, dlg); 0783 }); 0784 0785 dlg->setPrompt(info.prompt); 0786 dlg->setUsername(username); 0787 if (info.caption.isEmpty()) { 0788 dlg->setWindowTitle(i18n("Authentication Dialog")); 0789 } else { 0790 dlg->setWindowTitle(info.caption); 0791 } 0792 0793 if (!info.comment.isEmpty()) { 0794 dlg->addCommentLine(info.commentLabel, info.comment); 0795 } 0796 0797 if (!password.isEmpty()) { 0798 dlg->setPassword(password); 0799 } 0800 0801 if (info.readOnly) { 0802 dlg->setUsernameReadOnly(true); 0803 } else { 0804 dlg->setKnownLogins(knownLogins); 0805 } 0806 0807 if (hasWalletData) { 0808 dlg->setKeepPassword(true); 0809 } 0810 0811 if (info.getExtraField(QString::fromLatin1(s_domain)).isValid()) { 0812 dlg->setDomain(info.getExtraField(QString::fromLatin1(s_domain)).toString()); 0813 } 0814 0815 if (info.getExtraField(QString::fromLatin1(s_anonymous)).isValid() && password.isEmpty() && username.isEmpty()) { 0816 dlg->setAnonymousMode(info.getExtraField(QString::fromLatin1(s_anonymous)).toBool()); 0817 } 0818 0819 const QVariant userContextHelp = info.getExtraField(QString::fromLatin1(s_usernameContextHelp)); 0820 if (userContextHelp.isValid()) { 0821 dlg->setUsernameContextHelp(userContextHelp.toString()); 0822 } 0823 0824 #ifndef Q_OS_MACOS 0825 dlg->setAttribute(Qt::WA_NativeWindow, true); 0826 KWindowSystem::setMainWindow(dlg->windowHandle(), request->windowId); 0827 #else 0828 KWindowSystem::forceActiveWindow(dlg->winId(), 0); 0829 #endif 0830 0831 qCDebug(category) << "Showing password dialog" << dlg << ", window-id=" << request->windowId; 0832 m_authInProgress.insert(dlg, request); 0833 dlg->open(); 0834 } 0835 0836 void KPasswdServer::sendResponse(KPasswdServer::Request *request) 0837 { 0838 Q_ASSERT(request); 0839 if (!request) { 0840 return; 0841 } 0842 0843 qCDebug(category) << "key=" << request->key; 0844 if (request->isAsync) { 0845 Q_EMIT queryAuthInfoAsyncResult(request->requestId, m_seqNr, request->info); 0846 } else { 0847 QByteArray replyData; 0848 QDataStream stream2(&replyData, QIODevice::WriteOnly); 0849 stream2 << request->info; 0850 QDBusConnection::sessionBus().send(request->transaction.createReply(QVariantList{QVariant(replyData), QVariant(m_seqNr)})); 0851 } 0852 0853 // Check all requests in the wait queue. 0854 Request *waitRequest; 0855 QMutableListIterator<Request *> it(m_authWait); 0856 while (it.hasNext()) { 0857 waitRequest = it.next(); 0858 0859 if (!hasPendingQuery(waitRequest->key, waitRequest->info)) { 0860 const AuthInfoContainer *result = findAuthInfoItem(waitRequest->key, waitRequest->info); 0861 QByteArray replyData; 0862 0863 QDataStream stream2(&replyData, QIODevice::WriteOnly); 0864 0865 KIO::AuthInfo rcinfo; 0866 if (!result || result->isCanceled) { 0867 waitRequest->info.setModified(false); 0868 stream2 << waitRequest->info; 0869 } else { 0870 updateAuthExpire(waitRequest->key, result, waitRequest->windowId, false); 0871 copyAuthInfo(result, rcinfo); 0872 stream2 << rcinfo; 0873 } 0874 0875 if (waitRequest->isAsync) { 0876 Q_EMIT checkAuthInfoAsyncResult(waitRequest->requestId, m_seqNr, rcinfo); 0877 } else { 0878 QDBusConnection::sessionBus().send(waitRequest->transaction.createReply(QVariantList{QVariant(replyData), QVariant(m_seqNr)})); 0879 } 0880 0881 delete waitRequest; 0882 it.remove(); 0883 } 0884 } 0885 0886 // Re-enable password request processing for the current window id again. 0887 m_authPrompted.removeAll(QString::number(request->windowId)); 0888 m_authPrompted.removeAll(request->key); 0889 0890 if (!m_authPending.isEmpty()) { 0891 QTimer::singleShot(0, this, &KPasswdServer::processRequest); 0892 } 0893 } 0894 0895 void KPasswdServer::passwordDialogDone(int result, KPasswordDialog *sender) 0896 { 0897 std::unique_ptr<Request> request(m_authInProgress.take(sender)); 0898 Q_ASSERT(request); // request should never be nullptr. 0899 0900 if (request) { 0901 KIO::AuthInfo &info = request->info; 0902 const bool bypassCacheAndKWallet = info.getExtraField(QString::fromLatin1(s_bypassCacheAndKwallet)).toBool(); 0903 0904 qCDebug(category) << "dialog result=" << result << ", bypassCacheAndKWallet?" << bypassCacheAndKWallet; 0905 if (sender && result == QDialog::Accepted) { 0906 info.username = sender->username(); 0907 info.password = sender->password(); 0908 info.keepPassword = sender->keepPassword(); 0909 0910 if (info.getExtraField(QString::fromLatin1(s_domain)).isValid()) { 0911 info.setExtraField(QString::fromLatin1(s_domain), sender->domain()); 0912 } 0913 if (info.getExtraField(QString::fromLatin1(s_anonymous)).isValid()) { 0914 info.setExtraField(QString::fromLatin1(s_anonymous), sender->anonymousMode()); 0915 } 0916 0917 // When the user checks "keep password", that means: 0918 // * if the wallet is enabled, store it there for long-term, and in kpasswdserver 0919 // only for the duration of the window (#92928) 0920 // * otherwise store in kpasswdserver for the duration of the KDE session. 0921 if (!bypassCacheAndKWallet) { 0922 /* 0923 NOTE: The following code changes the key under which the auth 0924 info is stored in memory if the request url contains a username. 0925 e.g. "ftp://user@localhost", but the user changes that username 0926 in the password dialog. 0927 0928 Since the key generated to store the credential contains the 0929 username from the request URL, the key must be updated on such 0930 changes. Otherwise, the key will not be found on subsequent 0931 requests and the user will be end up being prompted over and 0932 over to re-enter the password unnecessarily. 0933 */ 0934 if (!info.url.userName().isEmpty() && info.username != info.url.userName()) { 0935 const QString oldKey(request->key); 0936 removeAuthInfoItem(oldKey, info); 0937 info.url.setUserName(info.username); 0938 request->key = createCacheKey(info); 0939 updateCachedRequestKey(m_authPending, oldKey, request->key); 0940 updateCachedRequestKey(m_authWait, oldKey, request->key); 0941 } 0942 0943 #ifdef HAVE_KF5WALLET 0944 const bool skipAutoCaching = info.getExtraField(QString::fromLatin1(s_skipCachingOnQuery)).toBool(); 0945 if (!skipAutoCaching && info.keepPassword && openWallet(request->windowId)) { 0946 if (storeInWallet(m_wallet, request->key, info)) { 0947 // password is in wallet, don't keep it in memory after window is closed 0948 info.keepPassword = false; 0949 } 0950 } 0951 #endif 0952 addAuthInfoItem(request->key, info, request->windowId, m_seqNr, false); 0953 } 0954 info.setModified(true); 0955 } else { 0956 if (!bypassCacheAndKWallet && request->prompt) { 0957 addAuthInfoItem(request->key, info, 0, m_seqNr, true); 0958 } 0959 info.setModified(false); 0960 } 0961 0962 sendResponse(request.get()); 0963 } 0964 } 0965 0966 void KPasswdServer::retryDialogDone(int result, KMessageDialog *sender) 0967 { 0968 std::unique_ptr<Request> request(m_authRetryInProgress.take(sender)); 0969 Q_ASSERT(request); 0970 0971 if (request) { 0972 if (result == KMessageDialog::PrimaryAction) { 0973 showPasswordDialog(request.release()); 0974 } else { 0975 // NOTE: If the user simply cancels the retry dialog, we remove the 0976 // credential stored under this key because the original attempt to 0977 // use it has failed. Otherwise, the failed credential would be cached 0978 // and used subsequently. 0979 // 0980 // TODO: decide whether it should be removed from the wallet too. 0981 KIO::AuthInfo &info = request->info; 0982 removeAuthInfoItem(request->key, request->info); 0983 info.setModified(false); 0984 sendResponse(request.get()); 0985 } 0986 } 0987 } 0988 0989 void KPasswdServer::windowRemoved(WId id) 0990 { 0991 bool foundMatch = false; 0992 if (!m_authInProgress.isEmpty()) { 0993 const qlonglong windowId = static_cast<qlonglong>(id); 0994 QMutableHashIterator<QObject *, Request *> it(m_authInProgress); 0995 while (it.hasNext()) { 0996 it.next(); 0997 if (it.value()->windowId == windowId) { 0998 Request *request = it.value(); 0999 QObject *obj = it.key(); 1000 it.remove(); 1001 m_authPrompted.removeAll(QString::number(request->windowId)); 1002 m_authPrompted.removeAll(request->key); 1003 delete obj; 1004 delete request; 1005 foundMatch = true; 1006 } 1007 } 1008 } 1009 1010 if (!foundMatch && !m_authRetryInProgress.isEmpty()) { 1011 const qlonglong windowId = static_cast<qlonglong>(id); 1012 QMutableHashIterator<QObject *, Request *> it(m_authRetryInProgress); 1013 while (it.hasNext()) { 1014 it.next(); 1015 if (it.value()->windowId == windowId) { 1016 Request *request = it.value(); 1017 QObject *obj = it.key(); 1018 it.remove(); 1019 delete obj; 1020 delete request; 1021 } 1022 } 1023 } 1024 } 1025 1026 void KPasswdServer::updateCachedRequestKey(QList<KPasswdServer::Request *> &list, const QString &oldKey, const QString &newKey) 1027 { 1028 QListIterator<Request *> it(list); 1029 while (it.hasNext()) { 1030 Request *r = it.next(); 1031 if (r->key == oldKey) { 1032 r->key = newKey; 1033 } 1034 } 1035 } 1036 1037 #include "moc_kpasswdserver.cpp"