24#include "kimap_debug.h"
37static const sasl_callback_t callbacks[] = {
38 { SASL_CB_ECHOPROMPT, Q_NULLPTR,
nullptr },
39 { SASL_CB_NOECHOPROMPT, Q_NULLPTR,
nullptr },
40 { SASL_CB_GETREALM, Q_NULLPTR,
nullptr },
41 { SASL_CB_USER, Q_NULLPTR,
nullptr },
42 { SASL_CB_AUTHNAME, Q_NULLPTR,
nullptr },
43 { SASL_CB_PASS, Q_NULLPTR,
nullptr },
44 { SASL_CB_CANON_USER, Q_NULLPTR,
nullptr },
45 { SASL_CB_LIST_END, Q_NULLPTR,
nullptr }
50class LoginJobPrivate :
public JobPrivate
60 LoginJobPrivate(LoginJob *job,
Session *session,
const QString &name) : JobPrivate(session,
name), q(job)
63 client_interact = Q_NULLPTR;
65 ~LoginJobPrivate() { }
68 bool startAuthentication();
69 void sendPlainLogin();
71 void sslResponse(
bool response);
72 void saveServerGreeting(
const Message &response);
74 void retrieveCapabilities();
84 bool startTls =
false;
86 AuthState authState = Login;
88 bool plainLoginDisabled =
false;
89 bool connectionIsEncrypted =
false;
92 sasl_interact_t *client_interact;
96using namespace KIMAP2;
98bool LoginJobPrivate::sasl_interact()
100 qCDebug(KIMAP2_LOG) <<
"sasl_interact";
101 sasl_interact_t *interact = client_interact;
105 for (; interact->id != SASL_CB_LIST_END; interact++) {
106 if (interact->id == SASL_CB_AUTHNAME ||
107 interact->id == SASL_CB_PASS) {
113 interact = client_interact;
114 while (interact->id != SASL_CB_LIST_END) {
115 qCDebug(KIMAP2_LOG) <<
"SASL_INTERACT id:" << interact->id;
116 switch (interact->id) {
117 case SASL_CB_AUTHNAME:
118 if (!authorizationName.
isEmpty()) {
119 qCDebug(KIMAP2_LOG) <<
"SASL_CB_[AUTHNAME]: '" << authorizationName <<
"'";
120 interact->result = strdup(authorizationName.
toUtf8());
121 interact->len = strlen((
const char *) interact->result);
125 qCDebug(KIMAP2_LOG) <<
"SASL_CB_[USER|AUTHNAME]: '" << userName <<
"'";
126 interact->result = strdup(userName.
toUtf8());
127 interact->len = strlen((
const char *) interact->result);
130 qCDebug(KIMAP2_LOG) <<
"SASL_CB_PASS: [hidden]";
131 interact->result = strdup(password.
toUtf8());
132 interact->len = strlen((
const char *) interact->result);
135 interact->result = Q_NULLPTR;
145LoginJob::LoginJob(
Session *session)
146 : Job(*new LoginJobPrivate(this, session,
QString::fromUtf8(
"Login")))
148 qCDebug(KIMAP2_LOG) <<
this;
153 qCDebug(KIMAP2_LOG) <<
this;
156QString LoginJob::userName()
const
162void LoginJob::setUserName(
const QString &userName)
165 d->userName = userName;
168QString LoginJob::authorizationName()
const
171 return d->authorizationName;
174void LoginJob::setAuthorizationName(
const QString &authorizationName)
177 d->authorizationName = authorizationName;
180QString LoginJob::password()
const
186void LoginJob::setPassword(
const QString &password)
189 d->password = password;
218void LoginJob::doStart()
222 qCDebug(KIMAP2_LOG) <<
"doStart" <<
this;
224 connect(d->sessionInternal(), SIGNAL(encryptionNegotiationResult(
bool)),
this, SLOT(sslResponse(
bool)));
226 if (session()->state() == Session::Disconnected) {
227 auto guard =
new QObject(
this);
228 QObject::connect(session(), &Session::stateChanged, guard, [d, guard](KIMAP2::Session::State newState, KIMAP2::Session::State) {
229 qCDebug(KIMAP2_LOG) <<
"Session state changed" << newState;
235 d->sessionInternal()->startSsl(d->encryptionMode);
240 qCInfo(KIMAP2_LOG) <<
"Session is ready, carring on";
247void LoginJobPrivate::login()
250 if (q->session()->isConnected()) {
251 q->
setError(LoginJob::UserDefinedError);
259 qCInfo(KIMAP2_LOG) <<
"Starting with tls";
260 authState = LoginJobPrivate::StartTls;
266 retrieveCapabilities();
268 qCInfo(KIMAP2_LOG) <<
"Waiting for encryption before retrieveing capabilities.";
274void LoginJobPrivate::sslResponse(
bool response)
276 qCDebug(KIMAP2_LOG) <<
"Got an ssl response " << response;
277 connectionIsEncrypted = response;
282 if (m_session->state() != Session::Disconnected) {
283 retrieveCapabilities();
293void LoginJobPrivate::retrieveCapabilities()
295 qCDebug(KIMAP2_LOG) <<
"Retrieving capabilities.";
296 authState = LoginJobPrivate::Capability;
300void LoginJob::handleResponse(
const Message &response)
304 if (response.content.isEmpty()) {
309 QString commandName = QStringLiteral(
"Login");
310 if (d->authState == LoginJobPrivate::Capability) {
311 commandName = QStringLiteral(
"Capability");
312 }
else if (d->authState == LoginJobPrivate::StartTls) {
313 commandName = QStringLiteral(
"StartTls");
325 ResponseCode code =
OK;
327 qCDebug(KIMAP2_LOG) << commandName << tag;
331 }
else if (tag ==
"*") {
332 if (response.content.size() < 2) {
337 }
else if (d->tags.contains(tag)) {
338 if (response.content.size() < 2) {
340 }
else if (response.content[1].toString() ==
"OK") {
354 if (d->authState == LoginJobPrivate::Authenticate) {
355 sasl_dispose(&d->conn);
365 if (response.content[1].toString() ==
"CAPABILITY") {
367 while (p != response.content.end()) {
369 d->capabilities << capability;
371 d->plainLoginDisabled =
true;
375 qCInfo(KIMAP2_LOG) <<
"Capabilities updated: " << d->capabilities;
380 if (d->authState != LoginJobPrivate::Authenticate) {
388 if (response.content.size() > 1 && response.content.at(1).toString() ==
"OK") {
392 if (!d->authorizationName.isEmpty()) {
393 challengeResponse += d->authorizationName.toUtf8();
395 challengeResponse +=
'\0';
396 challengeResponse += d->userName.toUtf8();
397 challengeResponse +=
'\0';
398 challengeResponse += d->password.toUtf8();
399 challengeResponse = challengeResponse.
toBase64();
400 d->sessionInternal()->sendData(challengeResponse);
401 }
else if (response.content.size() >= 2) {
412 switch (d->authState) {
413 case LoginJobPrivate::StartTls:
415 d->sessionInternal()->startSsl(d->encryptionMode);
417 case LoginJobPrivate::Capability:
419 if (d->authMode.isEmpty()) {
420 if (d->plainLoginDisabled) {
428 bool authModeSupported =
false;
430 if (d->authMode ==
"PLAIN") {
431 authModeSupported =
true;
434 Q_FOREACH (
const QString &capability, d->capabilities) {
436 if (capability.
mid(5) == d->authMode) {
437 authModeSupported =
true;
442 if (!authModeSupported) {
444 setErrorText(
QString(
"Login failed, authentication mode %1 is not supported by the server.").arg(d->authMode));
446 }
else if (!d->startAuthentication()) {
452 case LoginJobPrivate::Authenticate:
453 sasl_dispose(&d->conn);
455 case LoginJobPrivate::Login:
456 d->saveServerGreeting(response);
464 if (code == MALFORMED) {
470bool LoginJobPrivate::startAuthentication()
479 authState = LoginJobPrivate::Authenticate;
480 const char *out = Q_NULLPTR;
482 const char *mechusing = Q_NULLPTR;
484 int result = sasl_client_new(
"imap", m_session->hostName().toLatin1(), Q_NULLPTR,
nullptr, callbacks, 0, &conn);
485 if (result != SASL_OK) {
486 qCWarning(KIMAP2_LOG) <<
"sasl_client_new failed with:" << result;
493 result = sasl_client_start(conn, authMode.
toLatin1(), &client_interact, capabilities.
contains(QStringLiteral(
"SASL-IR")) ? &out : Q_NULLPTR, &outlen, &mechusing);
495 if (result == SASL_INTERACT) {
496 if (!sasl_interact()) {
503 }
while (result == SASL_INTERACT);
505 if (result != SASL_CONTINUE && result != SASL_OK) {
506 qCWarning(KIMAP2_LOG) <<
"sasl_client_start failed with:" << result;
525void LoginJobPrivate::sendPlainLogin()
527 authState = LoginJobPrivate::Login;
528 qCDebug(KIMAP2_LOG) <<
"sending LOGIN";
530 '"' +
quoteIMAP(userName).toUtf8() +
'"' +
532 '"' +
quoteIMAP(password).toUtf8() +
'"');
535bool LoginJobPrivate::answerChallenge(
const QByteArray &data)
539 const char *out = Q_NULLPTR;
542 result = sasl_client_step(conn, challenge.
isEmpty() ? Q_NULLPTR : challenge.
data(),
547 if (result == SASL_INTERACT) {
548 if (!sasl_interact()) {
555 }
while (result == SASL_INTERACT);
557 if (result != SASL_CONTINUE && result != SASL_OK) {
558 qCWarning(KIMAP2_LOG) <<
"sasl_client_step failed with:" << result;
568 sessionInternal()->sendData(challenge);
576 d->encryptionMode = mode;
577 d->startTls = startTls;
583 return d->encryptionMode;
586void LoginJob::setAuthenticationMode(AuthenticationMode mode)
592 case Login: d->authMode = QStringLiteral(
"LOGIN");
594 case Plain: d->authMode = QStringLiteral(
"PLAIN");
596 case CramMD5: d->authMode = QStringLiteral(
"CRAM-MD5");
598 case DigestMD5: d->authMode = QStringLiteral(
"DIGEST-MD5");
600 case GSSAPI: d->authMode = QStringLiteral(
"GSSAPI");
602 case Anonymous: d->authMode = QStringLiteral(
"ANONYMOUS");
604 case XOAuth2: d->authMode = QStringLiteral(
"XOAUTH2");
607 d->authMode = QStringLiteral(
"");
611void LoginJob::connectionLost()
615 qCWarning(KIMAP2_LOG) <<
"Connection to server lost " << d->m_socketError;
616 if (d->m_socketError == QSslSocket::SslHandshakeFailedError) {
620 }
else if (d->m_socketError == QSslSocket::HostNotFoundError) {
631void LoginJobPrivate::saveServerGreeting(
const Message &response)
636 for (
int i = 2; i < response.content.size(); i++) {
637 if (response.content.at(i).type() == Message::Part::List) {
639 foreach (
const QByteArray &item, response.content.
at(i).toList()) {
642 serverGreeting.
chop(1);
643 serverGreeting += QStringLiteral(
") ");
648 serverGreeting.
chop(1);
651QString LoginJob::serverGreeting()
const
654 return d->serverGreeting;
657#include "moc_loginjob.cpp"
void setErrorText(const QString &errorText)
void setError(int errorCode)
QString name(StandardAction id)
KLEO_EXPORT std::unique_ptr< GpgME::DefaultAssuanTransaction > sendCommand(std::shared_ptr< GpgME::Context > &assuanContext, const std::string &command, GpgME::Error &err)
char at(qsizetype i) 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
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
QString mid(qsizetype position, qsizetype n) 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.
KIMAP2_EXPORT QString quoteIMAP(const QString &src)
Replaces " with \" and \ with \\ " and \ characters.