1/*
2 This file is part of the KDE Password Server
3
4 Copyright (C) 2002 Waldo Bastian (bastian@kde.org)
5 Copyright (C) 2005 David Faure (faure@kde.org)
6 Copyright (C) 2012 Dawit Alemayehu (adawit@kde.org)
7
8 This library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License
10 version 2 as published by the Free Software Foundation.
11
12 This software is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this library; see the file COPYING. If not, write to
19 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 Boston, MA 02110-1301, USA.
21*/
22//----------------------------------------------------------------------------
23//
24// KDE Password Server
25
26#include "kpasswdserver.h"
27
28#include "kpasswdserveradaptor.h"
29
30#include <kapplication.h>
31#include <klocale.h>
32#include <kmessagebox.h>
33#include <kdebug.h>
34#include <kpassworddialog.h>
35#include <kwallet.h>
36#include <kwindowsystem.h>
37
38#include <kpluginfactory.h>
39#include <kpluginloader.h>
40
41#include <QtCore/QTimer>
42
43#include <ctime>
44
45
46K_PLUGIN_FACTORY(KPasswdServerFactory,
47 registerPlugin<KPasswdServer>();
48 )
49K_EXPORT_PLUGIN(KPasswdServerFactory("kpasswdserver"))
50
51#define AUTHINFO_EXTRAFIELD_DOMAIN QLatin1String("domain")
52#define AUTHINFO_EXTRAFIELD_ANONYMOUS QLatin1String("anonymous")
53#define AUTHINFO_EXTRAFIELD_BYPASS_CACHE_AND_KWALLET QLatin1String("bypass-cache-and-kwallet")
54#define AUTHINFO_EXTRAFIELD_SKIP_CACHING_ON_QUERY QLatin1String("skip-caching-on-query")
55#define AUTHINFO_EXTRAFIELD_HIDE_USERNAME_INPUT QLatin1String("hide-username-line")
56
57static int debugArea() { static int s_area = KDebug::registerArea("KPasswdServer"); return s_area; }
58
59static qlonglong getRequestId()
60{
61 static qlonglong nextRequestId = 0;
62 return nextRequestId++;
63}
64
65bool
66KPasswdServer::AuthInfoContainer::Sorter::operator ()(AuthInfoContainer* n1, AuthInfoContainer* n2) const
67{
68 if (!n1 || !n2)
69 return 0;
70
71 const int l1 = n1->directory.length();
72 const int l2 = n2->directory.length();
73 return l1 < l2;
74}
75
76
77KPasswdServer::KPasswdServer(QObject* parent, const QList<QVariant>&)
78 : KDEDModule(parent)
79{
80 KIO::AuthInfo::registerMetaTypes();
81
82 m_seqNr = 0;
83 m_wallet = 0;
84 m_walletDisabled = false;
85
86 KPasswdServerAdaptor *adaptor = new KPasswdServerAdaptor(this);
87 // register separately from kded
88 QDBusConnection::sessionBus().registerService("org.kde.kpasswdserver");
89 // connect signals to the adaptor
90 connect(this,
91 SIGNAL(checkAuthInfoAsyncResult(qlonglong,qlonglong,KIO::AuthInfo)),
92 adaptor,
93 SIGNAL(checkAuthInfoAsyncResult(qlonglong,qlonglong,KIO::AuthInfo)));
94 connect(this,
95 SIGNAL(queryAuthInfoAsyncResult(qlonglong,qlonglong,KIO::AuthInfo)),
96 adaptor,
97 SIGNAL(queryAuthInfoAsyncResult(qlonglong,qlonglong,KIO::AuthInfo)));
98
99 connect(this, SIGNAL(windowUnregistered(qlonglong)),
100 this, SLOT(removeAuthForWindowId(qlonglong)));
101
102 connect(KWindowSystem::self(), SIGNAL(windowRemoved(WId)),
103 this, SLOT(windowRemoved(WId)));
104}
105
106KPasswdServer::~KPasswdServer()
107{
108 // TODO: what about clients waiting for requests? will they just
109 // notice kpasswdserver is gone from the dbus?
110 qDeleteAll(m_authPending);
111 qDeleteAll(m_authWait);
112 qDeleteAll(m_authDict);
113 qDeleteAll(m_authInProgress);
114 qDeleteAll(m_authRetryInProgress);
115 delete m_wallet;
116}
117
118// Helper - returns the wallet key to use for read/store/checking for existence.
119static QString makeWalletKey( const QString& key, const QString& realm )
120{
121 return realm.isEmpty() ? key : key + '-' + realm;
122}
123
124// Helper for storeInWallet/readFromWallet
125static QString makeMapKey( const char* key, int entryNumber )
126{
127 QString str = QLatin1String( key );
128 if ( entryNumber > 1 )
129 str += '-' + QString::number( entryNumber );
130 return str;
131}
132
133static bool storeInWallet( KWallet::Wallet* wallet, const QString& key, const KIO::AuthInfo &info )
134{
135 if ( !wallet->hasFolder( KWallet::Wallet::PasswordFolder() ) )
136 if ( !wallet->createFolder( KWallet::Wallet::PasswordFolder() ) )
137 return false;
138 wallet->setFolder( KWallet::Wallet::PasswordFolder() );
139 // Before saving, check if there's already an entry with this login.
140 // If so, replace it (with the new password). Otherwise, add a new entry.
141 typedef QMap<QString,QString> Map;
142 int entryNumber = 1;
143 Map map;
144 QString walletKey = makeWalletKey( key, info.realmValue );
145 kDebug(debugArea()) << "walletKey =" << walletKey << " reading existing map";
146 if ( wallet->readMap( walletKey, map ) == 0 ) {
147 Map::ConstIterator end = map.constEnd();
148 Map::ConstIterator it = map.constFind( "login" );
149 while ( it != end ) {
150 if ( it.value() == info.username ) {
151 break; // OK, overwrite this entry
152 }
153 it = map.constFind( QString( "login-" ) + QString::number( ++entryNumber ) );
154 }
155 // If no entry was found, create a new entry - entryNumber is set already.
156 }
157 const QString loginKey = makeMapKey( "login", entryNumber );
158 const QString passwordKey = makeMapKey( "password", entryNumber );
159 kDebug(debugArea()) << "writing to " << loginKey << "," << passwordKey;
160 // note the overwrite=true by default
161 map.insert( loginKey, info.username );
162 map.insert( passwordKey, info.password );
163 wallet->writeMap( walletKey, map );
164 return true;
165}
166
167
168static bool readFromWallet( KWallet::Wallet* wallet, const QString& key, const QString& realm, QString& username, QString& password, bool userReadOnly, QMap<QString,QString>& knownLogins )
169{
170 //kDebug(debugArea()) << "key =" << key << " username =" << username << " password =" /*<< password*/ << " userReadOnly =" << userReadOnly << " realm =" << realm;
171 if ( wallet->hasFolder( KWallet::Wallet::PasswordFolder() ) )
172 {
173 wallet->setFolder( KWallet::Wallet::PasswordFolder() );
174
175 QMap<QString,QString> map;
176 if ( wallet->readMap( makeWalletKey( key, realm ), map ) == 0 )
177 {
178 typedef QMap<QString,QString> Map;
179 int entryNumber = 1;
180 Map::ConstIterator end = map.constEnd();
181 Map::ConstIterator it = map.constFind( "login" );
182 while ( it != end ) {
183 //kDebug(debugArea()) << "found " << it.key() << "=" << it.value();
184 Map::ConstIterator pwdIter = map.constFind( makeMapKey( "password", entryNumber ) );
185 if ( pwdIter != end ) {
186 if ( it.value() == username )
187 password = pwdIter.value();
188 knownLogins.insert( it.value(), pwdIter.value() );
189 }
190
191 it = map.constFind( QString( "login-" ) + QString::number( ++entryNumber ) );
192 }
193 //kDebug(debugArea()) << knownLogins.count() << " known logins";
194
195 if ( !userReadOnly && !knownLogins.isEmpty() && username.isEmpty() ) {
196 // Pick one, any one...
197 username = knownLogins.begin().key();
198 password = knownLogins.begin().value();
199 //kDebug(debugArea()) << "picked the first one:" << username;
200 }
201
202 return true;
203 }
204 }
205 return false;
206}
207
208bool KPasswdServer::hasPendingQuery(const QString &key, const KIO::AuthInfo &info)
209{
210 const QString path2 (info.url.directory(KUrl::AppendTrailingSlash | KUrl::ObeyTrailingSlash));
211 Q_FOREACH(const Request *request, m_authPending) {
212 if (request->key != key) {
213 continue;
214 }
215
216 if (info.verifyPath) {
217 const QString path1 (request->info.url.directory(KUrl::AppendTrailingSlash |
218 KUrl::ObeyTrailingSlash));
219 if (!path2.startsWith(path1)) {
220 continue;
221 }
222 }
223
224 return true;
225 }
226
227 return false;
228}
229
230QByteArray
231KPasswdServer::checkAuthInfo(const QByteArray &data, qlonglong windowId, qlonglong usertime)
232{
233 KIO::AuthInfo info;
234 QDataStream stream(data);
235 stream >> info;
236 if (usertime != 0) {
237 kapp->updateUserTimestamp(usertime);
238 }
239
240 // if the check depends on a pending query, delay it
241 // until that query is finished.
242 const QString key (createCacheKey(info));
243 if (hasPendingQuery(key, info)) {
244 setDelayedReply(true);
245 Request *pendingCheck = new Request;
246 pendingCheck->isAsync = false;
247 if (calledFromDBus()) {
248 pendingCheck->transaction = message();
249 }
250 pendingCheck->key = key;
251 pendingCheck->info = info;
252 m_authWait.append(pendingCheck);
253 return data; // return value will be ignored
254 }
255
256 // kDebug(debugArea()) << "key =" << key << "user =" << info.username << "windowId =" << windowId;
257 const AuthInfoContainer *result = findAuthInfoItem(key, info);
258 if (!result || result->isCanceled)
259 {
260 if (!result &&
261 (info.username.isEmpty() || info.password.isEmpty()) &&
262 !KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::NetworkWallet(),
263 KWallet::Wallet::PasswordFolder(),
264 makeWalletKey(key, info.realmValue)))
265 {
266 QMap<QString, QString> knownLogins;
267 if (openWallet(windowId)) {
268 if (readFromWallet(m_wallet, key, info.realmValue, info.username,
269 info.password, info.readOnly, knownLogins))
270 {
271 info.setModified(true);
272 // fall through
273 }
274 }
275 } else {
276 info.setModified(false);
277 }
278 } else {
279 kDebug(debugArea()) << "Found cached authentication for" << key;
280 updateAuthExpire(key, result, windowId, false);
281 copyAuthInfo(result, info);
282 }
283
284 QByteArray data2;
285 QDataStream stream2(&data2, QIODevice::WriteOnly);
286 stream2 << info;
287 return data2;
288}
289
290qlonglong KPasswdServer::checkAuthInfoAsync(KIO::AuthInfo info, qlonglong windowId,
291 qlonglong usertime)
292{
293 if (usertime != 0) {
294 kapp->updateUserTimestamp(usertime);
295 }
296
297 // send the request id back to the client
298 qlonglong requestId = getRequestId();
299 kDebug(debugArea()) << "User =" << info.username << ", WindowId =" << windowId;
300 if (calledFromDBus()) {
301 QDBusMessage reply(message().createReply(requestId));
302 QDBusConnection::sessionBus().send(reply);
303 }
304
305 // if the check depends on a pending query, delay it
306 // until that query is finished.
307 const QString key (createCacheKey(info));
308 if (hasPendingQuery(key, info)) {
309 Request *pendingCheck = new Request;
310 pendingCheck->isAsync = true;
311 pendingCheck->requestId = requestId;
312 pendingCheck->key = key;
313 pendingCheck->info = info;
314 m_authWait.append(pendingCheck);
315 return 0; // ignored as we already sent a reply
316 }
317
318 const AuthInfoContainer *result = findAuthInfoItem(key, info);
319 if (!result || result->isCanceled)
320 {
321 if (!result &&
322 (info.username.isEmpty() || info.password.isEmpty()) &&
323 !KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::NetworkWallet(),
324 KWallet::Wallet::PasswordFolder(),
325 makeWalletKey(key, info.realmValue)))
326 {
327 QMap<QString, QString> knownLogins;
328 if (openWallet(windowId)) {
329 if (readFromWallet(m_wallet, key, info.realmValue, info.username,
330 info.password, info.readOnly, knownLogins))
331 {
332 info.setModified(true);
333 // fall through
334 }
335 }
336 } else {
337 info.setModified(false);
338 }
339 } else {
340 // kDebug(debugArea()) << "Found cached authentication for" << key;
341 updateAuthExpire(key, result, windowId, false);
342 copyAuthInfo(result, info);
343 }
344
345 emit checkAuthInfoAsyncResult(requestId, m_seqNr, info);
346 return 0; // ignored
347}
348
349QByteArray
350KPasswdServer::queryAuthInfo(const QByteArray &data, const QString &errorMsg,
351 qlonglong windowId, qlonglong seqNr, qlonglong usertime)
352{
353 KIO::AuthInfo info;
354 QDataStream stream(data);
355 stream >> info;
356
357 kDebug(debugArea()) << "User =" << info.username << ", WindowId =" << windowId
358 << "seqNr =" << seqNr << ", errorMsg =" << errorMsg;
359
360 if ( !info.password.isEmpty() ) { // should we really allow the caller to pre-fill the password?
361 kDebug(debugArea()) << "password was set by caller";
362 }
363 if (usertime != 0) {
364 kapp->updateUserTimestamp(usertime);
365 }
366
367 const QString key (createCacheKey(info));
368 Request *request = new Request;
369 setDelayedReply(true);
370 request->isAsync = false;
371 request->transaction = message();
372 request->key = key;
373 request->info = info;
374 request->windowId = windowId;
375 request->seqNr = seqNr;
376 if (errorMsg == "<NoAuthPrompt>")
377 {
378 request->errorMsg.clear();
379 request->prompt = false;
380 }
381 else
382 {
383 request->errorMsg = errorMsg;
384 request->prompt = true;
385 }
386 m_authPending.append(request);
387
388 if (m_authPending.count() == 1)
389 QTimer::singleShot(0, this, SLOT(processRequest()));
390
391 return QByteArray(); // return value is going to be ignored
392}
393
394qlonglong
395KPasswdServer::queryAuthInfoAsync(const KIO::AuthInfo &info, const QString &errorMsg,
396 qlonglong windowId, qlonglong seqNr, qlonglong usertime)
397{
398 kDebug(debugArea()) << "User =" << info.username << ", WindowId =" << windowId
399 << "seqNr =" << seqNr << ", errorMsg =" << errorMsg;
400
401 if (!info.password.isEmpty()) {
402 kDebug(debugArea()) << "password was set by caller";
403 }
404 if (usertime != 0) {
405 kapp->updateUserTimestamp(usertime);
406 }
407
408 const QString key (createCacheKey(info));
409 Request *request = new Request;
410 request->isAsync = true;
411 request->requestId = getRequestId();
412 request->key = key;
413 request->info = info;
414 request->windowId = windowId;
415 request->seqNr = seqNr;
416 if (errorMsg == "<NoAuthPrompt>") {
417 request->errorMsg.clear();
418 request->prompt = false;
419 } else {
420 request->errorMsg = errorMsg;
421 request->prompt = true;
422 }
423 m_authPending.append(request);
424
425 if (m_authPending.count() == 1) {
426 QTimer::singleShot(0, this, SLOT(processRequest()));
427 }
428
429 return request->requestId;
430}
431
432void
433KPasswdServer::addAuthInfo(const KIO::AuthInfo &info, qlonglong windowId)
434{
435 kDebug(debugArea()) << "User =" << info.username << ", Realm =" << info.realmValue << ", WindowId =" << windowId;
436 const QString key (createCacheKey(info));
437
438 m_seqNr++;
439
440 if (!m_walletDisabled && openWallet(windowId) && storeInWallet(m_wallet, key, info)) {
441 // Since storing the password in the wallet succeeded, make sure the
442 // password information is stored in memory only for the duration the
443 // windows associated with it are still around.
444 AuthInfo authToken (info);
445 authToken.keepPassword = false;
446 addAuthInfoItem(key, authToken, windowId, m_seqNr, false);
447 return;
448 }
449
450 addAuthInfoItem(key, info, windowId, m_seqNr, false);
451}
452
453void
454KPasswdServer::addAuthInfo(const QByteArray &data, qlonglong windowId)
455{
456 KIO::AuthInfo info;
457 QDataStream stream(data);
458 stream >> info;
459 addAuthInfo(info, windowId);
460}
461
462void
463KPasswdServer::removeAuthInfo(const QString& host, const QString& protocol, const QString& user)
464{
465 kDebug(debugArea()) << protocol << host << user;
466
467 QHashIterator< QString, AuthInfoContainerList* > dictIterator(m_authDict);
468 while (dictIterator.hasNext())
469 {
470 dictIterator.next();
471
472 AuthInfoContainerList *authList = dictIterator.value();
473 if (!authList)
474 continue;
475
476 Q_FOREACH(AuthInfoContainer *current, *authList)
477 {
478 kDebug(debugArea()) << "Evaluating: " << current->info.url.protocol()
479 << current->info.url.host()
480 << current->info.username;
481 if (current->info.url.protocol() == protocol &&
482 current->info.url.host() == host &&
483 (current->info.username == user || user.isEmpty()))
484 {
485 kDebug(debugArea()) << "Removing this entry";
486 removeAuthInfoItem(dictIterator.key(), current->info);
487 }
488 }
489 }
490}
491
492bool
493KPasswdServer::openWallet( qlonglong windowId )
494{
495 if ( m_wallet && !m_wallet->isOpen() ) { // forced closed
496 delete m_wallet;
497 m_wallet = 0;
498 }
499 if ( !m_wallet )
500 m_wallet = KWallet::Wallet::openWallet(
501 KWallet::Wallet::NetworkWallet(), (WId)(windowId));
502 return m_wallet != 0;
503}
504
505void
506KPasswdServer::processRequest()
507{
508 if (m_authPending.isEmpty()) {
509 return;
510 }
511
512 QScopedPointer<Request> request (m_authPending.takeFirst());
513
514 // Prevent multiple prompts originating from the same window or the same
515 // key (server address).
516 const QString windowIdStr = QString::number(request->windowId);
517 if (m_authPrompted.contains(windowIdStr) || m_authPrompted.contains(request->key)) {
518 m_authPending.prepend(request.take()); // put it back.
519 return;
520 }
521
522 m_authPrompted.append(windowIdStr);
523 m_authPrompted.append(request->key);
524
525 KIO::AuthInfo &info = request->info;
526
527 // NOTE: If info.username is empty and info.url.user() is not, set
528 // info.username to info.url.user() to ensure proper caching. See
529 // note passwordDialogDone.
530 if (info.username.isEmpty() && !info.url.user().isEmpty()) {
531 info.username = info.url.user();
532 }
533 const bool bypassCacheAndKWallet = info.getExtraField(AUTHINFO_EXTRAFIELD_BYPASS_CACHE_AND_KWALLET).toBool();
534
535 const AuthInfoContainer *result = findAuthInfoItem(request->key, request->info);
536 kDebug(debugArea()) << "key=" << request->key << ", user=" << info.username << "seqNr: request=" << request->seqNr << ", result=" << (result ? result->seqNr : -1);
537
538 if (!bypassCacheAndKWallet && result && (request->seqNr < result->seqNr))
539 {
540 kDebug(debugArea()) << "auto retry!";
541 if (result->isCanceled)
542 {
543 info.setModified(false);
544 }
545 else
546 {
547 updateAuthExpire(request->key, result, request->windowId, false);
548 copyAuthInfo(result, info);
549 }
550 }
551 else
552 {
553 m_seqNr++;
554 if (result && !request->errorMsg.isEmpty())
555 {
556 QString prompt (request->errorMsg.trimmed());
557 prompt += QLatin1Char('\n');
558 prompt += i18n("Do you want to retry?");
559
560 KDialog* dlg = new KDialog(0, Qt::Dialog);
561 connect(dlg, SIGNAL(finished(int)), this, SLOT(retryDialogDone(int)));
562 connect(this, SIGNAL(destroyed(QObject*)), dlg, SLOT(deleteLater()));
563 dlg->setPlainCaption(i18n("Retry Authentication"));
564 dlg->setWindowIcon(KIcon("dialog-password"));
565 dlg->setButtons(KDialog::Yes | KDialog::No);
566 dlg->setObjectName("warningOKCancel");
567 KGuiItem buttonContinue (i18nc("@action:button filter-continue", "Retry"));
568 dlg->setButtonGuiItem(KDialog::Yes, buttonContinue);
569 dlg->setButtonGuiItem(KDialog::No, KStandardGuiItem::cancel());
570 dlg->setDefaultButton(KDialog::Yes);
571 dlg->setEscapeButton(KDialog::No);
572
573 KMessageBox::createKMessageBox(dlg, QMessageBox::Warning, prompt,
574 QStringList(), QString(), 0L,
575 (KMessageBox::Notify | KMessageBox::NoExec));
576
577 #ifndef Q_WS_WIN
578 KWindowSystem::setMainWindow(dlg, request->windowId);
579 #else
580 KWindowSystem::setMainWindow(dlg, (HWND)(long)request->windowId);
581 #endif
582
583 kDebug(debugArea()) << "Calling open on retry dialog" << dlg;
584 m_authRetryInProgress.insert(dlg, request.take());
585 dlg->open();
586 return;
587 }
588
589 if (request->prompt)
590 {
591 showPasswordDialog(request.take());
592 return;
593 }
594 else
595 {
596 if (!bypassCacheAndKWallet && request->prompt)
597 {
598 addAuthInfoItem(request->key, info, 0, m_seqNr, true);
599 }
600 info.setModified( false );
601 }
602 }
603
604 sendResponse(request.data());
605}
606
607QString KPasswdServer::createCacheKey( const KIO::AuthInfo &info )
608{
609 if( !info.url.isValid() ) {
610 // Note that a null key will break findAuthInfoItem later on...
611 kWarning(debugArea()) << "createCacheKey: invalid URL " << info.url ;
612 return QString();
613 }
614
615 // Generate the basic key sequence.
616 QString key = info.url.protocol();
617 key += '-';
618 if (!info.url.user().isEmpty())
619 {
620 key += info.url.user();
621 key += '@';
622 }
623 key += info.url.host();
624 int port = info.url.port();
625 if( port )
626 {
627 key += ':';
628 key += QString::number(port);
629 }
630
631 return key;
632}
633
634void KPasswdServer::copyAuthInfo(const AuthInfoContainer *i, KIO::AuthInfo& info)
635{
636 info = i->info;
637 info.setModified(true);
638}
639
640const KPasswdServer::AuthInfoContainer *
641KPasswdServer::findAuthInfoItem(const QString &key, const KIO::AuthInfo &info)
642{
643 // kDebug(debugArea()) << "key=" << key << ", user=" << info.username;
644
645 AuthInfoContainerList *authList = m_authDict.value(key);
646 if (authList)
647 {
648 QString path2 = info.url.directory(KUrl::AppendTrailingSlash|KUrl::ObeyTrailingSlash);
649 Q_FOREACH(AuthInfoContainer *current, *authList)
650 {
651 if (current->expire == AuthInfoContainer::expTime &&
652 static_cast<qulonglong>(time(0)) > current->expireTime)
653 {
654 authList->removeOne(current);
655 delete current;
656 continue;
657 }
658
659 if (info.verifyPath)
660 {
661 QString path1 = current->directory;
662 if (path2.startsWith(path1) &&
663 (info.username.isEmpty() || info.username == current->info.username))
664 return current;
665 }
666 else
667 {
668 if (current->info.realmValue == info.realmValue &&
669 (info.username.isEmpty() || info.username == current->info.username))
670 return current; // TODO: Update directory info,
671 }
672 }
673 }
674 return 0;
675}
676
677void
678KPasswdServer::removeAuthInfoItem(const QString &key, const KIO::AuthInfo &info)
679{
680 AuthInfoContainerList *authList = m_authDict.value(key);
681 if (!authList)
682 return;
683
684 Q_FOREACH(AuthInfoContainer *current, *authList)
685 {
686 if (current->info.realmValue == info.realmValue)
687 {
688 authList->removeOne(current);
689 delete current;
690 }
691 }
692 if (authList->isEmpty())
693 {
694 delete m_authDict.take(key);
695 }
696}
697
698
699void
700KPasswdServer::addAuthInfoItem(const QString &key, const KIO::AuthInfo &info, qlonglong windowId, qlonglong seqNr, bool canceled)
701{
702 kDebug(debugArea()) << "key=" << key
703 << "window-id=" << windowId
704 << "username=" << info.username
705 << "realm=" << info.realmValue
706 << "seqNr=" << seqNr
707 << "keepPassword?" << info.keepPassword
708 << "canceled?" << canceled;
709 AuthInfoContainerList *authList = m_authDict.value(key);
710 if (!authList)
711 {
712 authList = new AuthInfoContainerList;
713 m_authDict.insert(key, authList);
714 }
715 AuthInfoContainer *authItem = 0;
716 Q_FOREACH(AuthInfoContainer* current, *authList)
717 {
718 if (current->info.realmValue == info.realmValue)
719 {
720 authList->removeAll(current);
721 authItem = current;
722 break;
723 }
724 }
725
726 if (!authItem)
727 {
728 kDebug(debugArea()) << "Creating AuthInfoContainer";
729 authItem = new AuthInfoContainer;
730 authItem->expire = AuthInfoContainer::expTime;
731 }
732
733 authItem->info = info;
734 authItem->directory = info.url.directory(KUrl::AppendTrailingSlash|KUrl::ObeyTrailingSlash);
735 authItem->seqNr = seqNr;
736 authItem->isCanceled = canceled;
737
738 updateAuthExpire(key, authItem, windowId, (info.keepPassword && !canceled));
739
740 // Insert into list, keep the list sorted "longest path" first.
741 authList->append(authItem);
742 qSort(authList->begin(), authList->end(), AuthInfoContainer::Sorter());
743}
744
745void
746KPasswdServer::updateAuthExpire(const QString &key, const AuthInfoContainer *auth, qlonglong windowId, bool keep)
747{
748 AuthInfoContainer *current = const_cast<AuthInfoContainer *>(auth);
749 Q_ASSERT(current);
750
751 kDebug(debugArea()) << "key=" << key << "expire=" << current->expire << "window-id=" << windowId << "keep=" << keep;
752
753 if (keep && !windowId)
754 {
755 current->expire = AuthInfoContainer::expNever;
756 }
757 else if (windowId && (current->expire != AuthInfoContainer::expNever))
758 {
759 current->expire = AuthInfoContainer::expWindowClose;
760 if (!current->windowList.contains(windowId))
761 current->windowList.append(windowId);
762 }
763 else if (current->expire == AuthInfoContainer::expTime)
764 {
765 current->expireTime = time(0) + 10;
766 }
767
768 // Update mWindowIdList
769 if (windowId)
770 {
771 QStringList& keysChanged = mWindowIdList[windowId]; // find or insert
772 if (!keysChanged.contains(key))
773 keysChanged.append(key);
774 }
775}
776
777void
778KPasswdServer::removeAuthForWindowId(qlonglong windowId)
779{
780 const QStringList keysChanged = mWindowIdList.value(windowId);
781 foreach (const QString &key, keysChanged)
782 {
783 AuthInfoContainerList *authList = m_authDict.value(key);
784 if (!authList)
785 continue;
786
787 QMutableListIterator<AuthInfoContainer*> it (*authList);
788 while (it.hasNext())
789 {
790 AuthInfoContainer* current = it.next();
791 if (current->expire == AuthInfoContainer::expWindowClose)
792 {
793 if (current->windowList.removeAll(windowId) && current->windowList.isEmpty())
794 {
795 delete current;
796 it.remove();
797 }
798 }
799 }
800 }
801}
802
803void KPasswdServer::showPasswordDialog (KPasswdServer::Request* request)
804{
805 KIO::AuthInfo &info = request->info;
806 const bool bypassCacheAndKWallet = info.getExtraField(AUTHINFO_EXTRAFIELD_BYPASS_CACHE_AND_KWALLET).toBool();
807
808 QString username = info.username;
809 QString password = info.password;
810 bool hasWalletData = false;
811 QMap<QString, QString> knownLogins;
812
813 if ( !bypassCacheAndKWallet
814 && ( username.isEmpty() || password.isEmpty() )
815 && !KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::NetworkWallet(), KWallet::Wallet::PasswordFolder(), makeWalletKey( request->key, info.realmValue )) )
816 {
817 // no login+pass provided, check if kwallet has one
818 if ( openWallet( request->windowId ) )
819 hasWalletData = readFromWallet( m_wallet, request->key, info.realmValue, username, password, info.readOnly, knownLogins );
820 }
821
822 // assemble dialog-flags
823 KPasswordDialog::KPasswordDialogFlags dialogFlags;
824
825 if (info.getExtraField(AUTHINFO_EXTRAFIELD_DOMAIN).isValid())
826 {
827 dialogFlags |= KPasswordDialog::ShowDomainLine;
828 if (info.getExtraFieldFlags(AUTHINFO_EXTRAFIELD_DOMAIN) & KIO::AuthInfo::ExtraFieldReadOnly)
829 {
830 dialogFlags |= KPasswordDialog::DomainReadOnly;
831 }
832 }
833
834 if (info.getExtraField(AUTHINFO_EXTRAFIELD_ANONYMOUS).isValid())
835 {
836 dialogFlags |= KPasswordDialog::ShowAnonymousLoginCheckBox;
837 }
838
839 if (!info.getExtraField(AUTHINFO_EXTRAFIELD_HIDE_USERNAME_INPUT).toBool())
840 {
841 dialogFlags |= KPasswordDialog::ShowUsernameLine;
842 }
843
844 // If wallet is not enabled and the caller explicitly requested for it,
845 // do not show the keep password checkbox.
846 if (info.keepPassword && KWallet::Wallet::isEnabled())
847 dialogFlags |= KPasswordDialog::ShowKeepPassword;
848
849 // instantiate dialog
850#ifndef Q_WS_WIN
851 kDebug(debugArea()) << "Widget for" << request->windowId << QWidget::find(request->windowId) << QApplication::activeWindow();
852#else
853 kDebug(debugArea()) << "Widget for" << request->windowId << QWidget::find((HWND)request->windowId) << QApplication::activeWindow();
854#endif
855
856 KPasswordDialog* dlg = new KPasswordDialog(0, dialogFlags);
857 connect(dlg, SIGNAL(finished(int)), this, SLOT(passwordDialogDone(int)));
858 connect(this, SIGNAL(destroyed(QObject*)), dlg, SLOT(deleteLater()));
859
860 dlg->setPrompt(info.prompt);
861 dlg->setUsername(username);
862 if (info.caption.isEmpty())
863 dlg->setPlainCaption( i18n("Authentication Dialog") );
864 else
865 dlg->setPlainCaption( info.caption );
866
867 if ( !info.comment.isEmpty() )
868 dlg->addCommentLine( info.commentLabel, info.comment );
869
870 if ( !password.isEmpty() )
871 dlg->setPassword( password );
872
873 if (info.readOnly)
874 dlg->setUsernameReadOnly( true );
875 else
876 dlg->setKnownLogins( knownLogins );
877
878 if (hasWalletData)
879 dlg->setKeepPassword( true );
880
881 if (info.getExtraField(AUTHINFO_EXTRAFIELD_DOMAIN).isValid ())
882 dlg->setDomain(info.getExtraField(AUTHINFO_EXTRAFIELD_DOMAIN).toString());
883
884 if (info.getExtraField(AUTHINFO_EXTRAFIELD_ANONYMOUS).isValid () && password.isEmpty() && username.isEmpty())
885 dlg->setAnonymousMode(info.getExtraField(AUTHINFO_EXTRAFIELD_ANONYMOUS).toBool());
886
887#ifndef Q_WS_WIN
888 KWindowSystem::setMainWindow(dlg, request->windowId);
889#else
890 KWindowSystem::setMainWindow(dlg, (HWND)request->windowId);
891#endif
892
893 kDebug(debugArea()) << "Showing password dialog" << dlg << ", window-id=" << request->windowId;
894 m_authInProgress.insert(dlg, request);
895 dlg->open();
896}
897
898
899void KPasswdServer::sendResponse (KPasswdServer::Request* request)
900{
901 Q_ASSERT(request);
902 if (!request) {
903 return;
904 }
905
906 kDebug(debugArea()) << "key=" << request->key;
907 if (request->isAsync) {
908 emit queryAuthInfoAsyncResult(request->requestId, m_seqNr, request->info);
909 } else {
910 QByteArray replyData;
911 QDataStream stream2(&replyData, QIODevice::WriteOnly);
912 stream2 << request->info;
913 QDBusConnection::sessionBus().send(request->transaction.createReply(QVariantList() << replyData << m_seqNr));
914 }
915
916 // Check all requests in the wait queue.
917 Request *waitRequest;
918 QMutableListIterator<Request*> it(m_authWait);
919 while (it.hasNext()) {
920 waitRequest = it.next();
921
922 if (!hasPendingQuery(waitRequest->key, waitRequest->info))
923 {
924 const AuthInfoContainer *result = findAuthInfoItem(waitRequest->key,
925 waitRequest->info);
926 QByteArray replyData;
927
928 QDataStream stream2(&replyData, QIODevice::WriteOnly);
929
930 KIO::AuthInfo rcinfo;
931 if (!result || result->isCanceled)
932 {
933 waitRequest->info.setModified(false);
934 stream2 << waitRequest->info;
935 }
936 else
937 {
938 updateAuthExpire(waitRequest->key, result, waitRequest->windowId, false);
939 copyAuthInfo(result, rcinfo);
940 stream2 << rcinfo;
941 }
942
943 if (waitRequest->isAsync) {
944 emit checkAuthInfoAsyncResult(waitRequest->requestId, m_seqNr, rcinfo);
945 } else {
946 QDBusConnection::sessionBus().send(waitRequest->transaction.createReply(QVariantList() << replyData << m_seqNr));
947 }
948
949 delete waitRequest;
950 it.remove();
951 }
952 }
953
954 // Re-enable password request processing for the current window id again.
955 m_authPrompted.removeAll(QString::number(request->windowId));
956 m_authPrompted.removeAll(request->key);
957
958 if (m_authPending.count())
959 QTimer::singleShot(0, this, SLOT(processRequest()));
960}
961
962void KPasswdServer::passwordDialogDone (int result)
963{
964 KPasswordDialog* dlg = qobject_cast<KPasswordDialog*>(sender());
965 Q_ASSERT(dlg);
966
967 QScopedPointer<Request> request (m_authInProgress.take(dlg));
968 Q_ASSERT(request); // request should never be NULL.
969
970 if (request) {
971 KIO::AuthInfo& info = request->info;
972 const bool bypassCacheAndKWallet = info.getExtraField(AUTHINFO_EXTRAFIELD_BYPASS_CACHE_AND_KWALLET).toBool();
973
974 kDebug(debugArea()) << "dialog result=" << result << ", bypassCacheAndKWallet?" << bypassCacheAndKWallet;
975 if (dlg && result == KDialog::Accepted) {
976 Q_ASSERT(dlg);
977 const QString oldUsername (info.username);
978 info.username = dlg->username();
979 info.password = dlg->password();
980 info.keepPassword = dlg->keepPassword();
981
982 if (info.getExtraField(AUTHINFO_EXTRAFIELD_DOMAIN).isValid ())
983 info.setExtraField(AUTHINFO_EXTRAFIELD_DOMAIN, dlg->domain());
984 if (info.getExtraField(AUTHINFO_EXTRAFIELD_ANONYMOUS).isValid ())
985 info.setExtraField(AUTHINFO_EXTRAFIELD_ANONYMOUS, dlg->anonymousMode());
986
987 // When the user checks "keep password", that means:
988 // * if the wallet is enabled, store it there for long-term, and in kpasswdserver
989 // only for the duration of the window (#92928)
990 // * otherwise store in kpasswdserver for the duration of the KDE session.
991 if (!bypassCacheAndKWallet) {
992 /*
993 NOTE: The following code changes the key under which the auth
994 info is stored in memory if the request url contains a username.
995 e.g. "ftp://user@localhost", but the user changes that username
996 in the password dialog.
997
998 Since the key generated to store the credential contains the
999 username from the request URL, the key must be updated on such
1000 changes. Otherwise, the key will not be found on subsequent
1001 requests and the user will be end up being prompted over and
1002 over to re-enter the password unnecessarily.
1003 */
1004 if (!info.url.user().isEmpty() && info.username != info.url.user()) {
1005 const QString oldKey(request->key);
1006 removeAuthInfoItem(oldKey, info);
1007 info.url.setUser(info.username);
1008 request->key = createCacheKey(info);
1009 updateCachedRequestKey(m_authPending, oldKey, request->key);
1010 updateCachedRequestKey(m_authWait, oldKey, request->key);
1011 }
1012
1013 const bool skipAutoCaching = info.getExtraField(AUTHINFO_EXTRAFIELD_SKIP_CACHING_ON_QUERY).toBool();
1014 if (!skipAutoCaching && info.keepPassword && openWallet(request->windowId)) {
1015 if ( storeInWallet( m_wallet, request->key, info ) )
1016 // password is in wallet, don't keep it in memory after window is closed
1017 info.keepPassword = false;
1018 }
1019 addAuthInfoItem(request->key, info, request->windowId, m_seqNr, false);
1020 }
1021 info.setModified( true );
1022 } else {
1023 if (!bypassCacheAndKWallet && request->prompt) {
1024 addAuthInfoItem(request->key, info, 0, m_seqNr, true);
1025 }
1026 info.setModified( false );
1027 }
1028
1029 sendResponse(request.data());
1030 }
1031
1032 dlg->deleteLater();
1033}
1034
1035void KPasswdServer::retryDialogDone (int result)
1036{
1037 KDialog* dlg = qobject_cast<KDialog*>(sender());
1038 Q_ASSERT(dlg);
1039
1040 QScopedPointer<Request> request (m_authRetryInProgress.take(dlg));
1041 Q_ASSERT(request);
1042
1043 if (request) {
1044 if (result == KDialog::Yes) {
1045 showPasswordDialog(request.take());
1046 } else {
1047 // NOTE: If the user simply cancels the retry dialog, we remove the
1048 // credential stored under this key because the original attempt to
1049 // use it has failed. Otherwise, the failed credential would be cached
1050 // and used subsequently.
1051 //
1052 // TODO: decide whether it should be removed from the wallet too.
1053 KIO::AuthInfo& info = request->info;
1054 removeAuthInfoItem(request->key, request->info);
1055 info.setModified(false);
1056 sendResponse(request.data());
1057 }
1058 }
1059}
1060
1061void KPasswdServer::windowRemoved (WId id)
1062{
1063 bool foundMatch = false;
1064 if (!m_authInProgress.isEmpty()) {
1065 const qlonglong windowId = (qlonglong)(id);
1066 QMutableHashIterator<QObject*, Request*> it (m_authInProgress);
1067 while (it.hasNext()) {
1068 it.next();
1069 if (it.value()->windowId == windowId) {
1070 Request* request = it.value();
1071 QObject* obj = it.key();
1072 it.remove();
1073 m_authPrompted.removeAll(QString::number(request->windowId));
1074 m_authPrompted.removeAll(request->key);
1075 delete obj;
1076 delete request;
1077 foundMatch = true;
1078 }
1079 }
1080 }
1081
1082 if (!foundMatch && !m_authRetryInProgress.isEmpty()) {
1083 const qlonglong windowId = (qlonglong)(id);
1084 QMutableHashIterator<QObject*, Request*> it (m_authRetryInProgress);
1085 while (it.hasNext()) {
1086 it.next();
1087 if (it.value()->windowId == windowId) {
1088 Request* request = it.value();
1089 QObject* obj = it.key();
1090 it.remove();
1091 delete obj;
1092 delete request;
1093 }
1094 }
1095 }
1096}
1097
1098void KPasswdServer::updateCachedRequestKey (QList<KPasswdServer::Request*>& list, const QString& oldKey, const QString& newKey)
1099{
1100 QListIterator<Request*> it (list);
1101 while (it.hasNext()) {
1102 Request* r = it.next();
1103 if (r->key == oldKey) {
1104 r->key = newKey;
1105 }
1106 }
1107}
1108
1109
1110#include "kpasswdserver.moc"
1111