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

mailtransport

  • mailtransport
servertest.cpp
1 /*
2  Copyright (c) 2006 - 2007 Volker Krause <vkrause@kde.org>
3  Copyright (C) 2007 KovoKs <info@kovoks.nl>
4  Copyright (c) 2008 Thomas McGuire <thomas.mcguire@gmx.net>
5 
6  This library is free software; you can redistribute it and/or modify it
7  under the terms of the GNU Library General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or (at your
9  option) any later version.
10 
11  This library is distributed in the hope that it will be useful, but WITHOUT
12  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
14  License for more details.
15 
16  You should have received a copy of the GNU Library General Public License
17  along with this library; see the file COPYING.LIB. If not, write to the
18  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19  02110-1301, USA.
20 */
21 
22 // Own
23 #include "servertest.h"
24 #include "socket.h"
25 
26 #include <mailtransport/transportbase.h>
27 #include <mailtransport/mailtransport_defs.h>
28 
29 // Qt
30 #include <QHostInfo>
31 #include <QProgressBar>
32 #include <QTimer>
33 
34 // KDE
35 #include <KLocalizedString>
36 #include <KDebug>
37 
38 using namespace MailTransport;
39 
40 namespace MailTransport
41 {
42 
43 class ServerTestPrivate
44 {
45  public:
46  ServerTestPrivate( ServerTest *test );
47 
48  ServerTest *const q;
49  QString server;
50  QString fakeHostname;
51  QString testProtocol;
52 
53  MailTransport::Socket *normalSocket;
54  MailTransport::Socket *secureSocket;
55 
56  QSet< int > connectionResults;
57  QHash< int, QList<int> > authenticationResults;
58  QSet< ServerTest::Capability > capabilityResults;
59  QHash< int, uint > customPorts;
60  QTimer *normalSocketTimer;
61  QTimer *secureSocketTimer;
62  QTimer *progressTimer;
63 
64  QProgressBar *testProgress;
65 
66  bool secureSocketFinished;
67  bool normalSocketFinished;
68  bool tlsFinished;
69  bool popSupportsTLS;
70  int normalStage;
71  int secureStage;
72  int encryptionMode;
73 
74  bool normalPossible;
75  bool securePossible;
76 
77  void finalResult();
78  void handleSMTPIMAPResponse( int type, const QString &text );
79  void sendInitialCapabilityQuery( MailTransport::Socket *socket );
80  bool handlePopConversation( MailTransport::Socket *socket, int type, int stage,
81  const QString &response, bool *shouldStartTLS );
82  QList< int > parseAuthenticationList( const QStringList &authentications );
83 
84  // slots
85  void slotNormalPossible();
86  void slotNormalNotPossible();
87  void slotSslPossible();
88  void slotSslNotPossible();
89  void slotTlsDone();
90  void slotReadNormal( const QString &text );
91  void slotReadSecure( const QString &text );
92  void slotUpdateProgress();
93 };
94 
95 }
96 
97 ServerTestPrivate::ServerTestPrivate( ServerTest *test )
98  : q( test ), testProgress( 0 ), secureSocketFinished( false ),
99  normalSocketFinished( false ), tlsFinished( false ),
100  normalPossible( true ), securePossible( true )
101 {
102 }
103 
104 void ServerTestPrivate::finalResult()
105 {
106  if ( !secureSocketFinished || !normalSocketFinished || !tlsFinished ) {
107  return;
108  }
109 
110  kDebug() << "Modes:" << connectionResults;
111  kDebug() << "Capabilities:" << capabilityResults;
112  kDebug() << "Normal:" << q->normalProtocols();
113  kDebug() << "SSL:" << q->secureProtocols();
114  kDebug() << "TLS:" << q->tlsProtocols();
115 
116  if ( testProgress ) {
117  testProgress->hide();
118  }
119  progressTimer->stop();
120  secureSocketFinished = false;
121  normalSocketFinished = false;
122  tlsFinished = false ;
123 
124  emit q->finished( connectionResults.toList() );
125 }
126 
127 QList< int > ServerTestPrivate::parseAuthenticationList( const QStringList &authentications )
128 {
129  QList< int > result;
130  for ( QStringList::ConstIterator it = authentications.begin();
131  it != authentications.end(); ++it ) {
132  QString current = (*it).toUpper();
133  if ( current == QLatin1String( "LOGIN" ) ) {
134  result << Transport::EnumAuthenticationType::LOGIN;
135  } else if ( current == QLatin1String( "PLAIN" ) ) {
136  result << Transport::EnumAuthenticationType::PLAIN;
137  } else if ( current == QLatin1String( "CRAM-MD5" ) ) {
138  result << Transport::EnumAuthenticationType::CRAM_MD5;
139  } else if ( current == QLatin1String( "DIGEST-MD5" ) ) {
140  result << Transport::EnumAuthenticationType::DIGEST_MD5;
141  } else if ( current == QLatin1String( "NTLM" ) ) {
142  result << Transport::EnumAuthenticationType::NTLM;
143  } else if ( current == QLatin1String( "GSSAPI" ) ) {
144  result << Transport::EnumAuthenticationType::GSSAPI;
145  } else if ( current == QLatin1String( "ANONYMOUS" ) ) {
146  result << Transport::EnumAuthenticationType::ANONYMOUS;
147  }
148  // APOP is handled by handlePopConversation()
149  }
150  kDebug() << authentications << result;
151 
152  // LOGIN doesn't offer anything over PLAIN, requires more server
153  // roundtrips and is not an official SASL mechanism, but a MS-ism,
154  // so only enable it if PLAIN isn't available:
155  if ( result.contains( Transport::EnumAuthenticationType::PLAIN ) ) {
156  result.removeAll( Transport::EnumAuthenticationType::LOGIN );
157  }
158 
159  return result;
160 }
161 
162 void ServerTestPrivate::handleSMTPIMAPResponse( int type, const QString &text )
163 {
164  if ( !text.contains( QLatin1String( "AUTH" ), Qt::CaseInsensitive ) ) {
165  kDebug() << "No authentication possible";
166  return;
167  }
168 
169  QStringList protocols;
170  protocols << QLatin1String( "LOGIN" ) << QLatin1String( "PLAIN" )
171  << QLatin1String( "CRAM-MD5" ) << QLatin1String( "DIGEST-MD5" )
172  << QLatin1String( "NTLM" ) << QLatin1String( "GSSAPI" )
173  << QLatin1String( "ANONYMOUS" );
174 
175  QStringList results;
176  for ( int i = 0; i < protocols.count(); ++i ) {
177  if ( text.contains( protocols.at( i ), Qt::CaseInsensitive ) ) {
178  results.append( protocols.at( i ) );
179  }
180  }
181 
182  authenticationResults[type] = parseAuthenticationList( results );
183 
184  // if we couldn't parse any authentication modes, default to clear-text
185  if ( authenticationResults[type].size() == 0 ) {
186  authenticationResults[type] << Transport::EnumAuthenticationType::CLEAR;
187  }
188 
189  kDebug() << "For type" << type << ", we have:" << authenticationResults[type];
190 }
191 
192 void ServerTestPrivate::slotNormalPossible()
193 {
194  normalSocketTimer->stop();
195  connectionResults << Transport::EnumEncryption::None;
196 }
197 
198 void ServerTestPrivate::sendInitialCapabilityQuery( MailTransport::Socket *socket )
199 {
200  if ( testProtocol == IMAP_PROTOCOL ) {
201  socket->write( QLatin1String( "1 CAPABILITY" ) );
202  }
203 
204  else if ( testProtocol == SMTP_PROTOCOL ) {
205 
206  // Detect the hostname which we send with the EHLO command.
207  // If there is a fake one set, use that, otherwise use the
208  // local host name (and make sure it contains a domain, so the
209  // server thinks it is valid).
210  QString hostname;
211  if ( !fakeHostname.isNull() ) {
212  hostname = fakeHostname;
213  } else {
214  hostname = QHostInfo::localHostName();
215  if ( hostname.isEmpty() ) {
216  hostname = QLatin1String( "localhost.invalid" );
217  } else if ( !hostname.contains( QChar::fromAscii( '.' ) ) ) {
218  hostname += QLatin1String( ".localnet" );
219  }
220  }
221  kDebug() << "Hostname for EHLO is" << hostname;
222 
223  socket->write( QLatin1String( "EHLO " ) + hostname );
224  }
225 }
226 
227 void ServerTestPrivate::slotTlsDone()
228 {
229 
230  // The server will not send a response after starting TLS. Therefore, we have to manually
231  // call slotReadNormal(), because this is not triggered by a data received signal this time.
232  slotReadNormal( QString() );
233 }
234 
235 bool ServerTestPrivate::handlePopConversation( MailTransport::Socket *socket, int type, int stage,
236  const QString &response, bool *shouldStartTLS )
237 {
238  Q_ASSERT( shouldStartTLS != 0 );
239 
240  // Initial Greeting
241  if ( stage == 0 ) {
242 
243  //Regexp taken from POP3 ioslave
244  QString responseWithoutCRLF = response;
245  responseWithoutCRLF.chop( 2 );
246  QRegExp re( QLatin1String( "<[A-Za-z0-9\\.\\-_]+@[A-Za-z0-9\\.\\-_]+>$" ),
247  Qt::CaseInsensitive );
248  if ( responseWithoutCRLF.indexOf( re ) != -1 ) {
249  authenticationResults[type] << Transport::EnumAuthenticationType::APOP;
250  }
251 
252  //Each server is supposed to support clear text login
253  authenticationResults[type] << Transport::EnumAuthenticationType::CLEAR;
254 
255  // If we are in TLS stage, the server does not send the initial greeting.
256  // Assume that the APOP availability is the same as with an unsecured connection.
257  if ( type == Transport::EnumEncryption::TLS &&
258  authenticationResults[Transport::EnumEncryption::None].
259  contains( Transport::EnumAuthenticationType::APOP ) ) {
260  authenticationResults[Transport::EnumEncryption::TLS]
261  << Transport::EnumAuthenticationType::APOP;
262  }
263 
264  socket->write( QLatin1String( "CAPA" ) );
265  return true;
266  }
267 
268  // CAPA result
269  else if ( stage == 1 ) {
270 // Example:
271 // CAPA
272 // +OK
273 // TOP
274 // USER
275 // SASL LOGIN CRAM-MD5
276 // UIDL
277 // RESP-CODES
278 // .
279  if ( response.contains( QLatin1String( "TOP" ) ) ) {
280  capabilityResults += ServerTest::Top;
281  }
282  if ( response.contains( QLatin1String( "PIPELINING" ) ) ) {
283  capabilityResults += ServerTest::Pipelining;
284  }
285  if ( response.contains( QLatin1String( "UIDL" ) ) ) {
286  capabilityResults += ServerTest::UIDL;
287  }
288  if ( response.contains( QLatin1String( "STLS" ) ) ) {
289  connectionResults << Transport::EnumEncryption::TLS;
290  popSupportsTLS = true;
291  }
292  socket->write( QLatin1String( "AUTH" ) );
293  return true;
294  }
295 
296  // AUTH response
297  else if ( stage == 2 ) {
298 // Example:
299 // C: AUTH
300 // S: +OK List of supported authentication methods follows
301 // S: LOGIN
302 // S: CRAM-MD5
303 // S:.
304  QString formattedReply = response;
305 
306  // Get rid of trailling ".CRLF"
307  formattedReply.chop( 3 );
308 
309  // Get rid of the first +OK line
310  formattedReply = formattedReply.right( formattedReply.size() -
311  formattedReply.indexOf( QLatin1Char( '\n' ) ) - 1 );
312  formattedReply =
313  formattedReply.replace( QLatin1Char( ' ' ), QLatin1Char( '-' ) ).
314  replace( QLatin1String( "\r\n" ), QLatin1String( " " ) );
315 
316  authenticationResults[type] +=
317  parseAuthenticationList( formattedReply.split( QLatin1Char( ' ' ) ) );
318  }
319 
320  *shouldStartTLS = popSupportsTLS;
321  return false;
322 }
323 
324 // slotReadNormal() handles normal (no) encryption and TLS encryption.
325 // At first, the communication is not encrypted, but if the server supports
326 // the STARTTLS/STLS keyword, the same authentication query is done again
327 // with TLS.
328 void ServerTestPrivate::slotReadNormal( const QString &text )
329 {
330  Q_ASSERT( encryptionMode != Transport::EnumEncryption::SSL );
331  static const int tlsHandshakeStage = 42;
332 
333  kDebug() << "Stage" << normalStage + 1 << ", Mode" << encryptionMode;
334 
335  // If we are in stage 42, we just do the handshake for TLS encryption and
336  // then reset the stage to -1, so that all authentication modes and
337  // capabilities are queried again for TLS encryption (some servers have
338  // different authentication methods in normal and in TLS mode).
339  if ( normalStage == tlsHandshakeStage ) {
340  Q_ASSERT( encryptionMode == Transport::EnumEncryption::TLS );
341  normalStage = -1;
342  normalSocket->startTLS();
343  return;
344  }
345 
346  bool shouldStartTLS = false;
347  normalStage++;
348 
349  // Handle the whole POP converstation separatly, it is very different from
350  // IMAP and SMTP
351  if ( testProtocol == POP_PROTOCOL ) {
352  if ( handlePopConversation( normalSocket, encryptionMode, normalStage, text,
353  &shouldStartTLS ) ) {
354  return;
355  }
356  } else {
357  // Handle the SMTP/IMAP conversation here. We just send the EHLO command in
358  // sendInitialCapabilityQuery.
359  if ( normalStage == 0 ) {
360  sendInitialCapabilityQuery( normalSocket );
361  return;
362  }
363 
364  if ( text.contains( QLatin1String( "STARTTLS" ), Qt::CaseInsensitive ) ) {
365  connectionResults << Transport::EnumEncryption::TLS;
366  shouldStartTLS = true;
367  }
368  handleSMTPIMAPResponse( encryptionMode, text );
369  }
370 
371  // If we reach here, the normal authentication/capabilities query is completed.
372  // Now do the same for TLS.
373  normalSocketFinished = true;
374 
375  // If the server announced that STARTTLS/STLS is available, we'll add TLS to the
376  // connection result, do the command and set the stage to 42 to start the handshake.
377  if ( shouldStartTLS && encryptionMode == Transport::EnumEncryption::None ) {
378  kDebug() << "Trying TLS...";
379  connectionResults << Transport::EnumEncryption::TLS;
380  if ( testProtocol == POP_PROTOCOL ) {
381  normalSocket->write( QLatin1String( "STLS" ) );
382  } else if ( testProtocol == IMAP_PROTOCOL ) {
383  normalSocket->write( QLatin1String( "2 STARTTLS" ) );
384  } else {
385  normalSocket->write( QLatin1String( "STARTTLS" ) );
386  }
387  encryptionMode = Transport::EnumEncryption::TLS;
388  normalStage = tlsHandshakeStage;
389  return;
390  }
391 
392  // If we reach here, either the TLS authentication/capabilities query is finished
393  // or the server does not support the STARTTLS/STLS command.
394  tlsFinished = true;
395  finalResult();
396 }
397 
398 void ServerTestPrivate::slotReadSecure( const QString &text )
399 {
400  secureStage++;
401  if ( testProtocol == POP_PROTOCOL ) {
402  bool dummy;
403  if ( handlePopConversation( secureSocket, Transport::EnumEncryption::SSL,
404  secureStage, text, &dummy ) ) {
405  return;
406  }
407  } else {
408  if ( secureStage == 0 ) {
409  sendInitialCapabilityQuery( secureSocket );
410  return;
411  }
412  handleSMTPIMAPResponse( Transport::EnumEncryption::SSL, text );
413  }
414  secureSocketFinished = true;
415  finalResult();
416 }
417 
418 void ServerTestPrivate::slotNormalNotPossible()
419 {
420  normalSocketTimer->stop();
421  normalPossible = false;
422  normalSocketFinished = true;
423  tlsFinished = true;
424  finalResult();
425 }
426 
427 void ServerTestPrivate::slotSslPossible()
428 {
429  secureSocketTimer->stop();
430  connectionResults << Transport::EnumEncryption::SSL;
431 }
432 
433 void ServerTestPrivate::slotSslNotPossible()
434 {
435  secureSocketTimer->stop();
436  securePossible = false;
437  secureSocketFinished = true;
438  finalResult();
439 }
440 
441 void ServerTestPrivate::slotUpdateProgress()
442 {
443  if ( testProgress ) {
444  testProgress->setValue( testProgress->value() + 1 );
445  }
446 }
447 
448 //---------------------- end private class -----------------------//
449 
450 ServerTest::ServerTest( QWidget *parent )
451  : QWidget( parent ), d( new ServerTestPrivate( this ) )
452 {
453  d->normalSocketTimer = new QTimer( this );
454  d->normalSocketTimer->setSingleShot( true );
455  connect( d->normalSocketTimer, SIGNAL(timeout()), SLOT(slotNormalNotPossible()) );
456 
457  d->secureSocketTimer = new QTimer( this );
458  d->secureSocketTimer->setSingleShot( true );
459  connect( d->secureSocketTimer, SIGNAL(timeout()), SLOT(slotSslNotPossible()) );
460 
461  d->progressTimer = new QTimer( this );
462  connect( d->progressTimer, SIGNAL(timeout()), SLOT(slotUpdateProgress()) );
463 }
464 
465 ServerTest::~ServerTest()
466 {
467  delete d;
468 }
469 
470 void ServerTest::start()
471 {
472  kDebug() << d;
473 
474  d->connectionResults.clear();
475  d->authenticationResults.clear();
476  d->capabilityResults.clear();
477  d->popSupportsTLS = false;
478  d->normalStage = -1;
479  d->secureStage = -1;
480  d->encryptionMode = Transport::EnumEncryption::None;
481  d->normalPossible = true;
482  d->securePossible = true;
483 
484  if ( d->testProgress ) {
485  d->testProgress->setMaximum( 20 );
486  d->testProgress->setValue( 0 );
487  d->testProgress->setTextVisible( true );
488  d->testProgress->show();
489  d->progressTimer->start( 1000 );
490  }
491 
492  d->normalSocket = new MailTransport::Socket( this );
493  d->secureSocket = new MailTransport::Socket( this );
494  d->normalSocket->setObjectName( QLatin1String( "normal" ) );
495  d->normalSocket->setServer( d->server );
496  d->normalSocket->setProtocol( d->testProtocol );
497  if ( d->testProtocol == IMAP_PROTOCOL ) {
498  d->normalSocket->setPort( IMAP_PORT );
499  d->secureSocket->setPort( IMAPS_PORT );
500  } else if ( d->testProtocol == SMTP_PROTOCOL ) {
501  d->normalSocket->setPort( SMTP_PORT );
502  d->secureSocket->setPort( SMTPS_PORT );
503  } else if ( d->testProtocol == POP_PROTOCOL ) {
504  d->normalSocket->setPort( POP_PORT );
505  d->secureSocket->setPort( POPS_PORT );
506  }
507 
508  if ( d->customPorts.contains( Transport::EnumEncryption::None ) ) {
509  d->normalSocket->setPort( d->customPorts.value( Transport::EnumEncryption::None ) );
510  }
511  if ( d->customPorts.contains( Transport::EnumEncryption::SSL ) ) {
512  d->secureSocket->setPort( d->customPorts.value( Transport::EnumEncryption::SSL ) );
513  }
514 
515  connect( d->normalSocket, SIGNAL(connected()), SLOT(slotNormalPossible()) );
516  connect( d->normalSocket, SIGNAL(failed()), SLOT(slotNormalNotPossible()) );
517  connect( d->normalSocket, SIGNAL(data(QString)),
518  SLOT(slotReadNormal(QString)) );
519  connect( d->normalSocket, SIGNAL(tlsDone()), SLOT(slotTlsDone()));
520  d->normalSocket->reconnect();
521  d->normalSocketTimer->start( 10000 );
522 
523  d->secureSocket->setObjectName( QLatin1String( "secure" ) );
524  d->secureSocket->setServer( d->server );
525  d->secureSocket->setProtocol( d->testProtocol + QLatin1Char( 's' ) );
526  d->secureSocket->setSecure( true );
527  connect( d->secureSocket, SIGNAL(connected()), SLOT(slotSslPossible()) );
528  connect( d->secureSocket, SIGNAL(failed()), SLOT(slotSslNotPossible()) );
529  connect( d->secureSocket, SIGNAL(data(QString)),
530  SLOT(slotReadSecure(QString)) );
531  d->secureSocket->reconnect();
532  d->secureSocketTimer->start( 10000 );
533 }
534 
535 void ServerTest::setFakeHostname( const QString &fakeHostname )
536 {
537  d->fakeHostname = fakeHostname;
538 }
539 
540 QString ServerTest::fakeHostname()
541 {
542  return d->fakeHostname;
543 }
544 
545 void ServerTest::setServer( const QString &server )
546 {
547  d->server = server;
548 }
549 
550 void ServerTest::setPort( Transport::EnumEncryption::type encryptionMode, uint port )
551 {
552  Q_ASSERT( encryptionMode == Transport::EnumEncryption::None ||
553  encryptionMode == Transport::EnumEncryption::SSL );
554  d->customPorts.insert( encryptionMode, port );
555 }
556 
557 void ServerTest::setProgressBar( QProgressBar *pb )
558 {
559  d->testProgress = pb;
560 }
561 
562 void ServerTest::setProtocol( const QString &protocol )
563 {
564  d->testProtocol = protocol;
565 }
566 
567 QString ServerTest::protocol()
568 {
569  return d->testProtocol;
570 }
571 
572 QString ServerTest::server()
573 {
574  return d->server;
575 }
576 
577 int ServerTest::port( Transport::EnumEncryption::type encryptionMode )
578 {
579  Q_ASSERT( encryptionMode == Transport::EnumEncryption::None ||
580  encryptionMode == Transport::EnumEncryption::SSL );
581  if ( d->customPorts.contains( encryptionMode ) ) {
582  return d->customPorts.value( static_cast<int>( encryptionMode ) );
583  } else {
584  return -1;
585  }
586 }
587 
588 QProgressBar *ServerTest::progressBar()
589 {
590  return d->testProgress;
591 }
592 
593 QList< int > ServerTest::normalProtocols()
594 {
595  return d->authenticationResults[TransportBase::EnumEncryption::None];
596 }
597 
598 bool ServerTest::isNormalPossible()
599 {
600  return d->normalPossible;
601 }
602 
603 QList< int > ServerTest::tlsProtocols()
604 {
605  return d->authenticationResults[TransportBase::EnumEncryption::TLS];
606 }
607 
608 QList< int > ServerTest::secureProtocols()
609 {
610  return d->authenticationResults[Transport::EnumEncryption::SSL];
611 }
612 
613 bool ServerTest::isSecurePossible()
614 {
615  return d->securePossible;
616 }
617 
618 QList< ServerTest::Capability > ServerTest::capabilities() const
619 {
620  return d->capabilityResults.toList();
621 }
622 
623 #include "servertest.moc"
This file is part of the KDE documentation.
Documentation copyright © 1996-2012 The KDE developers.
Generated on Tue Dec 4 2012 14:35:48 by doxygen 1.8.1.2 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.9.4 API Reference

Skip menu "kdepimlibs-4.9.4 API Reference"
  • akonadi
  •   contact
  •   kmime
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  •   richtextbuilders
  • 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