2 This file is part of the KDE Password Server
4 Copyright (C) 2002 Waldo Bastian (bastian@kde.org)
5 Copyright (C) 2005 David Faure (faure@kde.org)
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License
9 version 2 as published by the Free Software Foundation.
11 This software is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this library; see the file COPYING. If not, write to
18 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 Boston, MA 02110-1301, USA.
21 //----------------------------------------------------------------------------
23 // KDE Password Server
25 #include "kpasswdserver.h"
29 #include <QtCore/QTimer>
31 #include <kapplication.h>
33 #include <kmessagebox.h>
35 #include <kpassworddialog.h>
37 #include <kwindowsystem.h>
39 #include <kpluginfactory.h>
40 #include <kpluginloader.h>
42 K_PLUGIN_FACTORY(KPasswdServerFactory
,
43 registerPlugin
<KPasswdServer
>();
45 K_EXPORT_PLUGIN(KPasswdServerFactory("kpasswdserver"))
47 #define AUTHINFO_EXTRAFIELD_DOMAIN "domain"
48 #define AUTHINFO_EXTRAFIELD_ANONYMOUS "anonymous"
49 #define AUTHINFO_EXTRAFIELD_BYPASS_CACHE_AND_KWALLET "bypass-cache-and-kwallet"
52 KPasswdServer::AuthInfoContainerList::compareItems(Q3PtrCollection::Item n1
, Q3PtrCollection::Item n2
)
57 AuthInfoContainer
*i1
= (AuthInfoContainer
*) n1
;
58 AuthInfoContainer
*i2
= (AuthInfoContainer
*) n2
;
60 int l1
= i1
->directory
.length();
61 int l2
= i2
->directory
.length();
71 KPasswdServer::KPasswdServer(QObject
* parent
, const QList
<QVariant
>&)
74 m_authPending
.setAutoDelete(true);
77 connect(this, SIGNAL(windowUnregistered(qlonglong
)),
78 this, SLOT(removeAuthForWindowId(qlonglong
)));
81 KPasswdServer::~KPasswdServer()
83 qDeleteAll(m_authDict
);
87 // Helper - returns the wallet key to use for read/store/checking for existence.
88 static QString
makeWalletKey( const QString
& key
, const QString
& realm
)
90 return realm
.isEmpty() ? key
: key
+ '-' + realm
;
93 // Helper for storeInWallet/readFromWallet
94 static QString
makeMapKey( const char* key
, int entryNumber
)
96 QString str
= QLatin1String( key
);
97 if ( entryNumber
> 1 )
98 str
+= '-' + QString::number( entryNumber
);
102 static bool storeInWallet( KWallet::Wallet
* wallet
, const QString
& key
, const KIO::AuthInfo
&info
)
104 if ( !wallet
->hasFolder( KWallet::Wallet::PasswordFolder() ) )
105 if ( !wallet
->createFolder( KWallet::Wallet::PasswordFolder() ) )
107 wallet
->setFolder( KWallet::Wallet::PasswordFolder() );
108 // Before saving, check if there's already an entry with this login.
109 // If so, replace it (with the new password). Otherwise, add a new entry.
110 typedef QMap
<QString
,QString
> Map
;
113 QString walletKey
= makeWalletKey( key
, info
.realmValue
);
114 kDebug(130) << "walletKey =" << walletKey
<< " reading existing map";
115 if ( wallet
->readMap( walletKey
, map
) == 0 ) {
116 Map::ConstIterator end
= map
.constEnd();
117 Map::ConstIterator it
= map
.constFind( "login" );
118 while ( it
!= end
) {
119 if ( it
.value() == info
.username
) {
120 break; // OK, overwrite this entry
122 it
= map
.constFind( QString( "login-" ) + QString::number( ++entryNumber
) );
124 // If no entry was found, create a new entry - entryNumber is set already.
126 const QString loginKey
= makeMapKey( "login", entryNumber
);
127 const QString passwordKey
= makeMapKey( "password", entryNumber
);
128 kDebug(130) << "writing to " << loginKey
<< "," << passwordKey
;
129 // note the overwrite=true by default
130 map
.insert( loginKey
, info
.username
);
131 map
.insert( passwordKey
, info
.password
);
132 wallet
->writeMap( walletKey
, map
);
137 static bool readFromWallet( KWallet::Wallet
* wallet
, const QString
& key
, const QString
& realm
, QString
& username
, QString
& password
, bool userReadOnly
, QMap
<QString
,QString
>& knownLogins
)
139 //kDebug(130) << "key =" << key << " username =" << username << " password =" /*<< password*/ << " userReadOnly =" << userReadOnly << " realm =" << realm;
140 if ( wallet
->hasFolder( KWallet::Wallet::PasswordFolder() ) )
142 wallet
->setFolder( KWallet::Wallet::PasswordFolder() );
144 QMap
<QString
,QString
> map
;
145 if ( wallet
->readMap( makeWalletKey( key
, realm
), map
) == 0 )
147 typedef QMap
<QString
,QString
> Map
;
149 Map::ConstIterator end
= map
.constEnd();
150 Map::ConstIterator it
= map
.constFind( "login" );
151 while ( it
!= end
) {
152 //kDebug(130) << "found " << it.key() << "=" << it.value();
153 Map::ConstIterator pwdIter
= map
.constFind( makeMapKey( "password", entryNumber
) );
154 if ( pwdIter
!= end
) {
155 if ( it
.value() == username
)
156 password
= pwdIter
.value();
157 knownLogins
.insert( it
.value(), pwdIter
.value() );
160 it
= map
.constFind( QString( "login-" ) + QString::number( ++entryNumber
) );
162 //kDebug(130) << knownLogins.count() << " known logins";
164 if ( !userReadOnly
&& !knownLogins
.isEmpty() && username
.isEmpty() ) {
165 // Pick one, any one...
166 username
= knownLogins
.begin().key();
167 password
= knownLogins
.begin().value();
168 //kDebug(130) << "picked the first one:" << username;
178 KPasswdServer::checkAuthInfo(const QByteArray
&data
, qlonglong windowId
, qlonglong usertime
, const QDBusMessage
&msg
)
181 QDataStream
stream(data
);
183 kDebug(130) << "User =" << info
.username
<< ", WindowId =" << windowId
<< endl
;
185 kapp
->updateUserTimestamp( usertime
);
187 QString key
= createCacheKey(info
);
189 Request
*request
= m_authPending
.first();
190 QString path2
= info
.url
.directory(KUrl::AppendTrailingSlash
|KUrl::ObeyTrailingSlash
);
191 for(; request
; request
= m_authPending
.next())
193 if (request
->key
!= key
)
198 QString path1
= request
->info
.url
.directory(KUrl::AppendTrailingSlash
|KUrl::ObeyTrailingSlash
);
199 if (!path2
.startsWith(path1
))
203 msg
.setDelayedReply(true);
204 request
= new Request
;
205 request
->transaction
= msg
;
207 request
->info
= info
;
208 m_authWait
.append(request
);
209 return data
; // return value will be ignored
212 const AuthInfoContainer
*result
= findAuthInfoItem(key
, info
);
213 if (!result
|| result
->isCanceled
)
216 (info
.username
.isEmpty() || info
.password
.isEmpty()) &&
217 !KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::NetworkWallet(),
218 KWallet::Wallet::PasswordFolder(), makeWalletKey(key
, info
.realmValue
)))
220 QMap
<QString
, QString
> knownLogins
;
221 if (openWallet(windowId
)) {
222 if (readFromWallet(m_wallet
, key
, info
.realmValue
, info
.username
, info
.password
,
223 info
.readOnly
, knownLogins
))
225 info
.setModified(true);
230 info
.setModified(false);
234 QDataStream
stream(&data2
, QIODevice::WriteOnly
);
239 updateAuthExpire(key
, result
, windowId
, false);
241 info
= copyAuthInfo(result
);
243 QDataStream
stream2(&data2
, QIODevice::WriteOnly
);
249 KPasswdServer::queryAuthInfo(const QByteArray
&data
, const QString
&errorMsg
, qlonglong windowId
,
250 qlonglong seqNr
, qlonglong usertime
, const QDBusMessage
&msg
)
253 QDataStream
stream(data
);
255 kDebug(130) << "User =" << info
.username
<< ", Message= " << info
.prompt
256 << ", WindowId =" << windowId
<< endl
;
257 if ( !info
.password
.isEmpty() ) // should we really allow the caller to pre-fill the password?
258 kDebug(130) << "password was set by caller";
260 kapp
->updateUserTimestamp( usertime
);
262 QString key
= createCacheKey(info
);
263 Request
*request
= new Request
;
264 msg
.setDelayedReply(true);
265 request
->transaction
= msg
;
267 request
->info
= info
;
268 request
->windowId
= windowId
;
269 request
->seqNr
= seqNr
;
270 if (errorMsg
== "<NoAuthPrompt>")
272 request
->errorMsg
.clear();
273 request
->prompt
= false;
277 request
->errorMsg
= errorMsg
;
278 request
->prompt
= true;
280 m_authPending
.append(request
);
282 if (m_authPending
.count() == 1)
283 QTimer::singleShot(0, this, SLOT(processRequest()));
285 return QByteArray(); // return value is going to be ignored
289 KPasswdServer::addAuthInfo(const QByteArray
&data
, qlonglong windowId
)
292 QDataStream
stream(data
);
294 kDebug(130) << "User =" << info
.username
<< ", RealmValue= " << info
.realmValue
295 << ", WindowId = " << windowId
<< endl
;
296 QString key
= createCacheKey(info
);
300 addAuthInfoItem(key
, info
, windowId
, m_seqNr
, false);
304 KPasswdServer::removeAuthInfo(const QString
& host
, const QString
& protocol
, const QString
& user
)
306 kDebug(130) << "Wanted" << protocol
<< host
<< user
;
308 QHashIterator
< QString
, AuthInfoContainerList
* > dictIterator(m_authDict
);
309 while (dictIterator
.hasNext())
313 AuthInfoContainerList
*authList
= dictIterator
.value();
317 for(AuthInfoContainer
*current
= authList
->first(); current
; )
319 kDebug(130) << "Evaluating: " << current
->info
.url
.protocol()
320 << current
->info
.url
.host()
321 << current
->info
.username
;
322 if (current
->info
.url
.protocol() == protocol
&&
323 current
->info
.url
.host() == host
&&
324 current
->info
.username
== user
)
326 kDebug(130) << "Removing this entry";
327 removeAuthInfoItem(dictIterator
.key(), current
->info
);
328 current
= authList
->current();
332 current
= authList
->next();
339 KPasswdServer::openWallet( int windowId
)
341 if ( m_wallet
&& !m_wallet
->isOpen() ) { // forced closed
346 m_wallet
= KWallet::Wallet::openWallet(
347 KWallet::Wallet::NetworkWallet(), (WId
) windowId
);
348 return m_wallet
!= 0;
352 KPasswdServer::processRequest()
354 Request
*request
= m_authPending
.first();
358 KIO::AuthInfo
&info
= request
->info
;
359 bool bypassCacheAndKWallet
= info
.getExtraField(AUTHINFO_EXTRAFIELD_BYPASS_CACHE_AND_KWALLET
).toBool() == true;
361 kDebug(130) << "User =" << info
.username
<< ", Message =" << info
.prompt
<< endl
;
362 const AuthInfoContainer
*result
= findAuthInfoItem(request
->key
, request
->info
);
364 if (!bypassCacheAndKWallet
&& result
&& (request
->seqNr
< result
->seqNr
))
366 kDebug(130) << "auto retry!";
367 if (result
->isCanceled
)
369 info
.setModified(false);
373 updateAuthExpire(request
->key
, result
, request
->windowId
, false);
374 info
= copyAuthInfo(result
);
380 bool askPw
= request
->prompt
;
381 if (result
&& !info
.username
.isEmpty() &&
382 !request
->errorMsg
.isEmpty())
384 QString prompt
= request
->errorMsg
+ " ";
385 prompt
+= i18n("Do you want to retry?");
386 int dlgResult
= KMessageBox::warningContinueCancel(0, prompt
,
387 i18n("Authentication"), KGuiItem(i18n("Retry")));
388 if (dlgResult
!= KMessageBox::Continue
)
392 int dlgResult
= QDialog::Rejected
;
395 QString username
= info
.username
;
396 QString password
= info
.password
;
397 bool hasWalletData
= false;
398 QMap
<QString
, QString
> knownLogins
;
400 if ( !bypassCacheAndKWallet
401 && ( username
.isEmpty() || password
.isEmpty() )
402 && !KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::NetworkWallet(), KWallet::Wallet::PasswordFolder(), makeWalletKey( request
->key
, info
.realmValue
)) )
404 // no login+pass provided, check if kwallet has one
405 if ( openWallet( request
->windowId
) )
406 hasWalletData
= readFromWallet( m_wallet
, request
->key
, info
.realmValue
, username
, password
, info
.readOnly
, knownLogins
);
409 // assemble dialog-flags
410 KPasswordDialog::KPasswordDialogFlags dialogFlags
;
412 if (info
.getExtraField(AUTHINFO_EXTRAFIELD_DOMAIN
).isValid())
414 dialogFlags
|= KPasswordDialog::ShowDomainLine
;
415 if (info
.getExtraFieldFlags(AUTHINFO_EXTRAFIELD_DOMAIN
) & KIO::AuthInfo::ExtraFieldReadOnly
)
417 dialogFlags
|= KPasswordDialog::DomainReadOnly
;
421 if (info
.getExtraField(AUTHINFO_EXTRAFIELD_ANONYMOUS
).isValid())
423 dialogFlags
|= KPasswordDialog::ShowAnonymousLoginCheckBox
;
426 dialogFlags
|= (info
.keepPassword
? ( KPasswordDialog::ShowUsernameLine
427 | KPasswordDialog::ShowKeepPassword
) : KPasswordDialog::ShowUsernameLine
);
429 // instantiate dialog
430 KPasswordDialog
dlg( 0l, dialogFlags
) ;
431 dlg
.setPrompt(info
.prompt
);
432 dlg
.setUsername(username
);
433 if (info
.caption
.isEmpty())
434 dlg
.setPlainCaption( i18n("Authorization Dialog") );
436 dlg
.setPlainCaption( info
.caption
);
438 if ( !info
.comment
.isEmpty() )
439 dlg
.addCommentLine( info
.commentLabel
, info
.comment
);
441 if ( !password
.isEmpty() )
442 dlg
.setPassword( password
);
445 dlg
.setUsernameReadOnly( true );
447 dlg
.setKnownLogins( knownLogins
);
450 dlg
.setKeepPassword( true );
452 if (info
.getExtraField(AUTHINFO_EXTRAFIELD_DOMAIN
).isValid ())
453 dlg
.setDomain(info
.getExtraField(AUTHINFO_EXTRAFIELD_DOMAIN
).toString());
455 if (info
.getExtraField(AUTHINFO_EXTRAFIELD_ANONYMOUS
).isValid ())
456 dlg
.setAnonymousMode(info
.getExtraField(AUTHINFO_EXTRAFIELD_ANONYMOUS
).toBool());
459 KWindowSystem::setMainWindow(&dlg
, request
->windowId
);
461 KWindowSystem::setMainWindow(&dlg
, (HWND
)(long)request
->windowId
);
464 dlgResult
= dlg
.exec();
466 if (dlgResult
== QDialog::Accepted
)
468 info
.username
= dlg
.username();
469 info
.password
= dlg
.password();
470 info
.keepPassword
= dlg
.keepPassword();
472 if (info
.getExtraField(AUTHINFO_EXTRAFIELD_DOMAIN
).isValid ())
473 info
.setExtraField(AUTHINFO_EXTRAFIELD_DOMAIN
, dlg
.domain());
474 if (info
.getExtraField(AUTHINFO_EXTRAFIELD_ANONYMOUS
).isValid ())
475 info
.setExtraField(AUTHINFO_EXTRAFIELD_ANONYMOUS
, dlg
.anonymousMode());
477 // When the user checks "keep password", that means:
478 // * if the wallet is enabled, store it there for long-term, and in kpasswdserver
479 // only for the duration of the window (#92928)
480 // * otherwise store in kpasswdserver for the duration of the KDE session.
481 if ( !bypassCacheAndKWallet
&& info
.keepPassword
) {
482 if ( openWallet( request
->windowId
) ) {
483 if ( storeInWallet( m_wallet
, request
->key
, info
) )
484 // password is in wallet, don't keep it in memory after window is closed
485 info
.keepPassword
= false;
490 if ( dlgResult
!= QDialog::Accepted
)
492 if (!bypassCacheAndKWallet
&& request
->prompt
)
494 addAuthInfoItem(request
->key
, info
, 0, m_seqNr
, true);
496 info
.setModified( false );
500 if (!bypassCacheAndKWallet
)
502 addAuthInfoItem(request
->key
, info
, request
->windowId
, m_seqNr
, false);
504 info
.setModified( true );
508 QByteArray replyData
;
510 QDataStream
stream2(&replyData
, QIODevice::WriteOnly
);
512 QDBusConnection::sessionBus().send(request
->transaction
.createReply(QVariantList() << replyData
<< m_seqNr
));
514 m_authPending
.remove((unsigned int) 0);
516 // Check all requests in the wait queue.
517 for (Request
*waitRequest
= m_authWait
.first(); waitRequest
; )
519 bool keepQueued
= false;
520 QString key
= waitRequest
->key
;
522 request
= m_authPending
.first();
523 QString path2
= waitRequest
->info
.url
.directory(KUrl::AppendTrailingSlash
|KUrl::ObeyTrailingSlash
);
524 for(; request
; request
= m_authPending
.next())
526 if (request
->key
!= key
)
531 QString path1
= request
->info
.url
.directory(KUrl::AppendTrailingSlash
|KUrl::ObeyTrailingSlash
);
532 if (!path2
.startsWith(path1
))
541 waitRequest
= m_authWait
.next();
545 const AuthInfoContainer
*result
= findAuthInfoItem(waitRequest
->key
, waitRequest
->info
);
546 QByteArray replyData
;
548 QDataStream
stream2(&replyData
, QIODevice::WriteOnly
);
550 if (!result
|| result
->isCanceled
)
552 waitRequest
->info
.setModified(false);
553 stream2
<< waitRequest
->info
;
557 updateAuthExpire(waitRequest
->key
, result
, waitRequest
->windowId
, false);
558 KIO::AuthInfo info
= copyAuthInfo(result
);
562 QDBusConnection::sessionBus().send(waitRequest
->transaction
.createReply(QVariantList() << replyData
<< m_seqNr
));
565 waitRequest
= m_authWait
.current();
569 if (m_authPending
.count())
570 QTimer::singleShot(0, this, SLOT(processRequest()));
574 QString
KPasswdServer::createCacheKey( const KIO::AuthInfo
&info
)
576 if( !info
.url
.isValid() ) {
577 // Note that a null key will break findAuthInfoItem later on...
578 kWarning(130) << "createCacheKey: invalid URL " << info
.url
;
582 // Generate the basic key sequence.
583 QString key
= info
.url
.protocol();
585 if (!info
.url
.user().isEmpty())
587 key
+= info
.url
.user();
590 key
+= info
.url
.host();
591 int port
= info
.url
.port();
595 key
+= QString::number(port
);
602 KPasswdServer::copyAuthInfo(const AuthInfoContainer
*i
)
604 KIO::AuthInfo result
= i
->info
;
605 result
.setModified(true);
610 const KPasswdServer::AuthInfoContainer
*
611 KPasswdServer::findAuthInfoItem(const QString
&key
, const KIO::AuthInfo
&info
)
613 AuthInfoContainerList
*authList
= m_authDict
.value(key
);
617 QString path2
= info
.url
.directory(KUrl::AppendTrailingSlash
|KUrl::ObeyTrailingSlash
);
618 for(AuthInfoContainer
*current
= authList
->first();
621 if ((current
->expire
== AuthInfoContainer::expTime
) &&
622 (difftime(time(0), current
->expireTime
) > 0))
625 current
= authList
->current();
631 QString path1
= current
->directory
;
632 if (path2
.startsWith(path1
) &&
633 (info
.username
.isEmpty() || info
.username
== current
->info
.username
))
638 if (current
->info
.realmValue
== info
.realmValue
&&
639 (info
.username
.isEmpty() || info
.username
== current
->info
.username
))
640 return current
; // TODO: Update directory info,
643 current
= authList
->next();
649 KPasswdServer::removeAuthInfoItem(const QString
&key
, const KIO::AuthInfo
&info
)
651 AuthInfoContainerList
*authList
= m_authDict
.value(key
);
655 for(AuthInfoContainer
*current
= authList
->first();
658 if (current
->info
.realmValue
== info
.realmValue
)
661 current
= authList
->current();
665 current
= authList
->next();
668 if (authList
->isEmpty())
670 delete m_authDict
.take(key
);
676 KPasswdServer::addAuthInfoItem(const QString
&key
, const KIO::AuthInfo
&info
, qlonglong windowId
, qlonglong seqNr
, bool canceled
)
678 AuthInfoContainerList
*authList
= m_authDict
.value(key
);
681 authList
= new AuthInfoContainerList
;
682 m_authDict
.insert(key
, authList
);
684 AuthInfoContainer
*current
= authList
->first();
685 for(; current
; current
= authList
->next())
687 if (current
->info
.realmValue
== info
.realmValue
)
696 current
= new AuthInfoContainer
;
697 current
->expire
= AuthInfoContainer::expTime
;
698 kDebug(130) << "Creating AuthInfoContainer";
702 kDebug(130) << "Updating AuthInfoContainer";
705 current
->info
= info
;
706 current
->directory
= info
.url
.directory(KUrl::AppendTrailingSlash
|KUrl::ObeyTrailingSlash
);
707 current
->seqNr
= seqNr
;
708 current
->isCanceled
= canceled
;
710 updateAuthExpire(key
, current
, windowId
, info
.keepPassword
&& !canceled
);
712 // Insert into list, keep the list sorted "longest path" first.
713 authList
->inSort(current
);
717 KPasswdServer::updateAuthExpire(const QString
&key
, const AuthInfoContainer
*auth
, qlonglong windowId
, bool keep
)
719 AuthInfoContainer
*current
= const_cast<AuthInfoContainer
*>(auth
);
722 current
->expire
= AuthInfoContainer::expNever
;
724 else if (windowId
&& (current
->expire
!= AuthInfoContainer::expNever
))
726 current
->expire
= AuthInfoContainer::expWindowClose
;
727 if (!current
->windowList
.contains(windowId
))
728 current
->windowList
.append(windowId
);
730 else if (current
->expire
== AuthInfoContainer::expTime
)
732 current
->expireTime
= time(0)+10;
735 // Update mWindowIdList
738 QStringList
*keysChanged
= mWindowIdList
.value(windowId
);
741 keysChanged
= new QStringList
;
742 mWindowIdList
.insert(windowId
, keysChanged
);
744 if (!keysChanged
->contains(key
))
745 keysChanged
->append(key
);
750 KPasswdServer::removeAuthForWindowId(qlonglong windowId
)
752 QStringList
*keysChanged
= mWindowIdList
.value(windowId
);
753 if (!keysChanged
) return;
755 foreach (const QString
&key
, *keysChanged
)
757 AuthInfoContainerList
*authList
= m_authDict
.value(key
);
761 AuthInfoContainer
*current
= authList
->first();
764 if (current
->expire
== AuthInfoContainer::expWindowClose
)
766 if (current
->windowList
.removeAll(windowId
) && current
->windowList
.isEmpty())
769 current
= authList
->current();
773 current
= authList
->next();
778 #include "kpasswdserver.moc"