00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020 #include "transportmanager.h"
00021 #include "addtransportdialog.h"
00022 #include "resourcesendjob_p.h"
00023 #include "mailtransport_defs.h"
00024 #include "sendmailconfigwidget.h"
00025 #include "sendmailjob.h"
00026 #include "smtpconfigwidget.h"
00027 #include "smtpjob.h"
00028 #include "transport.h"
00029 #include "transportconfigwidget.h"
00030 #include "transportjob.h"
00031 #include "transporttype.h"
00032 #include "transporttype_p.h"
00033 #include "transportconfigdialog.h"
00034
00035 #include <QApplication>
00036 #include <QtDBus/QDBusConnection>
00037 #include <QtDBus/QDBusConnectionInterface>
00038 #include <QPointer>
00039 #include <QRegExp>
00040 #include <QStringList>
00041
00042 #include <KConfig>
00043 #include <KConfigGroup>
00044 #include <KDebug>
00045 #include <kemailsettings.h>
00046 #include <KLocale>
00047 #include <KMessageBox>
00048 #include <KRandom>
00049 #include <KUrl>
00050 #include <KWallet/Wallet>
00051
00052 #include <akonadi/agentinstance.h>
00053 #include <akonadi/agentmanager.h>
00054
00055 using namespace MailTransport;
00056 using namespace KWallet;
00057
00058 namespace MailTransport {
00063 class TransportManagerPrivate
00064 {
00065 public:
00066 TransportManagerPrivate( TransportManager *parent )
00067 : q( parent )
00068 {
00069 }
00070
00071 ~TransportManagerPrivate() {
00072 delete config;
00073 qDeleteAll( transports );
00074 }
00075
00076 KConfig *config;
00077 QList<Transport *> transports;
00078 TransportType::List types;
00079 bool myOwnChange;
00080 bool appliedChange;
00081 KWallet::Wallet *wallet;
00082 bool walletOpenFailed;
00083 bool walletAsyncOpen;
00084 int defaultTransportId;
00085 bool isMainInstance;
00086 QList<TransportJob *> walletQueue;
00087 TransportManager *q;
00088
00089 void readConfig();
00090 void writeConfig();
00091 void fillTypes();
00092 int createId() const;
00093 void prepareWallet();
00094 void validateDefault();
00095 void migrateToWallet();
00096
00097
00098 void slotTransportsChanged();
00099 void slotWalletOpened( bool success );
00100 void dbusServiceOwnerChanged( const QString &service,
00101 const QString &oldOwner,
00102 const QString &newOwner );
00103 void agentTypeAdded( const Akonadi::AgentType &atype );
00104 void agentTypeRemoved( const Akonadi::AgentType &atype );
00105 void jobResult( KJob *job );
00106 };
00107
00108 }
00109
00110 class StaticTransportManager : public TransportManager
00111 {
00112 public:
00113 StaticTransportManager() : TransportManager() {}
00114 };
00115
00116 StaticTransportManager *sSelf = 0;
00117
00118 static void destroyStaticTransportManager() {
00119 delete sSelf;
00120 }
00121
00122 TransportManager::TransportManager()
00123 : QObject(), d( new TransportManagerPrivate( this ) )
00124 {
00125 KGlobal::locale()->insertCatalog( QLatin1String( "libmailtransport" ) );
00126 qAddPostRoutine( destroyStaticTransportManager );
00127 d->myOwnChange = false;
00128 d->appliedChange = false;
00129 d->wallet = 0;
00130 d->walletOpenFailed = false;
00131 d->walletAsyncOpen = false;
00132 d->defaultTransportId = -1;
00133 d->config = new KConfig( QLatin1String( "mailtransports" ) );
00134
00135 QDBusConnection::sessionBus().registerObject( DBUS_OBJECT_PATH, this,
00136 QDBusConnection::ExportScriptableSlots |
00137 QDBusConnection::ExportScriptableSignals );
00138
00139 QDBusConnection::sessionBus().connect( QString(), QString(),
00140 DBUS_INTERFACE_NAME, DBUS_CHANGE_SIGNAL,
00141 this, SLOT(slotTransportsChanged()) );
00142
00143 d->isMainInstance =
00144 QDBusConnection::sessionBus().registerService( DBUS_SERVICE_NAME );
00145 connect( QDBusConnection::sessionBus().interface(),
00146 SIGNAL(serviceOwnerChanged(QString,QString,QString)),
00147 SLOT(dbusServiceOwnerChanged(QString,QString,QString)) );
00148
00149 d->fillTypes();
00150 }
00151
00152 TransportManager::~TransportManager()
00153 {
00154 qRemovePostRoutine( destroyStaticTransportManager );
00155 delete d;
00156 }
00157
00158 TransportManager *TransportManager::self()
00159 {
00160 if ( !sSelf ) {
00161 sSelf = new StaticTransportManager;
00162 sSelf->d->readConfig();
00163 }
00164 return sSelf;
00165 }
00166
00167 Transport *TransportManager::transportById( int id, bool def ) const
00168 {
00169 foreach ( Transport *t, d->transports ) {
00170 if ( t->id() == id ) {
00171 return t;
00172 }
00173 }
00174
00175 if ( def || ( id == 0 && d->defaultTransportId != id ) ) {
00176 return transportById( d->defaultTransportId, false );
00177 }
00178 return 0;
00179 }
00180
00181 Transport *TransportManager::transportByName( const QString &name, bool def ) const
00182 {
00183 foreach ( Transport *t, d->transports ) {
00184 if ( t->name() == name ) {
00185 return t;
00186 }
00187 }
00188 if ( def ) {
00189 return transportById( 0, false );
00190 }
00191 return 0;
00192 }
00193
00194 QList< Transport * > TransportManager::transports() const
00195 {
00196 return d->transports;
00197 }
00198
00199 TransportType::List TransportManager::types() const
00200 {
00201 return d->types;
00202 }
00203
00204 Transport *TransportManager::createTransport() const
00205 {
00206 int id = d->createId();
00207 Transport *t = new Transport( QString::number( id ) );
00208 t->setId( id );
00209 return t;
00210 }
00211
00212 void TransportManager::addTransport( Transport *transport )
00213 {
00214 if ( d->transports.contains( transport ) ) {
00215 kDebug() << "Already have this transport.";
00216 return;
00217 }
00218
00219 kDebug() << "Added transport" << transport;
00220 d->transports.append( transport );
00221 d->validateDefault();
00222 emitChangesCommitted();
00223 }
00224
00225 void TransportManager::schedule( TransportJob *job )
00226 {
00227 connect( job, SIGNAL(result(KJob*)), SLOT(jobResult(KJob*)) );
00228
00229
00230 if ( !job->transport()->isComplete() ) {
00231 kDebug() << "job waits for wallet:" << job;
00232 d->walletQueue << job;
00233 loadPasswordsAsync();
00234 return;
00235 }
00236
00237 job->start();
00238 }
00239
00240 void TransportManager::createDefaultTransport()
00241 {
00242 KEMailSettings kes;
00243 Transport *t = createTransport();
00244 t->setName( i18n( "Default Transport" ) );
00245 t->setHost( kes.getSetting( KEMailSettings::OutServer ) );
00246 if ( t->isValid() ) {
00247 t->writeConfig();
00248 addTransport( t );
00249 } else {
00250 kWarning() << "KEMailSettings does not contain a valid transport.";
00251 }
00252 }
00253
00254 bool TransportManager::showTransportCreationDialog( QWidget *parent,
00255 ShowCondition showCondition )
00256 {
00257 if ( showCondition == IfNoTransportExists ) {
00258 if ( !isEmpty() ) {
00259 return true;
00260 }
00261
00262 const int response = KMessageBox::messageBox( parent,
00263 KMessageBox::WarningContinueCancel,
00264 i18n( "You must create an outgoing account before sending." ),
00265 i18n( "Create Account Now?" ),
00266 KGuiItem( i18n( "Create Account Now" ) ) );
00267 if ( response != KMessageBox::Continue ) {
00268 return false;
00269 }
00270 }
00271
00272 QPointer<AddTransportDialog> dialog = new AddTransportDialog( parent );
00273 const bool accepted = ( dialog->exec() == QDialog::Accepted );
00274 delete dialog;
00275 return accepted;
00276 }
00277
00278 bool TransportManager::configureTransport( Transport *transport, QWidget *parent )
00279 {
00280 if( transport->type() == Transport::EnumType::Akonadi ) {
00281 using namespace Akonadi;
00282 AgentInstance instance = AgentManager::self()->instance( transport->host() );
00283 if( !instance.isValid() ) {
00284 kWarning() << "Invalid resource instance" << transport->host();
00285 }
00286 instance.configure( parent );
00287 transport->writeConfig();
00288 return true;
00289 }
00290
00291 QPointer<KDialog> dialog = new KDialog( parent );
00292 TransportConfigWidget *configWidget = 0;
00293 switch( transport->type() ) {
00294 case Transport::EnumType::SMTP:
00295 {
00296 configWidget = new SMTPConfigWidget( transport, dialog );
00297 break;
00298 }
00299 case Transport::EnumType::Sendmail:
00300 {
00301 configWidget = new SendmailConfigWidget( transport, dialog );
00302 break;
00303 }
00304 default:
00305 {
00306 Q_ASSERT( false );
00307 delete dialog;
00308 return false;
00309 }
00310 }
00311 dialog->setMainWidget( configWidget );
00312 dialog->setCaption( i18n( "Configure account" ) );
00313 dialog->setButtons( KDialog::Ok | KDialog::Cancel );
00314 bool okClicked = ( dialog->exec() == QDialog::Accepted );
00315 if( okClicked ) {
00316 configWidget->apply();
00317 }
00318 delete dialog;
00319 return okClicked;
00320 }
00321
00322 TransportJob *TransportManager::createTransportJob( int transportId )
00323 {
00324 Transport *t = transportById( transportId, false );
00325 if ( !t ) {
00326 return 0;
00327 }
00328 t = t->clone();
00329 t->updatePasswordState();
00330 switch ( t->type() ) {
00331 case Transport::EnumType::SMTP:
00332 return new SmtpJob( t, this );
00333 case Transport::EnumType::Sendmail:
00334 return new SendmailJob( t, this );
00335 case Transport::EnumType::Akonadi:
00336 return new ResourceSendJob( t, this );
00337 }
00338 Q_ASSERT( false );
00339 return 0;
00340 }
00341
00342 TransportJob *TransportManager::createTransportJob( const QString &transport )
00343 {
00344 bool ok = false;
00345 Transport *t = 0;
00346
00347 int transportId = transport.toInt( &ok );
00348 if ( ok ) {
00349 t = transportById( transportId );
00350 }
00351
00352 if ( !t ) {
00353 t = transportByName( transport, false );
00354 }
00355
00356 if ( t ) {
00357 return createTransportJob( t->id() );
00358 }
00359
00360 return 0;
00361 }
00362
00363 bool TransportManager::isEmpty() const
00364 {
00365 return d->transports.isEmpty();
00366 }
00367
00368 QList<int> TransportManager::transportIds() const
00369 {
00370 QList<int> rv;
00371 foreach ( Transport *t, d->transports ) {
00372 rv << t->id();
00373 }
00374 return rv;
00375 }
00376
00377 QStringList TransportManager::transportNames() const
00378 {
00379 QStringList rv;
00380 foreach ( Transport *t, d->transports ) {
00381 rv << t->name();
00382 }
00383 return rv;
00384 }
00385
00386 QString TransportManager::defaultTransportName() const
00387 {
00388 Transport *t = transportById( d->defaultTransportId, false );
00389 if ( t ) {
00390 return t->name();
00391 }
00392 return QString();
00393 }
00394
00395 int TransportManager::defaultTransportId() const
00396 {
00397 return d->defaultTransportId;
00398 }
00399
00400 void TransportManager::setDefaultTransport( int id )
00401 {
00402 if ( id == d->defaultTransportId || !transportById( id, false ) ) {
00403 return;
00404 }
00405 d->defaultTransportId = id;
00406 d->writeConfig();
00407 }
00408
00409 void TransportManager::removeTransport( int id )
00410 {
00411 Transport *t = transportById( id, false );
00412 if ( !t ) {
00413 return;
00414 }
00415 emit transportRemoved( t->id(), t->name() );
00416
00417
00418 if( t->type() == Transport::EnumType::Akonadi ) {
00419 using namespace Akonadi;
00420 const AgentInstance instance = AgentManager::self()->instance( t->host() );
00421 if( !instance.isValid() ) {
00422 kWarning() << "Could not find resource instance.";
00423 }
00424 AgentManager::self()->removeInstance( instance );
00425 }
00426
00427 d->transports.removeAll( t );
00428 d->validateDefault();
00429 QString group = t->currentGroup();
00430 delete t;
00431 d->config->deleteGroup( group );
00432 d->writeConfig();
00433
00434 }
00435
00436 void TransportManagerPrivate::readConfig()
00437 {
00438 QList<Transport *> oldTransports = transports;
00439 transports.clear();
00440
00441 QRegExp re( QLatin1String( "^Transport (.+)$" ) );
00442 QStringList groups = config->groupList().filter( re );
00443 foreach ( const QString &s, groups ) {
00444 re.indexIn( s );
00445 Transport *t = 0;
00446
00447
00448 foreach ( Transport *old, oldTransports ) {
00449 if ( old->currentGroup() == QLatin1String( "Transport " ) + re.cap( 1 ) ) {
00450 kDebug() << "reloading existing transport:" << s;
00451 t = old;
00452 t->readConfig();
00453 oldTransports.removeAll( old );
00454 break;
00455 }
00456 }
00457
00458 if ( !t ) {
00459 t = new Transport( re.cap( 1 ) );
00460 }
00461 if ( t->id() <= 0 ) {
00462 t->setId( createId() );
00463 t->writeConfig();
00464 }
00465 transports.append( t );
00466 }
00467
00468 qDeleteAll( oldTransports );
00469 oldTransports.clear();
00470
00471
00472 KConfigGroup group( config, "General" );
00473 defaultTransportId = group.readEntry( "default-transport", 0 );
00474 if ( defaultTransportId == 0 ) {
00475
00476 QString name = group.readEntry( "default-transport", QString() );
00477 if ( !name.isEmpty() ) {
00478 Transport *t = q->transportByName( name, false );
00479 if ( t ) {
00480 defaultTransportId = t->id();
00481 writeConfig();
00482 }
00483 }
00484 }
00485 validateDefault();
00486 migrateToWallet();
00487 }
00488
00489 void TransportManagerPrivate::writeConfig()
00490 {
00491 KConfigGroup group( config, "General" );
00492 group.writeEntry( "default-transport", defaultTransportId );
00493 config->sync();
00494 q->emitChangesCommitted();
00495 }
00496
00497 void TransportManagerPrivate::fillTypes()
00498 {
00499 Q_ASSERT( types.isEmpty() );
00500
00501
00502 {
00503 TransportType type;
00504 type.d->mType = Transport::EnumType::SMTP;
00505 type.d->mName = i18nc( "@option SMTP transport", "SMTP" );
00506 type.d->mDescription = i18n( "An SMTP server on the Internet" );
00507 types << type;
00508 }
00509
00510
00511 {
00512 TransportType type;
00513 type.d->mType = Transport::EnumType::Sendmail;
00514 type.d->mName = i18nc( "@option sendmail transport", "Sendmail" );
00515 type.d->mDescription = i18n( "A local sendmail installation" );
00516 types << type;
00517 }
00518
00519
00520 {
00521 using namespace Akonadi;
00522 foreach ( const AgentType &atype, AgentManager::self()->types() ) {
00523
00524
00525 if( atype.capabilities().contains( QLatin1String( "MailTransport" ) ) ) {
00526 TransportType type;
00527 type.d->mType = Transport::EnumType::Akonadi;
00528 type.d->mAgentType = atype;
00529 type.d->mName = atype.name();
00530 type.d->mDescription = atype.description();
00531 types << type;
00532 kDebug() << "Found Akonadi type" << atype.name();
00533 }
00534 }
00535
00536
00537 QObject::connect( AgentManager::self(), SIGNAL(typeAdded(Akonadi::AgentType)),
00538 q, SLOT(agentTypeAdded(Akonadi::AgentType)) );
00539 QObject::connect( AgentManager::self(), SIGNAL(typeRemoved(Akonadi::AgentType)),
00540 q, SLOT(agentTypeRemoved(Akonadi::AgentType)) );
00541 }
00542
00543 kDebug() << "Have SMTP, Sendmail, and" << types.count() - 2 << "Akonadi types.";
00544 }
00545
00546 void TransportManager::emitChangesCommitted()
00547 {
00548 d->myOwnChange = true;
00549 d->appliedChange = false;
00550 emit transportsChanged();
00551 emit changesCommitted();
00552 }
00553
00554 void TransportManagerPrivate::slotTransportsChanged()
00555 {
00556 if ( myOwnChange && appliedChange ) {
00557 myOwnChange = false;
00558 appliedChange = false;
00559 return;
00560 }
00561
00562 kDebug();
00563 config->reparseConfiguration();
00564
00565 readConfig();
00566 appliedChange = true;
00567 emit q->transportsChanged();
00568 }
00569
00570 int TransportManagerPrivate::createId() const
00571 {
00572 QList<int> usedIds;
00573 foreach ( Transport *t, transports ) {
00574 usedIds << t->id();
00575 }
00576 usedIds << 0;
00577 int newId;
00578 do {
00579 newId = KRandom::random();
00580 } while ( usedIds.contains( newId ) );
00581 return newId;
00582 }
00583
00584 KWallet::Wallet * TransportManager::wallet()
00585 {
00586 if ( d->wallet && d->wallet->isOpen() ) {
00587 return d->wallet;
00588 }
00589
00590 if ( !Wallet::isEnabled() || d->walletOpenFailed ) {
00591 return 0;
00592 }
00593
00594 WId window = 0;
00595 if ( qApp->activeWindow() ) {
00596 window = qApp->activeWindow()->winId();
00597 } else if ( !QApplication::topLevelWidgets().isEmpty() ) {
00598 window = qApp->topLevelWidgets().first()->winId();
00599 }
00600
00601 delete d->wallet;
00602 d->wallet = Wallet::openWallet( Wallet::NetworkWallet(), window );
00603
00604 if ( !d->wallet ) {
00605 d->walletOpenFailed = true;
00606 return 0;
00607 }
00608
00609 d->prepareWallet();
00610 return d->wallet;
00611 }
00612
00613 void TransportManagerPrivate::prepareWallet()
00614 {
00615 if ( !wallet ) {
00616 return;
00617 }
00618 if ( !wallet->hasFolder( WALLET_FOLDER ) ) {
00619 wallet->createFolder( WALLET_FOLDER );
00620 }
00621 wallet->setFolder( WALLET_FOLDER );
00622 }
00623
00624 void TransportManager::loadPasswords()
00625 {
00626 foreach ( Transport *t, d->transports ) {
00627 t->readPassword();
00628 }
00629
00630
00631 foreach ( TransportJob *job, d->walletQueue ) {
00632 job->start();
00633 }
00634 d->walletQueue.clear();
00635
00636 emit passwordsChanged();
00637 }
00638
00639 void TransportManager::loadPasswordsAsync()
00640 {
00641 kDebug();
00642
00643
00644 bool found = false;
00645 foreach ( Transport *t, d->transports ) {
00646 if ( !t->isComplete() ) {
00647 found = true;
00648 break;
00649 }
00650 }
00651 if ( !found ) {
00652 return;
00653 }
00654
00655
00656 if ( !d->wallet && !d->walletOpenFailed ) {
00657 WId window = 0;
00658 if ( qApp->activeWindow() ) {
00659 window = qApp->activeWindow()->winId();
00660 } else if ( !QApplication::topLevelWidgets().isEmpty() ) {
00661 window = qApp->topLevelWidgets().first()->winId();
00662 }
00663
00664 d->wallet = Wallet::openWallet( Wallet::NetworkWallet(), window,
00665 Wallet::Asynchronous );
00666 if ( d->wallet ) {
00667 connect( d->wallet, SIGNAL(walletOpened(bool)), SLOT(slotWalletOpened(bool)) );
00668 d->walletAsyncOpen = true;
00669 } else {
00670 d->walletOpenFailed = true;
00671 loadPasswords();
00672 }
00673 return;
00674 }
00675 if ( d->wallet && !d->walletAsyncOpen ) {
00676 loadPasswords();
00677 }
00678 }
00679
00680 void TransportManagerPrivate::slotWalletOpened( bool success )
00681 {
00682 kDebug();
00683 walletAsyncOpen = false;
00684 if ( !success ) {
00685 walletOpenFailed = true;
00686 delete wallet;
00687 wallet = 0;
00688 } else {
00689 prepareWallet();
00690 }
00691 q->loadPasswords();
00692 }
00693
00694 void TransportManagerPrivate::validateDefault()
00695 {
00696 if ( !q->transportById( defaultTransportId, false ) ) {
00697 if ( q->isEmpty() ) {
00698 defaultTransportId = -1;
00699 } else {
00700 defaultTransportId = transports.first()->id();
00701 writeConfig();
00702 }
00703 }
00704 }
00705
00706 void TransportManagerPrivate::migrateToWallet()
00707 {
00708
00709 static bool firstRun = true;
00710 if ( !firstRun ) {
00711 return;
00712 }
00713 firstRun = false;
00714
00715
00716 if ( !isMainInstance ) {
00717 return;
00718 }
00719
00720
00721 QStringList names;
00722 foreach ( Transport *t, transports ) {
00723 if ( t->needsWalletMigration() ) {
00724 names << t->name();
00725 }
00726 }
00727 if ( names.isEmpty() ) {
00728 return;
00729 }
00730
00731
00732 int result = KMessageBox::questionYesNoList(
00733 0,
00734 i18n( "The following mail transports store their passwords in an "
00735 "unencrypted configuration file.\nFor security reasons, "
00736 "please consider migrating these passwords to KWallet, the "
00737 "KDE Wallet management tool,\nwhich stores sensitive data "
00738 "for you in a strongly encrypted file.\n"
00739 "Do you want to migrate your passwords to KWallet?" ),
00740 names, i18n( "Question" ),
00741 KGuiItem( i18n( "Migrate" ) ), KGuiItem( i18n( "Keep" ) ),
00742 QString::fromAscii( "WalletMigrate" ) );
00743 if ( result != KMessageBox::Yes ) {
00744 return;
00745 }
00746
00747
00748 foreach ( Transport *t, transports ) {
00749 if ( t->needsWalletMigration() ) {
00750 t->migrateToWallet();
00751 }
00752 }
00753 }
00754
00755 void TransportManagerPrivate::dbusServiceOwnerChanged( const QString &service,
00756 const QString &oldOwner,
00757 const QString &newOwner )
00758 {
00759 Q_UNUSED( oldOwner );
00760 if ( service == DBUS_SERVICE_NAME && newOwner.isEmpty() ) {
00761 QDBusConnection::sessionBus().registerService( DBUS_SERVICE_NAME );
00762 }
00763 }
00764
00765 void TransportManagerPrivate::agentTypeAdded( const Akonadi::AgentType &atype )
00766 {
00767 using namespace Akonadi;
00768 if( atype.capabilities().contains( QLatin1String( "MailTransport" ) ) ) {
00769 TransportType type;
00770 type.d->mType = Transport::EnumType::Akonadi;
00771 type.d->mAgentType = atype;
00772 type.d->mName = atype.name();
00773 type.d->mDescription = atype.description();
00774 types << type;
00775 kDebug() << "Added new Akonadi type" << atype.name();
00776 }
00777 }
00778
00779 void TransportManagerPrivate::agentTypeRemoved( const Akonadi::AgentType &atype )
00780 {
00781 using namespace Akonadi;
00782 foreach ( const TransportType &type, types ) {
00783 if( type.type() == Transport::EnumType::Akonadi &&
00784 type.agentType() == atype ) {
00785 types.removeAll( type );
00786 kDebug() << "Removed Akonadi type" << atype.name();
00787 }
00788 }
00789 }
00790
00791 void TransportManagerPrivate::jobResult( KJob *job )
00792 {
00793 walletQueue.removeAll( static_cast<TransportJob*>( job ) );
00794 }
00795
00796 #include "transportmanager.moc"