delay a few things on startup, such as setting the visibility mode, which ensures...
[personal-kdebase.git] / runtime / kpasswdserver / kpasswdserver.cpp
blob67b736184c9b0bd4723ee7f3e22d19c93451a6fe
1 /*
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"
27 #include <time.h>
29 #include <QtCore/QTimer>
31 #include <kapplication.h>
32 #include <klocale.h>
33 #include <kmessagebox.h>
34 #include <kdebug.h>
35 #include <kpassworddialog.h>
36 #include <kwallet.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"
51 int
52 KPasswdServer::AuthInfoContainerList::compareItems(Q3PtrCollection::Item n1, Q3PtrCollection::Item n2)
54 if (!n1 || !n2)
55 return 0;
57 AuthInfoContainer *i1 = (AuthInfoContainer *) n1;
58 AuthInfoContainer *i2 = (AuthInfoContainer *) n2;
60 int l1 = i1->directory.length();
61 int l2 = i2->directory.length();
63 if (l1 > l2)
64 return -1;
65 if (l1 < l2)
66 return 1;
67 return 0;
71 KPasswdServer::KPasswdServer(QObject* parent, const QList<QVariant>&)
72 : KDEDModule(parent)
74 m_authPending.setAutoDelete(true);
75 m_seqNr = 0;
76 m_wallet = 0;
77 connect(this, SIGNAL(windowUnregistered(qlonglong)),
78 this, SLOT(removeAuthForWindowId(qlonglong)));
81 KPasswdServer::~KPasswdServer()
83 qDeleteAll(m_authDict);
84 delete m_wallet;
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 );
99 return str;
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() ) )
106 return false;
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;
111 int entryNumber = 1;
112 Map 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 );
133 return true;
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;
148 int entryNumber = 1;
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;
171 return true;
174 return false;
177 QByteArray
178 KPasswdServer::checkAuthInfo(const QByteArray &data, qlonglong windowId, qlonglong usertime, const QDBusMessage &msg)
180 KIO::AuthInfo info;
181 QDataStream stream(data);
182 stream >> info;
183 kDebug(130) << "User =" << info.username << ", WindowId =" << windowId << endl;
184 if( usertime != 0 )
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)
194 continue;
196 if (info.verifyPath)
198 QString path1 = request->info.url.directory(KUrl::AppendTrailingSlash|KUrl::ObeyTrailingSlash);
199 if (!path2.startsWith(path1))
200 continue;
203 msg.setDelayedReply(true);
204 request = new Request;
205 request->transaction = msg;
206 request->key = key;
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)
215 if (!result &&
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);
226 // fall through
229 } else {
230 info.setModified(false);
233 QByteArray data2;
234 QDataStream stream(&data2, QIODevice::WriteOnly);
235 stream << info;
236 return data2;
239 updateAuthExpire(key, result, windowId, false);
241 info = copyAuthInfo(result);
242 QByteArray data2;
243 QDataStream stream2(&data2, QIODevice::WriteOnly);
244 stream2 << info;
245 return data2;
248 QByteArray
249 KPasswdServer::queryAuthInfo(const QByteArray &data, const QString &errorMsg, qlonglong windowId,
250 qlonglong seqNr, qlonglong usertime, const QDBusMessage &msg)
252 KIO::AuthInfo info;
253 QDataStream stream(data);
254 stream >> info;
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";
259 if( usertime != 0 )
260 kapp->updateUserTimestamp( usertime );
262 QString key = createCacheKey(info);
263 Request *request = new Request;
264 msg.setDelayedReply(true);
265 request->transaction = msg;
266 request->key = key;
267 request->info = info;
268 request->windowId = windowId;
269 request->seqNr = seqNr;
270 if (errorMsg == "<NoAuthPrompt>")
272 request->errorMsg.clear();
273 request->prompt = false;
275 else
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
288 void
289 KPasswdServer::addAuthInfo(const QByteArray &data, qlonglong windowId)
291 KIO::AuthInfo info;
292 QDataStream stream(data);
293 stream >> info;
294 kDebug(130) << "User =" << info.username << ", RealmValue= " << info.realmValue
295 << ", WindowId = " << windowId << endl;
296 QString key = createCacheKey(info);
298 m_seqNr++;
300 addAuthInfoItem(key, info, windowId, m_seqNr, false);
303 void
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())
311 dictIterator.next();
313 AuthInfoContainerList *authList = dictIterator.value();
314 if (!authList)
315 continue;
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();
330 else
332 current = authList->next();
338 bool
339 KPasswdServer::openWallet( int windowId )
341 if ( m_wallet && !m_wallet->isOpen() ) { // forced closed
342 delete m_wallet;
343 m_wallet = 0;
345 if ( !m_wallet )
346 m_wallet = KWallet::Wallet::openWallet(
347 KWallet::Wallet::NetworkWallet(), (WId) windowId );
348 return m_wallet != 0;
351 void
352 KPasswdServer::processRequest()
354 Request *request = m_authPending.first();
355 if (!request)
356 return;
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);
371 else
373 updateAuthExpire(request->key, result, request->windowId, false);
374 info = copyAuthInfo(result);
377 else
379 m_seqNr++;
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)
389 askPw = false;
392 int dlgResult = QDialog::Rejected;
393 if (askPw)
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") );
435 else
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 );
444 if (info.readOnly)
445 dlg.setUsernameReadOnly( true );
446 else
447 dlg.setKnownLogins( knownLogins );
449 if (hasWalletData)
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());
458 #ifndef Q_WS_WIN
459 KWindowSystem::setMainWindow(&dlg, request->windowId);
460 #else
461 KWindowSystem::setMainWindow(&dlg, (HWND)(long)request->windowId);
462 #endif
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 );
498 else
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);
511 stream2 << info;
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)
527 continue;
529 if (info.verifyPath)
531 QString path1 = request->info.url.directory(KUrl::AppendTrailingSlash|KUrl::ObeyTrailingSlash);
532 if (!path2.startsWith(path1))
533 continue;
536 keepQueued = true;
537 break;
539 if (keepQueued)
541 waitRequest = m_authWait.next();
543 else
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;
555 else
557 updateAuthExpire(waitRequest->key, result, waitRequest->windowId, false);
558 KIO::AuthInfo info = copyAuthInfo(result);
559 stream2 << info;
562 QDBusConnection::sessionBus().send(waitRequest->transaction.createReply(QVariantList() << replyData << m_seqNr));
564 m_authWait.remove();
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 ;
579 return QString();
582 // Generate the basic key sequence.
583 QString key = info.url.protocol();
584 key += '-';
585 if (!info.url.user().isEmpty())
587 key += info.url.user();
588 key += '@';
590 key += info.url.host();
591 int port = info.url.port();
592 if( port )
594 key += ':';
595 key += QString::number(port);
598 return key;
601 KIO::AuthInfo
602 KPasswdServer::copyAuthInfo(const AuthInfoContainer *i)
604 KIO::AuthInfo result = i->info;
605 result.setModified(true);
607 return result;
610 const KPasswdServer::AuthInfoContainer *
611 KPasswdServer::findAuthInfoItem(const QString &key, const KIO::AuthInfo &info)
613 AuthInfoContainerList *authList = m_authDict.value(key);
614 if (!authList)
615 return 0;
617 QString path2 = info.url.directory(KUrl::AppendTrailingSlash|KUrl::ObeyTrailingSlash);
618 for(AuthInfoContainer *current = authList->first();
619 current; )
621 if ((current->expire == AuthInfoContainer::expTime) &&
622 (difftime(time(0), current->expireTime) > 0))
624 authList->remove();
625 current = authList->current();
626 continue;
629 if (info.verifyPath)
631 QString path1 = current->directory;
632 if (path2.startsWith(path1) &&
633 (info.username.isEmpty() || info.username == current->info.username))
634 return current;
636 else
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();
645 return 0;
648 void
649 KPasswdServer::removeAuthInfoItem(const QString &key, const KIO::AuthInfo &info)
651 AuthInfoContainerList *authList = m_authDict.value(key);
652 if (!authList)
653 return;
655 for(AuthInfoContainer *current = authList->first();
656 current; )
658 if (current->info.realmValue == info.realmValue)
660 authList->remove();
661 current = authList->current();
663 else
665 current = authList->next();
668 if (authList->isEmpty())
670 delete m_authDict.take(key);
675 void
676 KPasswdServer::addAuthInfoItem(const QString &key, const KIO::AuthInfo &info, qlonglong windowId, qlonglong seqNr, bool canceled)
678 AuthInfoContainerList *authList = m_authDict.value(key);
679 if (!authList)
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)
689 authList->take();
690 break;
694 if (!current)
696 current = new AuthInfoContainer;
697 current->expire = AuthInfoContainer::expTime;
698 kDebug(130) << "Creating AuthInfoContainer";
700 else
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);
716 void
717 KPasswdServer::updateAuthExpire(const QString &key, const AuthInfoContainer *auth, qlonglong windowId, bool keep)
719 AuthInfoContainer *current = const_cast<AuthInfoContainer *>(auth);
720 if (keep)
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
736 if (windowId)
738 QStringList *keysChanged = mWindowIdList.value(windowId);
739 if (!keysChanged)
741 keysChanged = new QStringList;
742 mWindowIdList.insert(windowId, keysChanged);
744 if (!keysChanged->contains(key))
745 keysChanged->append(key);
749 void
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);
758 if (!authList)
759 continue;
761 AuthInfoContainer *current = authList->first();
762 for(; current; )
764 if (current->expire == AuthInfoContainer::expWindowClose)
766 if (current->windowList.removeAll(windowId) && current->windowList.isEmpty())
768 authList->remove();
769 current = authList->current();
770 continue;
773 current = authList->next();
778 #include "kpasswdserver.moc"