File indexing completed on 2024-12-01 03:41:09
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 <kwindowsystem.h> 0021 0022 #ifdef HAVE_KF6WALLET 0023 #include <KWallet> 0024 #endif 0025 0026 #include <QPushButton> 0027 #include <QTimer> 0028 #include <ctime> 0029 0030 #include "../gui/config-kiogui.h" 0031 0032 #if HAVE_X11 0033 #include <KUserTimestamp> 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_KF6WALLET 0091 delete m_wallet; 0092 #endif 0093 } 0094 0095 #ifdef HAVE_KF6WALLET 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 HAVE_X11 0225 if (usertime != 0) { 0226 KUserTimestamp::updateUserTimestamp(usertime); 0227 } 0228 #endif 0229 0230 // if the check depends on a pending query, delay it 0231 // until that query is finished. 0232 const QString key(createCacheKey(info)); 0233 if (hasPendingQuery(key, info)) { 0234 setDelayedReply(true); 0235 Request *pendingCheck = new Request; 0236 pendingCheck->isAsync = false; 0237 if (calledFromDBus()) { 0238 pendingCheck->transaction = message(); 0239 } 0240 pendingCheck->key = key; 0241 pendingCheck->info = info; 0242 m_authWait.append(pendingCheck); 0243 return data; // return value will be ignored 0244 } 0245 0246 // qCDebug(category) << "key =" << key << "user =" << info.username << "windowId =" << windowId; 0247 const AuthInfoContainer *result = findAuthInfoItem(key, info); 0248 if (!result || result->isCanceled) { 0249 #ifdef HAVE_KF6WALLET 0250 if (!result && !m_walletDisabled && (info.username.isEmpty() || info.password.isEmpty()) 0251 && !KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::NetworkWallet(), KWallet::Wallet::PasswordFolder(), makeWalletKey(key, info.realmValue))) { 0252 QMap<QString, QString> knownLogins; 0253 if (openWallet(windowId)) { 0254 if (readFromWallet(m_wallet, key, info.realmValue, info.username, info.password, info.readOnly, knownLogins)) { 0255 info.setModified(true); 0256 // fall through 0257 } 0258 } 0259 } else { 0260 info.setModified(false); 0261 } 0262 #else 0263 info.setModified(false); 0264 #endif 0265 } else { 0266 qCDebug(category) << "Found cached authentication for" << key; 0267 updateAuthExpire(key, result, windowId, false); 0268 copyAuthInfo(result, info); 0269 } 0270 0271 QByteArray data2; 0272 QDataStream stream2(&data2, QIODevice::WriteOnly); 0273 stream2 << info; 0274 return data2; 0275 } 0276 0277 qlonglong KPasswdServer::checkAuthInfoAsync(KIO::AuthInfo info, qlonglong windowId, qlonglong usertime) 0278 { 0279 #if HAVE_X11 0280 if (usertime != 0) { 0281 KUserTimestamp::updateUserTimestamp(usertime); 0282 } 0283 #endif 0284 0285 // send the request id back to the client 0286 qlonglong requestId = getRequestId(); 0287 qCDebug(category) << "User =" << info.username << ", WindowId =" << windowId; 0288 if (calledFromDBus()) { 0289 QDBusMessage reply(message().createReply(requestId)); 0290 QDBusConnection::sessionBus().send(reply); 0291 } 0292 0293 // if the check depends on a pending query, delay it 0294 // until that query is finished. 0295 const QString key(createCacheKey(info)); 0296 if (hasPendingQuery(key, info)) { 0297 Request *pendingCheck = new Request; 0298 pendingCheck->isAsync = true; 0299 pendingCheck->requestId = requestId; 0300 pendingCheck->key = key; 0301 pendingCheck->info = info; 0302 m_authWait.append(pendingCheck); 0303 return 0; // ignored as we already sent a reply 0304 } 0305 0306 const AuthInfoContainer *result = findAuthInfoItem(key, info); 0307 if (!result || result->isCanceled) { 0308 #ifdef HAVE_KF6WALLET 0309 if (!result && !m_walletDisabled && (info.username.isEmpty() || info.password.isEmpty()) 0310 && !KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::NetworkWallet(), KWallet::Wallet::PasswordFolder(), makeWalletKey(key, info.realmValue))) { 0311 QMap<QString, QString> knownLogins; 0312 if (openWallet(windowId)) { 0313 if (readFromWallet(m_wallet, key, info.realmValue, info.username, info.password, info.readOnly, knownLogins)) { 0314 info.setModified(true); 0315 // fall through 0316 } 0317 } 0318 } else { 0319 info.setModified(false); 0320 } 0321 #else 0322 info.setModified(false); 0323 #endif 0324 } else { 0325 // qCDebug(category) << "Found cached authentication for" << key; 0326 updateAuthExpire(key, result, windowId, false); 0327 copyAuthInfo(result, info); 0328 } 0329 0330 Q_EMIT checkAuthInfoAsyncResult(requestId, m_seqNr, info); 0331 return 0; // ignored 0332 } 0333 0334 // deprecated method, not used anymore. TODO KF6: REMOVE 0335 QByteArray KPasswdServer::queryAuthInfo(const QByteArray &data, const QString &errorMsg, qlonglong windowId, qlonglong seqNr, qlonglong usertime) 0336 { 0337 KIO::AuthInfo info; 0338 QDataStream stream(data); 0339 stream >> info; 0340 0341 qCDebug(category) << "User =" << info.username << ", WindowId =" << windowId << "seqNr =" << seqNr << ", errorMsg =" << errorMsg; 0342 0343 if (!info.password.isEmpty()) { // should we really allow the caller to pre-fill the password? 0344 qCDebug(category) << "password was set by caller"; 0345 } 0346 #if HAVE_X11 0347 if (usertime != 0) { 0348 KUserTimestamp::updateUserTimestamp(usertime); 0349 } 0350 #endif 0351 0352 const QString key(createCacheKey(info)); 0353 Request *request = new Request; 0354 setDelayedReply(true); 0355 request->isAsync = false; 0356 request->transaction = message(); 0357 request->key = key; 0358 request->info = info; 0359 request->windowId = windowId; 0360 request->seqNr = seqNr; 0361 if (errorMsg == QLatin1String("<NoAuthPrompt>")) { 0362 request->errorMsg.clear(); 0363 request->prompt = false; 0364 } else { 0365 request->errorMsg = errorMsg; 0366 request->prompt = true; 0367 } 0368 m_authPending.append(request); 0369 0370 if (m_authPending.count() == 1) { 0371 QTimer::singleShot(0, this, &KPasswdServer::processRequest); 0372 } 0373 0374 return QByteArray(); // return value is going to be ignored 0375 } 0376 0377 qlonglong KPasswdServer::queryAuthInfoAsync(const KIO::AuthInfo &info, const QString &errorMsg, qlonglong windowId, qlonglong seqNr, qlonglong usertime) 0378 { 0379 qCDebug(category) << "User =" << info.username << ", WindowId =" << windowId << "seqNr =" << seqNr << ", errorMsg =" << errorMsg; 0380 0381 if (!info.password.isEmpty()) { 0382 qCDebug(category) << "password was set by caller"; 0383 } 0384 #if HAVE_X11 0385 if (usertime != 0) { 0386 KUserTimestamp::updateUserTimestamp(usertime); 0387 } 0388 #endif 0389 0390 const QString key(createCacheKey(info)); 0391 Request *request = new Request; 0392 request->isAsync = true; 0393 request->requestId = getRequestId(); 0394 request->key = key; 0395 request->info = info; 0396 request->windowId = windowId; 0397 request->seqNr = seqNr; 0398 if (errorMsg == QLatin1String("<NoAuthPrompt>")) { 0399 request->errorMsg.clear(); 0400 request->prompt = false; 0401 } else { 0402 request->errorMsg = errorMsg; 0403 request->prompt = true; 0404 } 0405 m_authPending.append(request); 0406 0407 if (m_authPending.count() == 1) { 0408 QTimer::singleShot(0, this, &KPasswdServer::processRequest); 0409 } 0410 0411 return request->requestId; 0412 } 0413 0414 void KPasswdServer::addAuthInfo(const KIO::AuthInfo &info, qlonglong windowId) 0415 { 0416 qCDebug(category) << "User =" << info.username << ", Realm =" << info.realmValue << ", WindowId =" << windowId; 0417 if (!info.keepPassword) { 0418 qWarning() << "This KIO worker is caching a password in KWallet even though the user didn't ask for it!"; 0419 } 0420 const QString key(createCacheKey(info)); 0421 0422 m_seqNr++; 0423 0424 #ifdef HAVE_KF6WALLET 0425 if (!m_walletDisabled && openWallet(windowId) && storeInWallet(m_wallet, key, info)) { 0426 // Since storing the password in the wallet succeeded, make sure the 0427 // password information is stored in memory only for the duration the 0428 // windows associated with it are still around. 0429 KIO::AuthInfo authToken(info); 0430 authToken.keepPassword = false; 0431 addAuthInfoItem(key, authToken, windowId, m_seqNr, false); 0432 return; 0433 } 0434 #endif 0435 0436 addAuthInfoItem(key, info, windowId, m_seqNr, false); 0437 } 0438 0439 // deprecated method, not used anymore. TODO KF6: REMOVE 0440 void KPasswdServer::addAuthInfo(const QByteArray &data, qlonglong windowId) 0441 { 0442 KIO::AuthInfo info; 0443 QDataStream stream(data); 0444 stream >> info; 0445 addAuthInfo(info, windowId); 0446 } 0447 0448 void KPasswdServer::removeAuthInfo(const QString &host, const QString &protocol, const QString &user) 0449 { 0450 qCDebug(category) << protocol << host << user; 0451 0452 QHashIterator<QString, AuthInfoContainerList *> dictIterator(m_authDict); 0453 while (dictIterator.hasNext()) { 0454 dictIterator.next(); 0455 0456 const AuthInfoContainerList *authList = dictIterator.value(); 0457 if (!authList) { 0458 continue; 0459 } 0460 0461 for (const AuthInfoContainer ¤t : *authList) { 0462 qCDebug(category) << "Evaluating: " << current.info.url.scheme() << current.info.url.host() << current.info.username; 0463 if (current.info.url.scheme() == protocol && current.info.url.host() == host && (current.info.username == user || user.isEmpty())) { 0464 qCDebug(category) << "Removing this entry"; 0465 removeAuthInfoItem(dictIterator.key(), current.info); // warning, this can modify m_authDict! 0466 } 0467 } 0468 } 0469 } 0470 0471 #ifdef HAVE_KF6WALLET 0472 bool KPasswdServer::openWallet(qlonglong windowId) 0473 { 0474 if (m_wallet && !m_wallet->isOpen()) { // forced closed 0475 delete m_wallet; 0476 m_wallet = nullptr; 0477 } 0478 if (!m_wallet) { 0479 m_wallet = KWallet::Wallet::openWallet(KWallet::Wallet::NetworkWallet(), static_cast<WId>(windowId)); 0480 } 0481 return m_wallet != nullptr; 0482 } 0483 #endif 0484 0485 void KPasswdServer::processRequest() 0486 { 0487 if (m_authPending.isEmpty()) { 0488 return; 0489 } 0490 0491 std::unique_ptr<Request> request(m_authPending.takeFirst()); 0492 0493 // Prevent multiple prompts originating from the same window or the same 0494 // key (server address). 0495 const QString windowIdStr = QString::number(request->windowId); 0496 if (m_authPrompted.contains(windowIdStr) || m_authPrompted.contains(request->key)) { 0497 m_authPending.prepend(request.release()); // put it back. 0498 return; 0499 } 0500 0501 m_authPrompted.append(windowIdStr); 0502 m_authPrompted.append(request->key); 0503 0504 KIO::AuthInfo &info = request->info; 0505 0506 // NOTE: If info.username is empty and info.url.userName() is not, set 0507 // info.username to info.url.userName() to ensure proper caching. See 0508 // note passwordDialogDone. 0509 if (info.username.isEmpty() && !info.url.userName().isEmpty()) { 0510 info.username = info.url.userName(); 0511 } 0512 const bool bypassCacheAndKWallet = info.getExtraField(QString::fromLatin1(s_bypassCacheAndKwallet)).toBool(); 0513 0514 const AuthInfoContainer *result = findAuthInfoItem(request->key, request->info); 0515 qCDebug(category) << "key=" << request->key << ", user=" << info.username << "seqNr: request=" << request->seqNr 0516 << ", result=" << (result ? result->seqNr : -1); 0517 0518 if (!bypassCacheAndKWallet && result && (request->seqNr < result->seqNr)) { 0519 qCDebug(category) << "auto retry!"; 0520 if (result->isCanceled) { 0521 info.setModified(false); 0522 } else { 0523 updateAuthExpire(request->key, result, request->windowId, false); 0524 copyAuthInfo(result, info); 0525 } 0526 } else { 0527 m_seqNr++; 0528 if (result && !request->errorMsg.isEmpty()) { 0529 const QString prompt = request->errorMsg.trimmed() + QLatin1Char('\n') + i18n("Do you want to retry?"); 0530 0531 KMessageDialog *dlg = new KMessageDialog(KMessageDialog::WarningContinueCancel, prompt, nullptr); 0532 dlg->setAttribute(Qt::WA_DeleteOnClose); 0533 dlg->setWindowTitle(i18n("Retry Authentication")); 0534 dlg->setWindowIcon(QIcon::fromTheme(QStringLiteral("dialog-password"))); 0535 dlg->setObjectName(QStringLiteral("warningOKCancel")); 0536 KGuiItem retryButton(i18nc("@action:button filter-continue", "Retry")); 0537 0538 dlg->setButtons(retryButton); 0539 0540 connect(dlg, &QDialog::finished, this, [this, dlg](int result) { 0541 retryDialogDone(result, dlg); 0542 }); 0543 0544 dlg->setAttribute(Qt::WA_NativeWindow, true); 0545 KWindowSystem::setMainWindow(dlg->windowHandle(), request->windowId); 0546 0547 qCDebug(category) << "Calling open on retry dialog" << dlg; 0548 m_authRetryInProgress.insert(dlg, request.release()); 0549 dlg->open(); 0550 return; 0551 } 0552 0553 if (request->prompt) { 0554 showPasswordDialog(request.release()); 0555 return; 0556 } else { 0557 if (!bypassCacheAndKWallet && request->prompt) { 0558 addAuthInfoItem(request->key, info, 0, m_seqNr, true); 0559 } 0560 info.setModified(false); 0561 } 0562 } 0563 0564 sendResponse(request.get()); 0565 } 0566 0567 QString KPasswdServer::createCacheKey(const KIO::AuthInfo &info) 0568 { 0569 if (!info.url.isValid()) { 0570 // Note that a null key will break findAuthInfoItem later on... 0571 qCWarning(category) << "createCacheKey: invalid URL " << info.url; 0572 return QString(); 0573 } 0574 0575 // Generate the basic key sequence. 0576 QString key = info.url.scheme(); 0577 key += QLatin1Char('-'); 0578 if (!info.url.userName().isEmpty()) { 0579 key += info.url.userName() + QLatin1Char('@'); 0580 } 0581 key += info.url.host(); 0582 int port = info.url.port(); 0583 if (port) { 0584 key += QLatin1Char(':') + QString::number(port); 0585 } 0586 0587 return key; 0588 } 0589 0590 void KPasswdServer::copyAuthInfo(const AuthInfoContainer *i, KIO::AuthInfo &info) 0591 { 0592 info = i->info; 0593 info.setModified(true); 0594 } 0595 0596 const KPasswdServer::AuthInfoContainer *KPasswdServer::findAuthInfoItem(const QString &key, const KIO::AuthInfo &info) 0597 { 0598 // qCDebug(category) << "key=" << key << ", user=" << info.username; 0599 0600 AuthInfoContainerList *authList = m_authDict.value(key); 0601 if (authList) { 0602 QString path2 = info.url.path().left(info.url.path().indexOf(QLatin1Char('/')) + 1); 0603 auto it = authList->begin(); 0604 while (it != authList->end()) { 0605 const AuthInfoContainer ¤t = (*it); 0606 if (current.expire == AuthInfoContainer::expTime && static_cast<qulonglong>(time(nullptr)) > current.expireTime) { 0607 it = authList->erase(it); 0608 continue; 0609 } 0610 0611 if (info.verifyPath) { 0612 const QString path1 = current.directory; 0613 if (path2.startsWith(path1) && (info.username.isEmpty() || info.username == current.info.username)) { 0614 return ¤t; 0615 } 0616 } else { 0617 if (current.info.realmValue == info.realmValue && (info.username.isEmpty() || info.username == current.info.username)) { 0618 return ¤t; // TODO: Update directory info 0619 } 0620 } 0621 0622 ++it; 0623 } 0624 } 0625 return nullptr; 0626 } 0627 0628 void KPasswdServer::removeAuthInfoItem(const QString &key, const KIO::AuthInfo &info) 0629 { 0630 AuthInfoContainerList *authList = m_authDict.value(key); 0631 if (!authList) { 0632 return; 0633 } 0634 0635 auto it = authList->begin(); 0636 while (it != authList->end()) { 0637 if ((*it).info.realmValue == info.realmValue) { 0638 it = authList->erase(it); 0639 } else { 0640 ++it; 0641 } 0642 } 0643 if (authList->isEmpty()) { 0644 delete m_authDict.take(key); 0645 } 0646 } 0647 0648 void KPasswdServer::addAuthInfoItem(const QString &key, const KIO::AuthInfo &info, qlonglong windowId, qlonglong seqNr, bool canceled) 0649 { 0650 qCDebug(category) << "key=" << key << "window-id=" << windowId << "username=" << info.username << "realm=" << info.realmValue << "seqNr=" << seqNr 0651 << "keepPassword?" << info.keepPassword << "canceled?" << canceled; 0652 AuthInfoContainerList *authList = m_authDict.value(key); 0653 if (!authList) { 0654 authList = new AuthInfoContainerList; 0655 m_authDict.insert(key, authList); 0656 } 0657 bool found = false; 0658 AuthInfoContainer authItem; 0659 auto it = authList->begin(); 0660 while (it != authList->end()) { 0661 if ((*it).info.realmValue == info.realmValue) { 0662 authItem = (*it); 0663 it = authList->erase(it); 0664 found = true; 0665 break; 0666 } else { 0667 ++it; 0668 } 0669 } 0670 0671 if (!found) { 0672 qCDebug(category) << "Creating AuthInfoContainer"; 0673 authItem.expire = AuthInfoContainer::expTime; 0674 } 0675 0676 authItem.info = info; 0677 authItem.directory = info.url.path().left(info.url.path().indexOf(QLatin1Char('/')) + 1); 0678 authItem.seqNr = seqNr; 0679 authItem.isCanceled = canceled; 0680 0681 updateAuthExpire(key, &authItem, windowId, (info.keepPassword && !canceled)); 0682 0683 // Insert into list, keep the list sorted "longest path" first. 0684 authList->append(authItem); 0685 std::sort(authList->begin(), authList->end(), AuthInfoContainer::Sorter()); 0686 } 0687 0688 void KPasswdServer::updateAuthExpire(const QString &key, const AuthInfoContainer *auth, qlonglong windowId, bool keep) 0689 { 0690 AuthInfoContainer *current = const_cast<AuthInfoContainer *>(auth); 0691 Q_ASSERT(current); 0692 0693 qCDebug(category) << "key=" << key << "expire=" << current->expire << "window-id=" << windowId << "keep=" << keep; 0694 0695 if (keep && !windowId) { 0696 current->expire = AuthInfoContainer::expNever; 0697 } else if (windowId && (current->expire != AuthInfoContainer::expNever)) { 0698 current->expire = AuthInfoContainer::expWindowClose; 0699 if (!current->windowList.contains(windowId)) { 0700 current->windowList.append(windowId); 0701 } 0702 } else if (current->expire == AuthInfoContainer::expTime) { 0703 current->expireTime = time(nullptr) + 10; 0704 } 0705 0706 // Update mWindowIdList 0707 if (windowId) { 0708 QStringList &keysChanged = mWindowIdList[windowId]; // find or insert 0709 if (!keysChanged.contains(key)) { 0710 keysChanged.append(key); 0711 } 0712 } 0713 } 0714 0715 void KPasswdServer::removeAuthForWindowId(qlonglong windowId) 0716 { 0717 const QStringList keysChanged = mWindowIdList.value(windowId); 0718 for (const QString &key : keysChanged) { 0719 AuthInfoContainerList *authList = m_authDict.value(key); 0720 if (!authList) { 0721 continue; 0722 } 0723 0724 QMutableListIterator<AuthInfoContainer> it(*authList); 0725 while (it.hasNext()) { 0726 AuthInfoContainer ¤t = it.next(); 0727 if (current.expire == AuthInfoContainer::expWindowClose) { 0728 if (current.windowList.removeAll(windowId) && current.windowList.isEmpty()) { 0729 it.remove(); 0730 } 0731 } 0732 } 0733 } 0734 } 0735 0736 void KPasswdServer::showPasswordDialog(KPasswdServer::Request *request) 0737 { 0738 KIO::AuthInfo &info = request->info; 0739 QString username = info.username; 0740 QString password = info.password; 0741 bool hasWalletData = false; 0742 QMap<QString, QString> knownLogins; 0743 0744 #ifdef HAVE_KF6WALLET 0745 const bool bypassCacheAndKWallet = info.getExtraField(QString::fromLatin1(s_bypassCacheAndKwallet)).toBool(); 0746 if (!bypassCacheAndKWallet && (username.isEmpty() || password.isEmpty()) && !m_walletDisabled 0747 && !KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::NetworkWallet(), 0748 KWallet::Wallet::PasswordFolder(), 0749 makeWalletKey(request->key, info.realmValue))) { 0750 // no login+pass provided, check if kwallet has one 0751 if (openWallet(request->windowId)) { 0752 hasWalletData = readFromWallet(m_wallet, request->key, info.realmValue, username, password, info.readOnly, knownLogins); 0753 } 0754 } 0755 #endif 0756 0757 // assemble dialog-flags 0758 KPasswordDialog::KPasswordDialogFlags dialogFlags; 0759 0760 if (info.getExtraField(QString::fromLatin1(s_domain)).isValid()) { 0761 dialogFlags |= KPasswordDialog::ShowDomainLine; 0762 if (info.getExtraFieldFlags(QString::fromLatin1(s_domain)) & KIO::AuthInfo::ExtraFieldReadOnly) { 0763 dialogFlags |= KPasswordDialog::DomainReadOnly; 0764 } 0765 } 0766 0767 if (info.getExtraField(QString::fromLatin1(s_anonymous)).isValid()) { 0768 dialogFlags |= KPasswordDialog::ShowAnonymousLoginCheckBox; 0769 } 0770 0771 if (!info.getExtraField(QString::fromLatin1(s_hideUsernameInput)).toBool()) { 0772 dialogFlags |= KPasswordDialog::ShowUsernameLine; 0773 } 0774 0775 #ifdef HAVE_KF6WALLET 0776 // If wallet is not enabled and the caller explicitly requested for it, 0777 // do not show the keep password checkbox. 0778 if (info.keepPassword && KWallet::Wallet::isEnabled()) { 0779 dialogFlags |= KPasswordDialog::ShowKeepPassword; 0780 } 0781 #endif 0782 0783 // instantiate dialog 0784 qCDebug(category) << "Widget for" << request->windowId << QWidget::find(request->windowId); 0785 0786 KPasswordDialog *dlg = new KPasswordDialog(nullptr, dialogFlags); 0787 dlg->setAttribute(Qt::WA_DeleteOnClose); 0788 0789 connect(dlg, &QDialog::finished, this, [this, dlg](int result) { 0790 passwordDialogDone(result, dlg); 0791 }); 0792 0793 dlg->setPrompt(info.prompt); 0794 dlg->setUsername(username); 0795 if (info.caption.isEmpty()) { 0796 dlg->setWindowTitle(i18n("Authentication Dialog")); 0797 } else { 0798 dlg->setWindowTitle(info.caption); 0799 } 0800 0801 if (!info.comment.isEmpty()) { 0802 dlg->addCommentLine(info.commentLabel, info.comment); 0803 } 0804 0805 if (!password.isEmpty()) { 0806 dlg->setPassword(password); 0807 } 0808 0809 if (info.readOnly) { 0810 dlg->setUsernameReadOnly(true); 0811 } else { 0812 dlg->setKnownLogins(knownLogins); 0813 } 0814 0815 if (hasWalletData) { 0816 dlg->setKeepPassword(true); 0817 } 0818 0819 if (info.getExtraField(QString::fromLatin1(s_domain)).isValid()) { 0820 dlg->setDomain(info.getExtraField(QString::fromLatin1(s_domain)).toString()); 0821 } 0822 0823 if (info.getExtraField(QString::fromLatin1(s_anonymous)).isValid() && password.isEmpty() && username.isEmpty()) { 0824 dlg->setAnonymousMode(info.getExtraField(QString::fromLatin1(s_anonymous)).toBool()); 0825 } 0826 0827 const QVariant userContextHelp = info.getExtraField(QString::fromLatin1(s_usernameContextHelp)); 0828 if (userContextHelp.isValid()) { 0829 dlg->setUsernameContextHelp(userContextHelp.toString()); 0830 } 0831 0832 #ifndef Q_OS_MACOS 0833 dlg->setAttribute(Qt::WA_NativeWindow, true); 0834 KWindowSystem::setMainWindow(dlg->windowHandle(), request->windowId); 0835 #endif 0836 0837 qCDebug(category) << "Showing password dialog" << dlg << ", window-id=" << request->windowId; 0838 m_authInProgress.insert(dlg, request); 0839 dlg->open(); 0840 } 0841 0842 void KPasswdServer::sendResponse(KPasswdServer::Request *request) 0843 { 0844 Q_ASSERT(request); 0845 if (!request) { 0846 return; 0847 } 0848 0849 qCDebug(category) << "key=" << request->key; 0850 if (request->isAsync) { 0851 Q_EMIT queryAuthInfoAsyncResult(request->requestId, m_seqNr, request->info); 0852 } else { 0853 QByteArray replyData; 0854 QDataStream stream2(&replyData, QIODevice::WriteOnly); 0855 stream2 << request->info; 0856 QDBusConnection::sessionBus().send(request->transaction.createReply(QVariantList{QVariant(replyData), QVariant(m_seqNr)})); 0857 } 0858 0859 // Check all requests in the wait queue. 0860 Request *waitRequest; 0861 QMutableListIterator<Request *> it(m_authWait); 0862 while (it.hasNext()) { 0863 waitRequest = it.next(); 0864 0865 if (!hasPendingQuery(waitRequest->key, waitRequest->info)) { 0866 const AuthInfoContainer *result = findAuthInfoItem(waitRequest->key, waitRequest->info); 0867 QByteArray replyData; 0868 0869 QDataStream stream2(&replyData, QIODevice::WriteOnly); 0870 0871 KIO::AuthInfo rcinfo; 0872 if (!result || result->isCanceled) { 0873 waitRequest->info.setModified(false); 0874 stream2 << waitRequest->info; 0875 } else { 0876 updateAuthExpire(waitRequest->key, result, waitRequest->windowId, false); 0877 copyAuthInfo(result, rcinfo); 0878 stream2 << rcinfo; 0879 } 0880 0881 if (waitRequest->isAsync) { 0882 Q_EMIT checkAuthInfoAsyncResult(waitRequest->requestId, m_seqNr, rcinfo); 0883 } else { 0884 QDBusConnection::sessionBus().send(waitRequest->transaction.createReply(QVariantList{QVariant(replyData), QVariant(m_seqNr)})); 0885 } 0886 0887 delete waitRequest; 0888 it.remove(); 0889 } 0890 } 0891 0892 // Re-enable password request processing for the current window id again. 0893 m_authPrompted.removeAll(QString::number(request->windowId)); 0894 m_authPrompted.removeAll(request->key); 0895 0896 if (!m_authPending.isEmpty()) { 0897 QTimer::singleShot(0, this, &KPasswdServer::processRequest); 0898 } 0899 } 0900 0901 void KPasswdServer::passwordDialogDone(int result, KPasswordDialog *sender) 0902 { 0903 std::unique_ptr<Request> request(m_authInProgress.take(sender)); 0904 Q_ASSERT(request); // request should never be nullptr. 0905 0906 if (request) { 0907 KIO::AuthInfo &info = request->info; 0908 const bool bypassCacheAndKWallet = info.getExtraField(QString::fromLatin1(s_bypassCacheAndKwallet)).toBool(); 0909 0910 qCDebug(category) << "dialog result=" << result << ", bypassCacheAndKWallet?" << bypassCacheAndKWallet; 0911 if (sender && result == QDialog::Accepted) { 0912 info.username = sender->username(); 0913 info.password = sender->password(); 0914 info.keepPassword = sender->keepPassword(); 0915 0916 if (info.getExtraField(QString::fromLatin1(s_domain)).isValid()) { 0917 info.setExtraField(QString::fromLatin1(s_domain), sender->domain()); 0918 } 0919 if (info.getExtraField(QString::fromLatin1(s_anonymous)).isValid()) { 0920 info.setExtraField(QString::fromLatin1(s_anonymous), sender->anonymousMode()); 0921 } 0922 0923 // When the user checks "keep password", that means: 0924 // * if the wallet is enabled, store it there for long-term, and in kpasswdserver 0925 // only for the duration of the window (#92928) 0926 // * otherwise store in kpasswdserver for the duration of the KDE session. 0927 if (!bypassCacheAndKWallet) { 0928 /* 0929 NOTE: The following code changes the key under which the auth 0930 info is stored in memory if the request url contains a username. 0931 e.g. "ftp://user@localhost", but the user changes that username 0932 in the password dialog. 0933 0934 Since the key generated to store the credential contains the 0935 username from the request URL, the key must be updated on such 0936 changes. Otherwise, the key will not be found on subsequent 0937 requests and the user will be end up being prompted over and 0938 over to re-enter the password unnecessarily. 0939 */ 0940 if (!info.url.userName().isEmpty() && info.username != info.url.userName()) { 0941 const QString oldKey(request->key); 0942 removeAuthInfoItem(oldKey, info); 0943 info.url.setUserName(info.username); 0944 request->key = createCacheKey(info); 0945 updateCachedRequestKey(m_authPending, oldKey, request->key); 0946 updateCachedRequestKey(m_authWait, oldKey, request->key); 0947 } 0948 0949 #ifdef HAVE_KF6WALLET 0950 const bool skipAutoCaching = info.getExtraField(QString::fromLatin1(s_skipCachingOnQuery)).toBool(); 0951 if (!skipAutoCaching && info.keepPassword && openWallet(request->windowId)) { 0952 if (storeInWallet(m_wallet, request->key, info)) { 0953 // password is in wallet, don't keep it in memory after window is closed 0954 info.keepPassword = false; 0955 } 0956 } 0957 #endif 0958 addAuthInfoItem(request->key, info, request->windowId, m_seqNr, false); 0959 } 0960 info.setModified(true); 0961 } else { 0962 if (!bypassCacheAndKWallet && request->prompt) { 0963 addAuthInfoItem(request->key, info, 0, m_seqNr, true); 0964 } 0965 info.setModified(false); 0966 } 0967 0968 sendResponse(request.get()); 0969 } 0970 } 0971 0972 void KPasswdServer::retryDialogDone(int result, KMessageDialog *sender) 0973 { 0974 std::unique_ptr<Request> request(m_authRetryInProgress.take(sender)); 0975 Q_ASSERT(request); 0976 0977 if (request) { 0978 if (result == KMessageDialog::PrimaryAction) { 0979 showPasswordDialog(request.release()); 0980 } else { 0981 // NOTE: If the user simply cancels the retry dialog, we remove the 0982 // credential stored under this key because the original attempt to 0983 // use it has failed. Otherwise, the failed credential would be cached 0984 // and used subsequently. 0985 // 0986 // TODO: decide whether it should be removed from the wallet too. 0987 KIO::AuthInfo &info = request->info; 0988 removeAuthInfoItem(request->key, request->info); 0989 info.setModified(false); 0990 sendResponse(request.get()); 0991 } 0992 } 0993 } 0994 0995 void KPasswdServer::windowRemoved(WId id) 0996 { 0997 bool foundMatch = false; 0998 if (!m_authInProgress.isEmpty()) { 0999 const qlonglong windowId = static_cast<qlonglong>(id); 1000 QMutableHashIterator<QObject *, Request *> it(m_authInProgress); 1001 while (it.hasNext()) { 1002 it.next(); 1003 if (it.value()->windowId == windowId) { 1004 Request *request = it.value(); 1005 QObject *obj = it.key(); 1006 it.remove(); 1007 m_authPrompted.removeAll(QString::number(request->windowId)); 1008 m_authPrompted.removeAll(request->key); 1009 delete obj; 1010 delete request; 1011 foundMatch = true; 1012 } 1013 } 1014 } 1015 1016 if (!foundMatch && !m_authRetryInProgress.isEmpty()) { 1017 const qlonglong windowId = static_cast<qlonglong>(id); 1018 QMutableHashIterator<QObject *, Request *> it(m_authRetryInProgress); 1019 while (it.hasNext()) { 1020 it.next(); 1021 if (it.value()->windowId == windowId) { 1022 Request *request = it.value(); 1023 QObject *obj = it.key(); 1024 it.remove(); 1025 delete obj; 1026 delete request; 1027 } 1028 } 1029 } 1030 } 1031 1032 void KPasswdServer::updateCachedRequestKey(QList<KPasswdServer::Request *> &list, const QString &oldKey, const QString &newKey) 1033 { 1034 QListIterator<Request *> it(list); 1035 while (it.hasNext()) { 1036 Request *r = it.next(); 1037 if (r->key == oldKey) { 1038 r->key = newKey; 1039 } 1040 } 1041 } 1042 1043 #include "moc_kpasswdserver.cpp"