11#include "ksmtp_debug.h"
12#include "serverresponse_p.h"
15#include <KLocalizedString>
17#include <QJsonDocument>
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}};
38class LoginJobPrivate :
public JobPrivate
42 : JobPrivate(session,
name)
43 , m_preferedAuthMode(
LoginJob::Login)
44 , m_actualAuthMode(
LoginJob::UnknownAuth)
49 ~LoginJobPrivate()
override =
default;
51 [[nodiscard]]
bool sasl_interact();
52 [[nodiscard]]
bool sasl_init();
53 [[nodiscard]]
bool sasl_challenge(
const QByteArray &data);
55 [[nodiscard]]
bool authenticate();
56 [[nodiscard]]
bool selectAuthentication();
58 [[nodiscard]] LoginJob::AuthMode authModeFromCommand(
const QByteArray &mech)
const;
59 [[nodiscard]]
QByteArray authCommand(LoginJob::AuthMode mode)
const;
63 LoginJob::AuthMode m_preferedAuthMode;
64 LoginJob::AuthMode m_actualAuthMode;
66 sasl_conn_t *m_saslConn =
nullptr;
67 sasl_interact_t *m_saslClient =
nullptr;
76LoginJob::LoginJob(
Session *session)
77 :
Job(*new LoginJobPrivate(this, session,
i18n(
"Login")))
81LoginJob::~LoginJob() =
default;
83void LoginJob::setUserName(
const QString &userName)
86 d->m_userName = userName;
89void LoginJob::setPassword(
const QString &password)
92 d->m_password = password;
95void LoginJob::setPreferedAuthMode(AuthMode mode)
99 if (mode == UnknownAuth) {
100 qCWarning(KSMTP_LOG) <<
"LoginJob: Cannot set preferred authentication mode to Unknown";
103 d->m_preferedAuthMode = mode;
106LoginJob::AuthMode LoginJob::usedAuthMode()
const
108 return d_func()->m_actualAuthMode;
111void LoginJob::doStart()
115 qFatal(
"LoginJob started despite session not being encrypted!");
118 if (!d->authenticate()) {
123void LoginJob::handleResponse(
const ServerResponse &r)
132 if (d->m_actualAuthMode == Plain) {
133 const QByteArray challengeResponse =
'\0' + d->m_userName.toUtf8() +
'\0' + d->m_password.toUtf8();
134 sendCommand(challengeResponse.
toBase64());
150bool LoginJobPrivate::selectAuthentication()
152 const QStringList availableModes = m_session->availableAuthModes();
155 m_actualAuthMode = m_preferedAuthMode;
157 m_actualAuthMode = LoginJob::Login;
159 m_actualAuthMode = LoginJob::Plain;
161 qCWarning(KSMTP_LOG) <<
"LoginJob: Couldn't choose an authentication method. Please retry with : " << availableModes;
162 q->
setError(KJob::UserDefinedError);
163 q->
setErrorText(
i18n(
"Could not authenticate to the SMTP server because no matching authentication method has been found"));
170bool LoginJobPrivate::sasl_init()
172 if (sasl_client_init(
nullptr) != SASL_OK) {
173 qCWarning(KSMTP_LOG) <<
"Failed to initialize SASL";
179bool LoginJobPrivate::sasl_interact()
181 sasl_interact_t *interact = m_saslClient;
183 while (interact->id != SASL_CB_LIST_END) {
184 qCDebug(KSMTP_LOG) <<
"SASL_INTERACT Id" << interact->id;
185 switch (interact->id) {
186 case SASL_CB_AUTHNAME: {
188 qCDebug(KSMTP_LOG) <<
"SASL_CB_[USER|AUTHNAME]: '" << m_userName <<
"'";
189 const auto username = m_userName.
toUtf8();
190 interact->result = strdup(username.constData());
191 interact->len = username.size();
195 qCDebug(KSMTP_LOG) <<
"SASL_CB_PASS: [hidden]";
196 const auto pass = m_password.
toUtf8();
197 interact->result = strdup(pass.constData());
198 interact->len = pass.size();
202 interact->result =
nullptr;
212bool LoginJobPrivate::sasl_challenge(
const QByteArray &challenge)
215 const char *out =
nullptr;
218 if (m_actualAuthMode == LoginJob::XOAuth2) {
221 const auto obj = doc.
object();
223 q->
setError(LoginJob::TokenExpired);
234 result = sasl_client_step(m_saslConn, challenge.
isEmpty() ?
nullptr : challenge.
constData(), challenge.
size(), &m_saslClient, &out, &outLen);
235 if (result == SASL_INTERACT) {
236 if (!sasl_interact()) {
237 q->
setError(LoginJob::UserDefinedError);
238 sasl_dispose(&m_saslConn);
246 if (result != SASL_OK && result != SASL_CONTINUE) {
248 qCWarning(KSMTP_LOG) <<
"sasl_client_step failed: " << result << saslError;
249 q->
setError(LoginJob::UserDefinedError);
251 sasl_dispose(&m_saslConn);
260bool LoginJobPrivate::authenticate()
262 if (!selectAuthentication()) {
267 q->
setError(LoginJob::UserDefinedError);
272 int result = sasl_client_new(
"smtp", m_session->hostName().toUtf8().constData(),
nullptr,
nullptr, callbacks, 0, &m_saslConn);
273 if (result != SASL_OK) {
275 q->
setError(LoginJob::UserDefinedError);
281 const char *out =
nullptr;
282 const char *actualMech =
nullptr;
283 const auto authMode = authCommand(m_actualAuthMode);
286 qCDebug(KSMTP_LOG) <<
"Trying authmod" << authMode;
287 result = sasl_client_start(m_saslConn, authMode.constData(), &m_saslClient, &out, &outLen, &actualMech);
288 if (result == SASL_INTERACT) {
289 if (!sasl_interact()) {
290 sasl_dispose(&m_saslConn);
291 q->
setError(LoginJob::UserDefinedError);
299 m_actualAuthMode = authModeFromCommand(actualMech);
301 if (result != SASL_CONTINUE && result != SASL_OK) {
303 qCWarning(KSMTP_LOG) <<
"sasl_client_start failed with:" << result << saslError;
304 q->
setError(LoginJob::UserDefinedError);
306 sasl_dispose(&m_saslConn);
311 q->sendCommand(
"AUTH " + authMode);
319LoginJob::AuthMode LoginJobPrivate::authModeFromCommand(
const QByteArray &mech)
const
321 if (qstrnicmp(mech.
constData(),
"PLAIN", 5) == 0) {
322 return LoginJob::Plain;
323 }
else if (qstrnicmp(mech.
constData(),
"LOGIN", 5) == 0) {
324 return LoginJob::Login;
325 }
else if (qstrnicmp(mech.
constData(),
"CRAM-MD5", 8) == 0) {
326 return LoginJob::CramMD5;
327 }
else if (qstrnicmp(mech.
constData(),
"DIGEST-MD5", 10) == 0) {
328 return LoginJob::DigestMD5;
329 }
else if (qstrnicmp(mech.
constData(),
"GSSAPI", 6) == 0) {
330 return LoginJob::GSSAPI;
331 }
else if (qstrnicmp(mech.
constData(),
"NTLM", 4) == 0) {
332 return LoginJob::NTLM;
333 }
else if (qstrnicmp(mech.
constData(),
"ANONYMOUS", 9) == 0) {
334 return LoginJob::Anonymous;
335 }
else if (qstrnicmp(mech.
constData(),
"XOAUTH2", 7) == 0) {
336 return LoginJob::XOAuth2;
338 return LoginJob::UnknownAuth;
342QByteArray LoginJobPrivate::authCommand(LoginJob::AuthMode mode)
const
345 case LoginJob::Plain:
346 return QByteArrayLiteral(
"PLAIN");
347 case LoginJob::Login:
348 return QByteArrayLiteral(
"LOGIN");
349 case LoginJob::CramMD5:
350 return QByteArrayLiteral(
"CRAM-MD5");
351 case LoginJob::DigestMD5:
352 return QByteArrayLiteral(
"DIGEST-MD5");
353 case LoginJob::GSSAPI:
354 return QByteArrayLiteral(
"GSSAPI");
356 return QByteArrayLiteral(
"NTLM");
357 case LoginJob::Anonymous:
358 return QByteArrayLiteral(
"ANONYMOUS");
359 case LoginJob::XOAuth2:
360 return QByteArrayLiteral(
"XOAUTH2");
361 case LoginJob::UnknownAuth:
367#include "moc_loginjob.cpp"
void setErrorText(const QString &errorText)
void setError(int errorCode)
@ Authenticated
The Session is ready to send email.
@ Unencrypted
Use no encryption.
QString i18n(const char *text, const TYPE &arg...)
char * toString(const EngineQuery &query)
QString name(StandardAction id)
const char * constData() 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
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
bool isNull() const const
bool isObject() const const
QJsonObject object() const const
QString fromLatin1(QByteArrayView str)
QString fromUtf8(QByteArrayView str)
QByteArray toUtf8() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const