/*************************************************************************** accountsmanager.cpp - description ------------------- begin : Sat May 3 2008 copyright : (C) 2008 by Valerio Pilo email : valerio@kmess.org ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "accountsmanager.h" #include "utils/kmessconfig.h" #include "currentaccount.h" #include "kmess.h" #include "kmessapplication.h" #include "kmessdebug.h" #include <QDir> #include <KConfig> #include <KLocale> #include <KMessageBox> #ifdef KMESSDEBUG_ACCOUNTSMANAGER #define KMESSDEBUG_ACCOUNTSMANAGER_KWALLET #endif // Initialize the singleton instance to zero AccountsManager* AccountsManager::instance_(0); /** * Constructor */ 00047 AccountsManager::AccountsManager() : doPasswordRead_( false ), doPasswordWrite_( false ), passwordManager_( 0 ) { // Load the saved accounts from disk and create them readProperties(); } // Destructor AccountsManager::~AccountsManager() { // Delete the accounts qDeleteAll( accounts_ ); accounts_.clear(); // Delete the password manager instance delete passwordManager_; #ifdef KMESSDEBUG_ACCOUNTSMANAGER kDebug() << "DESTROYED."; #endif } // Add a new account to the list void AccountsManager::addAccount( Account *account ) { // Append account accounts_.append( account ); #ifdef KMESSDEBUG_ACCOUNTSMANAGER kDebug() << "New account created:" << account->getHandle() << "."; #endif emit accountAdded( account ); } // An account's settings have been changed void AccountsManager::changeAccount( Account *account, QString oldHandle, QString oldFriendlyName ) { if( KMESS_NULL( account ) ) return; QString newHandle( account->getHandle() ); // We're saving settings for a new account if( getAccountByHandle( oldHandle ) == 0 ) { #ifdef KMESSDEBUG_ACCOUNTSMANAGER kDebug() << "Saving settings for new account" << newHandle; #endif // Add the account to the account list. addAccount( account ); } #ifdef KMESSDEBUG_ACCOUNTSMANAGER else { kDebug() << "Saving settings for " << oldHandle; } #endif // If the account is set to use autologin, make sure that all other accounts do not if( ! account->isDeleted() && account->getUseAutologin() ) { foreach( Account *otherAccount, accounts_ ) { if( otherAccount != account ) { otherAccount->setUseAutologin( false ); } } } // Rename the account's directory if the handle has changed if( oldHandle != newHandle ) { #ifdef KMESSDEBUG_ACCOUNTSMANAGER kDebug() << "Moving account dir from" << oldHandle << "to" << newHandle; #endif QString accountsPath( KMessConfig::instance()->getAccountsDirectory() ); QDir accountsDir( accountsPath ); if( accountsDir.exists( newHandle ) ) { kWarning() << "Destination account directory" << newHandle << "already exists!"; } if( accountsDir.rename( oldHandle, newHandle ) ) { kWarning() << "Unable to move account directory" << oldHandle << "to" << newHandle << "!"; } } // Commit configuration changes to disk, now // Makes sure the settings are not lost if KMess is terminated or crashes. account->saveProperties(); // Emit a signal so that the UI can be updated emit accountChanged( account, oldHandle, oldFriendlyName ); } // Verify is there is a saved account with the specified handle bool AccountsManager::contains( Account *account ) const { if( account == CurrentAccount::instance() ) { kWarning() << "Called contains() on the current account!"; return true; } return accounts_.contains( account ); } // Delete the given account void AccountsManager::deleteAccount( Account *account ) { if( KMESS_NULL(account) ) return; emit accountDeleted( account ); QString handle( account->getHandle() ); KMessApplication *kmessApp = static_cast<KMessApplication*>( kapp ); if( kmessApp->getContactListWindow()->isConnected() && CurrentAccount::instance()->getHandle() == handle ) { #ifdef KMESSDEBUG_ACCOUNTSMANAGER kDebug() << "Cannot delete current account:" << handle; #endif return; } #ifdef KMESSDEBUG_ACCOUNTSMANAGER kDebug() << "Deleting account for " << handle << "."; #endif // Remove the account from the list of accounts if( accounts_.removeAll( account ) == 0 ) { kWarning() << "Account" << handle << "not found in collection!"; } // Delete the account directory KMessConfig::instance()->destroyConfigDir( KMessConfig::instance()->getAccountsDirectory() + "/" + handle ); // Delete the account's contents account->setDeleted(); account->deleteLater(); } /** * Delete the instance of the accounts manager */ 00214 void AccountsManager::destroy() { delete instance_; instance_ = 0; } // Return the account for a given handle Account *AccountsManager::getAccountByHandle( const QString &handle ) { if( handle.isEmpty() ) { return 0; } foreach( Account *account, accounts_ ) { if( account->getHandle() == handle ) { return account; } } return 0; } // Get the list of all saved accounts const QList<Account *> AccountsManager::getAccounts() const { return accounts_; } // Initialize the KWallet password manager void AccountsManager::initializePasswordManager( bool block ) { // Don't initialize if it is already if( passwordManager_ != 0 ) { #ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET kDebug() << "KWallet is already initialized."; #endif return; } // Obtain the window ID of the main KMess window KMessApplication *kmessApp = static_cast<KMessApplication*>( kapp ); WId wId = kmessApp->getContactListWindow()->window()->winId(); if( block ) { // Open KWallet (synchronously - blocks KMess until the password is given) passwordManager_ = KWallet::Wallet::openWallet( KWallet::Wallet::LocalWallet(), wId, KWallet::Wallet::Synchronous ); // Call the slot manually slotWalletOpen( true ); return; } else { // Open KWallet (asynchronously - the slot will be called when - and if - the wallet will be open) passwordManager_ = KWallet::Wallet::openWallet( KWallet::Wallet::LocalWallet(), wId, KWallet::Wallet::Asynchronous ); if( passwordManager_ ) { connect( passwordManager_, SIGNAL( walletOpened(bool) ), this, SLOT ( slotWalletOpen(bool) ) ); } else { // If the wallet service is not available, initialize the plain text password storage slotWalletOpen( false ); return; } } } // Queue a read for all passwords void AccountsManager::readPasswords( bool block ) { doPasswordRead_ = true; initializePasswordManager( block ); } // Save passwords for all accounts (only actually saves when there are accounts to be updated!) void AccountsManager::savePasswords( bool block ) { bool passwordsToWrite = false; // check if there are passwords to be written foreach( const Account *account, accounts_ ) { if( account->getSavedPassword() != account->getPassword() ) { passwordsToWrite = true; break; } } if( passwordsToWrite ) { #ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET kDebug() << "There were passwords to be updated, scheduling an update."; #endif doPasswordWrite_ = true; initializePasswordManager( block ); } #ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET else { kDebug() << "There were no passwords to be updated, skipping save request."; } #endif } // Read the passwords from the just opened wallet void AccountsManager::slotWalletOpen( bool success ) { // if the wallet couldn't be opened, delete it if( ! success || ( passwordManager_ != 0 && ! passwordManager_->isOpen() ) ) { #ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET kDebug() << "Wallet opening failed, falling back to plain text passwords."; #endif delete passwordManager_; passwordManager_ = 0; } // initialize the wallet if( passwordManager_ ) { setupPasswordManager(); } // Read the passwords if( doPasswordRead_ ) { if( passwordManager_ == 0 ) { KConfigGroup passwordGroup( KMessConfig::instance()->getGlobalConfig( "Passwords" ) ); const QStringList& passwordAccountList( passwordGroup.keyList() ); #ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET kDebug() << "Reading passwords from plain-text storage instead of KWallet!"; #endif // Read the passwords from the KMess config file foreach( const QString &accountHandle, passwordAccountList ) { Account *account = getAccountByHandle( accountHandle ); // Skip passwords for invalid and deleted accounts, and remove them too if( account == 0 ) { passwordGroup.deleteEntry( accountHandle ); continue; } #ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET kDebug() << "Password have been read for account: " << accountHandle; #endif QString password = passwordGroup.readEntry( accountHandle, QString() ); account->setPassword( password ); account->setSavedPassword( password ); } } else // passwordManager_ != 0 { QString password; // Read the password for each account foreach( Account *account, accounts_ ) { if( passwordManager_->readPassword( account->getHandle(), password ) != 0 || password.isEmpty() ) { continue; } #ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET kDebug() << "Password have been read for account: " << account->getHandle(); #endif account->setPassword( password ); account->setSavedPassword( password ); } } emit passwordsReady(); } // Write the passwords if( doPasswordWrite_ ) { if( passwordManager_ == 0 ) { kWarning() << "KWallet is not available: saving passwords in plain text."; KConfigGroup passwordGroup( KMessConfig::instance()->getGlobalConfig( "Passwords" ) ); // Store the password for each account foreach( Account *account, accounts_ ) { const QString& handle( account->getHandle() ); // If we have already the entry, check if the user want still save the password.. if( passwordGroup.hasKey( handle ) ) { // If the password should not be saved, then delete any already saved password if( account->getSavePassword() == false || account->getPassword().isEmpty() ) { #ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET kDebug() << "Password for account" << handle << "removed."; #endif account->setSavedPassword( "" ); passwordGroup.deleteEntry( handle ); continue; } } // Save the password account->setSavedPassword( account->getPassword() ); passwordGroup.writeEntry( handle, account->getPassword() ); #ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET kDebug() << "Password for account" << handle << "saved."; #endif } } else // passwordManager_ != 0 { // Store the password for each account foreach( Account *account, accounts_ ) { const QString& handle( account->getHandle() ); bool savePassword = ( account->getSavePassword() && ! account->getPassword().isEmpty() ); // If the password should not be saved, then delete any already saved password if( savePassword == false && passwordManager_->hasEntry( handle ) ) { #ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET kDebug() << "Removing account" << handle << "from wallet"; #endif account->setSavedPassword( "" ); passwordManager_->removeEntry( handle ); continue; } // Update the password if we should else if( savePassword == true && account->getSavedPassword() != account->getPassword() && passwordManager_->writePassword( handle, account->getPassword() ) != 0 ) { #ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET kDebug() << "Couldn't store password for account" << handle; #endif account->setSavedPassword( "" ); continue; } account->setSavedPassword( account->getPassword() ); } // Save the passwords to disk now passwordManager_->sync(); } } doPasswordRead_ = false; doPasswordWrite_ = false; // Clean up the password manager, it's not needed anymore // (not cleaning it up could result in weird issues if the program crashes or so) delete passwordManager_; passwordManager_ = 0; } // Set up the passwordManager_ (change to the KMess folder, import plaintext passwords, etc) void AccountsManager::setupPasswordManager() { #ifdef KMESSTEST KMESS_ASSERT( passwordManager_->isOpen() ); #endif // Look for an existing kmess folder and create one if needed if( ! passwordManager_->hasFolder( "KMess" ) ) { passwordManager_->createFolder( "KMess" ); } // Set the current folder for password storing, it should't fail if( ! passwordManager_->setFolder( "KMess" ) ) { #ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET kWarning() << "Setting KWallet folder failed!"; #endif delete passwordManager_; passwordManager_ = 0; return; } // Wallet is open: verify if the user has any useable plaintext password set. // If so, ask if they should be deleted or copied to the wallet :) KConfigGroup passwordGroup( KMessConfig::instance()->getGlobalConfig( "Passwords" ) ); const QStringList& passwordAccountList( passwordGroup.keyList() ); if( ! passwordAccountList.isEmpty() ) { QStringList validAccounts; foreach( const QString &accountHandle, passwordAccountList ) { Account *account = getAccountByHandle( accountHandle ); // Skip passwords for invalid and deleted accounts, and remove them too if( account == 0 ) { passwordGroup.deleteEntry( accountHandle ); continue; } validAccounts << accountHandle; } // There are some accounts passwords. Ask the user if( validAccounts.count() > 0 ) { kWarning() << "Insecure account passwords found for accounts: " << validAccounts; int res = KMessageBox::questionYesNoCancel( 0, i18n( "<html>Some insecurely stored account passwords have been found.<br />" "Do you want to import the passwords to the KDE Wallet " "named '%1', keep the insecurely stored passwords, or delete " "them permanently?<br /><br />" "<i>Note: it is not recommended to keep insecurely stored passwords " "if the KDE Wallet is available, because your passwords " "will be easily readable in the KMess configuration files.</i></html>", KWallet::Wallet::LocalWallet() ), i18nc( "Dialog box caption", "Secure Password Storage" ), KGuiItem( i18nc( "Dialog button: Import passwords to a KDE wallet", "Import" ), "document-import" ), // Yes option KGuiItem( i18nc( "Dialog button: Delete insecurely stored passwords", "Delete" ), "edit-delete-shred" ), // No option KGuiItem( i18nc( "Dialog button: Keep insecurely stored passwords", "Keep" ), "mail-mark-notjunk" ), // Cancel option "insecurePasswordsDetected" ); switch( res ) { case KMessageBox::Yes: // Import passwords to the wallet, and remove the plaintext ones #ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET kDebug() << "Importing plaintext passwords to the wallet."; #endif foreach( const QString &accountHandle, passwordAccountList ) { Account *account = getAccountByHandle( accountHandle ); if( account != 0 ) { passwordManager_->writePassword( accountHandle, passwordGroup.readEntry( accountHandle, QString() ) ); passwordGroup.deleteEntry( accountHandle ); #ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET kDebug() << "Imported password for account:" << accountHandle; #endif } } break; case KMessageBox::No: // Only delete the plain text passwords #ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET kDebug() << "Deleting plain text account passwords."; #endif foreach( const QString &accountHandle, passwordAccountList ) { #ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET kDebug() << "Deleting plaintext password for account:" << accountHandle; #endif passwordGroup.deleteEntry( accountHandle ); } break; case KMessageBox::Cancel: // Do nothing (useful if you wish to postpone the decision) #ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET kDebug() << "No action against plaintext passwords will be taken now."; #endif break; } } } #ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET kDebug() << "KWallet successfully opened."; #endif } /** * Return a singleton instance of the accounts manager */ 00636 AccountsManager* AccountsManager::instance() { // If the instance is null, create a new accounts manager and return that. if ( instance_ == 0 ) { instance_ = new AccountsManager(); } return instance_; } /** * Load the accounts from disk */ 00651 void AccountsManager::readProperties() { // Obtain the list of account directories in the KMess config directory QStringList accountList( KMessConfig::instance()->getAccountsList() ); // Add each one of the saved accounts to the internal list foreach( const QString &handle, accountList ) { if( Account::isValidEmail( handle ) ) { Account *account = new Account(); account->readProperties( handle ); addAccount( account ); } } } // Show the settings dialog for a given account void AccountsManager::showAccountSettings( Account *account, QWidget *parentWindow, AccountSettingsDialog::Page startingPage ) { // No account has been specified, create one if( ! account ) { #ifdef KMESSDEBUG_ACCOUNTSMANAGER kDebug() << "Opening settings for a new account"; #endif // Create an account to store the profile information account = new Account(); // Assume the user *does* want to save it's settings, // otherwise he/she would have used the main login dialog. account->setGuestAccount( false ); } #ifdef KMESSDEBUG_ACCOUNTSMANAGER else { kDebug() << "Opening settings for " << account->getHandle(); } #endif CurrentAccount *currentAccount = CurrentAccount::instance(); KMess *kmess = static_cast<KMessApplication*>( kapp )->getContactListWindow(); bool isCurrentAccount = ( account->getHandle() == currentAccount->getHandle() ); bool isConnectedCurrentAccount = isCurrentAccount && ( kmess->isConnected() ); // If we are changing the current account, directly change the CurrentAccount object // instead of the stored account object if( isConnectedCurrentAccount ) { account = currentAccount; } // Show the settings dialog AccountSettingsDialog *accountSettingsDialog = AccountSettingsDialog::instance( parentWindow ); connect( accountSettingsDialog, SIGNAL( deleteAccount(Account*) ), this, SLOT ( deleteAccount(Account*) ) ); connect( accountSettingsDialog, SIGNAL( changedSettings(Account*,QString,QString) ), this, SLOT ( changeAccount(Account*,QString,QString) ) ); accountSettingsDialog->loadSettings( account, isConnectedCurrentAccount, startingPage ); accountSettingsDialog->show(); accountSettingsDialog->raise(); } #include "accountsmanager.moc"