mailtransport
smtpjob.cpp
00001 /* 00002 Copyright (c) 2007 Volker Krause <vkrause@kde.org> 00003 00004 Based on KMail code by: 00005 Copyright (c) 1996-1998 Stefan Taferner <taferner@kde.org> 00006 00007 This library is free software; you can redistribute it and/or modify it 00008 under the terms of the GNU Library General Public License as published by 00009 the Free Software Foundation; either version 2 of the License, or (at your 00010 option) any later version. 00011 00012 This library is distributed in the hope that it will be useful, but WITHOUT 00013 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 00014 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public 00015 License for more details. 00016 00017 You should have received a copy of the GNU Library General Public License 00018 along with this library; see the file COPYING.LIB. If not, write to the 00019 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 00020 02110-1301, USA. 00021 */ 00022 00023 #include "smtpjob.h" 00024 #include "transport.h" 00025 #include "mailtransport_defs.h" 00026 #include "precommandjob.h" 00027 #include "smtp/smtpsession.h" 00028 00029 #include <QBuffer> 00030 #include <QHash> 00031 00032 #include <KLocalizedString> 00033 #include <KUrl> 00034 #include <KIO/Job> 00035 #include <KIO/Scheduler> 00036 #include <KPasswordDialog> 00037 00038 using namespace MailTransport; 00039 00040 class SlavePool 00041 { 00042 public: 00043 SlavePool() : ref( 0 ) {} 00044 int ref; 00045 QHash<int,KIO::Slave*> slaves; 00046 00047 void removeSlave( KIO::Slave *slave, bool disconnect = false ) 00048 { 00049 kDebug() << "Removing slave" << slave << "from pool"; 00050 const int slaveKey = slaves.key( slave ); 00051 if ( slaveKey > 0 ) { 00052 slaves.remove( slaveKey ); 00053 if ( disconnect ) { 00054 KIO::Scheduler::disconnectSlave( slave ); 00055 } 00056 } 00057 } 00058 }; 00059 00060 K_GLOBAL_STATIC( SlavePool, s_slavePool ) 00061 00062 00066 class SmtpJobPrivate 00067 { 00068 public: 00069 SmtpJobPrivate( SmtpJob *parent ) : q( parent ) {} 00070 00071 void smtpSessionResult( SmtpSession * session ) 00072 { 00073 #ifdef MAILTRANSPORT_INPROCESS_SMTP 00074 if ( !session->errorMessage().isEmpty() ) { 00075 q->setError( KJob::UserDefinedError ); 00076 q->setErrorText( session->errorMessage() ); 00077 } 00078 q->emitResult(); 00079 #endif 00080 } 00081 00082 SmtpJob *q; 00083 KIO::Slave *slave; 00084 enum State { 00085 Idle, Precommand, Smtp 00086 } currentState; 00087 bool finished; 00088 }; 00089 00090 SmtpJob::SmtpJob( Transport *transport, QObject *parent ) 00091 : TransportJob( transport, parent ), d( new SmtpJobPrivate( this ) ) 00092 { 00093 d->currentState = SmtpJobPrivate::Idle; 00094 d->slave = 0; 00095 d->finished = false; 00096 if ( !s_slavePool.isDestroyed() ) { 00097 s_slavePool->ref++; 00098 } 00099 KIO::Scheduler::connect( SIGNAL(slaveError(KIO::Slave*,int,QString)), 00100 this, SLOT(slaveError(KIO::Slave*,int,QString)) ); 00101 } 00102 00103 SmtpJob::~SmtpJob() 00104 { 00105 if ( !s_slavePool.isDestroyed() ) { 00106 s_slavePool->ref--; 00107 if ( s_slavePool->ref == 0 ) { 00108 kDebug() << "clearing SMTP slave pool" << s_slavePool->slaves.count(); 00109 foreach ( KIO::Slave *slave, s_slavePool->slaves ) { 00110 if ( slave ) { 00111 KIO::Scheduler::disconnectSlave( slave ); 00112 } 00113 } 00114 s_slavePool->slaves.clear(); 00115 } 00116 } 00117 delete d; 00118 } 00119 00120 void SmtpJob::doStart() 00121 { 00122 if ( s_slavePool.isDestroyed() ) { 00123 return; 00124 } 00125 00126 if ( s_slavePool->slaves.contains( transport()->id() ) || 00127 transport()->precommand().isEmpty() ) { 00128 d->currentState = SmtpJobPrivate::Smtp; 00129 startSmtpJob(); 00130 } else { 00131 d->currentState = SmtpJobPrivate::Precommand; 00132 PrecommandJob *job = new PrecommandJob( transport()->precommand(), this ); 00133 addSubjob( job ); 00134 job->start(); 00135 } 00136 } 00137 00138 void SmtpJob::startSmtpJob() 00139 { 00140 if ( s_slavePool.isDestroyed() ) { 00141 return; 00142 } 00143 00144 KUrl destination; 00145 destination.setProtocol( ( transport()->encryption() == Transport::EnumEncryption::SSL ) ? 00146 SMTPS_PROTOCOL : SMTP_PROTOCOL ); 00147 destination.setHost( transport()->host().trimmed() ); 00148 destination.setPort( transport()->port() ); 00149 00150 destination.addQueryItem( QLatin1String( "headers" ), QLatin1String( "0" ) ); 00151 destination.addQueryItem( QLatin1String( "from" ), sender() ); 00152 00153 foreach ( const QString &str, to() ) { 00154 destination.addQueryItem( QLatin1String( "to" ), str ); 00155 } 00156 foreach ( const QString &str, cc() ) { 00157 destination.addQueryItem( QLatin1String( "cc" ), str ); 00158 } 00159 foreach ( const QString &str, bcc() ) { 00160 destination.addQueryItem( QLatin1String( "bcc" ), str ); 00161 } 00162 00163 if ( transport()->specifyHostname() ) { 00164 destination.addQueryItem( QLatin1String( "hostname" ), transport()->localHostname() ); 00165 } 00166 00167 if ( transport()->requiresAuthentication() ) { 00168 if( ( transport()->userName().isEmpty() || transport()->password().isEmpty() ) && 00169 transport()->authenticationType() != Transport::EnumAuthenticationType::GSSAPI ) { 00170 QString user = transport()->userName(); 00171 QString passwd = transport()->password(); 00172 int result; 00173 00174 bool keep = transport()->storePassword(); 00175 00176 KPasswordDialog dlg( 0, KPasswordDialog::ShowUsernameLine | KPasswordDialog::ShowKeepPassword ); 00177 dlg.setPrompt( i18n( "You need to supply a username and a password to use this SMTP server." ) ); 00178 dlg.setKeepPassword( keep ); 00179 dlg.addCommentLine( QString(), transport()->name() ); 00180 dlg.setUsername( user ); 00181 dlg.setPassword( passwd ); 00182 00183 result = dlg.exec(); 00184 00185 if ( result != QDialog::Accepted ) { 00186 setError( KilledJobError ); 00187 emitResult(); 00188 return; 00189 } 00190 transport()->setUserName( dlg.username() ); 00191 transport()->setPassword( dlg.password() ); 00192 transport()->setStorePassword( dlg.keepPassword() ); 00193 transport()->writeConfig(); 00194 } 00195 destination.setUser( transport()->userName() ); 00196 destination.setPass( transport()->password() ); 00197 } 00198 00199 // dotstuffing is now done by the slave (see setting of metadata) 00200 if ( !data().isEmpty() ) { 00201 // allow +5% for subsequent LF->CRLF and dotstuffing (an average 00202 // over 2G-lines gives an average line length of 42-43): 00203 destination.addQueryItem( QLatin1String( "size" ), 00204 QString::number( qRound( data().length() * 1.05 ) ) ); 00205 } 00206 00207 destination.setPath( QLatin1String( "/send" ) ); 00208 00209 #ifndef MAILTRANSPORT_INPROCESS_SMTP 00210 d->slave = s_slavePool->slaves.value( transport()->id() ); 00211 if ( !d->slave ) { 00212 KIO::MetaData slaveConfig; 00213 slaveConfig.insert( QLatin1String( "tls" ), 00214 ( transport()->encryption() == Transport::EnumEncryption::TLS ) ? 00215 QLatin1String( "on" ) : QLatin1String( "off" ) ); 00216 if ( transport()->requiresAuthentication() ) { 00217 slaveConfig.insert( QLatin1String( "sasl" ), transport()->authenticationTypeString() ); 00218 } 00219 d->slave = KIO::Scheduler::getConnectedSlave( destination, slaveConfig ); 00220 kDebug() << "Created new SMTP slave" << d->slave; 00221 s_slavePool->slaves.insert( transport()->id(), d->slave ); 00222 } else { 00223 kDebug() << "Re-using existing slave" << d->slave; 00224 } 00225 00226 KIO::TransferJob *job = KIO::put( destination, -1, KIO::HideProgressInfo ); 00227 if ( !d->slave || !job ) { 00228 setError( UserDefinedError ); 00229 setErrorText( i18n( "Unable to create SMTP job." ) ); 00230 emitResult(); 00231 return; 00232 } 00233 00234 job->addMetaData( QLatin1String( "lf2crlf+dotstuff" ), QLatin1String( "slave" ) ); 00235 connect( job, SIGNAL(dataReq(KIO::Job*,QByteArray&)), 00236 SLOT(dataRequest(KIO::Job*,QByteArray&)) ); 00237 00238 addSubjob( job ); 00239 KIO::Scheduler::assignJobToSlave( d->slave, job ); 00240 #else 00241 SmtpSession *session = new SmtpSession( this ); 00242 connect( session, SIGNAL(result(MailTransport::SmtpSession*)), SLOT(smtpSessionResult(MailTransport::SmtpSession*)) ); 00243 session->setUseTLS( transport()->encryption() == Transport::EnumEncryption::TLS ); 00244 if ( transport()->requiresAuthentication() ) 00245 session->setSaslMethod( transport()->authenticationTypeString() ); 00246 session->sendMessage( destination, buffer() ); 00247 #endif 00248 00249 setTotalAmount( KJob::Bytes, data().length() ); 00250 } 00251 00252 bool SmtpJob::doKill() 00253 { 00254 if ( s_slavePool.isDestroyed() ) { 00255 return false; 00256 } 00257 00258 if ( !hasSubjobs() ) { 00259 return true; 00260 } 00261 if ( d->currentState == SmtpJobPrivate::Precommand ) { 00262 return subjobs().first()->kill(); 00263 } else if ( d->currentState == SmtpJobPrivate::Smtp ) { 00264 KIO::SimpleJob *job = static_cast<KIO::SimpleJob*>( subjobs().first() ); 00265 clearSubjobs(); 00266 KIO::Scheduler::cancelJob( job ); 00267 s_slavePool->removeSlave( d->slave ); 00268 return true; 00269 } 00270 return false; 00271 } 00272 00273 void SmtpJob::slotResult( KJob *job ) 00274 { 00275 if ( s_slavePool.isDestroyed() ) { 00276 return; 00277 } 00278 00279 // The job has finished, so we don't care about any further errors. Set 00280 // d->finished to true, so slaveError() knows about this and doesn't call 00281 // emitResult() anymore. 00282 // Sometimes, the SMTP slave emits more than one error 00283 // 00284 // The first error causes slotResult() to be called, but not slaveError(), since 00285 // the scheduler doesn't emit errors for connected slaves. 00286 // 00287 // The second error then causes slaveError() to be called (as the slave is no 00288 // longer connected), which does emitResult() a second time, which is invalid 00289 // (and triggers an assert in KMail). 00290 d->finished = true; 00291 00292 // Normally, calling TransportJob::slotResult() whould set the proper error code 00293 // for error() via KComposite::slotResult(). However, we can't call that here, 00294 // since that also emits the result signal. 00295 // In KMail, when there are multiple mails in the outbox, KMail tries to send 00296 // the next mail when it gets the result signal, which then would reuse the 00297 // old broken slave from the slave pool if there was an error. 00298 // To prevent that, we call TransportJob::slotResult() only after removing the 00299 // slave from the pool and calculate the error code ourselves. 00300 int errorCode = error(); 00301 if ( !errorCode ) { 00302 errorCode = job->error(); 00303 } 00304 00305 if ( errorCode && d->currentState == SmtpJobPrivate::Smtp ) { 00306 s_slavePool->removeSlave( d->slave, errorCode != KIO::ERR_SLAVE_DIED ); 00307 TransportJob::slotResult( job ); 00308 return; 00309 } 00310 00311 TransportJob::slotResult( job ); 00312 if ( !error() && d->currentState == SmtpJobPrivate::Precommand ) { 00313 d->currentState = SmtpJobPrivate::Smtp; 00314 startSmtpJob(); 00315 return; 00316 } 00317 if ( !error() ) { 00318 emitResult(); 00319 } 00320 } 00321 00322 void SmtpJob::dataRequest( KIO::Job *job, QByteArray &data ) 00323 { 00324 if ( s_slavePool.isDestroyed() ) { 00325 return; 00326 } 00327 00328 Q_UNUSED( job ); 00329 Q_ASSERT( job ); 00330 if ( buffer()->atEnd() ) { 00331 data.clear(); 00332 } else { 00333 Q_ASSERT( buffer()->isOpen() ); 00334 data = buffer()->read( 32 * 1024 ); 00335 } 00336 setProcessedAmount( KJob::Bytes, buffer()->pos() ); 00337 } 00338 00339 void SmtpJob::slaveError( KIO::Slave *slave, int errorCode, const QString &errorMsg ) 00340 { 00341 if ( s_slavePool.isDestroyed() ) { 00342 return; 00343 } 00344 00345 s_slavePool->removeSlave( slave, errorCode != KIO::ERR_SLAVE_DIED ); 00346 if ( d->slave == slave && !d->finished ) { 00347 setError( errorCode ); 00348 setErrorText( KIO::buildErrorString( errorCode, errorMsg ) ); 00349 emitResult(); 00350 } 00351 } 00352 00353 #include "smtpjob.moc"