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 | |
46 | K_PLUGIN_FACTORY(KPasswdServerFactory, |
47 | registerPlugin<KPasswdServer>(); |
48 | ) |
49 | K_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 | |
57 | static int debugArea() { static int s_area = KDebug::registerArea("KPasswdServer" ); return s_area; } |
58 | |
59 | static qlonglong getRequestId() |
60 | { |
61 | static qlonglong nextRequestId = 0; |
62 | return nextRequestId++; |
63 | } |
64 | |
65 | bool |
66 | KPasswdServer::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 | |
77 | KPasswdServer::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 | |
106 | KPasswdServer::~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. |
119 | static QString makeWalletKey( const QString& key, const QString& realm ) |
120 | { |
121 | return realm.isEmpty() ? key : key + '-' + realm; |
122 | } |
123 | |
124 | // Helper for storeInWallet/readFromWallet |
125 | static 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 | |
133 | static 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 | |
168 | static 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 | |
208 | bool 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 | |
230 | QByteArray |
231 | KPasswdServer::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 | |
290 | qlonglong 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 | |
349 | QByteArray |
350 | KPasswdServer::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 | |
394 | qlonglong |
395 | KPasswdServer::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 | |
432 | void |
433 | KPasswdServer::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 | |
453 | void |
454 | KPasswdServer::addAuthInfo(const QByteArray &data, qlonglong windowId) |
455 | { |
456 | KIO::AuthInfo info; |
457 | QDataStream stream(data); |
458 | stream >> info; |
459 | addAuthInfo(info, windowId); |
460 | } |
461 | |
462 | void |
463 | KPasswdServer::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 | |
492 | bool |
493 | KPasswdServer::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 | |
505 | void |
506 | KPasswdServer::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 | |
607 | QString 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 | |
634 | void KPasswdServer::copyAuthInfo(const AuthInfoContainer *i, KIO::AuthInfo& info) |
635 | { |
636 | info = i->info; |
637 | info.setModified(true); |
638 | } |
639 | |
640 | const KPasswdServer::AuthInfoContainer * |
641 | KPasswdServer::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 | |
677 | void |
678 | KPasswdServer::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 | |
699 | void |
700 | KPasswdServer::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 | |
745 | void |
746 | KPasswdServer::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 | |
777 | void |
778 | KPasswdServer::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 | |
803 | void 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 | |
899 | void 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 | |
962 | void 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 | |
1035 | void 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 | |
1061 | void 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 | |
1098 | void 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 | |