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