• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdepimlibs-4.14.10 API Reference
  • KDE Home
  • Contact Us
 

mailtransport

  • mailtransport
  • smtp
smtpsession.cpp
1/*
2 Copyright (c) 2010 Volker Krause <vkrause@kde.org>
3
4 This library is free software; you can redistribute it and/or modify it
5 under the terms of the GNU Library General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or (at your
7 option) any later version.
8
9 This library is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12 License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to the
16 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 02110-1301, USA.
18*/
19
20#include "smtpsession.h"
21
22#include "common.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"
28
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 <KDebug>
37
38#include <QtCore/QQueue>
39
40using namespace MailTransport;
41using namespace KioSMTP;
42
43class MailTransport::SmtpSessionPrivate : public KioSMTP::SMTPSessionInterface
44{
45 public:
46 explicit SmtpSessionPrivate( SmtpSession *session ) :
47 useTLS( true ),
48 socket( 0 ),
49 currentCommand( 0 ),
50 currentTransactionState( 0 ),
51 state( Initial ),
52 q( session )
53 {}
54
55 void dataReq() { /* noop */ };
56 int readData( QByteArray &ba )
57 {
58 if ( data->atEnd() ) {
59 ba.clear();
60 return 0;
61 } else {
62 Q_ASSERT( data->isOpen() );
63 ba = data->read( 32 * 1024 );
64 return ba.size();
65 }
66 }
67
68 void error( int id, const QString &msg )
69 {
70 kDebug() << id << msg;
71 // clear state so further replies don't end up in failed commands etc.
72 currentCommand = 0;
73 currentTransactionState = 0;
74
75 if ( errorMessage.isEmpty() ) {
76 errorMessage = KIO::buildErrorString( id, msg );
77 }
78 q->disconnectFromHost();
79 }
80
81 void informationMessageBox( const QString &msg, const QString &caption )
82 {
83 KMessageBox::information( 0, msg, caption );
84 }
85
86 bool openPasswordDialog( KIO::AuthInfo &authInfo ) {
87 return KIO::PasswordDialog::getNameAndPassword(
88 authInfo.username,
89 authInfo.password,
90 &( authInfo.keepPassword ),
91 authInfo.prompt,
92 authInfo.readOnly,
93 authInfo.caption,
94 authInfo.comment,
95 authInfo.commentLabel ) == KIO::PasswordDialog::Accepted;
96 }
97
98 bool startSsl()
99 {
100 kDebug();
101 Q_ASSERT( socket );
102 socket->setAdvertisedSslVersion( KTcpSocket::TlsV1 );
103 socket->ignoreSslErrors();
104 socket->startClientEncryption();
105 const bool encrypted = socket->waitForEncrypted( 60 * 1000 );
106
107 const KSslCipher cipher = socket->sessionCipher();
108 if ( !encrypted ||
109 socket->sslErrors().count() > 0 ||
110 socket->encryptionMode() != KTcpSocket::SslClientMode ||
111 cipher.isNull() ||
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.";
118
119 if ( KIO::SslUi::askIgnoreSslErrors( socket ) ) {
120 return true;
121 } else {
122 return false;
123 }
124 } else {
125 kDebug() << "TLS negotiation done.";
126 return true;
127 }
128 }
129
130 bool lf2crlfAndDotStuffingRequested() const { return true; }
131 QString requestedSaslMethod() const { return saslMethod; }
132 TLSRequestState tlsRequested() const { return useTLS ? ForceTLS : ForceNoTLS; }
133
134 void socketConnected()
135 {
136 kDebug();
137 if ( destination.protocol() == QLatin1String( "smtps" ) ) {
138 if ( !startSsl() ) {
139 error( KIO::ERR_SLAVE_DEFINED, i18n( "SSL negotiation failed." ) );
140 }
141 }
142 }
143
144 void socketDisconnected()
145 {
146 kDebug();
147 emit q->result( q );
148 q->deleteLater();
149 }
150
151 void socketError( KTcpSocket::Error err )
152 {
153 kDebug() << err;
154 error( KIO::ERR_CONNECTION_BROKEN, socket->errorString() );
155
156 if ( socket->state() != KTcpSocket::ConnectedState ) {
157 // we have been disconnected by the error condition already, so just signal error result
158 emit q->result( q );
159 q->deleteLater();
160 }
161 }
162
163 bool sendCommandLine( const QByteArray &cmdline )
164 {
165 if ( cmdline.length() < 4096 ) {
166 kDebug( 7112 ) << "C: >>" << cmdline.trimmed().data() << "<<";
167 } else {
168 kDebug( 7112 ) << "C: <" << cmdline.length() << " bytes>";
169 }
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." ) );
175 return false;
176 }
177 return true;
178 }
179
180 bool run( int type, TransactionState * ts = 0 )
181 {
182 return run( Command::createSimpleCommand( type, this ), ts );
183 }
184
185 bool run( Command *cmd, TransactionState *ts = 0 )
186 {
187 Q_ASSERT( cmd );
188 Q_ASSERT( !currentCommand );
189 Q_ASSERT( !currentTransactionState || currentTransactionState == ts );
190
191 // ### WTF?
192 if ( cmd->doNotExecute( ts ) ) {
193 return true;
194 }
195
196 currentCommand = cmd;
197 currentTransactionState = ts;
198
199 while ( !cmd->isComplete() && !cmd->needsResponse() ) {
200 const QByteArray cmdLine = cmd->nextCommandLine( ts );
201 if ( ts && ts->failedFatally() ) {
202 q->disconnectFromHost( false );
203 return false;
204 }
205 if ( cmdLine.isEmpty() ) {
206 continue;
207 }
208 if ( !sendCommandLine( cmdLine ) ) {
209 q->disconnectFromHost( false );
210 return false;
211 }
212 }
213 return true;
214 }
215
216 void queueCommand( int type )
217 {
218 queueCommand( Command::createSimpleCommand( type, this ) );
219 }
220
221 void queueCommand( KioSMTP::Command * command )
222 {
223 mPendingCommandQueue.enqueue( command );
224 }
225
226 bool runQueuedCommands( TransactionState *ts )
227 {
228 Q_ASSERT( ts );
229 Q_ASSERT( !currentTransactionState || ts == currentTransactionState );
230 currentTransactionState = ts;
231 kDebug( canPipelineCommands(), 7112 ) << "using pipelining";
232
233 while ( !mPendingCommandQueue.isEmpty() ) {
234 QByteArray cmdline = collectPipelineCommands( ts );
235 if ( ts->failedFatally() ) {
236 q->disconnectFromHost( false );
237 return false;
238 }
239 if ( ts->failed() ) {
240 break;
241 }
242 if ( cmdline.isEmpty() ) {
243 continue;
244 }
245 if ( !sendCommandLine( cmdline ) || ts->failedFatally() ) {
246 q->disconnectFromHost( false );
247 return false;
248 }
249 if ( !mSentCommandQueue.isEmpty() ) {
250 return true; // wait for responses
251 }
252 }
253
254 if ( ts->failed() ) {
255 kDebug() << "transaction state failed: " << ts->errorCode() << ts->errorMessage();
256 if ( errorMessage.isEmpty() ) {
257 errorMessage = ts->errorMessage();
258 }
259 state = SmtpSessionPrivate::Reset;
260 if ( !run( Command::RSET, currentTransactionState ) ) {
261 q->disconnectFromHost( false );
262 }
263 return false;
264 }
265
266 delete currentTransactionState;
267 currentTransactionState = 0;
268 return true;
269 }
270
271 QByteArray collectPipelineCommands( TransactionState *ts )
272 {
273 Q_ASSERT( ts );
274 QByteArray cmdLine;
275 unsigned int cmdLine_len = 0;
276
277 while ( !mPendingCommandQueue.isEmpty() ) {
278
279 Command * cmd = mPendingCommandQueue.head();
280
281 if ( cmd->doNotExecute( ts ) ) {
282 delete mPendingCommandQueue.dequeue();
283 if ( cmdLine_len ) {
284 break;
285 } else {
286 continue;
287 }
288 }
289
290 if ( cmdLine_len && cmd->mustBeFirstInPipeline() ) {
291 break;
292 }
293
294 if ( cmdLine_len && !canPipelineCommands() ) {
295 break;
296 }
297
298 while ( !cmd->isComplete() && !cmd->needsResponse() ) {
299 const QByteArray currentCmdLine = cmd->nextCommandLine( ts );
300 if ( ts->failedFatally() ) {
301 return cmdLine;
302 }
303 const unsigned int currentCmdLine_len = currentCmdLine.length();
304
305 cmdLine_len += currentCmdLine_len;
306 cmdLine += currentCmdLine;
307
308 // If we are executing the transfer command, don't collect the whole
309 // command line (which may be several MBs) before sending it, but instead
310 // send the data each time we have collected 32 KB of the command line.
311 //
312 // This way, the progress information in clients like KMail works correctly,
313 // because otherwise, the TransferCommand would read the whole data from the
314 // job at once, then sending it. The progress update on the client however
315 // happens when sending data to the job, not when this slave writes the data
316 // to the socket. Therefore that progress update is incorrect.
317 //
318 // 32 KB seems to be a sensible limit. Additionally, a job can only transfer
319 // 32 KB at once anyway.
320 if ( dynamic_cast<TransferCommand *>( cmd ) != 0 &&
321 cmdLine_len >= 32 * 1024 ) {
322 return cmdLine;
323 }
324 }
325
326 mSentCommandQueue.enqueue( mPendingCommandQueue.dequeue() );
327
328 if ( cmd->mustBeLastInPipeline() ) {
329 break;
330 }
331 }
332
333 return cmdLine;
334 }
335
336 void receivedNewData()
337 {
338 kDebug();
339 while ( socket->canReadLine() ) {
340 const QByteArray buffer = socket->readLine();
341 kDebug() << "S: >>" << buffer << "<<";
342 currentResponse.parseLine( buffer, buffer.size() );
343 // ...until the response is complete or the parser is so confused
344 // that it doesn't think a RSET would help anymore:
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() ) );
351 }
352 }
353 }
354
355 void handleResponse( const KioSMTP::Response &response )
356 {
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 );
363 }
364 delete mSentCommandQueue.dequeue();
365
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(); // we are done
373 }
374 }
375 return;
376 }
377
378 if ( currentCommand ) {
379 if ( !currentCommand->processResponse( response, currentTransactionState ) ) {
380 q->disconnectFromHost( false );
381 }
382 while ( !currentCommand->isComplete() && !currentCommand->needsResponse() ) {
383 const QByteArray cmdLine = currentCommand->nextCommandLine( currentTransactionState );
384 if ( currentTransactionState && currentTransactionState->failedFatally() ) {
385 q->disconnectFromHost( false );
386 }
387 if ( cmdLine.isEmpty() ) {
388 continue;
389 }
390 if ( !sendCommandLine( cmdLine ) ) {
391 q->disconnectFromHost( false );
392 }
393 }
394 if ( currentCommand->isComplete() ) {
395 Command *cmd = currentCommand;
396 currentCommand = 0;
397 currentTransactionState = 0;
398 handleCommand( cmd );
399 }
400 return;
401 }
402
403 // command-less responses
404 switch ( state ) {
405 case Initial: // server greeting
406 {
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() ) );
411 break;
412 }
413 state = EHLOPreTls;
414 EHLOCommand *ehloCmdPreTLS = new EHLOCommand( this, myHostname );
415 run( ehloCmdPreTLS );
416 break;
417 }
418 default: error( KIO::ERR_SLAVE_DEFINED, i18n( "Unhandled response" ) );
419 }
420 }
421
422 void handleCommand( Command *cmd )
423 {
424 switch ( state ) {
425 case StartTLS:
426 {
427 // re-issue EHLO to refresh the capability list (could be have
428 // been faked before TLS was enabled):
429 state = EHLOPostTls;
430 EHLOCommand *ehloCmdPostTLS = new EHLOCommand( this, myHostname );
431 run( ehloCmdPostTLS );
432 break;
433 }
434 case EHLOPreTls:
435 {
436 if ( ( haveCapability( "STARTTLS" ) &&
437 tlsRequested() != SMTPSessionInterface::ForceNoTLS ) ||
438 tlsRequested() == SMTPSessionInterface::ForceTLS )
439 {
440 state = StartTLS;
441 run( Command::STARTTLS );
442 break;
443 }
444 }
445 // fall through
446 case EHLOPostTls:
447 {
448 // return with success if the server doesn't support SMTP-AUTH or an user
449 // name is not specified and metadata doesn't tell us to force it.
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:" );
456
457 QStringList strList;
458 if ( !requestedSaslMethod().isEmpty() ) {
459 strList.append( requestedSaslMethod() );
460 } else {
461 strList = capabilities().saslMethodsQSL();
462 }
463
464 state = Authenticated;
465 AuthCommand *authCmd =
466 new AuthCommand( this, strList.join( QLatin1String( " " ) ).toLatin1(),
467 destination.host(), authInfo );
468 run( authCmd );
469 break;
470 }
471 }
472 // fall through
473 case Authenticated:
474 {
475 state = Sending;
476 queueCommand( new MailFromCommand( this, request.fromAddress().toLatin1(),
477 request.is8BitBody(), request.size() ) );
478 // Loop through our To and CC recipients, and send the proper
479 // SMTP commands, for the benefit of the server.
480 const QStringList recipients = request.recipients();
481 for ( QStringList::const_iterator it = recipients.begin(); it != recipients.end(); ++it ) {
482 queueCommand( new RcptToCommand( this, ( *it ).toLatin1() ) );
483 }
484
485 queueCommand( Command::DATA );
486 queueCommand( new TransferCommand( this, QByteArray() ) );
487
488 TransactionState *ts = new TransactionState;
489 if ( !runQueuedCommands( ts ) ) {
490 if ( ts->errorCode() ) {
491 error( ts->errorCode(), ts->errorMessage() );
492 }
493 }
494 break;
495 }
496 case Reset:
497 q->disconnectFromHost( true );
498 break;
499 default:
500 error( KIO::ERR_SLAVE_DEFINED, i18n( "Unhandled command response." ) );
501 }
502
503 delete cmd;
504 }
505
506 public:
507 QString saslMethod;
508 bool useTLS;
509
510 KUrl destination;
511 KTcpSocket *socket;
512 QIODevice *data;
513 KioSMTP::Response currentResponse;
514 KioSMTP::Command * currentCommand;
515 KioSMTP::TransactionState *currentTransactionState;
516 KIO::AuthInfo authInfo;
517 KioSMTP::Request request;
518 QString errorMessage;
519 QString myHostname;
520
521 enum State {
522 Initial,
523 EHLOPreTls,
524 StartTLS,
525 EHLOPostTls,
526 Authenticated,
527 Sending,
528 Reset
529 };
530 State state;
531
532 typedef QQueue<KioSMTP::Command*> CommandQueue;
533 CommandQueue mPendingCommandQueue;
534 CommandQueue mSentCommandQueue;
535
536 static bool saslInitialized;
537
538 private:
539 SmtpSession *q;
540};
541
542bool SmtpSessionPrivate::saslInitialized = false;
543
544SmtpSession::SmtpSession( QObject *parent ) :
545 QObject( parent ),
546 d( new SmtpSessionPrivate( this ) )
547{
548 kDebug();
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 );
554
555 if ( !d->saslInitialized ) {
556 if ( !initSASL() ) {
557 exit( -1 );
558 }
559 d->saslInitialized = true;
560 }
561}
562
563SmtpSession::~SmtpSession()
564{
565 kDebug();
566 delete d;
567}
568
569void SmtpSession::setSaslMethod( const QString &method )
570{
571 d->saslMethod = method;
572}
573
574void SmtpSession::setUseTLS( bool useTLS )
575{
576 d->useTLS = useTLS;
577}
578
579void SmtpSession::connectToHost( const KUrl &url )
580{
581 kDebug() << url;
582 d->socket->connectToHost( url.host(), url.port() );
583}
584
585void SmtpSession::disconnectFromHost( bool nice )
586{
587 if ( d->socket->state() == KTcpSocket::ConnectedState ) {
588 if ( nice ) {
589 d->run( Command::QUIT );
590 }
591
592 d->socket->disconnectFromHost();
593
594 d->clearCapabilities();
595 qDeleteAll( d->mPendingCommandQueue );
596 d->mPendingCommandQueue.clear();
597 qDeleteAll( d->mSentCommandQueue );
598 d->mSentCommandQueue.clear();
599 }
600}
601
602void SmtpSession::sendMessage( const KUrl &destination, QIODevice *data )
603{
604 d->destination = destination;
605 if ( d->socket->state() != KTcpSocket::ConnectedState &&
606 d->socket->state() != KTcpSocket::ConnectingState ) {
607 connectToHost( destination );
608 }
609
610 d->data = data;
611 d->request = Request::fromURL( destination ); // parse settings from URL's query
612
613 if ( !d->request.heloHostname().isEmpty() ) {
614 d->myHostname = d->request.heloHostname();
615 } else {
616 d->myHostname = QHostInfo::localHostName();
617 if ( d->myHostname.isEmpty() ) {
618 d->myHostname = QLatin1String( "localhost.invalid" );
619 } else if ( !d->myHostname.contains( QLatin1Char( '.' ) ) ) {
620 d->myHostname += QLatin1String( ".localnet" );
621 }
622 }
623}
624
625QString SmtpSession::errorMessage() const
626{
627 return d->errorMessage;
628}
629
630#include "moc_smtpsession.cpp"
MailTransport::SmtpSession
Connection to an SMTP server.
Definition smtpsession.h:34
MailTransport::SmtpSession::result
void result(MailTransport::SmtpSession *session)
Emitted when an email transfer has been completed.
MailTransport::SmtpSession::setUseTLS
void setUseTLS(bool useTLS)
Enable TLS encryption.
Definition smtpsession.cpp:574
MailTransport::SmtpSession::disconnectFromHost
void disconnectFromHost(bool nice=true)
Close the connection to the SMTP server.
Definition smtpsession.cpp:585
MailTransport::SmtpSession::setSaslMethod
void setSaslMethod(const QString &method)
Sets the SASL method used for authentication.
Definition smtpsession.cpp:569
MailTransport::SmtpSession::sendMessage
void sendMessage(const KUrl &destination, QIODevice *data)
Send a message.
Definition smtpsession.cpp:602
MailTransport::SmtpSession::errorMessage
QString errorMessage() const
Returns the error nmeesage, if any.
Definition smtpsession.cpp:625
MailTransport::SmtpSession::connectToHost
void connectToHost(const KUrl &url)
Open connection to host.
Definition smtpsession.cpp:579
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Wed Jan 24 2024 00:00:00 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

mailtransport

Skip menu "mailtransport"
  • Main Page
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • Related Pages

kdepimlibs-4.14.10 API Reference

Skip menu "kdepimlibs-4.14.10 API Reference"
  • akonadi
  •   contact
  •   kmime
  •   socialutils
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal