10#include <KLocalizedString>
12#include "kimap_debug.h"
14#include "capabilitiesjob.h"
16#include "response_p.h"
26static const sasl_callback_t callbacks[] = {{SASL_CB_ECHOPROMPT,
nullptr,
nullptr},
27 {SASL_CB_NOECHOPROMPT,
nullptr,
nullptr},
28 {SASL_CB_GETREALM,
nullptr,
nullptr},
29 {SASL_CB_USER,
nullptr,
nullptr},
30 {SASL_CB_AUTHNAME,
nullptr,
nullptr},
31 {SASL_CB_PASS,
nullptr,
nullptr},
32 {SASL_CB_CANON_USER,
nullptr,
nullptr},
33 {SASL_CB_LIST_END,
nullptr,
nullptr}};
37class LoginJobPrivate :
public JobPrivate
40 enum AuthState { PreStartTlsCapability = 0, StartTls, Capability, Login, Authenticate };
42 LoginJobPrivate(LoginJob *job,
Session *session,
const QString &name)
43 : JobPrivate(session,
name)
45 , encryptionMode(LoginJob::Unencrypted)
47 , plainLoginDisabled(false)
50 client_interact =
nullptr;
57 bool startAuthentication();
59 void sslResponse(
bool response);
60 void saveServerGreeting(
const Response &response);
69 LoginJob::EncryptionMode encryptionMode;
73 bool plainLoginDisabled;
76 sasl_interact_t *client_interact;
82bool LoginJobPrivate::sasl_interact()
84 qCDebug(KIMAP_LOG) <<
"sasl_interact";
85 sasl_interact_t *interact = client_interact;
89 for (; interact->id != SASL_CB_LIST_END; interact++) {
90 if (interact->id == SASL_CB_AUTHNAME || interact->id == SASL_CB_PASS) {
96 interact = client_interact;
97 while (interact->id != SASL_CB_LIST_END) {
98 qCDebug(KIMAP_LOG) <<
"SASL_INTERACT id:" << interact->id;
99 switch (interact->id) {
100 case SASL_CB_AUTHNAME:
101 if (!authorizationName.
isEmpty()) {
102 qCDebug(KIMAP_LOG) <<
"SASL_CB_[AUTHNAME]: '" << authorizationName <<
"'";
104 interact->len = strlen((
const char *)interact->result);
109 qCDebug(KIMAP_LOG) <<
"SASL_CB_[USER|AUTHNAME]: '" << userName <<
"'";
111 interact->len = strlen((
const char *)interact->result);
114 qCDebug(KIMAP_LOG) <<
"SASL_CB_PASS: [hidden]";
116 interact->len = strlen((
const char *)interact->result);
119 interact->result =
nullptr;
128LoginJob::LoginJob(
Session *session)
129 : Job(*new LoginJobPrivate(this, session,
i18n(
"Login")))
132 qCDebug(KIMAP_LOG) <<
this;
137 qCDebug(KIMAP_LOG) <<
this;
140QString LoginJob::userName()
const
146void LoginJob::setUserName(
const QString &userName)
149 d->userName = userName;
152QString LoginJob::authorizationName()
const
155 return d->authorizationName;
158void LoginJob::setAuthorizationName(
const QString &authorizationName)
161 d->authorizationName = authorizationName;
164QString LoginJob::password()
const
170void LoginJob::setPassword(
const QString &password)
173 d->password = password;
176void LoginJob::doStart()
180 qCDebug(KIMAP_LOG) <<
this;
182 if (session()->state() == Session::Authenticated || session()->state() == Session::Selected) {
190 connect(d->sessionInternal(), &KIMAP::SessionPrivate::encryptionNegotiationResult,
this, [d](
bool result) {
191 d->sslResponse(result);
195 EncryptionMode encryptionMode = d->encryptionMode;
197 const auto negotiatedEncryption = d->sessionInternal()->negotiatedEncryption();
200 d->sslResponse(
true);
204 if (encryptionMode == SSLorTLS) {
206 }
else if (encryptionMode == STARTTLS) {
208 d->authState = LoginJobPrivate::PreStartTlsCapability;
209 d->tags << d->sessionInternal()->sendCommand(
"CAPABILITY");
210 }
else if (encryptionMode == Unencrypted) {
211 if (d->authMode.isEmpty()) {
212 d->authState = LoginJobPrivate::Login;
213 qCDebug(KIMAP_LOG) <<
"sending LOGIN";
214 d->tags << d->sessionInternal()->sendCommand(
"LOGIN",
217 if (!d->startAuthentication()) {
224void LoginJob::handleResponse(
const Response &response)
228 if (response.content.isEmpty()) {
234 if (d->authState == LoginJobPrivate::Capability) {
235 commandName =
i18n(
"Capability");
236 }
else if (d->authState == LoginJobPrivate::StartTls) {
237 commandName =
i18n(
"StartTls");
240 enum ResponseCode {
OK, ERR, UNTAGGED, CONTINUATION, MALFORMED };
243 ResponseCode code =
OK;
245 qCDebug(KIMAP_LOG) << commandName << tag;
249 }
else if (tag ==
"*") {
250 if (response.content.size() < 2) {
255 }
else if (d->tags.contains(tag)) {
256 if (response.content.size() < 2) {
258 }
else if (response.content[1].toString() ==
"OK") {
272 if (d->authState == LoginJobPrivate::Authenticate) {
273 sasl_dispose(&d->conn);
283 if (response.content[1].toString() ==
"CAPABILITY") {
284 d->capabilities.clear();
286 while (p != response.content.end()) {
288 d->capabilities << capability;
290 d->plainLoginDisabled =
true;
294 qCDebug(KIMAP_LOG) <<
"Capabilities updated: " << d->capabilities;
299 if (d->authState != LoginJobPrivate::Authenticate) {
307 if (response.content.size() > 1 && response.content.at(1).toString() ==
"OK") {
311 if (!d->authorizationName.isEmpty()) {
312 challengeResponse += d->authorizationName.toUtf8();
314 challengeResponse +=
'\0';
315 challengeResponse += d->userName.toUtf8();
316 challengeResponse +=
'\0';
317 challengeResponse += d->password.toUtf8();
318 challengeResponse = challengeResponse.
toBase64();
319 d->sessionInternal()->sendData(challengeResponse);
320 }
else if (response.content.size() >= 2) {
332 switch (d->authState) {
333 case LoginJobPrivate::PreStartTlsCapability:
335 d->authState = LoginJobPrivate::StartTls;
336 d->tags << d->sessionInternal()->sendCommand(
"STARTTLS");
338 qCWarning(KIMAP_LOG) <<
"STARTTLS not supported by server!";
340 setErrorText(
i18n(
"STARTTLS is not supported by the server, try using SSL/TLS instead."));
345 case LoginJobPrivate::StartTls:
349 case LoginJobPrivate::Capability:
351 if (d->encryptionMode != Unencrypted && d->sessionInternal()->negotiatedEncryption() ==
QSsl::UnknownProtocol) {
352 setError(LoginJob::UserDefinedError);
359 if (d->authMode.isEmpty()) {
360 if (d->plainLoginDisabled) {
365 d->authState = LoginJobPrivate::Login;
366 d->tags << d->sessionInternal()->sendCommand(
"LOGIN",
371 bool authModeSupported =
false;
373 for (
const QString &capability : std::as_const(d->capabilities)) {
375 if (
QStringView(capability).mid(5) == d->authMode) {
376 authModeSupported =
true;
381 if (!authModeSupported) {
383 setErrorText(
i18n(
"Login failed, authentication mode %1 is not supported by the server.", d->authMode));
385 }
else if (!d->startAuthentication()) {
391 case LoginJobPrivate::Authenticate:
392 sasl_dispose(&d->conn);
395 case LoginJobPrivate::Login:
396 d->saveServerGreeting(response);
402 if (code == MALFORMED) {
403 setErrorText(
i18n(
"%1 failed, malformed reply from the server.", commandName));
408bool LoginJobPrivate::startAuthentication()
412 q->
setError(LoginJob::UserDefinedError);
413 q->
setErrorText(
i18n(
"Login failed, client cannot initialize the SASL library."));
417 authState = LoginJobPrivate::Authenticate;
418 const char *out =
nullptr;
420 const char *mechusing =
nullptr;
422 int result = sasl_client_new(
"imap", m_session->hostName().toLatin1().constData(),
nullptr,
nullptr, callbacks, 0, &conn);
423 if (result != SASL_OK) {
425 qCWarning(KIMAP_LOG) <<
"sasl_client_new failed with:" << result << saslError;
426 q->
setError(LoginJob::UserDefinedError);
432 qCDebug(KIMAP_LOG) <<
"Trying authmod" << authMode.
toLatin1();
433 result = sasl_client_start(conn,
440 if (result == SASL_INTERACT) {
441 if (!sasl_interact()) {
443 q->
setError(LoginJob::UserDefinedError);
447 }
while (result == SASL_INTERACT);
449 if (result != SASL_CONTINUE && result != SASL_OK) {
451 qCWarning(KIMAP_LOG) <<
"sasl_client_start failed with:" << result << saslError;
452 q->
setError(LoginJob::UserDefinedError);
462 tags << sessionInternal()->sendCommand(
"AUTHENTICATE", authMode.
toLatin1());
464 tags << sessionInternal()->sendCommand(
"AUTHENTICATE", authMode.
toLatin1() +
' ' + challenge);
470bool LoginJobPrivate::answerChallenge(
const QByteArray &data)
474 const char *out =
nullptr;
477 result = sasl_client_step(conn, challenge.
isEmpty() ?
nullptr : challenge.
data(), challenge.
size(), &client_interact, &out, &outlen);
479 if (result == SASL_INTERACT) {
480 if (!sasl_interact()) {
481 q->
setError(LoginJob::UserDefinedError);
486 }
while (result == SASL_INTERACT);
488 if (result != SASL_CONTINUE && result != SASL_OK) {
490 qCWarning(KIMAP_LOG) <<
"sasl_client_step failed with:" << result << saslError;
491 q->
setError(LoginJob::UserDefinedError);
500 sessionInternal()->sendData(challenge);
505void LoginJobPrivate::sslResponse(
bool response)
508 authState = LoginJobPrivate::Capability;
509 tags << sessionInternal()->sendCommand(
"CAPABILITY");
511 q->
setError(LoginJob::UserDefinedError);
513 encryptionMode = LoginJob::Unencrypted;
518void LoginJob::setEncryptionMode(EncryptionMode mode)
521 d->encryptionMode = mode;
524LoginJob::EncryptionMode LoginJob::encryptionMode()
527 return d->encryptionMode;
530void LoginJob::setAuthenticationMode(AuthenticationMode mode)
538 d->authMode = QStringLiteral(
"LOGIN");
541 d->authMode = QStringLiteral(
"PLAIN");
544 d->authMode = QStringLiteral(
"CRAM-MD5");
547 d->authMode = QStringLiteral(
"DIGEST-MD5");
550 d->authMode = QStringLiteral(
"GSSAPI");
553 d->authMode = QStringLiteral(
"ANONYMOUS");
556 d->authMode = QStringLiteral(
"XOAUTH2");
563void LoginJob::connectionLost()
567 qCWarning(KIMAP_LOG) <<
"Connection to server lost " << d->m_socketError;
579void LoginJobPrivate::saveServerGreeting(
const Response &response)
584 for (
int i = 2; i < response.content.size(); i++) {
585 if (response.content.at(i).type() == Response::Part::List) {
591 serverGreeting.
chop(1);
592 serverGreeting += QStringLiteral(
") ");
597 serverGreeting.
chop(1);
600QString LoginJob::serverGreeting()
const
603 return d->serverGreeting;
606#include "moc_loginjob.cpp"
void setErrorText(const QString &errorText)
void setError(int errorCode)
QString i18n(const char *text, const TYPE &arg...)
QString name(StandardAction id)
const char * constData() const const
QByteArray first(qsizetype n) const const
QByteArray fromBase64(const QByteArray &base64, Base64Options options)
QByteArray fromRawData(const char *data, qsizetype size)
bool isEmpty() const const
qsizetype size() const const
QByteArray toBase64(Base64Options options) const const
const_reference at(qsizetype i) const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QByteArray toLatin1() const const
QByteArray toUtf8() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
This file is part of the IMAP support library and defines the RfcCodecs class.
KIMAP_EXPORT QString quoteIMAP(const QString &src)
Replaces " with \" and \ with \\ " and \ characters.