kpasswdserver.cpp

00001 /*
00002     This file is part of the KDE Password Server
00003 
00004     Copyright (C) 2002 Waldo Bastian (bastian@kde.org)
00005     Copyright (C) 2005 David Faure (faure@kde.org)
00006 
00007     This library is free software; you can redistribute it and/or
00008     modify it under the terms of the GNU General Public License
00009     version 2 as published by the Free Software Foundation.
00010 
00011     This software is distributed in the hope that it will be useful,
00012     but WITHOUT ANY WARRANTY; without even the implied warranty of
00013     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014     General Public License for more details.
00015 
00016     You should have received a copy of the GNU General Public License
00017     along with this library; see the file COPYING. If not, write to
00018     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00019     Boston, MA 02110-1301, USA.
00020 */
00021 //----------------------------------------------------------------------------
00022 //
00023 // KDE Password Server
00024 // $Id: kpasswdserver.cpp 465272 2005-09-29 09:47:40Z mueller $
00025 
00026 #include "kpasswdserver.h"
00027 
00028 #include <time.h>
00029 
00030 #include <qtimer.h>
00031 
00032 #include <kapplication.h>
00033 #include <klocale.h>
00034 #include <kmessagebox.h>
00035 #include <kdebug.h>
00036 #include <kio/passdlg.h>
00037 #include <kwallet.h>
00038 
00039 #include "config.h"
00040 #ifdef Q_WS_X11
00041 #include <X11/X.h>
00042 #include <X11/Xlib.h>
00043 #endif
00044 
00045 extern "C" {
00046     KDE_EXPORT KDEDModule *create_kpasswdserver(const QCString &name)
00047     {
00048        return new KPasswdServer(name);
00049     }
00050 }
00051 
00052 int
00053 KPasswdServer::AuthInfoList::compareItems(QPtrCollection::Item n1, QPtrCollection::Item n2)
00054 {
00055    if (!n1 || !n2)
00056       return 0;
00057 
00058    AuthInfo *i1 = (AuthInfo *) n1;
00059    AuthInfo *i2 = (AuthInfo *) n2;
00060 
00061    int l1 = i1->directory.length();
00062    int l2 = i2->directory.length();
00063 
00064    if (l1 > l2)
00065       return -1;
00066    if (l1 < l2)
00067       return 1;
00068    return 0;
00069 }
00070 
00071 
00072 KPasswdServer::KPasswdServer(const QCString &name)
00073  : KDEDModule(name)
00074 {
00075     m_authDict.setAutoDelete(true);
00076     m_authPending.setAutoDelete(true);
00077     m_seqNr = 0;
00078     m_wallet = 0;
00079     connect(this, SIGNAL(windowUnregistered(long)),
00080             this, SLOT(removeAuthForWindowId(long)));
00081 }
00082 
00083 KPasswdServer::~KPasswdServer()
00084 {
00085     delete m_wallet;
00086 }
00087 
00088 // Helper - returns the wallet key to use for read/store/checking for existence.
00089 static QString makeWalletKey( const QString& key, const QString& realm )
00090 {
00091     return realm.isEmpty() ? key : key + '-' + realm;
00092 }
00093 
00094 // Helper for storeInWallet/readFromWallet
00095 static QString makeMapKey( const char* key, int entryNumber )
00096 {
00097     QString str = QString::fromLatin1( key );
00098     if ( entryNumber > 1 )
00099         str += "-" + QString::number( entryNumber );
00100     return str;
00101 }
00102 
00103 static bool storeInWallet( KWallet::Wallet* wallet, const QString& key, const KIO::AuthInfo &info )
00104 {
00105     if ( !wallet->hasFolder( KWallet::Wallet::PasswordFolder() ) )
00106         if ( !wallet->createFolder( KWallet::Wallet::PasswordFolder() ) )
00107             return false;
00108     wallet->setFolder( KWallet::Wallet::PasswordFolder() );
00109     // Before saving, check if there's already an entry with this login.
00110     // If so, replace it (with the new password). Otherwise, add a new entry.
00111     typedef QMap<QString,QString> Map;
00112     int entryNumber = 1;
00113     Map map;
00114     QString walletKey = makeWalletKey( key, info.realmValue );
00115     kdDebug(130) << "storeInWallet: walletKey=" << walletKey << "  reading existing map" << endl;
00116     if ( wallet->readMap( walletKey, map ) == 0 ) {
00117         Map::ConstIterator end = map.end();
00118         Map::ConstIterator it = map.find( "login" );
00119         while ( it != end ) {
00120             if ( it.data() == info.username ) {
00121                 break; // OK, overwrite this entry
00122             }
00123             it = map.find( QString( "login-" ) + QString::number( ++entryNumber ) );
00124         }
00125         // If no entry was found, create a new entry - entryNumber is set already.
00126     }
00127     const QString loginKey = makeMapKey( "login", entryNumber );
00128     const QString passwordKey = makeMapKey( "password", entryNumber );
00129     kdDebug(130) << "storeInWallet: writing to " << loginKey << "," << passwordKey << endl;
00130     // note the overwrite=true by default
00131     map.insert( loginKey, info.username );
00132     map.insert( passwordKey, info.password );
00133     wallet->writeMap( walletKey, map );
00134     return true;
00135 }
00136 
00137 
00138 static bool readFromWallet( KWallet::Wallet* wallet, const QString& key, const QString& realm, QString& username, QString& password, bool userReadOnly, QMap<QString,QString>& knownLogins )
00139 {
00140     //kdDebug(130) << "readFromWallet: key=" << key << " username=" << username << " password=" /*<< password*/ << " userReadOnly=" << userReadOnly << " realm=" << realm << endl;
00141     if ( wallet->hasFolder( KWallet::Wallet::PasswordFolder() ) )
00142     {
00143         wallet->setFolder( KWallet::Wallet::PasswordFolder() );
00144 
00145         QMap<QString,QString> map;
00146         if ( wallet->readMap( makeWalletKey( key, realm ), map ) == 0 )
00147         {
00148             typedef QMap<QString,QString> Map;
00149             int entryNumber = 1;
00150             Map::ConstIterator end = map.end();
00151             Map::ConstIterator it = map.find( "login" );
00152             while ( it != end ) {
00153                 //kdDebug(130) << "readFromWallet: found " << it.key() << "=" << it.data() << endl;
00154                 Map::ConstIterator pwdIter = map.find( makeMapKey( "password", entryNumber ) );
00155                 if ( pwdIter != end ) {
00156                     if ( it.data() == username )
00157                         password = pwdIter.data();
00158                     knownLogins.insert( it.data(), pwdIter.data() );
00159                 }
00160 
00161                 it = map.find( QString( "login-" ) + QString::number( ++entryNumber ) );
00162             }
00163             //kdDebug(130) << knownLogins.count() << " known logins" << endl;
00164 
00165             if ( !userReadOnly && !knownLogins.isEmpty() && username.isEmpty() ) {
00166                 // Pick one, any one...
00167                 username = knownLogins.begin().key();
00168                 password = knownLogins.begin().data();
00169                 //kdDebug(130) << "readFromWallet: picked the first one : " << username << endl;
00170             }
00171 
00172             return true;
00173         }
00174     }
00175     return false;
00176 }
00177 
00178 KIO::AuthInfo
00179 KPasswdServer::checkAuthInfo(KIO::AuthInfo info, long windowId)
00180 {
00181     kdDebug(130) << "KPasswdServer::checkAuthInfo: User= " << info.username
00182               << ", WindowId = " << windowId << endl;
00183 
00184     QString key = createCacheKey(info);
00185 
00186     Request *request = m_authPending.first();
00187     QString path2 = info.url.directory(false, false);
00188     for(; request; request = m_authPending.next())
00189     {
00190        if (request->key != key)
00191            continue;
00192 
00193        if (info.verifyPath)
00194        {
00195           QString path1 = request->info.url.directory(false, false);
00196           if (!path2.startsWith(path1))
00197              continue;
00198        }
00199 
00200        request = new Request;
00201        request->client = callingDcopClient();
00202        request->transaction = request->client->beginTransaction();
00203        request->key = key;
00204        request->info = info;
00205        m_authWait.append(request);
00206        return info;
00207     }
00208 
00209     const AuthInfo *result = findAuthInfoItem(key, info);
00210     if (!result || result->isCanceled)
00211     {
00212        if (!result &&
00213            (info.username.isEmpty() || info.password.isEmpty()) &&
00214            !KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::NetworkWallet(),
00215                                              KWallet::Wallet::PasswordFolder(), makeWalletKey(key, info.realmValue)))
00216        {
00217           QMap<QString, QString> knownLogins;
00218           if (openWallet(windowId)) {
00219               if (readFromWallet(m_wallet, key, info.realmValue, info.username, info.password,
00220                              info.readOnly, knownLogins))
00221           {
00222               info.setModified(true);
00223               return info;
00224           }
00225       }
00226        }
00227 
00228        info.setModified(false);
00229        return info;
00230     }
00231 
00232     updateAuthExpire(key, result, windowId, false);
00233 
00234     return copyAuthInfo(result);
00235 }
00236 
00237 KIO::AuthInfo
00238 KPasswdServer::queryAuthInfo(KIO::AuthInfo info, QString errorMsg, long windowId, long seqNr)
00239 {
00240     kdDebug(130) << "KPasswdServer::queryAuthInfo: User= " << info.username
00241               << ", Message= " << info.prompt << ", WindowId = " << windowId << endl;
00242     if ( !info.password.isEmpty() ) // should we really allow the caller to pre-fill the password?
00243         kdDebug(130) <<  "password was set by caller" << endl;
00244 
00245     QString key = createCacheKey(info);
00246     Request *request = new Request;
00247     request->client = callingDcopClient();
00248     request->transaction = request->client->beginTransaction();
00249     request->key = key;
00250     request->info = info;
00251     request->windowId = windowId;
00252     request->seqNr = seqNr;
00253     if (errorMsg == "<NoAuthPrompt>")
00254     {
00255        request->errorMsg = QString::null;
00256        request->prompt = false;
00257     }
00258     else
00259     {
00260        request->errorMsg = errorMsg;
00261        request->prompt = true;
00262     }
00263     m_authPending.append(request);
00264 
00265     if (m_authPending.count() == 1)
00266        QTimer::singleShot(0, this, SLOT(processRequest()));
00267 
00268     return info;
00269 }
00270 
00271 void
00272 KPasswdServer::addAuthInfo(KIO::AuthInfo info, long windowId)
00273 {
00274     kdDebug(130) << "KPasswdServer::addAuthInfo: User= " << info.username
00275               << ", RealmValue= " << info.realmValue << ", WindowId = " << windowId << endl;
00276     QString key = createCacheKey(info);
00277 
00278     m_seqNr++;
00279 
00280     addAuthInfoItem(key, info, windowId, m_seqNr, false);
00281 }
00282 
00283 bool
00284 KPasswdServer::openWallet( WId windowId )
00285 {
00286     if ( m_wallet && !m_wallet->isOpen() ) { // forced closed
00287         delete m_wallet;
00288         m_wallet = 0;
00289     }
00290     if ( !m_wallet )
00291         m_wallet = KWallet::Wallet::openWallet(
00292             KWallet::Wallet::NetworkWallet(), windowId );
00293     return m_wallet != 0;
00294 }
00295 
00296 void
00297 KPasswdServer::processRequest()
00298 {
00299     Request *request = m_authPending.first();
00300     if (!request)
00301        return;
00302 
00303     KIO::AuthInfo &info = request->info;
00304 
00305     kdDebug(130) << "KPasswdServer::processRequest: User= " << info.username
00306               << ", Message= " << info.prompt << endl;
00307     const AuthInfo *result = findAuthInfoItem(request->key, request->info);
00308 
00309     if (result && (request->seqNr < result->seqNr))
00310     {
00311         kdDebug(130) << "KPasswdServer::processRequest: auto retry!" << endl;
00312         if (result->isCanceled)
00313         {
00314            info.setModified(false);
00315         }
00316         else
00317         {
00318            updateAuthExpire(request->key, result, request->windowId, false);
00319            info = copyAuthInfo(result);
00320         }
00321     }
00322     else
00323     {
00324         m_seqNr++;
00325         bool askPw = request->prompt;
00326         if (result && !info.username.isEmpty() &&
00327             !request->errorMsg.isEmpty())
00328         {
00329            QString prompt = request->errorMsg;
00330            prompt += i18n("  Do you want to retry?");
00331            int dlgResult = KMessageBox::warningContinueCancel(0, prompt,
00332                            i18n("Authentication"), i18n("Retry"));
00333            if (dlgResult != KMessageBox::Continue)
00334               askPw = false;
00335         }
00336 
00337         int dlgResult = QDialog::Rejected;
00338         if (askPw)
00339         {
00340             QString username = info.username;
00341             QString password = info.password;
00342             bool hasWalletData = false;
00343             QMap<QString, QString> knownLogins;
00344 
00345             if ( ( username.isEmpty() || password.isEmpty() )
00346                 && !KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::NetworkWallet(), KWallet::Wallet::PasswordFolder(), makeWalletKey( request->key, info.realmValue )) )
00347             {
00348                 // no login+pass provided, check if kwallet has one
00349                 if ( openWallet( request->windowId ) )
00350                     hasWalletData = readFromWallet( m_wallet, request->key, info.realmValue, username, password, info.readOnly, knownLogins );
00351             }
00352 
00353             KIO::PasswordDialog dlg( info.prompt, username, info.keepPassword );
00354             if (info.caption.isEmpty())
00355                dlg.setPlainCaption( i18n("Authorization Dialog") );
00356             else
00357                dlg.setPlainCaption( info.caption );
00358 
00359             if ( !info.comment.isEmpty() )
00360                dlg.addCommentLine( info.commentLabel, info.comment );
00361 
00362             if ( !password.isEmpty() )
00363                dlg.setPassword( password );
00364 
00365             if (info.readOnly)
00366                dlg.setUserReadOnly( true );
00367             else
00368                dlg.setKnownLogins( knownLogins );
00369 
00370             if (hasWalletData)
00371                 dlg.setKeepPassword( true );
00372 
00373 #ifdef Q_WS_X11
00374             XSetTransientForHint( qt_xdisplay(), dlg.winId(), request->windowId);
00375 #endif
00376 
00377             dlgResult = dlg.exec();
00378 
00379             if (dlgResult == QDialog::Accepted)
00380             {
00381                info.username = dlg.username();
00382                info.password = dlg.password();
00383                info.keepPassword = dlg.keepPassword();
00384 
00385                // When the user checks "keep password", that means:
00386                // * if the wallet is enabled, store it there for long-term, and in kpasswdserver
00387                // only for the duration of the window (#92928)
00388                // * otherwise store in kpasswdserver for the duration of the KDE session.
00389                if ( info.keepPassword ) {
00390                    if ( openWallet( request->windowId ) ) {
00391                        if ( storeInWallet( m_wallet, request->key, info ) )
00392                            // password is in wallet, don't keep it in memory after window is closed
00393                            info.keepPassword = false;
00394                    }
00395                }
00396             }
00397         }
00398         if ( dlgResult != QDialog::Accepted )
00399         {
00400             addAuthInfoItem(request->key, info, 0, m_seqNr, true);
00401             info.setModified( false );
00402         }
00403         else
00404         {
00405             addAuthInfoItem(request->key, info, request->windowId, m_seqNr, false);
00406             info.setModified( true );
00407         }
00408     }
00409 
00410     QCString replyType;
00411     QByteArray replyData;
00412 
00413     QDataStream stream2(replyData, IO_WriteOnly);
00414     stream2 << info << m_seqNr;
00415     replyType = "KIO::AuthInfo";
00416     request->client->endTransaction( request->transaction,
00417                                      replyType, replyData);
00418 
00419     m_authPending.remove((unsigned int) 0);
00420 
00421     // Check all requests in the wait queue.
00422     for(Request *waitRequest = m_authWait.first();
00423         waitRequest; )
00424     {
00425        bool keepQueued = false;
00426        QString key = waitRequest->key;
00427 
00428        request = m_authPending.first();
00429        QString path2 = waitRequest->info.url.directory(false, false);
00430        for(; request; request = m_authPending.next())
00431        {
00432            if (request->key != key)
00433                continue;
00434 
00435            if (info.verifyPath)
00436            {
00437                QString path1 = request->info.url.directory(false, false);
00438                if (!path2.startsWith(path1))
00439                    continue;
00440            }
00441 
00442            keepQueued = true;
00443            break;
00444        }
00445        if (keepQueued)
00446        {
00447            waitRequest = m_authWait.next();
00448        }
00449        else
00450        {
00451            const AuthInfo *result = findAuthInfoItem(waitRequest->key, waitRequest->info);
00452 
00453            QCString replyType;
00454            QByteArray replyData;
00455 
00456            QDataStream stream2(replyData, IO_WriteOnly);
00457 
00458            if (!result || result->isCanceled)
00459            {
00460                waitRequest->info.setModified(false);
00461                stream2 << waitRequest->info;
00462            }
00463            else
00464            {
00465                updateAuthExpire(waitRequest->key, result, waitRequest->windowId, false);
00466                KIO::AuthInfo info = copyAuthInfo(result);
00467                stream2 << info;
00468            }
00469 
00470            replyType = "KIO::AuthInfo";
00471            waitRequest->client->endTransaction( waitRequest->transaction,
00472                                                 replyType, replyData);
00473 
00474            m_authWait.remove();
00475            waitRequest = m_authWait.current();
00476        }
00477     }
00478 
00479     if (m_authPending.count())
00480        QTimer::singleShot(0, this, SLOT(processRequest()));
00481 
00482 }
00483 
00484 QString KPasswdServer::createCacheKey( const KIO::AuthInfo &info )
00485 {
00486     if( !info.url.isValid() ) {
00487         // Note that a null key will break findAuthInfoItem later on...
00488         kdWarning(130) << "createCacheKey: invalid URL " << info.url << endl;
00489         return QString::null;
00490     }
00491 
00492     // Generate the basic key sequence.
00493     QString key = info.url.protocol();
00494     key += '-';
00495     if (!info.url.user().isEmpty())
00496     {
00497        key += info.url.user();
00498        key += "@";
00499     }
00500     key += info.url.host();
00501     int port = info.url.port();
00502     if( port )
00503     {
00504       key += ':';
00505       key += QString::number(port);
00506     }
00507 
00508     return key;
00509 }
00510 
00511 KIO::AuthInfo
00512 KPasswdServer::copyAuthInfo(const AuthInfo *i)
00513 {
00514     KIO::AuthInfo result;
00515     result.url = i->url;
00516     result.username = i->username;
00517     result.password = i->password;
00518     result.realmValue = i->realmValue;
00519     result.digestInfo = i->digestInfo;
00520     result.setModified(true);
00521 
00522     return result;
00523 }
00524 
00525 const KPasswdServer::AuthInfo *
00526 KPasswdServer::findAuthInfoItem(const QString &key, const KIO::AuthInfo &info)
00527 {
00528    AuthInfoList *authList = m_authDict.find(key);
00529    if (!authList)
00530       return 0;
00531 
00532    QString path2 = info.url.directory(false, false);
00533    for(AuthInfo *current = authList->first();
00534        current; )
00535    {
00536        if ((current->expire == AuthInfo::expTime) &&
00537           (difftime(time(0), current->expireTime) > 0))
00538        {
00539           authList->remove();
00540           current = authList->current();
00541           continue;
00542        }
00543 
00544        if (info.verifyPath)
00545        {
00546           QString path1 = current->directory;
00547           if (path2.startsWith(path1) &&
00548               (info.username.isEmpty() || info.username == current->username))
00549              return current;
00550        }
00551        else
00552        {
00553           if (current->realmValue == info.realmValue &&
00554               (info.username.isEmpty() || info.username == current->username))
00555              return current; // TODO: Update directory info,
00556        }
00557 
00558        current = authList->next();
00559    }
00560    return 0;
00561 }
00562 
00563 void
00564 KPasswdServer::removeAuthInfoItem(const QString &key, const KIO::AuthInfo &info)
00565 {
00566    AuthInfoList *authList = m_authDict.find(key);
00567    if (!authList)
00568       return;
00569 
00570    for(AuthInfo *current = authList->first();
00571        current; )
00572    {
00573        if (current->realmValue == info.realmValue)
00574        {
00575           authList->remove();
00576           current = authList->current();
00577        }
00578        else
00579        {
00580           current = authList->next();
00581        }
00582    }
00583    if (authList->isEmpty())
00584    {
00585        m_authDict.remove(key);
00586    }
00587 }
00588 
00589 
00590 void
00591 KPasswdServer::addAuthInfoItem(const QString &key, const KIO::AuthInfo &info, long windowId, long seqNr, bool canceled)
00592 {
00593    AuthInfoList *authList = m_authDict.find(key);
00594    if (!authList)
00595    {
00596       authList = new AuthInfoList;
00597       m_authDict.insert(key, authList);
00598    }
00599    AuthInfo *current = authList->first();
00600    for(; current; current = authList->next())
00601    {
00602        if (current->realmValue == info.realmValue)
00603        {
00604           authList->take();
00605           break;
00606        }
00607    }
00608 
00609    if (!current)
00610    {
00611       current = new AuthInfo;
00612       current->expire = AuthInfo::expTime;
00613       kdDebug(130) << "Creating AuthInfo" << endl;
00614    }
00615    else
00616    {
00617       kdDebug(130) << "Updating AuthInfo" << endl;
00618    }
00619 
00620    current->url = info.url;
00621    current->directory = info.url.directory(false, false);
00622    current->username = info.username;
00623    current->password = info.password;
00624    current->realmValue = info.realmValue;
00625    current->digestInfo = info.digestInfo;
00626    current->seqNr = seqNr;
00627    current->isCanceled = canceled;
00628 
00629    updateAuthExpire(key, current, windowId, info.keepPassword && !canceled);
00630 
00631    // Insert into list, keep the list sorted "longest path" first.
00632    authList->inSort(current);
00633 }
00634 
00635 void
00636 KPasswdServer::updateAuthExpire(const QString &key, const AuthInfo *auth, long windowId, bool keep)
00637 {
00638    AuthInfo *current = const_cast<AuthInfo *>(auth);
00639    if (keep)
00640    {
00641       current->expire = AuthInfo::expNever;
00642    }
00643    else if (windowId && (current->expire != AuthInfo::expNever))
00644    {
00645       current->expire = AuthInfo::expWindowClose;
00646       if (!current->windowList.contains(windowId))
00647          current->windowList.append(windowId);
00648    }
00649    else if (current->expire == AuthInfo::expTime)
00650    {
00651       current->expireTime = time(0)+10;
00652    }
00653 
00654    // Update mWindowIdList
00655    if (windowId)
00656    {
00657       QStringList *keysChanged = mWindowIdList.find(windowId);
00658       if (!keysChanged)
00659       {
00660          keysChanged = new QStringList;
00661          mWindowIdList.insert(windowId, keysChanged);
00662       }
00663       if (!keysChanged->contains(key))
00664          keysChanged->append(key);
00665    }
00666 }
00667 
00668 void
00669 KPasswdServer::removeAuthForWindowId(long windowId)
00670 {
00671    QStringList *keysChanged = mWindowIdList.find(windowId);
00672    if (!keysChanged) return;
00673 
00674    for(QStringList::ConstIterator it = keysChanged->begin();
00675        it != keysChanged->end(); ++it)
00676    {
00677       QString key = *it;
00678       AuthInfoList *authList = m_authDict.find(key);
00679       if (!authList)
00680          continue;
00681 
00682       AuthInfo *current = authList->first();
00683       for(; current; )
00684       {
00685         if (current->expire == AuthInfo::expWindowClose)
00686         {
00687            if (current->windowList.remove(windowId) && current->windowList.isEmpty())
00688            {
00689               authList->remove();
00690               current = authList->current();
00691               continue;
00692            }
00693         }
00694         current = authList->next();
00695       }
00696    }
00697 }
00698 
00699 #include "kpasswdserver.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys