20 #include "smtpsession.h"
23 #include "smtp/smtpsessioninterface.h"
24 #include "smtp/request.h"
25 #include "smtp/response.h"
26 #include "smtp/command.h"
27 #include "smtp/transactionstate.h"
29 #include <ktcpsocket.h>
30 #include <KMessageBox>
31 #include <KIO/PasswordDialog>
32 #include <kio/authinfo.h>
33 #include <kio/global.h>
34 #include <kio/sslui.h>
35 #include <KLocalizedString>
38 #include <QtCore/QQueue>
40 using namespace MailTransport;
41 using namespace KioSMTP;
43 class MailTransport::SmtpSessionPrivate :
public KioSMTP::SMTPSessionInterface
46 explicit SmtpSessionPrivate(
SmtpSession *session ) :
50 currentTransactionState( 0 ),
58 if ( data->atEnd() ) {
62 Q_ASSERT( data->isOpen() );
63 ba = data->read( 32 * 1024 );
68 void error(
int id,
const QString &msg )
70 kDebug() <<
id << msg;
73 currentTransactionState = 0;
75 if ( errorMessage.isEmpty() ) {
76 errorMessage = KIO::buildErrorString(
id, msg );
78 q->disconnectFromHost();
81 void informationMessageBox(
const QString &msg,
const QString &caption )
83 KMessageBox::information( 0, msg, caption );
86 bool openPasswordDialog( KIO::AuthInfo &authInfo ) {
87 return KIO::PasswordDialog::getNameAndPassword(
90 &( authInfo.keepPassword ),
95 authInfo.commentLabel ) == KIO::PasswordDialog::Accepted;
102 socket->setAdvertisedSslVersion( KTcpSocket::TlsV1 );
103 socket->ignoreSslErrors();
104 socket->startClientEncryption();
105 const bool encrypted = socket->waitForEncrypted( 60 * 1000 );
107 const KSslCipher cipher = socket->sessionCipher();
109 socket->sslErrors().count() > 0 ||
110 socket->encryptionMode() != KTcpSocket::SslClientMode ||
112 cipher.usedBits() == 0 ) {
113 kDebug() <<
"Initial SSL handshake failed. cipher.isNull() is" << cipher.isNull()
114 <<
", cipher.usedBits() is" << cipher.usedBits()
115 <<
", the socket says:" << socket->errorString()
116 <<
"and the list of SSL errors contains"
117 << socket->sslErrors().count() <<
"items.";
119 if ( KIO::SslUi::askIgnoreSslErrors( socket ) ) {
125 kDebug() <<
"TLS negotiation done.";
130 bool lf2crlfAndDotStuffingRequested()
const {
return true; }
131 QString requestedSaslMethod()
const {
return saslMethod; }
132 TLSRequestState tlsRequested()
const {
return useTLS ? ForceTLS : ForceNoTLS; }
134 void socketConnected()
139 error( KIO::ERR_SLAVE_DEFINED, i18n(
"SSL negotiation failed." ) );
144 void socketDisconnected()
151 void socketError( KTcpSocket::Error err )
154 error( KIO::ERR_CONNECTION_BROKEN, socket->errorString() );
156 if ( socket->state() != KTcpSocket::ConnectedState ) {
163 bool sendCommandLine(
const QByteArray &cmdline )
165 if ( cmdline.
length() < 4096 ) {
166 kDebug( 7112 ) <<
"C: >>" << cmdline.
trimmed().
data() <<
"<<";
168 kDebug( 7112 ) <<
"C: <" << cmdline.
length() <<
" bytes>";
170 ssize_t numWritten, cmdline_len = cmdline.
length();
171 if ( ( numWritten = socket->write( cmdline ) ) != cmdline_len ) {
172 kDebug( 7112 ) <<
"Tried to write " << cmdline_len <<
" bytes, but only "
173 << numWritten <<
" were written!" << endl;
174 error( KIO::ERR_SLAVE_DEFINED, i18n (
"Writing to socket failed." ) );
180 bool run(
int type, TransactionState * ts = 0 )
182 return run( Command::createSimpleCommand( type,
this ), ts );
185 bool run( Command *cmd, TransactionState *ts = 0 )
188 Q_ASSERT( !currentCommand );
189 Q_ASSERT( !currentTransactionState || currentTransactionState == ts );
192 if ( cmd->doNotExecute( ts ) ) {
196 currentCommand = cmd;
197 currentTransactionState = ts;
199 while ( !cmd->isComplete() && !cmd->needsResponse() ) {
200 const QByteArray cmdLine = cmd->nextCommandLine( ts );
201 if ( ts && ts->failedFatally() ) {
202 q->disconnectFromHost(
false );
208 if ( !sendCommandLine( cmdLine ) ) {
209 q->disconnectFromHost(
false );
216 void queueCommand(
int type )
218 queueCommand( Command::createSimpleCommand( type,
this ) );
221 void queueCommand( KioSMTP::Command * command )
223 mPendingCommandQueue.enqueue( command );
226 bool runQueuedCommands( TransactionState *ts )
229 Q_ASSERT( !currentTransactionState || ts == currentTransactionState );
230 currentTransactionState = ts;
231 kDebug( canPipelineCommands(), 7112 ) <<
"using pipelining";
233 while ( !mPendingCommandQueue.isEmpty() ) {
234 QByteArray cmdline = collectPipelineCommands( ts );
235 if ( ts->failedFatally() ) {
236 q->disconnectFromHost(
false );
239 if ( ts->failed() ) {
245 if ( !sendCommandLine( cmdline ) || ts->failedFatally() ) {
246 q->disconnectFromHost(
false );
249 if ( !mSentCommandQueue.isEmpty() ) {
254 if ( ts->failed() ) {
255 kDebug() <<
"transaction state failed: " << ts->errorCode() << ts->errorMessage();
256 if ( errorMessage.isEmpty() ) {
257 errorMessage = ts->errorMessage();
259 state = SmtpSessionPrivate::Reset;
260 if ( !
run( Command::RSET, currentTransactionState ) ) {
261 q->disconnectFromHost(
false );
266 delete currentTransactionState;
267 currentTransactionState = 0;
271 QByteArray collectPipelineCommands( TransactionState *ts )
275 unsigned int cmdLine_len = 0;
277 while ( !mPendingCommandQueue.isEmpty() ) {
279 Command * cmd = mPendingCommandQueue.head();
281 if ( cmd->doNotExecute( ts ) ) {
282 delete mPendingCommandQueue.dequeue();
290 if ( cmdLine_len && cmd->mustBeFirstInPipeline() ) {
294 if ( cmdLine_len && !canPipelineCommands() ) {
298 while ( !cmd->isComplete() && !cmd->needsResponse() ) {
299 const QByteArray currentCmdLine = cmd->nextCommandLine( ts );
300 if ( ts->failedFatally() ) {
303 const unsigned int currentCmdLine_len = currentCmdLine.
length();
305 cmdLine_len += currentCmdLine_len;
306 cmdLine += currentCmdLine;
320 if ( dynamic_cast<TransferCommand *>( cmd ) != 0 &&
321 cmdLine_len >= 32 * 1024 ) {
326 mSentCommandQueue.enqueue( mPendingCommandQueue.dequeue() );
328 if ( cmd->mustBeLastInPipeline() ) {
336 void receivedNewData()
339 while ( socket->canReadLine() ) {
341 kDebug() <<
"S: >>" << buffer <<
"<<";
342 currentResponse.parseLine( buffer, buffer.
size() );
345 if ( currentResponse.isComplete() ) {
346 handleResponse( currentResponse );
347 currentResponse = Response();
348 }
else if ( !currentResponse.isWellFormed() ) {
349 error( KIO::ERR_NO_CONTENT,
350 i18n(
"Invalid SMTP response (%1) received.", currentResponse.code() ) );
355 void handleResponse(
const KioSMTP::Response &response )
357 if ( !mSentCommandQueue.isEmpty() ) {
358 Command *cmd = mSentCommandQueue.head();
359 Q_ASSERT( cmd->isComplete() );
360 cmd->processResponse( response, currentTransactionState );
361 if ( currentTransactionState->failedFatally() ) {
362 q->disconnectFromHost(
false );
364 delete mSentCommandQueue.dequeue();
366 if ( mSentCommandQueue.isEmpty() ) {
367 if ( !mPendingCommandQueue.isEmpty() ) {
368 runQueuedCommands( currentTransactionState );
369 }
else if ( state == Sending ) {
370 delete currentTransactionState;
371 currentTransactionState = 0;
372 q->disconnectFromHost();
378 if ( currentCommand ) {
379 if ( !currentCommand->processResponse( response, currentTransactionState ) ) {
380 q->disconnectFromHost(
false );
382 while ( !currentCommand->isComplete() && !currentCommand->needsResponse() ) {
383 const QByteArray cmdLine = currentCommand->nextCommandLine( currentTransactionState );
384 if ( currentTransactionState && currentTransactionState->failedFatally() ) {
385 q->disconnectFromHost(
false );
390 if ( !sendCommandLine( cmdLine ) ) {
391 q->disconnectFromHost(
false );
394 if ( currentCommand->isComplete() ) {
395 Command *cmd = currentCommand;
397 currentTransactionState = 0;
398 handleCommand( cmd );
407 if ( !response.isOk() ) {
408 error( KIO::ERR_COULD_NOT_LOGIN,
409 i18n(
"The server (%1) did not accept the connection.\n%2",
410 destination.host(), response.errorMessage() ) );
414 EHLOCommand *ehloCmdPreTLS =
new EHLOCommand(
this, myHostname );
415 run( ehloCmdPreTLS );
418 default: error( KIO::ERR_SLAVE_DEFINED, i18n(
"Unhandled response" ) );
422 void handleCommand( Command *cmd )
430 EHLOCommand *ehloCmdPostTLS =
new EHLOCommand(
this, myHostname );
431 run( ehloCmdPostTLS );
436 if ( ( haveCapability(
"STARTTLS" ) &&
437 tlsRequested() != SMTPSessionInterface::ForceNoTLS ) ||
438 tlsRequested() == SMTPSessionInterface::ForceTLS )
441 run( Command::STARTTLS );
450 if ( !destination.user().isEmpty() ||
451 haveCapability(
"AUTH" ) ||
452 !requestedSaslMethod().isEmpty() ) {
453 authInfo.username = destination.user();
454 authInfo.password = destination.password();
455 authInfo.prompt = i18n(
"Username and password for your SMTP account:" );
458 if ( !requestedSaslMethod().isEmpty() ) {
459 strList.
append( requestedSaslMethod() );
461 strList = capabilities().saslMethodsQSL();
464 state = Authenticated;
465 AuthCommand *authCmd =
467 destination.host(), authInfo );
476 queueCommand(
new MailFromCommand(
this, request.fromAddress().toLatin1(),
477 request.is8BitBody(), request.size() ) );
480 const QStringList recipients = request.recipients();
482 queueCommand(
new RcptToCommand(
this, ( *it ).toLatin1() ) );
485 queueCommand( Command::DATA );
486 queueCommand(
new TransferCommand(
this,
QByteArray() ) );
488 TransactionState *ts =
new TransactionState;
489 if ( !runQueuedCommands( ts ) ) {
490 if ( ts->errorCode() ) {
491 error( ts->errorCode(), ts->errorMessage() );
497 q->disconnectFromHost(
true );
500 error( KIO::ERR_SLAVE_DEFINED, i18n(
"Unhandled command response." ) );
513 KioSMTP::Response currentResponse;
514 KioSMTP::Command * currentCommand;
515 KioSMTP::TransactionState *currentTransactionState;
516 KIO::AuthInfo authInfo;
517 KioSMTP::Request request;
533 CommandQueue mPendingCommandQueue;
534 CommandQueue mSentCommandQueue;
536 static bool saslInitialized;
542 bool SmtpSessionPrivate::saslInitialized =
false;
544 SmtpSession::SmtpSession(
QObject *parent ) :
546 d( new SmtpSessionPrivate( this ) )
549 d->socket =
new KTcpSocket(
this );
550 connect( d->socket, SIGNAL(connected()), SLOT(socketConnected()) );
551 connect( d->socket, SIGNAL(disconnected()), SLOT(socketDisconnected()) );
552 connect( d->socket, SIGNAL(error(KTcpSocket::Error)), SLOT(socketError(KTcpSocket::Error)) );
553 connect( d->socket, SIGNAL(readyRead()), SLOT(receivedNewData()), Qt::QueuedConnection );
555 if ( !d->saslInitialized ) {
559 d->saslInitialized =
true;
563 SmtpSession::~SmtpSession()
571 d->saslMethod = method;
582 d->socket->connectToHost( url.host(), url.port() );
587 if ( d->socket->state() == KTcpSocket::ConnectedState ) {
589 d->run( Command::QUIT );
592 d->socket->disconnectFromHost();
594 d->clearCapabilities();
595 qDeleteAll( d->mPendingCommandQueue );
596 d->mPendingCommandQueue.clear();
597 qDeleteAll( d->mSentCommandQueue );
598 d->mSentCommandQueue.clear();
604 d->destination = destination;
605 if ( d->socket->state() != KTcpSocket::ConnectedState &&
606 d->socket->state() != KTcpSocket::ConnectingState ) {
611 d->request = Request::fromURL( destination );
613 if ( !d->request.heloHostname().isEmpty() ) {
614 d->myHostname = d->request.heloHostname();
617 if ( d->myHostname.isEmpty() ) {
619 }
else if ( !d->myHostname.contains(
QLatin1Char(
'.' ) ) ) {
627 return d->errorMessage;
630 #include "moc_smtpsession.cpp"
QByteArray trimmed() const
Connection to an SMTP server.
QString join(const QString &separator) const
void setSaslMethod(const QString &method)
Sets the SASL method used for authentication.
void append(const T &value)
void setUseTLS(bool useTLS)
Enable TLS encryption.
void disconnectFromHost(bool nice=true)
Close the connection to the SMTP server.
QFuture< T > run(Function function,...)
void connectToHost(const KUrl &url)
Open connection to host.
QByteArray toLatin1() const
QString errorMessage() const
Returns the error nmeesage, if any.
void sendMessage(const KUrl &destination, QIODevice *data)
Send a message.