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 if (d->encryptionMode == SslV2 || d->encryptionMode == SslV3 || d->encryptionMode == SslV3_1 || d->encryptionMode == AnySslVersion) {
00171 KTcpSocket::SslVersion version = KTcpSocket::SslV2;
00172 if (d->encryptionMode == SslV3)
00173 version = KTcpSocket::SslV3;
00174 if (d->encryptionMode == SslV3_1)
00175 version = KTcpSocket::SslV3_1;
00176 if (d->encryptionMode == AnySslVersion)
00177 version = KTcpSocket::AnySslVersion;
00178 d->sessionInternal()->startSsl(version);
00179 } else if (d->encryptionMode == Unencrypted ) {
00180 if (d->authMode.isEmpty()) {
00181 d->tags << d->sessionInternal()->sendCommand( "LOGIN",
00182 quoteIMAP( d->userName ).toUtf8()
00183 +' '
00184 +quoteIMAP(d->password ).toUtf8() );
00185 } else {
00186 if (!d->startAuthentication()) {
00187 emitResult();
00188 }
00189 }
00190 } else if (d->encryptionMode == TlsV1) {
00191 d->authState = LoginJobPrivate::StartTls;
00192 d->tags << d->sessionInternal()->sendCommand( "STARTTLS" );
00193 }
00194 }
00195
00196 void LoginJob::handleResponse( const Message &response )
00197 {
00198 Q_D(LoginJob);
00199
00200
00201 QString commandName = i18n("Login");
00202 if (d->authState == LoginJobPrivate::Capability) {
00203 commandName = i18n("Capability");
00204 } else if (d->authState == LoginJobPrivate::StartTls) {
00205 commandName = i18n("StartTls");
00206 }
00207
00208 if ( !response.content.isEmpty()
00209 && d->tags.contains( response.content.first().toString() ) ) {
00210 if ( response.content.size() < 2 ) {
00211 setErrorText( i18n("%1 failed, malformed reply from the server.", commandName) );
00212 emitResult();
00213 } else if ( response.content[1].toString() != "OK" ) {
00214
00215 if (d->authState == LoginJobPrivate::Authenticate) {
00216 sasl_dispose( &d->conn );
00217 }
00218
00219 setError( UserDefinedError );
00220 setErrorText( i18n("%1 failed, server replied: %2", commandName, response.toString().constData()) );
00221 emitResult();
00222 } else if ( response.content[1].toString() == "OK") {
00223 if (d->authState == LoginJobPrivate::Authenticate) {
00224 sasl_dispose( &d->conn );
00225 emitResult();
00226 } else if (d->authState == LoginJobPrivate::Capability) {
00227
00228
00229 if (d->authMode.isEmpty()) {
00230 if (d->plainLoginDisabled) {
00231 setError( UserDefinedError );
00232 setErrorText( i18n("Login failed, plain login is disabled by the server.") );
00233 emitResult();
00234 } else {
00235 d->authState = LoginJobPrivate::Login;
00236 d->tags << d->sessionInternal()->sendCommand( "LOGIN",
00237 quoteIMAP( d->userName ).toUtf8()
00238 +' '
00239 +quoteIMAP( d->password ).toUtf8() );
00240 }
00241 }
00242
00243
00244 Q_FOREACH(const QString &capability, d->capabilities) {
00245 if (capability.startsWith(QLatin1String("AUTH="))) {
00246 QString authType = capability.mid(5);
00247 if (authType == d->authMode) {
00248 if (!d->startAuthentication()) {
00249 emitResult();
00250 }
00251 }
00252 }
00253 }
00254 } else if (d->authState == LoginJobPrivate::StartTls) {
00255 d->sessionInternal()->startSsl(KTcpSocket::TlsV1);
00256 } else {
00257 emitResult();
00258 }
00259 }
00260 } else if ( response.content.size() >= 2 ) {
00261 if ( d->authState == LoginJobPrivate::Authenticate ) {
00262 if (!d->answerChallenge(response.content[1].toString())) {
00263 emitResult();
00264 }
00265 } else if ( response.content[1].toString()=="CAPABILITY" ) {
00266 bool authModeSupported = d->authMode.isEmpty();
00267 for (int i = 2; i < response.content.size(); ++i) {
00268 QString capability = response.content[i].toString();
00269 d->capabilities << capability;
00270 if (capability == "LOGINDISABLED") {
00271 d->plainLoginDisabled = true;
00272 }
00273 QString authMode = capability.mid(5);
00274 if (authMode == d->authMode) {
00275 authModeSupported = true;
00276 }
00277 }
00278 kDebug() << "Capabilities after STARTTLS: " << d->capabilities;
00279 if (!authModeSupported) {
00280 setError( UserDefinedError );
00281 setErrorText( i18n("Login failed, authentication mode %1 is not supported by the server.", d->authMode) );
00282 d->authState = LoginJobPrivate::Login;
00283 }
00284 }
00285 }
00286 }
00287
00288 bool LoginJobPrivate::startAuthentication()
00289 {
00290
00291 if (!initSASL()) {
00292 q->setError( LoginJob::UserDefinedError );
00293 q->setErrorText( i18n("Login failed, client cannot initialize the SASL library.") );
00294 return false;
00295 }
00296
00297 authState = LoginJobPrivate::Authenticate;
00298 const char *out = 0;
00299 uint outlen = 0;
00300 const char *mechusing = 0;
00301
00302 int result = sasl_client_new( "imap", m_session->hostName().toLatin1(), 0, 0, callbacks, 0, &conn );
00303 if ( result != SASL_OK ) {
00304 kDebug() <<"sasl_client_new failed with:" << result;
00305 q->setError( LoginJob::UserDefinedError );
00306 q->setErrorText( QString::fromUtf8( sasl_errdetail( conn ) ) );
00307 return false;
00308 }
00309
00310 do {
00311 result = sasl_client_start(conn, authMode.toLatin1(), &client_interact, capabilities.contains("SASL-IR") ? &out : 0, &outlen, &mechusing);
00312
00313 if ( result == SASL_INTERACT ) {
00314 if ( !sasl_interact() ) {
00315 sasl_dispose( &conn );
00316 q->setError( LoginJob::UserDefinedError );
00317 return false;
00318 }
00319 }
00320 } while ( result == SASL_INTERACT );
00321
00322 if ( result != SASL_CONTINUE && result != SASL_OK ) {
00323 kDebug() <<"sasl_client_start failed with:" << result;
00324 q->setError( LoginJob::UserDefinedError );
00325 q->setErrorText( QString::fromUtf8( sasl_errdetail( conn ) ) );
00326 sasl_dispose( &conn );
00327 return false;
00328 }
00329
00330 QByteArray tmp = QByteArray::fromRawData( out, outlen );
00331 QByteArray challenge = tmp.toBase64();
00332
00333 tags << sessionInternal()->sendCommand( "AUTHENTICATE", authMode.toLatin1() + ' ' + challenge );
00334
00335 return true;
00336 }
00337
00338 bool LoginJobPrivate::answerChallenge(const QByteArray &data)
00339 {
00340 QByteArray challenge = data;
00341 int result = -1;
00342 const char *out = 0;
00343 uint outlen = 0;
00344 do {
00345 result = sasl_client_step(conn, challenge.isEmpty() ? 0 : challenge.data(),
00346 challenge.size(),
00347 &client_interact,
00348 &out, &outlen);
00349
00350 if (result == SASL_INTERACT) {
00351 if ( !sasl_interact() ) {
00352 q->setError( LoginJob::UserDefinedError );
00353 sasl_dispose( &conn );
00354 return false;
00355 }
00356 }
00357 } while ( result == SASL_INTERACT );
00358
00359 if ( result != SASL_CONTINUE && result != SASL_OK ) {
00360 kDebug() <<"sasl_client_step failed with:" << result;
00361 q->setError( LoginJob::UserDefinedError );
00362 q->setErrorText( QString::fromUtf8( sasl_errdetail( conn ) ) );
00363 sasl_dispose( &conn );
00364 return false;
00365 }
00366
00367 QByteArray tmp = QByteArray::fromRawData( out, outlen );
00368 challenge = tmp.toBase64();
00369
00370 sessionInternal()->sendData( challenge );
00371
00372 return true;
00373 }
00374
00375 void LoginJobPrivate::sslResponse(bool response)
00376 {
00377 if (response) {
00378 authState = LoginJobPrivate::Capability;
00379 tags << sessionInternal()->sendCommand( "CAPABILITY" );
00380 } else {
00381 q->setError( LoginJob::UserDefinedError );
00382 q->setErrorText( i18n("Login failed, TLS negotiation failed." ));
00383 encryptionMode = LoginJob::Unencrypted;
00384 q->emitResult();
00385 }
00386 }
00387
00388 void LoginJob::setEncryptionMode(EncryptionMode mode)
00389 {
00390 Q_D(LoginJob);
00391 d->encryptionMode = mode;
00392 }
00393
00394 LoginJob::EncryptionMode LoginJob::encryptionMode()
00395 {
00396 Q_D(LoginJob);
00397 return d->encryptionMode;
00398 }
00399
00400 void LoginJob::setAuthenticationMode(AuthenticationMode mode)
00401 {
00402 Q_D(LoginJob);
00403 switch (mode)
00404 {
00405 case ClearText: d->authMode = "";
00406 break;
00407 case Login: d->authMode = "LOGIN";
00408 break;
00409 case Plain: d->authMode = "PLAIN";
00410 break;
00411 case CramMD5: d->authMode = "CRAM-MD5";
00412 break;
00413 case DigestMD5: d->authMode = "DIGEST-MD5";
00414 break;
00415 case GSSAPI: d->authMode = "GSSAPI";
00416 break;
00417 case Anonymous: d->authMode = "ANONYMOUS";
00418 break;
00419 default:
00420 d->authMode = "";
00421 }
00422 }
00423
00424 void LoginJob::connectionLost()
00425 {
00426 Q_D(LoginJob);
00427
00428
00429
00430 if (d->authState != LoginJobPrivate::StartTls) {
00431 emitResult();
00432 }
00433
00434 }
00435
00436
00437 #include "loginjob.moc"