00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022 #include "loginjob.h"
00023
00024 #include <KDE/KLocale>
00025 #include <KDE/KDebug>
00026 #include <ktcpsocket.h>
00027
00028 #include "job_p.h"
00029 #include "message_p.h"
00030 #include "session_p.h"
00031 #include "rfccodecs.h"
00032
00033 #include "common.h"
00034
00035 extern "C" {
00036 #include <sasl/sasl.h>
00037 }
00038
00039 static sasl_callback_t callbacks[] = {
00040 { SASL_CB_ECHOPROMPT, NULL, NULL },
00041 { SASL_CB_NOECHOPROMPT, NULL, NULL },
00042 { SASL_CB_GETREALM, NULL, NULL },
00043 { SASL_CB_USER, NULL, NULL },
00044 { SASL_CB_AUTHNAME, NULL, NULL },
00045 { SASL_CB_PASS, NULL, NULL },
00046 { SASL_CB_CANON_USER, NULL, NULL },
00047 { SASL_CB_LIST_END, NULL, NULL }
00048 };
00049
00050 namespace KIMAP
00051 {
00052 class LoginJobPrivate : public JobPrivate
00053 {
00054 public:
00055 enum AuthState {
00056 StartTls = 0,
00057 Capability,
00058 Login,
00059 Authenticate
00060 };
00061
00062 LoginJobPrivate( LoginJob *job, Session *session, const QString& name ) : JobPrivate(session, name), q(job), encryptionMode(LoginJob::Unencrypted), authState(Login), plainLoginDisabled(false) {
00063 conn = 0;
00064 client_interact = 0;
00065 }
00066 ~LoginJobPrivate() { }
00067 bool sasl_interact();
00068
00069 bool startAuthentication();
00070 bool answerChallenge(const QByteArray &data);
00071 void sslResponse(bool response);
00072
00073 LoginJob *q;
00074
00075 QString userName;
00076 QString password;
00077
00078 LoginJob::EncryptionMode encryptionMode;
00079 QString authMode;
00080 AuthState authState;
00081 QStringList capabilities;
00082 bool plainLoginDisabled;
00083
00084 sasl_conn_t *conn;
00085 sasl_interact_t *client_interact;
00086 };
00087 }
00088
00089 using namespace KIMAP;
00090
00091 bool LoginJobPrivate::sasl_interact()
00092 {
00093 kDebug() <<"sasl_interact";
00094 sasl_interact_t *interact = client_interact;
00095
00096
00097
00098 for ( ; interact->id != SASL_CB_LIST_END; interact++ ) {
00099 if ( interact->id == SASL_CB_AUTHNAME ||
00100 interact->id == SASL_CB_PASS ) {
00101
00102 break;
00103 }
00104 }
00105
00106 interact = client_interact;
00107 while( interact->id != SASL_CB_LIST_END ) {
00108 kDebug() <<"SASL_INTERACT id:" << interact->id;
00109 switch( interact->id ) {
00110 case SASL_CB_USER:
00111 case SASL_CB_AUTHNAME:
00112 kDebug() <<"SASL_CB_[USER|AUTHNAME]: '" << userName <<"'";
00113 interact->result = strdup( userName.toUtf8() );
00114 interact->len = strlen( (const char *) interact->result );
00115 break;
00116 case SASL_CB_PASS:
00117 kDebug() <<"SASL_CB_PASS: [hidden]";
00118 interact->result = strdup( password.toUtf8() );
00119 interact->len = strlen( (const char *) interact->result );
00120 break;
00121 default:
00122 interact->result = 0;
00123 interact->len = 0;
00124 break;
00125 }
00126 interact++;
00127 }
00128 return true;
00129 }
00130
00131
00132 LoginJob::LoginJob( Session *session )
00133 : Job( *new LoginJobPrivate(this, session, i18n("Login")) )
00134 {
00135 Q_D(LoginJob);
00136 connect(d->sessionInternal(), SIGNAL(encryptionNegotiationResult(bool)), this, SLOT(sslResponse(bool)));
00137 }
00138
00139 LoginJob::~LoginJob()
00140 {
00141 }
00142
00143 QString LoginJob::userName() const
00144 {
00145 Q_D(const LoginJob);
00146 return d->userName;
00147 }
00148
00149 void LoginJob::setUserName( const QString &userName )
00150 {
00151 Q_D(LoginJob);
00152 d->userName = userName;
00153 }
00154
00155 QString LoginJob::password() const
00156 {
00157 Q_D(const LoginJob);
00158 return d->password;
00159 }
00160
00161 void LoginJob::setPassword( const QString &password )
00162 {
00163 Q_D(LoginJob);
00164 d->password = password;
00165 }
00166
00167 void LoginJob::doStart()
00168 {
00169 Q_D(LoginJob);
00170
00171
00172 if ( session()->state() == Session::Authenticated || session()->state() == Session::Selected ) {
00173 setError( UserDefinedError );
00174 setErrorText( i18n("IMAP session in the wrong state for authentication") );
00175 emitResult();
00176 return;
00177 }
00178
00179
00180 EncryptionMode encryptionMode = d->encryptionMode;
00181
00182 switch ( d->sessionInternal()->negotiatedEncryption() ) {
00183 case KTcpSocket::UnknownSslVersion:
00184 break;
00185
00186
00187
00188
00189
00190 case KTcpSocket::SslV2:
00191 if ( encryptionMode==SslV2 ) {
00192 encryptionMode = Unencrypted;
00193 }
00194 break;
00195 case KTcpSocket::SslV3:
00196 if ( encryptionMode==SslV3 ) {
00197 encryptionMode = Unencrypted;
00198 }
00199 break;
00200 case KTcpSocket::TlsV1:
00201 if ( encryptionMode==TlsV1 ) {
00202 encryptionMode = Unencrypted;
00203 }
00204 break;
00205 case KTcpSocket::AnySslVersion:
00206 if ( encryptionMode==AnySslVersion ) {
00207 encryptionMode = Unencrypted;
00208 }
00209 break;
00210 }
00211
00212 if (encryptionMode == SslV2
00213 || encryptionMode == SslV3
00214 || encryptionMode == SslV3_1
00215 || encryptionMode == AnySslVersion) {
00216 KTcpSocket::SslVersion version = KTcpSocket::SslV2;
00217 if (encryptionMode == SslV3)
00218 version = KTcpSocket::SslV3;
00219 if (encryptionMode == SslV3_1)
00220 version = KTcpSocket::SslV3_1;
00221 if (encryptionMode == AnySslVersion)
00222 version = KTcpSocket::AnySslVersion;
00223 d->sessionInternal()->startSsl(version);
00224
00225 } else if (encryptionMode == TlsV1) {
00226 d->authState = LoginJobPrivate::StartTls;
00227 d->tags << d->sessionInternal()->sendCommand( "STARTTLS" );
00228
00229 } else if (encryptionMode == Unencrypted ) {
00230 if (d->authMode.isEmpty()) {
00231 d->authState = LoginJobPrivate::Login;
00232 d->tags << d->sessionInternal()->sendCommand( "LOGIN",
00233 quoteIMAP( d->userName ).toUtf8()
00234 +' '
00235 +quoteIMAP(d->password ).toUtf8() );
00236 } else {
00237 if (!d->startAuthentication()) {
00238 emitResult();
00239 }
00240 }
00241 }
00242 }
00243
00244 void LoginJob::handleResponse( const Message &response )
00245 {
00246 Q_D(LoginJob);
00247
00248
00249 QString commandName = i18n("Login");
00250 if (d->authState == LoginJobPrivate::Capability) {
00251 commandName = i18n("Capability");
00252 } else if (d->authState == LoginJobPrivate::StartTls) {
00253 commandName = i18n("StartTls");
00254 }
00255
00256 if ( d->authMode == QLatin1String( "PLAIN" ) && !response.content.isEmpty() && response.content.first().toString()=="+" ) {
00257 if ( response.content.size()>1 && response.content.at( 1 ).toString()=="OK" ) {
00258 return;
00259 }
00260
00261 QByteArray challengeResponse;
00262 challengeResponse+= '\0';
00263 challengeResponse+= d->userName.toUtf8();
00264 challengeResponse+= '\0';
00265 challengeResponse+= d->password.toUtf8();
00266 challengeResponse = challengeResponse.toBase64();
00267 d->sessionInternal()->sendData( challengeResponse );
00268
00269 } else if ( !response.content.isEmpty()
00270 && d->tags.contains( response.content.first().toString() ) ) {
00271 if ( response.content.size() < 2 ) {
00272 setErrorText( i18n("%1 failed, malformed reply from the server.", commandName) );
00273 emitResult();
00274 } else if ( response.content[1].toString() != "OK" ) {
00275
00276 if (d->authState == LoginJobPrivate::Authenticate) {
00277 sasl_dispose( &d->conn );
00278 }
00279
00280 setError( UserDefinedError );
00281 setErrorText( i18n("%1 failed, server replied: %2", commandName, response.toString().constData()) );
00282 emitResult();
00283 } else if ( response.content[1].toString() == "OK") {
00284 if (d->authState == LoginJobPrivate::Authenticate) {
00285 sasl_dispose( &d->conn );
00286 emitResult();
00287 } else if (d->authState == LoginJobPrivate::Capability) {
00288
00289
00290 if (d->authMode.isEmpty()) {
00291 if (d->plainLoginDisabled) {
00292 setError( UserDefinedError );
00293 setErrorText( i18n("Login failed, plain login is disabled by the server.") );
00294 emitResult();
00295 } else {
00296 d->authState = LoginJobPrivate::Login;
00297 d->tags << d->sessionInternal()->sendCommand( "LOGIN",
00298 quoteIMAP( d->userName ).toUtf8()
00299 +' '
00300 +quoteIMAP( d->password ).toUtf8() );
00301 }
00302 }
00303
00304
00305 Q_FOREACH(const QString &capability, d->capabilities) {
00306 if (capability.startsWith(QLatin1String("AUTH="))) {
00307 QString authType = capability.mid(5);
00308 if (authType == d->authMode) {
00309 if (!d->startAuthentication()) {
00310 emitResult();
00311 }
00312 }
00313 }
00314 }
00315 } else if (d->authState == LoginJobPrivate::StartTls) {
00316 d->sessionInternal()->startSsl(KTcpSocket::TlsV1);
00317 } else {
00318 emitResult();
00319 }
00320 }
00321 } else if ( response.content.size() >= 2 ) {
00322 if ( d->authState == LoginJobPrivate::Authenticate ) {
00323 if (!d->answerChallenge(QByteArray::fromBase64(response.content[1].toString()))) {
00324 emitResult();
00325 }
00326 } else if ( response.content[1].toString()=="CAPABILITY" ) {
00327 bool authModeSupported = d->authMode.isEmpty();
00328 for (int i = 2; i < response.content.size(); ++i) {
00329 QString capability = response.content[i].toString();
00330 d->capabilities << capability;
00331 if (capability == "LOGINDISABLED") {
00332 d->plainLoginDisabled = true;
00333 }
00334 QString authMode = capability.mid(5);
00335 if (authMode == d->authMode) {
00336 authModeSupported = true;
00337 }
00338 }
00339 kDebug() << "Capabilities after STARTTLS: " << d->capabilities;
00340 if (!authModeSupported) {
00341 setError( UserDefinedError );
00342 setErrorText( i18n("Login failed, authentication mode %1 is not supported by the server.", d->authMode) );
00343 d->authState = LoginJobPrivate::Login;
00344 }
00345 }
00346 }
00347 }
00348
00349 bool LoginJobPrivate::startAuthentication()
00350 {
00351
00352 if (!initSASL()) {
00353 q->setError( LoginJob::UserDefinedError );
00354 q->setErrorText( i18n("Login failed, client cannot initialize the SASL library.") );
00355 return false;
00356 }
00357
00358 authState = LoginJobPrivate::Authenticate;
00359 const char *out = 0;
00360 uint outlen = 0;
00361 const char *mechusing = 0;
00362
00363 int result = sasl_client_new( "imap", m_session->hostName().toLatin1(), 0, 0, callbacks, 0, &conn );
00364 if ( result != SASL_OK ) {
00365 kDebug() <<"sasl_client_new failed with:" << result;
00366 q->setError( LoginJob::UserDefinedError );
00367 q->setErrorText( QString::fromUtf8( sasl_errdetail( conn ) ) );
00368 return false;
00369 }
00370
00371 do {
00372 result = sasl_client_start(conn, authMode.toLatin1(), &client_interact, capabilities.contains("SASL-IR") ? &out : 0, &outlen, &mechusing);
00373
00374 if ( result == SASL_INTERACT ) {
00375 if ( !sasl_interact() ) {
00376 sasl_dispose( &conn );
00377 q->setError( LoginJob::UserDefinedError );
00378 return false;
00379 }
00380 }
00381 } while ( result == SASL_INTERACT );
00382
00383 if ( result != SASL_CONTINUE && result != SASL_OK ) {
00384 kDebug() <<"sasl_client_start failed with:" << result;
00385 q->setError( LoginJob::UserDefinedError );
00386 q->setErrorText( QString::fromUtf8( sasl_errdetail( conn ) ) );
00387 sasl_dispose( &conn );
00388 return false;
00389 }
00390
00391 QByteArray tmp = QByteArray::fromRawData( out, outlen );
00392 QByteArray challenge = tmp.toBase64();
00393
00394 if ( challenge.isEmpty() ) {
00395 tags << sessionInternal()->sendCommand( "AUTHENTICATE", authMode.toLatin1() );
00396 } else {
00397 tags << sessionInternal()->sendCommand( "AUTHENTICATE", authMode.toLatin1() + ' ' + challenge );
00398 }
00399
00400 return true;
00401 }
00402
00403 bool LoginJobPrivate::answerChallenge(const QByteArray &data)
00404 {
00405 QByteArray challenge = data;
00406 int result = -1;
00407 const char *out = 0;
00408 uint outlen = 0;
00409 do {
00410 result = sasl_client_step(conn, challenge.isEmpty() ? 0 : challenge.data(),
00411 challenge.size(),
00412 &client_interact,
00413 &out, &outlen);
00414
00415 if (result == SASL_INTERACT) {
00416 if ( !sasl_interact() ) {
00417 q->setError( LoginJob::UserDefinedError );
00418 sasl_dispose( &conn );
00419 return false;
00420 }
00421 }
00422 } while ( result == SASL_INTERACT );
00423
00424 if ( result != SASL_CONTINUE && result != SASL_OK ) {
00425 kDebug() <<"sasl_client_step failed with:" << result;
00426 q->setError( LoginJob::UserDefinedError );
00427 q->setErrorText( QString::fromUtf8( sasl_errdetail( conn ) ) );
00428 sasl_dispose( &conn );
00429 return false;
00430 }
00431
00432 QByteArray tmp = QByteArray::fromRawData( out, outlen );
00433 challenge = tmp.toBase64();
00434
00435 sessionInternal()->sendData( challenge );
00436
00437 return true;
00438 }
00439
00440 void LoginJobPrivate::sslResponse(bool response)
00441 {
00442 if (response) {
00443 authState = LoginJobPrivate::Capability;
00444 tags << sessionInternal()->sendCommand( "CAPABILITY" );
00445 } else {
00446 q->setError( LoginJob::UserDefinedError );
00447 q->setErrorText( i18n("Login failed, TLS negotiation failed." ));
00448 encryptionMode = LoginJob::Unencrypted;
00449 q->emitResult();
00450 }
00451 }
00452
00453 void LoginJob::setEncryptionMode(EncryptionMode mode)
00454 {
00455 Q_D(LoginJob);
00456 d->encryptionMode = mode;
00457 }
00458
00459 LoginJob::EncryptionMode LoginJob::encryptionMode()
00460 {
00461 Q_D(LoginJob);
00462 return d->encryptionMode;
00463 }
00464
00465 void LoginJob::setAuthenticationMode(AuthenticationMode mode)
00466 {
00467 Q_D(LoginJob);
00468 switch (mode)
00469 {
00470 case ClearText: d->authMode = "";
00471 break;
00472 case Login: d->authMode = "LOGIN";
00473 break;
00474 case Plain: d->authMode = "PLAIN";
00475 break;
00476 case CramMD5: d->authMode = "CRAM-MD5";
00477 break;
00478 case DigestMD5: d->authMode = "DIGEST-MD5";
00479 break;
00480 case GSSAPI: d->authMode = "GSSAPI";
00481 break;
00482 case Anonymous: d->authMode = "ANONYMOUS";
00483 break;
00484 default:
00485 d->authMode = "";
00486 }
00487 }
00488
00489 void LoginJob::connectionLost()
00490 {
00491 Q_D(LoginJob);
00492
00493
00494
00495 if (d->authState != LoginJobPrivate::StartTls) {
00496 emitResult();
00497 }
00498
00499 }
00500
00501
00502 #include "loginjob.moc"