10#include "servertest.h"
14#include <transportbase.h>
16#include <KLocalizedString>
21#include <QProgressBar>
22#include <QRegularExpression>
27#include "mailtransport_debug.h"
29using namespace MailTransport;
31namespace MailTransport
33class ServerTestPrivate
36 ServerTestPrivate(ServerTest *test);
43 MailTransport::Socket *normalSocket =
nullptr;
44 MailTransport::Socket *secureSocket =
nullptr;
46 QSet<int> connectionResults;
47 QHash<int, QList<int>> authenticationResults;
48 QSet<ServerTest::Capability> capabilityResults;
49 QHash<int, uint> customPorts;
50 QTimer *normalSocketTimer =
nullptr;
51 QTimer *secureSocketTimer =
nullptr;
52 QTimer *progressTimer =
nullptr;
54 QProgressBar *testProgress =
nullptr;
56 bool secureSocketFinished =
false;
57 bool normalSocketFinished =
false;
58 bool tlsFinished =
false;
64 bool normalPossible =
true;
65 bool securePossible =
true;
68 void handleSMTPIMAPResponse(
int type,
const QString &text);
69 void sendInitialCapabilityQuery(MailTransport::Socket *socket);
70 bool handlePopConversation(MailTransport::Socket *socket,
int type,
int stage,
const QString &response,
bool *shouldStartTLS);
71 bool handleNntpConversation(MailTransport::Socket *socket,
int type,
int *stage,
const QString &response,
bool *shouldStartTLS);
72 QList<int> parseAuthenticationList(
const QStringList &authentications);
74 [[nodiscard]]
inline bool isSupportedXOAuthServer(
const QString &server)
const
76 return server.endsWith(u
".gmail.com") || server.endsWith(u
".googlemail.com") || server.endsWith(u
".office365.com") || server.endsWith(u
".outlook.com")
77 || server.endsWith(u
".hotmail.com");
81 void slotNormalPossible();
82 void slotNormalNotPossible();
83 void slotSslPossible();
84 void slotSslNotPossible();
86 void slotReadNormal(
const QString &text);
87 void slotReadSecure(
const QString &text);
88 void slotUpdateProgress();
92ServerTestPrivate::ServerTestPrivate(
ServerTest *test)
97void ServerTestPrivate::finalResult()
99 if (!secureSocketFinished || !normalSocketFinished || !tlsFinished) {
103 qCDebug(MAILTRANSPORT_LOG) <<
"Modes:" << connectionResults;
104 qCDebug(MAILTRANSPORT_LOG) <<
"Capabilities:" << capabilityResults;
106 qCDebug(MAILTRANSPORT_LOG) <<
"SSL:" << q->secureProtocols();
107 qCDebug(MAILTRANSPORT_LOG) <<
"TLS:" << q->tlsProtocols();
110 testProgress->hide();
112 progressTimer->stop();
113 secureSocketFinished =
false;
114 normalSocketFinished =
false;
117 QList<int> resultsAsVector;
118 resultsAsVector.
reserve(connectionResults.size());
119 for (
int res : std::as_const(connectionResults)) {
120 resultsAsVector.
append(res);
123 Q_EMIT q->finished(resultsAsVector);
126QList<int> ServerTestPrivate::parseAuthenticationList(
const QStringList &authentications)
130 QString current = (*it).toUpper();
131 if (current == QLatin1StringView(
"LOGIN")) {
132 result << Transport::EnumAuthenticationType::LOGIN;
133 }
else if (current == QLatin1StringView(
"PLAIN")) {
134 result << Transport::EnumAuthenticationType::PLAIN;
135 }
else if (current == QLatin1StringView(
"CRAM-MD5")) {
136 result << Transport::EnumAuthenticationType::CRAM_MD5;
137 }
else if (current == QLatin1StringView(
"DIGEST-MD5")) {
138 result << Transport::EnumAuthenticationType::DIGEST_MD5;
139 }
else if (current == QLatin1StringView(
"NTLM")) {
140 result << Transport::EnumAuthenticationType::NTLM;
141 }
else if (current == QLatin1StringView(
"GSSAPI")) {
142 result << Transport::EnumAuthenticationType::GSSAPI;
143 }
else if (current == QLatin1StringView(
"ANONYMOUS")) {
144 result << Transport::EnumAuthenticationType::ANONYMOUS;
145 }
else if (current == QLatin1StringView(
"XOAUTH2")) {
146 if (isSupportedXOAuthServer(server)) {
147 result << Transport::EnumAuthenticationType::XOAUTH2;
152 qCDebug(MAILTRANSPORT_LOG) << authentications << result;
157 if (result.contains(Transport::EnumAuthenticationType::PLAIN)) {
158 result.removeAll(Transport::EnumAuthenticationType::LOGIN);
164void ServerTestPrivate::handleSMTPIMAPResponse(
int type,
const QString &text)
167 qCDebug(MAILTRANSPORT_LOG) <<
"No authentication possible";
171 QStringList protocols;
172 if (isSupportedXOAuthServer(server)) {
173 protocols << QStringLiteral(
"XOAUTH2");
176 protocols << QStringLiteral(
"LOGIN") << QStringLiteral(
"PLAIN") << QStringLiteral(
"CRAM-MD5") << QStringLiteral(
"DIGEST-MD5") << QStringLiteral(
"NTLM")
177 << QStringLiteral(
"GSSAPI") << QStringLiteral(
"ANONYMOUS");
180 for (
int i = 0; i < protocols.
count(); ++i) {
186 authenticationResults[
type] = parseAuthenticationList(results);
189 if (authenticationResults[type].isEmpty()) {
190 authenticationResults[
type] << Transport::EnumAuthenticationType::CLEAR;
193 qCDebug(MAILTRANSPORT_LOG) <<
"For type" <<
type <<
", we have:" << authenticationResults[
type];
196void ServerTestPrivate::slotNormalPossible()
198 normalSocketTimer->stop();
199 connectionResults << Transport::EnumEncryption::None;
202void ServerTestPrivate::sendInitialCapabilityQuery(MailTransport::Socket *socket)
204 if (testProtocol == IMAP_PROTOCOL) {
205 socket->
write(QStringLiteral(
"1 CAPABILITY"));
206 }
else if (testProtocol == SMTP_PROTOCOL) {
212 if (!fakeHostname.isNull()) {
217 hostname = QStringLiteral(
"localhost.invalid");
219 hostname += QLatin1StringView(
".localnet");
222 qCDebug(MAILTRANSPORT_LOG) <<
"Hostname for EHLO is" <<
hostname;
224 socket->
write(QLatin1StringView(
"EHLO ") + hostname);
228void ServerTestPrivate::slotTlsDone()
232 slotReadNormal(QString());
235bool ServerTestPrivate::handlePopConversation(MailTransport::Socket *socket,
int type,
int stage,
const QString &response,
bool *shouldStartTLS)
237 Q_ASSERT(shouldStartTLS !=
nullptr);
242 const QString responseWithoutCRLF = response.
isEmpty() ? response : response.
chopped(2);
244 if (responseWithoutCRLF.
indexOf(re) != -1) {
245 authenticationResults[
type] << Transport::EnumAuthenticationType::APOP;
249 authenticationResults[
type] << Transport::EnumAuthenticationType::CLEAR;
253 if (type == Transport::EnumEncryption::TLS
254 && authenticationResults[Transport::EnumEncryption::None].contains(Transport::EnumAuthenticationType::APOP)) {
255 authenticationResults[Transport::EnumEncryption::TLS] << Transport::EnumAuthenticationType::APOP;
258 socket->
write(QStringLiteral(
"CAPA"));
262 else if (stage == 1) {
272 if (response.
contains(QLatin1StringView(
"TOP"))) {
275 if (response.
contains(QLatin1StringView(
"PIPELINING"))) {
278 if (response.
contains(QLatin1StringView(
"UIDL"))) {
281 if (response.
contains(QLatin1StringView(
"STLS"))) {
282 connectionResults << Transport::EnumEncryption::TLS;
283 popSupportsTLS =
true;
285 socket->
write(QStringLiteral(
"AUTH"));
289 else if (stage == 2) {
296 QString formattedReply = response;
299 formattedReply.
chop(3);
302 formattedReply = formattedReply.
right(formattedReply.
size() - formattedReply.
indexOf(QLatin1Char(
'\n')) - 1);
303 formattedReply = formattedReply.
replace(QLatin1Char(
' '), QLatin1Char(
'-')).
replace(QLatin1StringView(
"\r\n"), QLatin1StringView(
" "));
305 authenticationResults[
type] += parseAuthenticationList(formattedReply.
split(QLatin1Char(
' ')));
308 *shouldStartTLS = popSupportsTLS;
312bool ServerTestPrivate::handleNntpConversation(MailTransport::Socket *socket,
int type,
int *stage,
const QString &response,
bool *shouldStartTLS)
314 Q_ASSERT(shouldStartTLS !=
nullptr);
315 Q_ASSERT(stage !=
nullptr);
319 if (response.
startsWith(QLatin1StringView(
"382 "))) {
326 socket->
write(QStringLiteral(
"CAPABILITIES"));
330 else if (*stage == 1) {
332 if (response.
startsWith(QLatin1StringView(
"500 "))) {
349 const QList<QStringView> lines = QStringView(response).split(QStringLiteral(
"\r\n"),
Qt::SkipEmptyParts);
350 for (
const QStringView line : lines) {
352 *shouldStartTLS =
true;
354 const QList<QStringView> authinfos = QStringView(line).split(QLatin1Char(
' '),
Qt::SkipEmptyParts);
355 const QString s(QStringLiteral(
"USER"));
356 const QStringView ref(s);
358 authenticationResults[
type].append(Transport::EnumAuthenticationType::CLEAR);
361 const QStringList auths = line.mid(5).toString().split(QLatin1Char(
' '),
Qt::SkipEmptyParts);
362 authenticationResults[
type] += parseAuthenticationList(auths);
363 }
else if (line == QLatin1Char(
'.')) {
380void ServerTestPrivate::slotReadNormal(
const QString &text)
382 Q_ASSERT(encryptionMode != Transport::EnumEncryption::SSL);
383 static const int tlsHandshakeStage = 42;
385 qCDebug(MAILTRANSPORT_LOG) <<
"Stage" << normalStage + 1 <<
", Mode" << encryptionMode;
391 if (normalStage == tlsHandshakeStage) {
392 Q_ASSERT(encryptionMode == Transport::EnumEncryption::TLS);
394 normalSocket->startTLS();
398 bool shouldStartTLS =
false;
403 if (testProtocol == POP_PROTOCOL) {
404 if (handlePopConversation(normalSocket, encryptionMode, normalStage, text, &shouldStartTLS)) {
407 }
else if (testProtocol == NNTP_PROTOCOL) {
408 if (handleNntpConversation(normalSocket, encryptionMode, &normalStage, text, &shouldStartTLS)) {
414 if (normalStage == 0) {
415 sendInitialCapabilityQuery(normalSocket);
420 connectionResults << Transport::EnumEncryption::TLS;
421 shouldStartTLS =
true;
423 handleSMTPIMAPResponse(encryptionMode, text);
428 normalSocketFinished =
true;
432 if (shouldStartTLS && encryptionMode == Transport::EnumEncryption::None) {
433 qCDebug(MAILTRANSPORT_LOG) <<
"Trying TLS...";
434 connectionResults << Transport::EnumEncryption::TLS;
435 if (testProtocol == POP_PROTOCOL) {
436 normalSocket->write(QStringLiteral(
"STLS"));
437 }
else if (testProtocol == IMAP_PROTOCOL) {
438 normalSocket->write(QStringLiteral(
"2 STARTTLS"));
440 normalSocket->write(QStringLiteral(
"STARTTLS"));
442 encryptionMode = Transport::EnumEncryption::TLS;
443 normalStage = tlsHandshakeStage;
453void ServerTestPrivate::slotReadSecure(
const QString &text)
456 if (testProtocol == POP_PROTOCOL) {
458 if (handlePopConversation(secureSocket, Transport::EnumEncryption::SSL, secureStage, text, &dummy)) {
461 }
else if (testProtocol == NNTP_PROTOCOL) {
463 if (handleNntpConversation(secureSocket, Transport::EnumEncryption::SSL, &secureStage, text, &dummy)) {
467 if (secureStage == 0) {
468 sendInitialCapabilityQuery(secureSocket);
471 handleSMTPIMAPResponse(Transport::EnumEncryption::SSL, text);
473 secureSocketFinished =
true;
477void ServerTestPrivate::slotNormalNotPossible()
479 if (testProtocol == SMTP_PROTOCOL && normalSocket->port() == SMTP_PORT) {
481 normalSocket->setPort(SMTP_OLD_PORT);
482 normalSocket->reconnect();
483 normalSocketTimer->start(10000);
487 normalSocketTimer->stop();
488 normalPossible =
false;
489 normalSocketFinished =
true;
494void ServerTestPrivate::slotSslPossible()
496 secureSocketTimer->stop();
497 connectionResults << Transport::EnumEncryption::SSL;
500void ServerTestPrivate::slotSslNotPossible()
502 secureSocketTimer->stop();
503 securePossible =
false;
504 secureSocketFinished =
true;
508void ServerTestPrivate::slotUpdateProgress()
511 testProgress->setValue(testProgress->value() + 1);
519 , d(new ServerTestPrivate(this))
521 d->normalSocketTimer =
new QTimer(
this);
522 d->normalSocketTimer->setSingleShot(
true);
523 connect(d->normalSocketTimer, SIGNAL(timeout()), SLOT(slotNormalNotPossible()));
525 d->secureSocketTimer =
new QTimer(
this);
526 d->secureSocketTimer->setSingleShot(
true);
527 connect(d->secureSocketTimer, SIGNAL(timeout()), SLOT(slotSslNotPossible()));
529 d->progressTimer =
new QTimer(
this);
530 connect(d->progressTimer, SIGNAL(timeout()), SLOT(slotUpdateProgress()));
537 qCDebug(MAILTRANSPORT_LOG) << d.get();
539 d->connectionResults.clear();
540 d->authenticationResults.clear();
541 d->capabilityResults.clear();
542 d->popSupportsTLS =
false;
545 d->encryptionMode = Transport::EnumEncryption::None;
546 d->normalPossible =
true;
547 d->securePossible =
true;
549 if (d->testProgress) {
550 d->testProgress->setMaximum(20);
551 d->testProgress->setValue(0);
552 d->testProgress->setFormat(
i18nc(
"Percent value; %p is the value, % is the percent sign",
"%p%"));
553 d->testProgress->setTextVisible(
true);
554 d->testProgress->show();
555 d->progressTimer->start(1000);
561 d->normalSocket->setServer(d->server);
562 d->normalSocket->setProtocol(d->testProtocol);
563 if (d->testProtocol == IMAP_PROTOCOL) {
564 d->normalSocket->setPort(IMAP_PORT);
565 d->secureSocket->setPort(IMAPS_PORT);
566 }
else if (d->testProtocol == SMTP_PROTOCOL) {
567 d->normalSocket->setPort(SMTP_PORT);
568 d->secureSocket->setPort(SMTPS_PORT);
569 }
else if (d->testProtocol == POP_PROTOCOL) {
570 d->normalSocket->setPort(POP_PORT);
571 d->secureSocket->setPort(POPS_PORT);
572 }
else if (d->testProtocol == NNTP_PROTOCOL) {
573 d->normalSocket->setPort(NNTP_PORT);
574 d->secureSocket->setPort(NNTPS_PORT);
577 if (d->customPorts.contains(Transport::EnumEncryption::None)) {
578 d->normalSocket->setPort(d->customPorts.value(Transport::EnumEncryption::None));
580 if (d->customPorts.contains(Transport::EnumEncryption::SSL)) {
581 d->secureSocket->setPort(d->customPorts.value(Transport::EnumEncryption::SSL));
584 connect(d->normalSocket, SIGNAL(connected()), SLOT(slotNormalPossible()));
585 connect(d->normalSocket, SIGNAL(failed()), SLOT(slotNormalNotPossible()));
587 connect(d->normalSocket, SIGNAL(tlsDone()), SLOT(slotTlsDone()));
588 d->normalSocket->reconnect();
589 d->normalSocketTimer->start(10000);
591 if (d->secureSocket->port() > 0) {
593 d->secureSocket->setServer(d->server);
594 d->secureSocket->setProtocol(d->testProtocol +
QLatin1Char(
's'));
595 d->secureSocket->setSecure(
true);
596 connect(d->secureSocket, SIGNAL(connected()), SLOT(slotSslPossible()));
597 connect(d->secureSocket, SIGNAL(failed()), SLOT(slotSslNotPossible()));
599 d->secureSocket->reconnect();
600 d->secureSocketTimer->start(10000);
602 d->slotSslNotPossible();
613 return d->fakeHostname;
623 Q_ASSERT(encryptionMode == Transport::EnumEncryption::None || encryptionMode == Transport::EnumEncryption::SSL);
624 d->customPorts.insert(encryptionMode,
port);
629 d->testProgress = pb;
634 d->testProtocol = protocol;
635 d->customPorts.clear();
640 return d->testProtocol;
650 Q_ASSERT(encryptionMode == Transport::EnumEncryption::None || encryptionMode == Transport::EnumEncryption::SSL);
651 if (d->customPorts.contains(encryptionMode)) {
652 return d->customPorts.value(
static_cast<int>(encryptionMode));
660 return d->testProgress;
665 return d->authenticationResults[TransportBase::EnumEncryption::None];
670 return d->normalPossible;
675 return d->authenticationResults[TransportBase::EnumEncryption::TLS];
680 return d->authenticationResults[Transport::EnumEncryption::SSL];
685 return d->securePossible;
690 return d->capabilityResults.values();
693#include "moc_servertest.cpp"
This class can be used to test certain server to see if they support stuff.
ServerTest(QObject *parent=nullptr)
Creates a new server test.
QString fakeHostname() const
void setProgressBar(QProgressBar *pb)
Makes pb the progressbar to use.
bool isSecurePossible() const
tells you if the ssl server is available
void setPort(Transport::EnumEncryption encryptionMode, uint port)
Set a custom port to use.
~ServerTest() override
Destroys the server test.
void setProtocol(const QString &protocol)
Sets protocol the protocol to test, currently supported are "smtp", "pop", "imap",...
QList< int > tlsProtocols() const
Get the protocols for the TLS connections.
@ Pipelining
POP3 only. The server supports pipeplining of commands.
@ UIDL
POP3 only. The server has support for unique identifiers.
@ Top
POP3 only. The server supports fetching only the headers.
QList< Capability > capabilities() const
Get the special capabilities of the server.
void start()
Starts the test.
void setServer(const QString &server)
Sets the server to test.
bool isNormalPossible() const
tells you if the normal server is available
void setFakeHostname(const QString &fakeHostname)
Sets a fake hostname for the test.
QList< int > secureProtocols() const
Get the protocols for the SSL connections.
QList< int > normalProtocols() const
Get the protocols for the normal connections.
int port(Transport::EnumEncryption encryptionMode) const
Responsible for communicating with the server, it's designed to work with the ServerTest class.
virtual void write(const QString &text)
Write text to the socket.
QString i18nc(const char *context, const char *text, const TYPE &arg...)
Internal file containing constant definitions etc.
Type type(const QSqlDatabase &db)
NETWORKMANAGERQT_EXPORT QString hostname()
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
bool contains(const AT &value) const const
qsizetype count() const const
void reserve(qsizetype size)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QObject * parent() const const
QString chopped(qsizetype len) const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
QString right(qsizetype n) const const
qsizetype size() const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const