• Skip to content
  • Skip to link menu
KDE 4.3 API Reference
  • KDE API Reference
  • KDE-PIM Libraries
  • Sitemap
  • Contact Us
 

KIMAP Library

loginjob.cpp

00001 /*
00002     Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
00003     Copyright (c) 2009 Andras Mantia <amantia@kde.org>
00004 
00005 
00006     This library is free software; you can redistribute it and/or modify it
00007     under the terms of the GNU Library General Public License as published by
00008     the Free Software Foundation; either version 2 of the License, or (at your
00009     option) any later version.
00010 
00011     This library is distributed in the hope that it will be useful, but WITHOUT
00012     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
00013     FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
00014     License for more details.
00015 
00016     You should have received a copy of the GNU Library General Public License
00017     along with this library; see the file COPYING.LIB.  If not, write to the
00018     Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
00019     02110-1301, USA.
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   //some mechanisms do not require username && pass, so it doesn't need a popup
00097   //window for getting this info
00098   for ( ; interact->id != SASL_CB_LIST_END; interact++ ) {
00099     if ( interact->id == SASL_CB_AUTHNAME ||
00100          interact->id == SASL_CB_PASS ) {
00101       //TODO: dialog for use name??
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->tag = 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->tag = d->sessionInternal()->sendCommand( "STARTTLS" );
00193   }
00194 }
00195 
00196 void LoginJob::handleResponse( const Message &response )
00197 {
00198   Q_D(LoginJob);
00199 
00200   //set the actual command name for standard responses
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        && response.content.first().toString() == d->tag ) {
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         //server replied with NO or BAD for SASL authentication
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 ); //SASL authentication done
00225         emitResult();
00226       } else if (d->authState == LoginJobPrivate::Capability) {
00227 
00228         //cleartext login, if enabled
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->tag = d->sessionInternal()->sendCommand( "LOGIN",
00237                                                         quoteIMAP( d->userName ).toUtf8()
00238                                                        +' '
00239                                                        +quoteIMAP( d->password ).toUtf8() );
00240           }
00241         }
00242 
00243         //find the selected SASL authentication method
00244         Q_FOREACH(QString capability, d->capabilities) {
00245           if (capability.startsWith("AUTH=")) {
00246             QString authType = capability.mid(5);
00247             if (authType == d->authMode) {
00248                 if (!d->startAuthentication()) {
00249                   emitResult(); //problem, we're done
00250                 }
00251             }
00252           }
00253         }
00254       } else if (d->authState == LoginJobPrivate::StartTls) {
00255         d->sessionInternal()->startSsl(KTcpSocket::TlsV1);
00256       } else {
00257         emitResult(); //got an OK, command done
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(); //error, we're done
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; //just to treat the upcoming OK correctly
00283       }
00284     }
00285   }
00286 }
00287 
00288 bool LoginJobPrivate::startAuthentication()
00289 {
00290   //SASL authentication
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 ); //TODO: check up the actual error
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   tag = 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 ); //TODO: check up the actual error
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 ); //TODO: check up the actual error
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     tag = 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   //don't emit the result if the connection was lost before getting the tls result, as it can mean
00429   //the TLS handshake failed and the socket was reconnected in normal mode
00430   if (d->authState != LoginJobPrivate::StartTls) {
00431     emitResult();
00432   }
00433 
00434 }
00435 
00436 
00437 #include "loginjob.moc"

KIMAP Library

Skip menu "KIMAP Library"
  • Main Page
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Class Members
  • Related Pages

KDE-PIM Libraries

Skip menu "KDE-PIM Libraries"
  • akonadi
  • kabc
  • kblog
  • kcal
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  • kldap
  • kmime
  • kpimidentities
  • kpimtextedit
  •   richtextbuilders
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Generated for KDE-PIM Libraries by doxygen 1.6.1
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal