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
41 PreStartTlsCapability = 0,
48 LoginJobPrivate(LoginJob *job,
Session *session,
const QString &name)
49 : JobPrivate(session,
name)
51 , encryptionMode(LoginJob::Unencrypted)
53 , plainLoginDisabled(false)
56 client_interact =
nullptr;
63 bool startAuthentication();
65 void sslResponse(
bool response);
66 void saveServerGreeting(
const Response &response);
75 LoginJob::EncryptionMode encryptionMode;
79 bool plainLoginDisabled;
82 sasl_interact_t *client_interact;
88bool LoginJobPrivate::sasl_interact()
90 qCDebug(KIMAP_LOG) <<
"sasl_interact";
91 sasl_interact_t *interact = client_interact;
95 for (; interact->id != SASL_CB_LIST_END; interact++) {
96 if (interact->id == SASL_CB_AUTHNAME || interact->id == SASL_CB_PASS) {
102 interact = client_interact;
103 while (interact->id != SASL_CB_LIST_END) {
104 qCDebug(KIMAP_LOG) <<
"SASL_INTERACT id:" << interact->id;
105 switch (interact->id) {
106 case SASL_CB_AUTHNAME:
107 if (!authorizationName.
isEmpty()) {
108 qCDebug(KIMAP_LOG) <<
"SASL_CB_[AUTHNAME]: '" << authorizationName <<
"'";
110 interact->len = strlen((
const char *)interact->result);
115 qCDebug(KIMAP_LOG) <<
"SASL_CB_[USER|AUTHNAME]: '" << userName <<
"'";
117 interact->len = strlen((
const char *)interact->result);
120 qCDebug(KIMAP_LOG) <<
"SASL_CB_PASS: [hidden]";
122 interact->len = strlen((
const char *)interact->result);
125 interact->result =
nullptr;
134LoginJob::LoginJob(
Session *session)
135 : Job(*new LoginJobPrivate(this, session,
i18n(
"Login")))
138 qCDebug(KIMAP_LOG) <<
this;
143 qCDebug(KIMAP_LOG) <<
this;
146QString LoginJob::userName()
const
152void LoginJob::setUserName(
const QString &userName)
155 d->userName = userName;
158QString LoginJob::authorizationName()
const
161 return d->authorizationName;
164void LoginJob::setAuthorizationName(
const QString &authorizationName)
167 d->authorizationName = authorizationName;
170QString LoginJob::password()
const
176void LoginJob::setPassword(
const QString &password)
179 d->password = password;
182void LoginJob::doStart()
186 qCDebug(KIMAP_LOG) <<
this;
188 if (session()->state() == Session::Authenticated || session()->state() == Session::Selected) {
196 connect(d->sessionInternal(), &KIMAP::SessionPrivate::encryptionNegotiationResult,
this, [d](
bool result) {
197 d->sslResponse(result);
201 EncryptionMode encryptionMode = d->encryptionMode;
203 const auto negotiatedEncryption = d->sessionInternal()->negotiatedEncryption();
206 d->sslResponse(
true);
210 if (encryptionMode == SSLorTLS) {
212 }
else if (encryptionMode == STARTTLS) {
214 d->authState = LoginJobPrivate::PreStartTlsCapability;
215 d->tags << d->sessionInternal()->sendCommand(
"CAPABILITY");
216 }
else if (encryptionMode == Unencrypted) {
217 if (d->authMode.isEmpty()) {
218 d->authState = LoginJobPrivate::Login;
219 qCDebug(KIMAP_LOG) <<
"sending LOGIN";
220 d->tags << d->sessionInternal()->sendCommand(
"LOGIN",
223 if (!d->startAuthentication()) {
230void LoginJob::handleResponse(
const Response &response)
234 if (response.content.isEmpty()) {
240 if (d->authState == LoginJobPrivate::Capability) {
241 commandName =
i18n(
"Capability");
242 }
else if (d->authState == LoginJobPrivate::StartTls) {
243 commandName =
i18n(
"StartTls");
255 ResponseCode code =
OK;
257 qCDebug(KIMAP_LOG) << commandName << tag;
261 }
else if (tag ==
"*") {
262 if (response.content.size() < 2) {
267 }
else if (d->tags.contains(tag)) {
268 if (response.content.size() < 2) {
270 }
else if (response.content[1].toString() ==
"OK") {
284 if (d->authState == LoginJobPrivate::Authenticate) {
285 sasl_dispose(&d->conn);
295 if (response.content[1].toString() ==
"CAPABILITY") {
296 d->capabilities.clear();
298 while (p != response.content.end()) {
300 d->capabilities << capability;
302 d->plainLoginDisabled =
true;
306 qCDebug(KIMAP_LOG) <<
"Capabilities updated: " << d->capabilities;
311 if (d->authState != LoginJobPrivate::Authenticate) {
319 if (response.content.size() > 1 && response.content.at(1).toString() ==
"OK") {
323 if (!d->authorizationName.isEmpty()) {
324 challengeResponse += d->authorizationName.toUtf8();
326 challengeResponse +=
'\0';
327 challengeResponse += d->userName.toUtf8();
328 challengeResponse +=
'\0';
329 challengeResponse += d->password.toUtf8();
330 challengeResponse = challengeResponse.
toBase64();
331 d->sessionInternal()->sendData(challengeResponse);
332 }
else if (response.content.size() >= 2) {
344 switch (d->authState) {
345 case LoginJobPrivate::PreStartTlsCapability:
347 d->authState = LoginJobPrivate::StartTls;
348 d->tags << d->sessionInternal()->sendCommand(
"STARTTLS");
350 qCWarning(KIMAP_LOG) <<
"STARTTLS not supported by server!";
352 setErrorText(
i18n(
"STARTTLS is not supported by the server, try using SSL/TLS instead."));
357 case LoginJobPrivate::StartTls:
361 case LoginJobPrivate::Capability:
363 if (d->encryptionMode != Unencrypted && d->sessionInternal()->negotiatedEncryption() ==
QSsl::UnknownProtocol) {
364 setError(LoginJob::UserDefinedError);
371 if (d->authMode.isEmpty()) {
372 if (d->plainLoginDisabled) {
377 d->authState = LoginJobPrivate::Login;
378 d->tags << d->sessionInternal()->sendCommand(
"LOGIN",
383 bool authModeSupported =
false;
385 for (
const QString &capability : std::as_const(d->capabilities)) {
387 if (
QStringView(capability).mid(5) == d->authMode) {
388 authModeSupported =
true;
393 if (!authModeSupported) {
395 setErrorText(
i18n(
"Login failed, authentication mode %1 is not supported by the server.", d->authMode));
397 }
else if (!d->startAuthentication()) {
403 case LoginJobPrivate::Authenticate:
404 sasl_dispose(&d->conn);
407 case LoginJobPrivate::Login:
408 d->saveServerGreeting(response);
414 if (code == MALFORMED) {
415 setErrorText(
i18n(
"%1 failed, malformed reply from the server.", commandName));
420bool LoginJobPrivate::startAuthentication()
424 q->
setError(LoginJob::UserDefinedError);
425 q->
setErrorText(
i18n(
"Login failed, client cannot initialize the SASL library."));
429 authState = LoginJobPrivate::Authenticate;
430 const char *out =
nullptr;
432 const char *mechusing =
nullptr;
434 int result = sasl_client_new(
"imap", m_session->hostName().toLatin1().constData(),
nullptr,
nullptr, callbacks, 0, &conn);
435 if (result != SASL_OK) {
437 qCWarning(KIMAP_LOG) <<
"sasl_client_new failed with:" << result << saslError;
438 q->
setError(LoginJob::UserDefinedError);
444 qCDebug(KIMAP_LOG) <<
"Trying authmod" << authMode.
toLatin1();
445 result = sasl_client_start(conn,
452 if (result == SASL_INTERACT) {
453 if (!sasl_interact()) {
455 q->
setError(LoginJob::UserDefinedError);
459 }
while (result == SASL_INTERACT);
461 if (result != SASL_CONTINUE && result != SASL_OK) {
463 qCWarning(KIMAP_LOG) <<
"sasl_client_start failed with:" << result << saslError;
464 q->
setError(LoginJob::UserDefinedError);
474 tags << sessionInternal()->sendCommand(
"AUTHENTICATE", authMode.
toLatin1());
476 tags << sessionInternal()->sendCommand(
"AUTHENTICATE", authMode.
toLatin1() +
' ' + challenge);
482bool LoginJobPrivate::answerChallenge(
const QByteArray &data)
486 const char *out =
nullptr;
489 result = sasl_client_step(conn, challenge.
isEmpty() ?
nullptr : challenge.
data(), challenge.
size(), &client_interact, &out, &outlen);
491 if (result == SASL_INTERACT) {
492 if (!sasl_interact()) {
493 q->
setError(LoginJob::UserDefinedError);
498 }
while (result == SASL_INTERACT);
500 if (result != SASL_CONTINUE && result != SASL_OK) {
502 qCWarning(KIMAP_LOG) <<
"sasl_client_step failed with:" << result << saslError;
503 q->
setError(LoginJob::UserDefinedError);
512 sessionInternal()->sendData(challenge);
517void LoginJobPrivate::sslResponse(
bool response)
520 authState = LoginJobPrivate::Capability;
521 tags << sessionInternal()->sendCommand(
"CAPABILITY");
523 q->
setError(LoginJob::UserDefinedError);
525 encryptionMode = LoginJob::Unencrypted;
530void LoginJob::setEncryptionMode(EncryptionMode mode)
533 d->encryptionMode = mode;
536LoginJob::EncryptionMode LoginJob::encryptionMode()
539 return d->encryptionMode;
542void LoginJob::setAuthenticationMode(AuthenticationMode mode)
550 d->authMode = QStringLiteral(
"LOGIN");
553 d->authMode = QStringLiteral(
"PLAIN");
556 d->authMode = QStringLiteral(
"CRAM-MD5");
559 d->authMode = QStringLiteral(
"DIGEST-MD5");
562 d->authMode = QStringLiteral(
"GSSAPI");
565 d->authMode = QStringLiteral(
"ANONYMOUS");
568 d->authMode = QStringLiteral(
"XOAUTH2");
575void LoginJob::connectionLost()
579 qCWarning(KIMAP_LOG) <<
"Connection to server lost " << d->m_socketError;
591void LoginJobPrivate::saveServerGreeting(
const Response &response)
596 for (
int i = 2; i < response.content.size(); i++) {
597 if (response.content.at(i).type() == Response::Part::List) {
603 serverGreeting.
chop(1);
604 serverGreeting += QStringLiteral(
") ");
609 serverGreeting.
chop(1);
612QString LoginJob::serverGreeting()
const
615 return d->serverGreeting;
618#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.