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>
36 #include <kpimutils/networkaccesshelper.h>
39 #include <QtCore/QQueue>
40 #include <QtNetwork/QHostInfo>
42 using namespace MailTransport;
43 using namespace KioSMTP;
45 class MailTransport::SmtpSessionPrivate :
public KioSMTP::SMTPSessionInterface
48 explicit SmtpSessionPrivate(
SmtpSession *session ) :
52 currentTransactionState( 0 ),
58 int readData( QByteArray &ba )
60 if ( data->atEnd() ) {
64 Q_ASSERT( data->isOpen() );
65 ba = data->read( 32 * 1024 );
70 void error(
int id,
const QString &msg )
72 kDebug() <<
id << msg;
75 currentTransactionState = 0;
77 if ( errorMessage.isEmpty() ) {
78 errorMessage = KIO::buildErrorString(
id, msg );
80 q->disconnectFromHost();
83 void informationMessageBox(
const QString &msg,
const QString &caption )
85 KMessageBox::information( 0, msg, caption );
88 bool openPasswordDialog( KIO::AuthInfo &authInfo ) {
89 return KIO::PasswordDialog::getNameAndPassword(
92 &( authInfo.keepPassword ),
97 authInfo.commentLabel ) == KIO::PasswordDialog::Accepted;
104 socket->setAdvertisedSslVersion( KTcpSocket::TlsV1 );
105 socket->ignoreSslErrors();
106 socket->startClientEncryption();
107 const bool encrypted = socket->waitForEncrypted( 60 * 1000 );
109 const KSslCipher cipher = socket->sessionCipher();
111 socket->sslErrors().count() > 0 ||
112 socket->encryptionMode() != KTcpSocket::SslClientMode ||
114 cipher.usedBits() == 0 ) {
115 kDebug() <<
"Initial SSL handshake failed. cipher.isNull() is" << cipher.isNull()
116 <<
", cipher.usedBits() is" << cipher.usedBits()
117 <<
", the socket says:" << socket->errorString()
118 <<
"and the list of SSL errors contains"
119 << socket->sslErrors().count() <<
"items.";
121 if ( KIO::SslUi::askIgnoreSslErrors( socket ) ) {
127 kDebug() <<
"TLS negotiation done.";
132 bool lf2crlfAndDotStuffingRequested()
const {
return true; }
133 QString requestedSaslMethod()
const {
return saslMethod; }
134 TLSRequestState tlsRequested()
const {
return useTLS ? ForceTLS : ForceNoTLS; }
136 void socketConnected()
139 if ( destination.protocol() == QLatin1String(
"smtps" ) ) {
141 error( KIO::ERR_SLAVE_DEFINED, i18n(
"SSL negotiation failed." ) );
146 void socketDisconnected()
153 void socketError( KTcpSocket::Error err )
156 error( KIO::ERR_CONNECTION_BROKEN, socket->errorString() );
158 if ( socket->state() != KTcpSocket::ConnectedState ) {
165 bool sendCommandLine(
const QByteArray &cmdline )
167 if ( cmdline.length() < 4096 ) {
168 kDebug( 7112 ) <<
"C: >>" << cmdline.trimmed().data() <<
"<<";
170 kDebug( 7112 ) <<
"C: <" << cmdline.length() <<
" bytes>";
172 ssize_t numWritten, cmdline_len = cmdline.length();
173 if ( ( numWritten = socket->write( cmdline ) ) != cmdline_len ) {
174 kDebug( 7112 ) <<
"Tried to write " << cmdline_len <<
" bytes, but only "
175 << numWritten <<
" were written!" << endl;
176 error( KIO::ERR_SLAVE_DEFINED, i18n (
"Writing to socket failed." ) );
182 bool run(
int type, TransactionState * ts = 0 )
184 return run( Command::createSimpleCommand( type,
this ), ts );
187 bool run( Command *cmd, TransactionState *ts = 0 )
190 Q_ASSERT( !currentCommand );
191 Q_ASSERT( !currentTransactionState || currentTransactionState == ts );
194 if ( cmd->doNotExecute( ts ) ) {
198 currentCommand = cmd;
199 currentTransactionState = ts;
201 while ( !cmd->isComplete() && !cmd->needsResponse() ) {
202 const QByteArray cmdLine = cmd->nextCommandLine( ts );
203 if ( ts && ts->failedFatally() ) {
204 q->disconnectFromHost(
false );
207 if ( cmdLine.isEmpty() ) {
210 if ( !sendCommandLine( cmdLine ) ) {
211 q->disconnectFromHost(
false );
218 void queueCommand(
int type )
220 queueCommand( Command::createSimpleCommand( type,
this ) );
223 void queueCommand( KioSMTP::Command * command )
225 mPendingCommandQueue.enqueue( command );
228 bool runQueuedCommands( TransactionState *ts )
231 Q_ASSERT( !currentTransactionState || ts == currentTransactionState );
232 currentTransactionState = ts;
233 kDebug( canPipelineCommands(), 7112 ) <<
"using pipelining";
235 while ( !mPendingCommandQueue.isEmpty() ) {
236 QByteArray cmdline = collectPipelineCommands( ts );
237 if ( ts->failedFatally() ) {
238 q->disconnectFromHost(
false );
241 if ( ts->failed() ) {
244 if ( cmdline.isEmpty() ) {
247 if ( !sendCommandLine( cmdline ) || ts->failedFatally() ) {
248 q->disconnectFromHost(
false );
251 if ( !mSentCommandQueue.isEmpty() ) {
256 if ( ts->failed() ) {
257 kDebug() <<
"transaction state failed: " << ts->errorCode() << ts->errorMessage();
258 if ( errorMessage.isEmpty() ) {
259 errorMessage = ts->errorMessage();
261 state = SmtpSessionPrivate::Reset;
262 if ( !run( Command::RSET, currentTransactionState ) ) {
263 q->disconnectFromHost(
false );
268 delete currentTransactionState;
269 currentTransactionState = 0;
273 QByteArray collectPipelineCommands( TransactionState *ts )
277 unsigned int cmdLine_len = 0;
279 while ( !mPendingCommandQueue.isEmpty() ) {
281 Command * cmd = mPendingCommandQueue.head();
283 if ( cmd->doNotExecute( ts ) ) {
284 delete mPendingCommandQueue.dequeue();
292 if ( cmdLine_len && cmd->mustBeFirstInPipeline() ) {
296 if ( cmdLine_len && !canPipelineCommands() ) {
300 while ( !cmd->isComplete() && !cmd->needsResponse() ) {
301 const QByteArray currentCmdLine = cmd->nextCommandLine( ts );
302 if ( ts->failedFatally() ) {
305 const unsigned int currentCmdLine_len = currentCmdLine.length();
307 cmdLine_len += currentCmdLine_len;
308 cmdLine += currentCmdLine;
322 if ( dynamic_cast<TransferCommand *>( cmd ) != 0 &&
323 cmdLine_len >= 32 * 1024 ) {
328 mSentCommandQueue.enqueue( mPendingCommandQueue.dequeue() );
330 if ( cmd->mustBeLastInPipeline() ) {
338 void receivedNewData()
341 while ( socket->canReadLine() ) {
342 const QByteArray buffer = socket->readLine();
343 kDebug() <<
"S: >>" << buffer <<
"<<";
344 currentResponse.parseLine( buffer, buffer.size() );
347 if ( currentResponse.isComplete() ) {
348 handleResponse( currentResponse );
349 currentResponse = Response();
350 }
else if ( !currentResponse.isWellFormed() ) {
351 error( KIO::ERR_NO_CONTENT,
352 i18n(
"Invalid SMTP response (%1) received.", currentResponse.code() ) );
357 void handleResponse(
const KioSMTP::Response &response )
359 if ( !mSentCommandQueue.isEmpty() ) {
360 Command *cmd = mSentCommandQueue.head();
361 Q_ASSERT( cmd->isComplete() );
362 cmd->processResponse( response, currentTransactionState );
363 if ( currentTransactionState->failedFatally() ) {
364 q->disconnectFromHost(
false );
366 delete mSentCommandQueue.dequeue();
368 if ( mSentCommandQueue.isEmpty() ) {
369 if ( !mPendingCommandQueue.isEmpty() ) {
370 runQueuedCommands( currentTransactionState );
371 }
else if ( state == Sending ) {
372 delete currentTransactionState;
373 currentTransactionState = 0;
374 q->disconnectFromHost();
380 if ( currentCommand ) {
381 if ( !currentCommand->processResponse( response, currentTransactionState ) ) {
382 q->disconnectFromHost(
false );
384 while ( !currentCommand->isComplete() && !currentCommand->needsResponse() ) {
385 const QByteArray cmdLine = currentCommand->nextCommandLine( currentTransactionState );
386 if ( currentTransactionState && currentTransactionState->failedFatally() ) {
387 q->disconnectFromHost(
false );
389 if ( cmdLine.isEmpty() ) {
392 if ( !sendCommandLine( cmdLine ) ) {
393 q->disconnectFromHost(
false );
396 if ( currentCommand->isComplete() ) {
397 Command *cmd = currentCommand;
399 currentTransactionState = 0;
400 handleCommand( cmd );
409 if ( !response.isOk() ) {
410 error( KIO::ERR_COULD_NOT_LOGIN,
411 i18n(
"The server (%1) did not accept the connection.\n%2",
412 destination.host(), response.errorMessage() ) );
416 EHLOCommand *ehloCmdPreTLS =
new EHLOCommand(
this, myHostname );
417 run( ehloCmdPreTLS );
420 default: error( KIO::ERR_SLAVE_DEFINED, i18n(
"Unhandled response" ) );
424 void handleCommand( Command *cmd )
432 EHLOCommand *ehloCmdPostTLS =
new EHLOCommand(
this, myHostname );
433 run( ehloCmdPostTLS );
438 if ( ( haveCapability(
"STARTTLS" ) &&
439 tlsRequested() != SMTPSessionInterface::ForceNoTLS ) ||
440 tlsRequested() == SMTPSessionInterface::ForceTLS )
443 run( Command::STARTTLS );
452 if ( !destination.user().isEmpty() ||
453 haveCapability(
"AUTH" ) ||
454 !requestedSaslMethod().isEmpty() ) {
455 authInfo.username = destination.user();
456 authInfo.password = destination.password();
457 authInfo.prompt = i18n(
"Username and password for your SMTP account:" );
460 if ( !requestedSaslMethod().isEmpty() ) {
461 strList.append( requestedSaslMethod() );
463 strList = capabilities().saslMethodsQSL();
466 state = Authenticated;
467 AuthCommand *authCmd =
468 new AuthCommand(
this, strList.join( QLatin1String(
" " ) ).toLatin1(),
469 destination.host(), authInfo );
478 queueCommand(
new MailFromCommand(
this, request.fromAddress().toLatin1(),
479 request.is8BitBody(), request.size() ) );
482 const QStringList recipients = request.recipients();
483 for ( QStringList::const_iterator it = recipients.begin(); it != recipients.end(); ++it ) {
484 queueCommand(
new RcptToCommand(
this, ( *it ).toLatin1() ) );
487 queueCommand( Command::DATA );
488 queueCommand(
new TransferCommand(
this, QByteArray() ) );
490 TransactionState *ts =
new TransactionState;
491 if ( !runQueuedCommands( ts ) ) {
492 if ( ts->errorCode() ) {
493 error( ts->errorCode(), ts->errorMessage() );
499 q->disconnectFromHost(
true );
502 error( KIO::ERR_SLAVE_DEFINED, i18n(
"Unhandled command response." ) );
515 KioSMTP::Response currentResponse;
516 KioSMTP::Command * currentCommand;
517 KioSMTP::TransactionState *currentTransactionState;
518 KIO::AuthInfo authInfo;
519 KioSMTP::Request request;
520 QString errorMessage;
534 typedef QQueue<KioSMTP::Command*> CommandQueue;
535 CommandQueue mPendingCommandQueue;
536 CommandQueue mSentCommandQueue;
538 static bool saslInitialized;
544 bool SmtpSessionPrivate::saslInitialized =
false;
546 SmtpSession::SmtpSession( QObject *parent ) :
548 d( new SmtpSessionPrivate( this ) )
551 d->socket =
new KTcpSocket(
this );
552 connect( d->socket, SIGNAL(connected()), SLOT(socketConnected()) );
553 connect( d->socket, SIGNAL(disconnected()), SLOT(socketDisconnected()) );
554 connect( d->socket, SIGNAL(error(KTcpSocket::Error)), SLOT(socketError(KTcpSocket::Error)) );
555 connect( d->socket, SIGNAL(readyRead()), SLOT(receivedNewData()), Qt::QueuedConnection );
558 KPIMUtils::NetworkAccessHelper *networkHelper =
new KPIMUtils::NetworkAccessHelper(
this );
559 networkHelper->establishConnection();
561 if ( !d->saslInitialized ) {
565 d->saslInitialized =
true;
569 SmtpSession::~SmtpSession()
577 d->saslMethod = method;
588 d->socket->connectToHost( url.host(), url.port() );
593 if ( d->socket->state() == KTcpSocket::ConnectedState ) {
595 d->run( Command::QUIT );
598 d->socket->disconnectFromHost();
600 d->clearCapabilities();
601 qDeleteAll( d->mPendingCommandQueue );
602 d->mPendingCommandQueue.clear();
603 qDeleteAll( d->mSentCommandQueue );
604 d->mSentCommandQueue.clear();
610 d->destination = destination;
611 if ( d->socket->state() != KTcpSocket::ConnectedState &&
612 d->socket->state() != KTcpSocket::ConnectingState ) {
617 d->request = Request::fromURL( destination );
619 if ( !d->request.heloHostname().isEmpty() ) {
620 d->myHostname = d->request.heloHostname();
622 d->myHostname = QHostInfo::localHostName();
623 if ( d->myHostname.isEmpty() ) {
624 d->myHostname = QLatin1String(
"localhost.invalid" );
625 }
else if ( !d->myHostname.contains( QLatin1Char(
'.' ) ) ) {
626 d->myHostname += QLatin1String(
".localnet" );
633 return d->errorMessage;
636 #include "moc_smtpsession.cpp"
Connection to an SMTP server.
void setSaslMethod(const QString &method)
Sets the SASL method used for authentication.
void setUseTLS(bool useTLS)
Enable TLS encryption.
void disconnectFromHost(bool nice=true)
Close the connection to the SMTP server.
void connectToHost(const KUrl &url)
Open connection to host.
QString errorMessage() const
Returns the error nmeesage, if any.
void sendMessage(const KUrl &destination, QIODevice *data)
Send a message.