23 #include <KDE/KLocalizedString>
25 #include <ktcpsocket.h>
28 #include "message_p.h"
29 #include "session_p.h"
35 #include <sasl/sasl.h>
38 static sasl_callback_t callbacks[] = {
39 { SASL_CB_ECHOPROMPT, NULL, NULL },
40 { SASL_CB_NOECHOPROMPT, NULL, NULL },
41 { SASL_CB_GETREALM, NULL, NULL },
42 { SASL_CB_USER, NULL, NULL },
43 { SASL_CB_AUTHNAME, NULL, NULL },
44 { SASL_CB_PASS, NULL, NULL },
45 { SASL_CB_CANON_USER, NULL, NULL },
46 { SASL_CB_LIST_END, NULL, NULL }
51 class LoginJobPrivate :
public JobPrivate
61 LoginJobPrivate( LoginJob *job, Session *session,
const QString& name ) : JobPrivate( session, name ), q( job ), encryptionMode( LoginJob::Unencrypted ), authState( Login ), plainLoginDisabled( false ) {
65 ~LoginJobPrivate() { }
68 bool startAuthentication();
70 void sslResponse(
bool response);
71 void saveServerGreeting(
const Message &response);
80 LoginJob::EncryptionMode encryptionMode;
84 bool plainLoginDisabled;
87 sasl_interact_t *client_interact;
91 using namespace KIMAP;
93 bool LoginJobPrivate::sasl_interact()
95 kDebug() <<
"sasl_interact";
96 sasl_interact_t *interact = client_interact;
100 for ( ; interact->id != SASL_CB_LIST_END; interact++ ) {
101 if ( interact->id == SASL_CB_AUTHNAME ||
102 interact->id == SASL_CB_PASS ) {
108 interact = client_interact;
109 while ( interact->id != SASL_CB_LIST_END ) {
110 kDebug() <<
"SASL_INTERACT id:" << interact->id;
111 switch ( interact->id ) {
112 case SASL_CB_AUTHNAME:
113 if ( !authorizationName.isEmpty() ) {
114 kDebug() <<
"SASL_CB_[AUTHNAME]: '" << authorizationName <<
"'";
115 interact->result = strdup( authorizationName.toUtf8() );
116 interact->len = strlen( (
const char *) interact->result );
120 kDebug() <<
"SASL_CB_[USER|AUTHNAME]: '" << userName <<
"'";
121 interact->result = strdup( userName.toUtf8() );
122 interact->len = strlen( (
const char *) interact->result );
125 kDebug() <<
"SASL_CB_PASS: [hidden]";
126 interact->result = strdup( password.toUtf8() );
127 interact->len = strlen( (
const char *) interact->result );
130 interact->result = 0;
139 LoginJob::LoginJob( Session *session )
140 : Job( *new LoginJobPrivate( this, session, i18n(
"Login" ) ) )
143 connect( d->sessionInternal(), SIGNAL(encryptionNegotiationResult(
bool)),
this, SLOT(sslResponse(
bool)) );
147 LoginJob::~LoginJob()
152 QString LoginJob::userName()
const
154 Q_D(
const LoginJob );
158 void LoginJob::setUserName(
const QString &userName )
161 d->userName = userName;
164 QString LoginJob::authorizationName()
const
166 Q_D(
const LoginJob );
167 return d->authorizationName;
170 void LoginJob::setAuthorizationName(
const QString& authorizationName )
173 d->authorizationName = authorizationName;
176 QString LoginJob::password()
const
178 Q_D(
const LoginJob );
182 void LoginJob::setPassword(
const QString &password )
185 d->password = password;
188 void LoginJob::doStart()
194 if ( session()->state() == Session::Authenticated || session()->state() == Session::Selected ) {
195 setError( UserDefinedError );
196 setErrorText( i18n(
"IMAP session in the wrong state for authentication" ) );
202 EncryptionMode encryptionMode = d->encryptionMode;
204 switch ( d->sessionInternal()->negotiatedEncryption() ) {
205 case KTcpSocket::UnknownSslVersion:
212 case KTcpSocket::SslV2:
213 if ( encryptionMode == SslV2 ) {
214 encryptionMode = Unencrypted;
217 case KTcpSocket::SslV3:
218 if ( encryptionMode == SslV3 ) {
219 encryptionMode = Unencrypted;
222 case KTcpSocket::TlsV1:
223 if ( encryptionMode == TlsV1 ) {
224 encryptionMode = Unencrypted;
227 case KTcpSocket::AnySslVersion:
228 if ( encryptionMode == AnySslVersion ) {
229 encryptionMode = Unencrypted;
234 if ( encryptionMode == SslV2 ||
235 encryptionMode == SslV3 ||
236 encryptionMode == SslV3_1 ||
237 encryptionMode == AnySslVersion ) {
238 KTcpSocket::SslVersion version = KTcpSocket::SslV2;
239 if ( encryptionMode == SslV3 ) {
240 version = KTcpSocket::SslV3;
242 if ( encryptionMode == SslV3_1 ) {
243 version = KTcpSocket::SslV3_1;
245 if ( encryptionMode == AnySslVersion ) {
246 version = KTcpSocket::AnySslVersion;
248 d->sessionInternal()->startSsl( version );
250 }
else if ( encryptionMode == TlsV1 ) {
251 d->authState = LoginJobPrivate::StartTls;
252 d->tags << d->sessionInternal()->sendCommand(
"STARTTLS" );
254 }
else if ( encryptionMode == Unencrypted ) {
255 if ( d->authMode.isEmpty() ) {
256 d->authState = LoginJobPrivate::Login;
257 kDebug() <<
"sending LOGIN";
258 d->tags << d->sessionInternal()->sendCommand(
"LOGIN",
259 '"' + quoteIMAP( d->userName ).
toUtf8() +
'"' +
261 '"' + quoteIMAP( d->password ).
toUtf8() +
'"' );
263 if ( !d->startAuthentication() ) {
270 void LoginJob::handleResponse(
const Message &response )
274 if ( response.content.isEmpty() ) {
279 QString commandName = i18n(
"Login" );
280 if ( d->authState == LoginJobPrivate::Capability ) {
281 commandName = i18n(
"Capability" );
282 }
else if ( d->authState == LoginJobPrivate::StartTls ) {
283 commandName = i18n(
"StartTls" );
294 QByteArray tag = response.content.first().toString();
295 ResponseCode code = OK;
297 kDebug() << commandName << tag;
301 }
else if ( tag ==
"*" ) {
302 if ( response.content.size() < 2 ) {
307 }
else if ( d->tags.contains( tag ) ) {
308 if ( response.content.size() < 2 ) {
310 }
else if ( response.content[1].toString() ==
"OK" ) {
324 if ( d->authState == LoginJobPrivate::Authenticate ) {
325 sasl_dispose( &d->conn );
328 setError( UserDefinedError );
329 setErrorText( i18n(
"%1 failed, server replied: %2", commandName,
QLatin1String(response.toString().constData()) ) );
335 if ( response.content[1].toString() ==
"CAPABILITY" ) {
337 while ( p != response.content.end() ) {
339 d->capabilities << capability;
341 d->plainLoginDisabled =
true;
345 kDebug() <<
"Capabilities updated: " << d->capabilities;
350 if ( d->authState != LoginJobPrivate::Authenticate ) {
358 if ( response.content.size()>1 && response.content.at( 1 ).toString() ==
"OK" ) {
362 if ( !d->authorizationName.isEmpty() ) {
363 challengeResponse+= d->authorizationName.toUtf8();
365 challengeResponse+=
'\0';
366 challengeResponse+= d->userName.toUtf8();
367 challengeResponse+=
'\0';
368 challengeResponse+= d->password.toUtf8();
369 challengeResponse = challengeResponse.
toBase64();
370 d->sessionInternal()->sendData( challengeResponse );
371 }
else if ( response.content.size() >= 2 ) {
375 }
else if ( d->authMode ==
QLatin1String(
"GSSAPI" ) && response.content.size() == 1 ) {
378 if ( !d->answerChallenge(
"" ) ) {
389 switch ( d->authState ) {
390 case LoginJobPrivate::StartTls:
391 d->sessionInternal()->startSsl( KTcpSocket::TlsV1 );
394 case LoginJobPrivate::Capability:
396 if ( d->authMode.isEmpty() ) {
397 if ( d->plainLoginDisabled ) {
398 setError( UserDefinedError );
399 setErrorText( i18n(
"Login failed, plain login is disabled by the server." ) );
402 d->authState = LoginJobPrivate::Login;
403 d->tags << d->sessionInternal()->sendCommand(
"LOGIN",
404 '"' + quoteIMAP( d->userName ).
toUtf8() +
'"' +
406 '"' + quoteIMAP( d->password ).
toUtf8() +
'"' );
409 bool authModeSupported =
false;
411 Q_FOREACH (
const QString &capability, d->capabilities ) {
413 if ( capability.
mid( 5 ) == d->authMode ) {
414 authModeSupported =
true;
419 if ( !authModeSupported ) {
420 setError( UserDefinedError );
421 setErrorText( i18n(
"Login failed, authentication mode %1 is not supported by the server.", d->authMode ) );
423 }
else if ( !d->startAuthentication() ) {
429 case LoginJobPrivate::Authenticate:
430 sasl_dispose( &d->conn );
432 case LoginJobPrivate::Login:
433 d->saveServerGreeting( response );
441 if ( code == MALFORMED ) {
442 setErrorText( i18n(
"%1 failed, malformed reply from the server.", commandName ) );
447 bool LoginJobPrivate::startAuthentication()
451 q->setError( LoginJob::UserDefinedError );
452 q->setErrorText( i18n(
"Login failed, client cannot initialize the SASL library." ) );
456 authState = LoginJobPrivate::Authenticate;
459 const char *mechusing = 0;
461 int result = sasl_client_new(
"imap", m_session->hostName().toLatin1(), 0, 0, callbacks, 0, &conn );
462 if ( result != SASL_OK ) {
463 kDebug() <<
"sasl_client_new failed with:" << result;
464 q->setError( LoginJob::UserDefinedError );
470 result = sasl_client_start( conn, authMode.toLatin1(), &client_interact, capabilities.contains(
QLatin1String(
"SASL-IR") ) ? &out : 0, &outlen, &mechusing );
472 if ( result == SASL_INTERACT ) {
473 if ( !sasl_interact() ) {
474 sasl_dispose( &conn );
475 q->setError( LoginJob::UserDefinedError );
479 }
while ( result == SASL_INTERACT );
481 if ( result != SASL_CONTINUE && result != SASL_OK ) {
482 kDebug() <<
"sasl_client_start failed with:" << result;
483 q->setError( LoginJob::UserDefinedError );
485 sasl_dispose( &conn );
493 tags << sessionInternal()->sendCommand(
"AUTHENTICATE", authMode.toLatin1() );
495 tags << sessionInternal()->sendCommand(
"AUTHENTICATE", authMode.toLatin1() +
' ' + challenge );
501 bool LoginJobPrivate::answerChallenge(
const QByteArray &data)
508 result = sasl_client_step( conn, challenge.
isEmpty() ? 0 : challenge.
data(),
513 if ( result == SASL_INTERACT ) {
514 if ( !sasl_interact() ) {
515 q->setError( LoginJob::UserDefinedError );
516 sasl_dispose( &conn );
520 }
while ( result == SASL_INTERACT );
522 if ( result != SASL_CONTINUE && result != SASL_OK ) {
523 kDebug() <<
"sasl_client_step failed with:" << result;
524 q->setError( LoginJob::UserDefinedError );
526 sasl_dispose( &conn );
533 sessionInternal()->sendData( challenge );
538 void LoginJobPrivate::sslResponse(
bool response)
541 authState = LoginJobPrivate::Capability;
542 tags << sessionInternal()->sendCommand(
"CAPABILITY" );
544 q->setError( LoginJob::UserDefinedError );
545 q->setErrorText( i18n(
"Login failed, TLS negotiation failed." ) );
546 encryptionMode = LoginJob::Unencrypted;
551 void LoginJob::setEncryptionMode(EncryptionMode mode)
554 d->encryptionMode = mode;
557 LoginJob::EncryptionMode LoginJob::encryptionMode()
560 return d->encryptionMode;
563 void LoginJob::setAuthenticationMode(AuthenticationMode mode)
588 void LoginJob::connectionLost()
594 if ( d->authState != LoginJobPrivate::StartTls ) {
595 kWarning() <<
"Connection to server lost " << d->m_socketError;
596 if ( d->m_socketError == KTcpSocket::SslHandshakeFailedError) {
597 setError( KJob::UserDefinedError );
598 setErrorText( i18n(
"SSL handshake failed." ) );
601 setError( ERR_COULD_NOT_CONNECT );
602 setErrorText( i18n(
"Connection to server lost." ) );
609 void LoginJobPrivate::saveServerGreeting(
const Message &response)
614 for (
int i = 2; i < response.content.size(); i++ ) {
615 if ( response.content.at( i ).type() == Message::Part::List ) {
617 foreach (
const QByteArray &item, response.content.
at( i ).toList() ) {
620 serverGreeting.chop( 1 );
626 serverGreeting.chop( 1 );
629 QString LoginJob::serverGreeting()
const
631 Q_D(
const LoginJob );
632 return d->serverGreeting;
635 #include "moc_loginjob.cpp"
This file is part of the IMAP support library and defines the RfcCodecs class.
QByteArray fromRawData(const char *data, int size)
QString fromUtf8(const char *str, int size)
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const
QString mid(int position, int n) const
QByteArray fromBase64(const QByteArray &base64)
QByteArray toBase64() const
QByteArray toUtf8() const