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