00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020 #include "smtpsession.h"
00021
00022 #include "common.h"
00023 #include "smtp/smtpsessioninterface.h"
00024 #include "smtp/request.h"
00025 #include "smtp/response.h"
00026 #include "smtp/command.h"
00027 #include "smtp/transactionstate.h"
00028
00029 #include <ktcpsocket.h>
00030 #include <KMessageBox>
00031 #include <KIO/PasswordDialog>
00032 #include <kio/authinfo.h>
00033 #include <kio/global.h>
00034 #include <kio/sslui.h>
00035 #include <KLocalizedString>
00036 #include <kpimutils/networkaccesshelper.h>
00037 #include <KDebug>
00038
00039 #include <QtCore/QQueue>
00040 #include <QtNetwork/QHostInfo>
00041
00042 using namespace MailTransport;
00043 using namespace KioSMTP;
00044
00045 class MailTransport::SmtpSessionPrivate : public KioSMTP::SMTPSessionInterface
00046 {
00047 public:
00048 explicit SmtpSessionPrivate( SmtpSession *session ) :
00049 useTLS( true ),
00050 socket( 0 ),
00051 currentCommand( 0 ),
00052 currentTransactionState( 0 ),
00053 state( Initial ),
00054 q( session )
00055 {}
00056
00057 void dataReq() { };
00058 int readData(QByteArray& ba)
00059 {
00060 if ( data->atEnd() ) {
00061 ba.clear();
00062 return 0;
00063 } else {
00064 Q_ASSERT( data->isOpen() );
00065 ba = data->read( 32 * 1024 );
00066 return ba.size();
00067 }
00068 }
00069
00070 void error(int id, const QString& msg)
00071 {
00072 kDebug() << id << msg;
00073
00074 currentCommand = 0;
00075 currentTransactionState = 0;
00076
00077 if ( errorMessage.isEmpty() )
00078 errorMessage = KIO::buildErrorString( id, msg );
00079 q->disconnectFromHost();
00080 }
00081
00082 void informationMessageBox(const QString& msg, const QString& caption)
00083 {
00084 KMessageBox::information( 0, msg, caption );
00085 }
00086
00087 bool openPasswordDialog(KIO::AuthInfo& authInfo) {
00088 return KIO::PasswordDialog::getNameAndPassword(
00089 authInfo.username,
00090 authInfo.password,
00091 &(authInfo.keepPassword),
00092 authInfo.prompt,
00093 authInfo.readOnly,
00094 authInfo.caption,
00095 authInfo.comment,
00096 authInfo.commentLabel
00097 ) == KIO::PasswordDialog::Accepted;
00098 }
00099
00100 bool startSsl()
00101 {
00102 kDebug();
00103 Q_ASSERT( socket );
00104 socket->setAdvertisedSslVersion( KTcpSocket::TlsV1 );
00105 socket->ignoreSslErrors();
00106 socket->startClientEncryption();
00107 const bool encrypted = socket->waitForEncrypted( 60 * 1000 );
00108
00109 const KSslCipher cipher = socket->sessionCipher();
00110 if ( !encrypted || socket->sslErrors().count() > 0 || socket->encryptionMode() != KTcpSocket::SslClientMode
00111 || cipher.isNull() || cipher.usedBits() == 0 )
00112 {
00113 kDebug() << "Initial SSL handshake failed. cipher.isNull() is" << cipher.isNull()
00114 << ", cipher.usedBits() is" << cipher.usedBits()
00115 << ", the socket says:" << socket->errorString()
00116 << "and the list of SSL errors contains"
00117 << socket->sslErrors().count() << "items.";
00118
00119 if ( KIO::SslUi::askIgnoreSslErrors( socket ) ) {
00120 return true;
00121 } else {
00122 return false;
00123 }
00124 } else {
00125 kDebug() << "TLS negotiation done.";
00126 return true;
00127 }
00128 }
00129
00130 bool lf2crlfAndDotStuffingRequested() const { return true; }
00131 QString requestedSaslMethod() const { return saslMethod; }
00132 TLSRequestState tlsRequested() const { return useTLS ? ForceTLS : ForceNoTLS; }
00133
00134 void socketConnected()
00135 {
00136 kDebug();
00137 if ( destination.protocol() == QLatin1String("smtps") ) {
00138 if ( !startSsl() ) {
00139 error( KIO::ERR_SLAVE_DEFINED, i18n( "SSL negotiation failed." ) );
00140 }
00141 }
00142 }
00143
00144 void socketDisconnected()
00145 {
00146 kDebug();
00147 emit q->result( q );
00148 q->deleteLater();
00149 }
00150
00151 void socketError( KTcpSocket::Error err )
00152 {
00153 kDebug() << err;
00154 error( KIO::ERR_CONNECTION_BROKEN, socket->errorString() );
00155 }
00156
00157 bool sendCommandLine( const QByteArray &cmdline )
00158 {
00159 if ( cmdline.length() < 4096 )
00160 kDebug(7112) << "C: >>" << cmdline.trimmed().data() << "<<";
00161 else
00162 kDebug(7112) << "C: <" << cmdline.length() << " bytes>";
00163 ssize_t numWritten, cmdline_len = cmdline.length();
00164 if ( (numWritten = socket->write( cmdline ) ) != cmdline_len ) {
00165 kDebug(7112) << "Tried to write " << cmdline_len << " bytes, but only "
00166 << numWritten << " were written!" << endl;
00167 error( KIO::ERR_SLAVE_DEFINED, i18n ("Writing to socket failed.") );
00168 return false;
00169 }
00170 return true;
00171 }
00172
00173 bool run( int type, TransactionState * ts = 0 )
00174 {
00175 return run( Command::createSimpleCommand( type, this ), ts );
00176 }
00177
00178 bool run( Command * cmd, TransactionState * ts = 0 )
00179 {
00180 Q_ASSERT( cmd );
00181 Q_ASSERT( !currentCommand );
00182 Q_ASSERT( !currentTransactionState || currentTransactionState == ts );
00183
00184
00185 if ( cmd->doNotExecute( ts ) )
00186 return true;
00187
00188 currentCommand = cmd;
00189 currentTransactionState = ts;
00190
00191 while ( !cmd->isComplete() && !cmd->needsResponse() ) {
00192 const QByteArray cmdLine = cmd->nextCommandLine( ts );
00193 if ( ts && ts->failedFatally() ) {
00194 q->disconnectFromHost( false );
00195 return false;
00196 }
00197 if ( cmdLine.isEmpty() )
00198 continue;
00199 if ( !sendCommandLine( cmdLine ) ) {
00200 q->disconnectFromHost( false );
00201 return false;
00202 }
00203 }
00204 return true;
00205 }
00206
00207 void queueCommand( int type )
00208 {
00209 queueCommand( Command::createSimpleCommand( type, this ) );
00210 }
00211
00212 void queueCommand( KioSMTP::Command * command )
00213 {
00214 mPendingCommandQueue.enqueue( command );
00215 }
00216
00217 bool runQueuedCommands( TransactionState *ts )
00218 {
00219 Q_ASSERT( ts );
00220 Q_ASSERT( !currentTransactionState || ts == currentTransactionState );
00221 currentTransactionState = ts;
00222 kDebug( canPipelineCommands(), 7112 ) << "using pipelining";
00223
00224 while( !mPendingCommandQueue.isEmpty() ) {
00225 QByteArray cmdline = collectPipelineCommands( ts );
00226 if ( ts->failedFatally() ) {
00227 q->disconnectFromHost( false );
00228 return false;
00229 }
00230 if ( ts->failed() )
00231 break;
00232 if ( cmdline.isEmpty() )
00233 continue;
00234 if ( !sendCommandLine( cmdline ) || ts->failedFatally() ) {
00235 q->disconnectFromHost( false );
00236 return false;
00237 }
00238 if ( !mSentCommandQueue.isEmpty() )
00239 return true;
00240 }
00241
00242 if ( ts->failed() ) {
00243 kDebug() << "transaction state failed: " << ts->errorCode() << ts->errorMessage();
00244 if ( errorMessage.isEmpty() )
00245 errorMessage = ts->errorMessage();
00246 state = SmtpSessionPrivate::Reset;
00247 if ( !run( Command::RSET, currentTransactionState ) )
00248 q->disconnectFromHost( false );
00249 return false;
00250 }
00251
00252 delete currentTransactionState;
00253 currentTransactionState = 0;
00254 return true;
00255 }
00256
00257 QByteArray collectPipelineCommands( TransactionState * ts )
00258 {
00259 Q_ASSERT( ts );
00260 QByteArray cmdLine;
00261 unsigned int cmdLine_len = 0;
00262
00263 while ( !mPendingCommandQueue.isEmpty() ) {
00264
00265 Command * cmd = mPendingCommandQueue.head();
00266
00267 if ( cmd->doNotExecute( ts ) ) {
00268 delete mPendingCommandQueue.dequeue();
00269 if ( cmdLine_len )
00270 break;
00271 else
00272 continue;
00273 }
00274
00275 if ( cmdLine_len && cmd->mustBeFirstInPipeline() )
00276 break;
00277
00278 if ( cmdLine_len && !canPipelineCommands() )
00279 break;
00280
00281 while ( !cmd->isComplete() && !cmd->needsResponse() ) {
00282 const QByteArray currentCmdLine = cmd->nextCommandLine( ts );
00283 if ( ts->failedFatally() )
00284 return cmdLine;
00285 const unsigned int currentCmdLine_len = currentCmdLine.length();
00286
00287 cmdLine_len += currentCmdLine_len;
00288 cmdLine += currentCmdLine;
00289
00290
00291
00292
00293
00294
00295
00296
00297
00298
00299
00300
00301
00302 if ( dynamic_cast<TransferCommand *>( cmd ) != 0 &&
00303 cmdLine_len >= 32 * 1024 ) {
00304 return cmdLine;
00305 }
00306 }
00307
00308 mSentCommandQueue.enqueue( mPendingCommandQueue.dequeue() );
00309
00310 if ( cmd->mustBeLastInPipeline() )
00311 break;
00312 }
00313
00314 return cmdLine;
00315 }
00316
00317 void receivedNewData()
00318 {
00319 kDebug();
00320 while ( socket->canReadLine() ) {
00321 const QByteArray buffer = socket->readLine();
00322 kDebug() << "S: >>" << buffer << "<<";
00323 currentResponse.parseLine( buffer, buffer.size() );
00324
00325
00326 if ( currentResponse.isComplete() ) {
00327 handleResponse( currentResponse );
00328 currentResponse = Response();
00329 } else if ( !currentResponse.isWellFormed() ) {
00330 error( KIO::ERR_NO_CONTENT, i18n("Invalid SMTP response (%1) received.", currentResponse.code()) );
00331 }
00332 }
00333 }
00334
00335 void handleResponse( const KioSMTP::Response &response )
00336 {
00337 if ( !mSentCommandQueue.isEmpty() ) {
00338 Command * cmd = mSentCommandQueue.head();
00339 Q_ASSERT( cmd->isComplete() );
00340 cmd->processResponse( response, currentTransactionState );
00341 if ( currentTransactionState->failedFatally() )
00342 q->disconnectFromHost( false );
00343 delete mSentCommandQueue.dequeue();
00344
00345 if ( mSentCommandQueue.isEmpty() ) {
00346 if ( !mPendingCommandQueue.isEmpty() )
00347 runQueuedCommands( currentTransactionState );
00348 else if ( state == Sending ) {
00349 delete currentTransactionState;
00350 currentTransactionState = 0;
00351 q->disconnectFromHost();
00352 }
00353 }
00354 return;
00355 }
00356
00357
00358 if ( currentCommand ) {
00359 if ( !currentCommand->processResponse( response, currentTransactionState ) ) {
00360 q->disconnectFromHost( false );
00361 }
00362 while ( !currentCommand->isComplete() && !currentCommand->needsResponse() ) {
00363 const QByteArray cmdLine = currentCommand->nextCommandLine( currentTransactionState );
00364 if ( currentTransactionState && currentTransactionState->failedFatally() ) {
00365 q->disconnectFromHost( false );
00366 }
00367 if ( cmdLine.isEmpty() )
00368 continue;
00369 if ( !sendCommandLine( cmdLine ) ) {
00370 q->disconnectFromHost( false );
00371 }
00372 }
00373 if ( currentCommand->isComplete() ) {
00374 Command *cmd = currentCommand;
00375 currentCommand = 0;
00376 currentTransactionState = 0;
00377 handleCommand( cmd );
00378 }
00379 return;
00380 }
00381
00382
00383 switch ( state ) {
00384 case Initial:
00385 {
00386 if ( !response.isOk() ) {
00387 error( KIO::ERR_COULD_NOT_LOGIN,
00388 i18n("The server (%1) did not accept the connection.\n"
00389 "%2", destination.host(), response.errorMessage() ) );
00390 break;
00391 }
00392 state = EHLOPreTls;
00393 EHLOCommand *ehloCmdPreTLS = new EHLOCommand( this, myHostname );
00394 run( ehloCmdPreTLS );
00395 break;
00396 }
00397 default: error( KIO::ERR_SLAVE_DEFINED, i18n( "Unhandled response" ) );
00398 }
00399 }
00400
00401 void handleCommand( Command *cmd )
00402 {
00403 switch ( state ) {
00404 case StartTLS:
00405 {
00406
00407
00408 state = EHLOPostTls;
00409 EHLOCommand *ehloCmdPostTLS = new EHLOCommand( this, myHostname );
00410 run( ehloCmdPostTLS );
00411 break;
00412 }
00413 case EHLOPreTls:
00414 {
00415 if ( ( haveCapability("STARTTLS") && tlsRequested() != SMTPSessionInterface::ForceNoTLS )
00416 || tlsRequested() == SMTPSessionInterface::ForceTLS )
00417 {
00418 state = StartTLS;
00419 run( Command::STARTTLS );
00420 break;
00421 }
00422 }
00423
00424 case EHLOPostTls:
00425 {
00426
00427
00428 if ( !destination.user().isEmpty() || haveCapability( "AUTH" ) || !requestedSaslMethod().isEmpty() )
00429 {
00430 authInfo.username = destination.user();
00431 authInfo.password = destination.password();
00432 authInfo.prompt = i18n("Username and password for your SMTP account:");
00433
00434 QStringList strList;
00435 if ( !requestedSaslMethod().isEmpty() )
00436 strList.append( requestedSaslMethod() );
00437 else
00438 strList = capabilities().saslMethodsQSL();
00439
00440 state = Authenticated;
00441 AuthCommand *authCmd = new AuthCommand( this, strList.join( QLatin1String(" ") ).toLatin1(), destination.host(), authInfo );
00442 run( authCmd );
00443 break;
00444 }
00445 }
00446
00447 case Authenticated:
00448 {
00449 state = Sending;
00450 queueCommand( new MailFromCommand( this, request.fromAddress().toLatin1(), request.is8BitBody(), request.size() ) );
00451
00452
00453 const QStringList recipients = request.recipients();
00454 for ( QStringList::const_iterator it = recipients.begin() ; it != recipients.end() ; ++it )
00455 queueCommand( new RcptToCommand( this, (*it).toLatin1() ) );
00456
00457 queueCommand( Command::DATA );
00458 queueCommand( new TransferCommand( this, QByteArray() ) );
00459
00460 TransactionState *ts = new TransactionState;
00461 if ( !runQueuedCommands( ts ) ) {
00462 if ( ts->errorCode() )
00463 error( ts->errorCode(), ts->errorMessage() );
00464 }
00465 break;
00466 }
00467 case Reset:
00468 q->disconnectFromHost( true );
00469 break;
00470 default:
00471 error( KIO::ERR_SLAVE_DEFINED, i18n( "Unhandled command response." ) );
00472 }
00473
00474 delete cmd;
00475 }
00476
00477 public:
00478 QString saslMethod;
00479 bool useTLS;
00480
00481 KUrl destination;
00482 KTcpSocket *socket;
00483 QIODevice *data;
00484 KioSMTP::Response currentResponse;
00485 KioSMTP::Command * currentCommand;
00486 KioSMTP::TransactionState *currentTransactionState;
00487 KIO::AuthInfo authInfo;
00488 KioSMTP::Request request;
00489 QString errorMessage;
00490 QString myHostname;
00491
00492 enum State {
00493 Initial,
00494 EHLOPreTls,
00495 StartTLS,
00496 EHLOPostTls,
00497 Authenticated,
00498 Sending,
00499 Reset
00500 };
00501 State state;
00502
00503 typedef QQueue<KioSMTP::Command*> CommandQueue;
00504 CommandQueue mPendingCommandQueue;
00505 CommandQueue mSentCommandQueue;
00506
00507 static bool saslInitialized;
00508
00509 private:
00510 SmtpSession *q;
00511 };
00512
00513 bool SmtpSessionPrivate::saslInitialized = false;
00514
00515
00516 SmtpSession::SmtpSession(QObject* parent) :
00517 QObject(parent),
00518 d( new SmtpSessionPrivate( this ) )
00519 {
00520 kDebug();
00521 d->socket = new KTcpSocket( this );
00522 connect( d->socket, SIGNAL(connected()), SLOT(socketConnected()) );
00523 connect( d->socket, SIGNAL(disconnected()), SLOT(socketDisconnected()) );
00524 connect( d->socket, SIGNAL(error(KTcpSocket::Error)), SLOT(socketError(KTcpSocket::Error)) );
00525 connect( d->socket, SIGNAL(readyRead()), SLOT(receivedNewData()), Qt::QueuedConnection );
00526
00527
00528 KPIMUtils::NetworkAccessHelper* networkHelper = new KPIMUtils::NetworkAccessHelper(this);
00529 networkHelper->establishConnection();
00530
00531 if ( !d->saslInitialized ) {
00532 if (!initSASL())
00533 exit(-1);
00534 d->saslInitialized = true;
00535 }
00536 }
00537
00538 SmtpSession::~SmtpSession()
00539 {
00540 kDebug();
00541 delete d;
00542 }
00543
00544 void SmtpSession::setSaslMethod(const QString& method)
00545 {
00546 d->saslMethod = method;
00547 }
00548
00549 void SmtpSession::setUseTLS(bool useTLS)
00550 {
00551 d->useTLS = useTLS;
00552 }
00553
00554 void SmtpSession::connectToHost(const KUrl& url)
00555 {
00556 kDebug() << url;
00557 d->socket->connectToHost( url.host(), url.port() );
00558 }
00559
00560 void SmtpSession::disconnectFromHost(bool nice)
00561 {
00562 if ( d->socket->state() == KTcpSocket::ConnectedState ) {
00563 if ( nice ) {
00564 d->run( Command::QUIT );
00565 }
00566
00567 d->socket->disconnectFromHost();
00568
00569 d->clearCapabilities();
00570 qDeleteAll( d->mPendingCommandQueue );
00571 d->mPendingCommandQueue.clear();
00572 qDeleteAll( d->mSentCommandQueue );
00573 d->mSentCommandQueue.clear();
00574 }
00575 }
00576
00577 void SmtpSession::sendMessage(const KUrl& destination, QIODevice* data)
00578 {
00579 d->destination = destination;
00580 if ( d->socket->state() != KTcpSocket::ConnectedState && d->socket->state() != KTcpSocket::ConnectingState ) {
00581 connectToHost( destination );
00582 }
00583
00584 d->data = data;
00585 d->request = Request::fromURL( destination );
00586
00587 if ( !d->request.heloHostname().isEmpty() ) {
00588 d->myHostname = d->request.heloHostname();
00589 } else {
00590 d->myHostname = QHostInfo::localHostName();
00591 if( d->myHostname.isEmpty() ) {
00592 d->myHostname = QLatin1String("localhost.invalid");
00593 } else if ( !d->myHostname.contains( QLatin1Char('.') ) ) {
00594 d->myHostname += QLatin1String(".localnet");
00595 }
00596 }
00597 }
00598
00599 QString SmtpSession::errorMessage() const
00600 {
00601 return d->errorMessage;
00602 }
00603
00604
00605 #include "smtpsession.moc"