summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Mollekopf <chrigi_1@fastmail.fm>2012-06-28 09:08:35 (GMT)
committerChristian Mollekopf <chrigi_1@fastmail.fm>2012-06-28 09:08:35 (GMT)
commitd70c9dc282b121c87e278900ee621dc05970935f (patch)
tree17c243d62022c65847b7fee3049835b164141664
parent98689b176fb4fe3752c4b9e313dc7516be84c6a0 (diff)
downloadlibcalendaring-d70c9dc282b121c87e278900ee621dc05970935f.tar.gz
The imap proxyauth patch.
-rw-r--r--kimap/loginjob.cpp256
-rw-r--r--kimap/loginjob.h19
2 files changed, 185 insertions, 90 deletions
diff --git a/kimap/loginjob.cpp b/kimap/loginjob.cpp
index 3b45f79..da869be 100644
--- a/kimap/loginjob.cpp
+++ b/kimap/loginjob.cpp
@@ -74,6 +74,7 @@ namespace KIMAP
LoginJob *q;
QString userName;
+ QString authorizationName;
QString password;
QString serverGreeting;
@@ -92,7 +93,7 @@ using namespace KIMAP;
bool LoginJobPrivate::sasl_interact()
{
- qDebug() <<"sasl_interact";
+ kDebug() <<"sasl_interact";
sasl_interact_t *interact = client_interact;
//some mechanisms do not require username && pass, so it doesn't need a popup
@@ -107,16 +108,22 @@ bool LoginJobPrivate::sasl_interact()
interact = client_interact;
while( interact->id != SASL_CB_LIST_END ) {
- qDebug() <<"SASL_INTERACT id:" << interact->id;
+ kDebug() <<"SASL_INTERACT id:" << interact->id;
switch( interact->id ) {
- case SASL_CB_USER:
case SASL_CB_AUTHNAME:
- qDebug() <<"SASL_CB_[USER|AUTHNAME]: '" << userName <<"'";
+ if ( !authorizationName.isEmpty() ) {
+ kDebug() <<"SASL_CB_[AUTHNAME]: '" << authorizationName <<"'";
+ interact->result = strdup( authorizationName.toUtf8() );
+ interact->len = strlen( (const char *) interact->result );
+ break;
+ }
+ case SASL_CB_USER:
+ kDebug() <<"SASL_CB_[USER|AUTHNAME]: '" << userName <<"'";
interact->result = strdup( userName.toUtf8() );
interact->len = strlen( (const char *) interact->result );
break;
case SASL_CB_PASS:
- qDebug() <<"SASL_CB_PASS: [hidden]";
+ kDebug() <<"SASL_CB_PASS: [hidden]";
interact->result = strdup( password.toUtf8() );
interact->len = strlen( (const char *) interact->result );
break;
@@ -154,6 +161,18 @@ void LoginJob::setUserName( const QString &userName )
d->userName = userName;
}
+QString LoginJob::authorizationName() const
+{
+ Q_D(const LoginJob);
+ return d->authorizationName;
+}
+
+void LoginJob::setAuthorizationName( const QString& authorizationName )
+{
+ Q_D(LoginJob);
+ d->authorizationName = authorizationName;
+}
+
QString LoginJob::password() const
{
Q_D(const LoginJob);
@@ -247,6 +266,9 @@ void LoginJob::handleResponse( const Message &response )
{
Q_D(LoginJob);
+ if ( response.content.isEmpty() )
+ return;
+
//set the actual command name for standard responses
QString commandName = i18n("Login");
if (d->authState == LoginJobPrivate::Capability) {
@@ -255,98 +277,152 @@ void LoginJob::handleResponse( const Message &response )
commandName = i18n("StartTls");
}
- if ( d->authMode == QLatin1String( "PLAIN" ) && !response.content.isEmpty() && response.content.first().toString()=="+" ) {
- if ( response.content.size()>1 && response.content.at( 1 ).toString()=="OK" ) {
- return;
- }
+ enum ResponseCode {
+ OK,
+ ERR,
+ UNTAGGED,
+ CONTINUATION,
+ MALFORMED
+ };
- QByteArray challengeResponse;
- challengeResponse+= '\0';
- challengeResponse+= d->userName.toUtf8();
- challengeResponse+= '\0';
- challengeResponse+= d->password.toUtf8();
- challengeResponse = challengeResponse.toBase64();
- d->sessionInternal()->sendData( challengeResponse );
-
- } else if ( !response.content.isEmpty()
- && d->tags.contains( response.content.first().toString() ) ) {
- if ( response.content.size() < 2 ) {
- setErrorText( i18n("%1 failed, malformed reply from the server.", commandName) );
- emitResult();
- } else if ( response.content[1].toString() != "OK" ) {
- //server replied with NO or BAD for SASL authentication
- if (d->authState == LoginJobPrivate::Authenticate) {
- sasl_dispose( &d->conn );
- }
+ QByteArray tag = response.content.first().toString();
+ ResponseCode code;
+
+ if ( tag == "+" ) {
+ code = CONTINUATION;
+ } else if ( tag == "*" ) {
+ if ( response.content.size() < 2 )
+ code = MALFORMED; // Received empty untagged response
+ else
+ code = UNTAGGED;
+ } else if ( d->tags.contains(tag) ) {
+ if ( response.content.size() < 2 )
+ code = MALFORMED;
+ else if ( response.content[1].toString() == "OK" )
+ code = OK;
+ else
+ code = ERR;
+ }
- setError( UserDefinedError );
- setErrorText( i18n("%1 failed, server replied: %2", commandName, response.toString().constData()) );
- emitResult();
- } else if ( response.content[1].toString() == "OK") {
- if (d->authState == LoginJobPrivate::Authenticate) {
- sasl_dispose( &d->conn ); //SASL authentication done
- d->saveServerGreeting( response );
- emitResult();
- } else if (d->authState == LoginJobPrivate::Capability) {
-
- //cleartext login, if enabled
- if (d->authMode.isEmpty()) {
- if (d->plainLoginDisabled) {
- setError( UserDefinedError );
- setErrorText( i18n("Login failed, plain login is disabled by the server.") );
- emitResult();
- } else {
- d->authState = LoginJobPrivate::Login;
- d->tags << d->sessionInternal()->sendCommand( "LOGIN",
- '"'+quoteIMAP( d->userName ).toUtf8()+'"'
- +' '
- +'"'+quoteIMAP( d->password ).toUtf8()+'"');
- }
- }
+ switch (code) {
+ case MALFORMED:
+ // We'll handle it later
+ break;
- //find the selected SASL authentication method
- Q_FOREACH(const QString &capability, d->capabilities) {
- if (capability.startsWith(QLatin1String("AUTH="))) {
- QString authType = capability.mid(5);
- if (authType == d->authMode) {
- if (!d->startAuthentication()) {
- emitResult(); //problem, we're done
- }
- }
- }
+ case ERR:
+ //server replied with NO or BAD for SASL authentication
+ if (d->authState == LoginJobPrivate::Authenticate)
+ sasl_dispose( &d->conn );
+
+ setError( UserDefinedError );
+ setErrorText( i18n("%1 failed, server replied: %2", commandName, response.toString().constData()) );
+ emitResult();
+ return;
+
+ case UNTAGGED:
+ // The only untagged response interesting for us here is CAPABILITY
+ if ( response.content[1].toString() == "CAPABILITY" ) {
+ QList<Message::Part>::const_iterator p = response.content.begin() + 2;
+ while (p != response.content.end()) {
+ QString capability = p->toString();
+ d->capabilities << capability;
+ if (capability == "LOGINDISABLED")
+ d->plainLoginDisabled = true;
+ ++p;
}
- } else if (d->authState == LoginJobPrivate::StartTls) {
- d->sessionInternal()->startSsl(KTcpSocket::TlsV1);
- } else {
- d->saveServerGreeting( response );
- emitResult(); //got an OK, command done
+ kDebug() << "Capabilities updated: " << d->capabilities;
}
- }
- } else if ( response.content.size() >= 2 ) {
- if ( d->authState == LoginJobPrivate::Authenticate ) {
- if (!d->answerChallenge(QByteArray::fromBase64(response.content[1].toString()))) {
- emitResult(); //error, we're done
+ break;
+
+ case CONTINUATION:
+ if (d->authState != LoginJobPrivate::Authenticate) {
+ // Received unexpected continuation response for something
+ // other than AUTHENTICATE command
+ code = MALFORMED;
+ break;
}
- } else if ( response.content[1].toString()=="CAPABILITY" ) {
- bool authModeSupported = d->authMode.isEmpty();
- for (int i = 2; i < response.content.size(); ++i) {
- QString capability = response.content[i].toString();
- d->capabilities << capability;
- if (capability == "LOGINDISABLED") {
- d->plainLoginDisabled = true;
+
+ if ( d->authMode == QLatin1String( "PLAIN" ) ) {
+ if ( response.content.size()>1 && response.content.at( 1 ).toString()=="OK" ) {
+ return;
+ }
+ QByteArray challengeResponse;
+ if ( !d->authorizationName.isEmpty() ) {
+ challengeResponse+= d->authorizationName.toUtf8();
}
- QString authMode = capability.mid(5);
- if (authMode == d->authMode) {
- authModeSupported = true;
+ challengeResponse+= '\0';
+ challengeResponse+= d->userName.toUtf8();
+ challengeResponse+= '\0';
+ challengeResponse+= d->password.toUtf8();
+ challengeResponse = challengeResponse.toBase64();
+ d->sessionInternal()->sendData( challengeResponse );
+ } else if ( response.content.size() >= 2 ) {
+ if (!d->answerChallenge(QByteArray::fromBase64(response.content[1].toString()))) {
+ emitResult(); //error, we're done
}
+ } else {
+ // Received empty continuation for authMode other than PLAIN
+ code = MALFORMED;
}
- qDebug() << "Capabilities after STARTTLS: " << d->capabilities;
- if (!authModeSupported) {
- setError( UserDefinedError );
- setErrorText( i18n("Login failed, authentication mode %1 is not supported by the server.", d->authMode) );
- d->authState = LoginJobPrivate::Login; //just to treat the upcoming OK correctly
+ break;
+
+ case OK:
+
+ switch (d->authState) {
+ case LoginJobPrivate::StartTls:
+ d->sessionInternal()->startSsl(KTcpSocket::TlsV1);
+ break;
+
+ case LoginJobPrivate::Capability:
+ //cleartext login, if enabled
+ if (d->authMode.isEmpty()) {
+ if (d->plainLoginDisabled) {
+ setError( UserDefinedError );
+ setErrorText( i18n("Login failed, plain login is disabled by the server.") );
+ emitResult();
+ } else {
+ d->authState = LoginJobPrivate::Login;
+ d->tags << d->sessionInternal()->sendCommand( "LOGIN",
+ '"'+quoteIMAP( d->userName ).toUtf8()+'"'
+ +' '
+ +'"'+quoteIMAP( d->password ).toUtf8()+'"');
+ }
+ } else {
+ bool authModeSupported = false;
+ //find the selected SASL authentication method
+ Q_FOREACH(const QString &capability, d->capabilities) {
+ if (capability.startsWith(QLatin1String("AUTH="))) {
+ if (capability.mid(5) == d->authMode) {
+ authModeSupported = true;
+ break;
+ }
+ }
+ }
+ if (!authModeSupported) {
+ setError( UserDefinedError );
+ setErrorText( i18n("Login failed, authentication mode %1 is not supported by the server.", d->authMode) );
+ emitResult();
+ } else if (!d->startAuthentication()) {
+ emitResult(); //problem, we're done
+ }
+ }
+ break;
+
+ case LoginJobPrivate::Authenticate:
+ sasl_dispose( &d->conn ); //SASL authentication done
+ // Fall through
+ case LoginJobPrivate::Login:
+ d->saveServerGreeting( response );
+ emitResult(); //got an OK, command done
+ break;
+
}
- }
+
+ }
+
+ if ( code == MALFORMED ) {
+ setErrorText( i18n("%1 failed, malformed reply from the server.", commandName) );
+ emitResult();
}
}
@@ -366,7 +442,7 @@ bool LoginJobPrivate::startAuthentication()
int result = sasl_client_new( "imap", m_session->hostName().toLatin1(), 0, 0, callbacks, 0, &conn );
if ( result != SASL_OK ) {
- qDebug() <<"sasl_client_new failed with:" << result;
+ kDebug() <<"sasl_client_new failed with:" << result;
q->setError( LoginJob::UserDefinedError );
q->setErrorText( QString::fromUtf8( sasl_errdetail( conn ) ) );
return false;
@@ -385,7 +461,7 @@ bool LoginJobPrivate::startAuthentication()
} while ( result == SASL_INTERACT );
if ( result != SASL_CONTINUE && result != SASL_OK ) {
- qDebug() <<"sasl_client_start failed with:" << result;
+ kDebug() <<"sasl_client_start failed with:" << result;
q->setError( LoginJob::UserDefinedError );
q->setErrorText( QString::fromUtf8( sasl_errdetail( conn ) ) );
sasl_dispose( &conn );
@@ -426,7 +502,7 @@ bool LoginJobPrivate::answerChallenge(const QByteArray &data)
} while ( result == SASL_INTERACT );
if ( result != SASL_CONTINUE && result != SASL_OK ) {
- qDebug() <<"sasl_client_step failed with:" << result;
+ kDebug() <<"sasl_client_step failed with:" << result;
q->setError( LoginJob::UserDefinedError ); //TODO: check up the actual error
q->setErrorText( QString::fromUtf8( sasl_errdetail( conn ) ) );
sasl_dispose( &conn );
diff --git a/kimap/loginjob.h b/kimap/loginjob.h
index 0967348..6064214 100644
--- a/kimap/loginjob.h
+++ b/kimap/loginjob.h
@@ -69,6 +69,25 @@ class KIMAP_EXPORT LoginJob : public Job
QString userName() const;
void setUserName( const QString &userName );
+ /**
+ * Get the authorization identity.
+ * @since 4.10
+ */
+ QString authorizationName() const;
+
+ /**
+ * Set the authorization identity.
+ *
+ * If set, proxy-authentication according to RFC4616 will be used.
+ *
+ * Note that this feature only works with the "PLAIN" AuthenticationMode.
+ *
+ * The @param authorizationName will be used together with the password() to get authenticated as userName() by the authorization of the provided credentials.
+ * This allows to login as a user using the admin credentials and the users name.
+ * @since 4.10
+ */
+ void setAuthorizationName( const QString &authorizationName );
+
QString password() const;
void setPassword( const QString &password );