24 #include <KDE/KLocale>
26 #include <ktcpsocket.h>
29 #include "message_p.h"
30 #include "session_p.h"
36 #include <sasl/sasl.h>
39 static sasl_callback_t callbacks[] = {
40 { SASL_CB_ECHOPROMPT, NULL, NULL },
41 { SASL_CB_NOECHOPROMPT, NULL, NULL },
42 { SASL_CB_GETREALM, NULL, NULL },
43 { SASL_CB_USER, NULL, NULL },
44 { SASL_CB_AUTHNAME, NULL, NULL },
45 { SASL_CB_PASS, NULL, NULL },
46 { SASL_CB_CANON_USER, NULL, NULL },
47 { SASL_CB_LIST_END, NULL, NULL }
52 class LoginJobPrivate :
public JobPrivate
62 LoginJobPrivate( LoginJob *job, Session *session,
const QString& name ) : JobPrivate( session, name ), q( job ), encryptionMode( LoginJob::Unencrypted ), authState( Login ), plainLoginDisabled( false ) {
66 ~LoginJobPrivate() { }
69 bool startAuthentication();
70 bool answerChallenge(
const QByteArray &data);
71 void sslResponse(
bool response);
72 void saveServerGreeting(
const Message &response);
77 QString authorizationName;
79 QString serverGreeting;
81 LoginJob::EncryptionMode encryptionMode;
84 QStringList capabilities;
85 bool plainLoginDisabled;
88 sasl_interact_t *client_interact;
92 using namespace KIMAP;
94 bool LoginJobPrivate::sasl_interact()
96 kDebug() <<
"sasl_interact";
97 sasl_interact_t *interact = client_interact;
101 for ( ; interact->id != SASL_CB_LIST_END; interact++ ) {
102 if ( interact->id == SASL_CB_AUTHNAME ||
103 interact->id == SASL_CB_PASS ) {
109 interact = client_interact;
110 while ( interact->id != SASL_CB_LIST_END ) {
111 kDebug() <<
"SASL_INTERACT id:" << interact->id;
112 switch ( interact->id ) {
113 case SASL_CB_AUTHNAME:
114 if ( !authorizationName.isEmpty() ) {
115 kDebug() <<
"SASL_CB_[AUTHNAME]: '" << authorizationName <<
"'";
116 interact->result = strdup( authorizationName.toUtf8() );
117 interact->len = strlen( (
const char *) interact->result );
121 kDebug() <<
"SASL_CB_[USER|AUTHNAME]: '" << userName <<
"'";
122 interact->result = strdup( userName.toUtf8() );
123 interact->len = strlen( (
const char *) interact->result );
126 kDebug() <<
"SASL_CB_PASS: [hidden]";
127 interact->result = strdup( password.toUtf8() );
128 interact->len = strlen( (
const char *) interact->result );
131 interact->result = 0;
140 LoginJob::LoginJob( Session *session )
141 : Job( *new LoginJobPrivate( this, session, i18n(
"Login" ) ) )
144 connect( d->sessionInternal(), SIGNAL(encryptionNegotiationResult(
bool)),
this, SLOT(sslResponse(
bool)) );
147 LoginJob::~LoginJob()
151 QString LoginJob::userName()
const
153 Q_D(
const LoginJob );
157 void LoginJob::setUserName(
const QString &userName )
160 d->userName = userName;
163 QString LoginJob::authorizationName()
const
165 Q_D(
const LoginJob );
166 return d->authorizationName;
169 void LoginJob::setAuthorizationName(
const QString& authorizationName )
172 d->authorizationName = authorizationName;
175 QString LoginJob::password()
const
177 Q_D(
const LoginJob );
181 void LoginJob::setPassword(
const QString &password )
184 d->password = password;
187 void LoginJob::doStart()
192 if ( session()->state() == Session::Authenticated || session()->state() == Session::Selected ) {
193 setError( UserDefinedError );
194 setErrorText( i18n(
"IMAP session in the wrong state for authentication" ) );
200 EncryptionMode encryptionMode = d->encryptionMode;
202 switch ( d->sessionInternal()->negotiatedEncryption() ) {
203 case KTcpSocket::UnknownSslVersion:
210 case KTcpSocket::SslV2:
211 if ( encryptionMode == SslV2 ) {
212 encryptionMode = Unencrypted;
215 case KTcpSocket::SslV3:
216 if ( encryptionMode == SslV3 ) {
217 encryptionMode = Unencrypted;
220 case KTcpSocket::TlsV1:
221 if ( encryptionMode == TlsV1 ) {
222 encryptionMode = Unencrypted;
225 case KTcpSocket::AnySslVersion:
226 if ( encryptionMode == AnySslVersion ) {
227 encryptionMode = Unencrypted;
232 if ( encryptionMode == SslV2 ||
233 encryptionMode == SslV3 ||
234 encryptionMode == SslV3_1 ||
235 encryptionMode == AnySslVersion ) {
236 KTcpSocket::SslVersion version = KTcpSocket::SslV2;
237 if ( encryptionMode == SslV3 ) {
238 version = KTcpSocket::SslV3;
240 if ( encryptionMode == SslV3_1 ) {
241 version = KTcpSocket::SslV3_1;
243 if ( encryptionMode == AnySslVersion ) {
244 version = KTcpSocket::AnySslVersion;
246 d->sessionInternal()->startSsl( version );
248 }
else if ( encryptionMode == TlsV1 ) {
249 d->authState = LoginJobPrivate::StartTls;
250 d->tags << d->sessionInternal()->sendCommand(
"STARTTLS" );
252 }
else if ( encryptionMode == Unencrypted ) {
253 if ( d->authMode.isEmpty() ) {
254 d->authState = LoginJobPrivate::Login;
255 d->tags << d->sessionInternal()->sendCommand(
"LOGIN",
256 '"' + quoteIMAP( d->userName ).toUtf8() +
'"' +
258 '"' + quoteIMAP( d->password ).toUtf8() +
'"' );
260 if ( !d->startAuthentication() ) {
267 void LoginJob::handleResponse(
const Message &response )
271 if ( response.content.isEmpty() ) {
276 QString commandName = i18n(
"Login" );
277 if ( d->authState == LoginJobPrivate::Capability ) {
278 commandName = i18n(
"Capability" );
279 }
else if ( d->authState == LoginJobPrivate::StartTls ) {
280 commandName = i18n(
"StartTls" );
291 QByteArray tag = response.content.first().toString();
296 }
else if ( tag ==
"*" ) {
297 if ( response.content.size() < 2 ) {
302 }
else if ( d->tags.contains( tag ) ) {
303 if ( response.content.size() < 2 ) {
305 }
else if ( response.content[1].toString() ==
"OK" ) {
319 if ( d->authState == LoginJobPrivate::Authenticate ) {
320 sasl_dispose( &d->conn );
323 setError( UserDefinedError );
324 setErrorText( i18n(
"%1 failed, server replied: %2", commandName, response.toString().constData() ) );
330 if ( response.content[1].toString() ==
"CAPABILITY" ) {
331 QList<Message::Part>::const_iterator p = response.content.begin() + 2;
332 while ( p != response.content.end() ) {
333 QString capability = p->toString();
334 d->capabilities << capability;
335 if ( capability ==
"LOGINDISABLED" ) {
336 d->plainLoginDisabled =
true;
340 kDebug() <<
"Capabilities updated: " << d->capabilities;
345 if ( d->authState != LoginJobPrivate::Authenticate ) {
352 if ( d->authMode == QLatin1String(
"PLAIN" ) ) {
353 if ( response.content.size()>1 && response.content.at( 1 ).toString() ==
"OK" ) {
356 QByteArray challengeResponse;
357 if ( !d->authorizationName.isEmpty() ) {
358 challengeResponse+= d->authorizationName.toUtf8();
360 challengeResponse+=
'\0';
361 challengeResponse+= d->userName.toUtf8();
362 challengeResponse+=
'\0';
363 challengeResponse+= d->password.toUtf8();
364 challengeResponse = challengeResponse.toBase64();
365 d->sessionInternal()->sendData( challengeResponse );
366 }
else if ( response.content.size() >= 2 ) {
367 if ( !d->answerChallenge( QByteArray::fromBase64( response.content[1].toString() ) ) ) {
378 switch ( d->authState ) {
379 case LoginJobPrivate::StartTls:
380 d->sessionInternal()->startSsl( KTcpSocket::TlsV1 );
383 case LoginJobPrivate::Capability:
385 if ( d->authMode.isEmpty() ) {
386 if ( d->plainLoginDisabled ) {
387 setError( UserDefinedError );
388 setErrorText( i18n(
"Login failed, plain login is disabled by the server." ) );
391 d->authState = LoginJobPrivate::Login;
392 d->tags << d->sessionInternal()->sendCommand(
"LOGIN",
393 '"' + quoteIMAP( d->userName ).toUtf8() +
'"' +
395 '"' + quoteIMAP( d->password ).toUtf8() +
'"' );
398 bool authModeSupported =
false;
400 Q_FOREACH (
const QString &capability, d->capabilities ) {
401 if ( capability.startsWith( QLatin1String(
"AUTH=" ) ) ) {
402 if ( capability.mid( 5 ) == d->authMode ) {
403 authModeSupported =
true;
408 if ( !authModeSupported ) {
409 setError( UserDefinedError );
410 setErrorText( i18n(
"Login failed, authentication mode %1 is not supported by the server.", d->authMode ) );
412 }
else if ( !d->startAuthentication() ) {
418 case LoginJobPrivate::Authenticate:
419 sasl_dispose( &d->conn );
421 case LoginJobPrivate::Login:
422 d->saveServerGreeting( response );
430 if ( code == MALFORMED ) {
431 setErrorText( i18n(
"%1 failed, malformed reply from the server.", commandName ) );
436 bool LoginJobPrivate::startAuthentication()
440 q->setError( LoginJob::UserDefinedError );
441 q->setErrorText( i18n(
"Login failed, client cannot initialize the SASL library." ) );
445 authState = LoginJobPrivate::Authenticate;
448 const char *mechusing = 0;
450 int result = sasl_client_new(
"imap", m_session->hostName().toLatin1(), 0, 0, callbacks, 0, &conn );
451 if ( result != SASL_OK ) {
452 kDebug() <<
"sasl_client_new failed with:" << result;
453 q->setError( LoginJob::UserDefinedError );
454 q->setErrorText( QString::fromUtf8( sasl_errdetail( conn ) ) );
459 result = sasl_client_start( conn, authMode.toLatin1(), &client_interact, capabilities.contains(
"SASL-IR" ) ? &out : 0, &outlen, &mechusing );
461 if ( result == SASL_INTERACT ) {
462 if ( !sasl_interact() ) {
463 sasl_dispose( &conn );
464 q->setError( LoginJob::UserDefinedError );
468 }
while ( result == SASL_INTERACT );
470 if ( result != SASL_CONTINUE && result != SASL_OK ) {
471 kDebug() <<
"sasl_client_start failed with:" << result;
472 q->setError( LoginJob::UserDefinedError );
473 q->setErrorText( QString::fromUtf8( sasl_errdetail( conn ) ) );
474 sasl_dispose( &conn );
478 QByteArray tmp = QByteArray::fromRawData( out, outlen );
479 QByteArray challenge = tmp.toBase64();
481 if ( challenge.isEmpty() ) {
482 tags << sessionInternal()->sendCommand(
"AUTHENTICATE", authMode.toLatin1() );
484 tags << sessionInternal()->sendCommand(
"AUTHENTICATE", authMode.toLatin1() +
' ' + challenge );
490 bool LoginJobPrivate::answerChallenge(
const QByteArray &data)
492 QByteArray challenge = data;
497 result = sasl_client_step( conn, challenge.isEmpty() ? 0 : challenge.data(),
502 if ( result == SASL_INTERACT ) {
503 if ( !sasl_interact() ) {
504 q->setError( LoginJob::UserDefinedError );
505 sasl_dispose( &conn );
509 }
while ( result == SASL_INTERACT );
511 if ( result != SASL_CONTINUE && result != SASL_OK ) {
512 kDebug() <<
"sasl_client_step failed with:" << result;
513 q->setError( LoginJob::UserDefinedError );
514 q->setErrorText( QString::fromUtf8( sasl_errdetail( conn ) ) );
515 sasl_dispose( &conn );
519 QByteArray tmp = QByteArray::fromRawData( out, outlen );
520 challenge = tmp.toBase64();
522 sessionInternal()->sendData( challenge );
527 void LoginJobPrivate::sslResponse(
bool response)
530 authState = LoginJobPrivate::Capability;
531 tags << sessionInternal()->sendCommand(
"CAPABILITY" );
533 q->setError( LoginJob::UserDefinedError );
534 q->setErrorText( i18n(
"Login failed, TLS negotiation failed." ) );
535 encryptionMode = LoginJob::Unencrypted;
540 void LoginJob::setEncryptionMode(EncryptionMode mode)
543 d->encryptionMode = mode;
546 LoginJob::EncryptionMode LoginJob::encryptionMode()
549 return d->encryptionMode;
552 void LoginJob::setAuthenticationMode(AuthenticationMode mode)
556 case ClearText: d->authMode =
"";
558 case Login: d->authMode =
"LOGIN";
560 case Plain: d->authMode =
"PLAIN";
562 case CramMD5: d->authMode =
"CRAM-MD5";
564 case DigestMD5: d->authMode =
"DIGEST-MD5";
566 case GSSAPI: d->authMode =
"GSSAPI";
568 case Anonymous: d->authMode =
"ANONYMOUS";
575 void LoginJob::connectionLost()
581 if ( d->authState != LoginJobPrivate::StartTls ) {
582 setError( ERR_COULD_NOT_CONNECT );
583 setErrorText( i18n(
"Connection to server lost." ) );
589 void LoginJobPrivate::saveServerGreeting(
const Message &response)
594 for (
int i = 2; i < response.content.size(); i++ ) {
595 if ( response.content.at( i ).type() == Message::Part::List ) {
596 serverGreeting +=
'(';
597 foreach (
const QByteArray &item, response.content.at( i ).toList() ) {
598 serverGreeting += item +
' ';
600 serverGreeting.chop( 1 );
601 serverGreeting +=
") ";
603 serverGreeting+=response.content.at( i ).toString() +
' ';
606 serverGreeting.chop( 1 );
609 QString LoginJob::serverGreeting()
const
611 Q_D(
const LoginJob );
612 return d->serverGreeting;
615 #include "moc_loginjob.cpp"