diff options
| author | Jeremy Lainé <jeremy.laine@m4x.org> | 2011-04-07 18:27:51 +0000 |
|---|---|---|
| committer | Jeremy Lainé <jeremy.laine@m4x.org> | 2011-04-07 18:27:51 +0000 |
| commit | 194762951e6a59808350032f38e05cd0e5d03807 (patch) | |
| tree | c766a97835dad98838906710be77a2197099e1e9 /src | |
| parent | fafebd0f90dd8b6fe311e0384898653632072539 (diff) | |
| download | qxmpp-194762951e6a59808350032f38e05cd0e5d03807.tar.gz | |
make password checking full asynchronous
Diffstat (limited to 'src')
| -rw-r--r-- | src/QXmppIncomingClient.cpp | 189 | ||||
| -rw-r--r-- | src/QXmppIncomingClient.h | 23 | ||||
| -rw-r--r-- | src/QXmppPasswordChecker.cpp | 224 | ||||
| -rw-r--r-- | src/QXmppPasswordChecker.h | 109 | ||||
| -rw-r--r-- | src/src.pro | 2 |
5 files changed, 420 insertions, 127 deletions
diff --git a/src/QXmppIncomingClient.cpp b/src/QXmppIncomingClient.cpp index cdee467c..7b8ce038 100644 --- a/src/QXmppIncomingClient.cpp +++ b/src/QXmppIncomingClient.cpp @@ -21,7 +21,6 @@ * */ -#include <QCryptographicHash> #include <QDomElement> #include <QSslKey> #include <QSslSocket> @@ -30,6 +29,7 @@ #include "QXmppBindIq.h" #include "QXmppConstants.h" #include "QXmppMessage.h" +#include "QXmppPasswordChecker.h" #include "QXmppSaslAuth.h" #include "QXmppSessionIq.h" #include "QXmppStreamFeatures.h" @@ -76,7 +76,7 @@ QXmppIncomingClient::QXmppIncomingClient(QSslSocket *socket, const QString &doma d->idleTimer = new QTimer(this); d->idleTimer->setSingleShot(true); bool check = connect(d->idleTimer, SIGNAL(timeout()), - this, SLOT(slotTimeout())); + this, SLOT(onTimeout())); Q_ASSERT(check); Q_UNUSED(check); } @@ -214,33 +214,22 @@ void QXmppIncomingClient::handleStanza(const QDomElement &nodeRecv) return; } - const QString username = QString::fromUtf8(auth[1]); - const QString password = QString::fromUtf8(auth[2]); + QXmppPasswordRequest request; + request.setDomain(d->domain); + request.setUsername(QString::fromUtf8(auth[1])); + request.setPassword(QString::fromUtf8(auth[2])); if (!d->passwordChecker) { // FIXME: what type of failure? - warning(QString("Cannot authenticate '%1', no password checker").arg(username)); + warning(QString("Cannot authenticate '%1', no password checker").arg(request.username())); sendData("<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>"); disconnectFromHost(); return; } - QXmppPasswordChecker::Error error = d->passwordChecker->checkPassword(username, d->domain, password); - if (error == QXmppPasswordChecker::NoError) - { - d->username = username; - info(QString("Authentication succeeded for '%1'").arg(d->username)); - sendData("<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>"); - } else if (error == QXmppPasswordChecker::AuthorizationError) { - warning(QString("Authentication failed for '%1'").arg(username)); - sendData("<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><not-authorized/></failure>"); - disconnectFromHost(); - return; - } else { - warning(QString("Temporary authentication failure for '%1'").arg(username)); - sendData("<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><temporary-auth-failure/></failure>"); - disconnectFromHost(); - return; - } + QXmppPasswordReply *reply = d->passwordChecker->checkPassword(request); + reply->setParent(this); + reply->setProperty("__sasl_username", request.username()); + connect(reply, SIGNAL(finished()), this, SLOT(onPasswordReply())); } else if (mechanism == "DIGEST-MD5") { @@ -269,13 +258,13 @@ void QXmppIncomingClient::handleStanza(const QDomElement &nodeRecv) } else if (nodeRecv.tagName() == "response") { - const QByteArray raw = QByteArray::fromBase64(nodeRecv.text().toAscii()); - QMap<QByteArray, QByteArray> response = QXmppSaslDigestMd5::parseMessage(raw); - if (d->saslStep == 1) { + const QByteArray raw = QByteArray::fromBase64(nodeRecv.text().toAscii()); + QMap<QByteArray, QByteArray> saslResponse = QXmppSaslDigestMd5::parseMessage(raw); + // check credentials - const QString username = QString::fromUtf8(response.value("username")); + const QString username = QString::fromUtf8(saslResponse.value("username")); if (!d->passwordChecker) { // FIXME: what type of failure? warning(QString("Cannot authenticate '%1', no password checker").arg(username)); @@ -284,34 +273,14 @@ void QXmppIncomingClient::handleStanza(const QDomElement &nodeRecv) return; } - QByteArray secret; - QXmppPasswordChecker::Error error = d->passwordChecker->getDigest(username, d->domain, secret); - if (error == QXmppPasswordChecker::TemporaryError) { - warning(QString("Temporary authentication failure for '%1'").arg(username)); - sendData("<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><temporary-auth-failure/></failure>"); - disconnectFromHost(); - return; - } - d->saslDigest.setSecret(secret); - d->saslDigest.setDigestUri(response.value("digest-uri")); - d->saslDigest.setNc(response.value("nc")); - d->saslDigest.setCnonce(response.value("cnonce")); - if (response["response"] != d->saslDigest.calculateDigest( - QByteArray("AUTHENTICATE:") + d->saslDigest.digestUri())) - { - sendData("<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><not-authorized/></failure>"); - disconnectFromHost(); - return; - } + QXmppPasswordRequest request; + request.setUsername(username); + request.setDomain(d->domain); - // send new challenge - d->username = username; - d->saslStep = 2; - QMap<QByteArray, QByteArray> challenge; - challenge["rspauth"] = d->saslDigest.calculateDigest( - QByteArray(":") + d->saslDigest.digestUri()); - const QByteArray data = QXmppSaslDigestMd5::serializeMessage(challenge).toBase64(); - sendData("<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" + data +"</challenge>"); + QXmppPasswordReply *reply = d->passwordChecker->getDigest(request); + reply->setParent(this); + reply->setProperty("__sasl_raw", raw); + connect(reply, SIGNAL(finished()), this, SLOT(onDigestReply())); } else if (d->saslStep == 2) { @@ -395,71 +364,75 @@ void QXmppIncomingClient::handleStanza(const QDomElement &nodeRecv) } } -void QXmppIncomingClient::slotTimeout() +void QXmppIncomingClient::onDigestReply() { - warning(QString("Idle timeout for %1").arg(jid())); - disconnectFromHost(); -} + QXmppPasswordReply *reply = qobject_cast<QXmppPasswordReply*>(sender()); + if (!reply) + return; + reply->deleteLater(); -/// Checks that the given credentials are valid. -/// -/// The base implementation requires that you reimplement getPassword(). -/// -/// \param username -/// \param domain -/// \param password + const QMap<QByteArray, QByteArray> saslResponse = QXmppSaslDigestMd5::parseMessage(reply->property("__sasl_raw").toByteArray()); + const QString username = QString::fromUtf8(saslResponse.value("username")); + if (reply->error() == QXmppPasswordReply::TemporaryError) { + warning(QString("Temporary authentication failure for '%1'").arg(username)); + sendData("<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><temporary-auth-failure/></failure>"); + disconnectFromHost(); + return; + } -QXmppPasswordChecker::Error QXmppPasswordChecker::checkPassword(const QString &username, const QString &domain, const QString &password) -{ - QString secret; - Error error = getPassword(username, domain, secret); - if (error != NoError) - return error; + d->saslDigest.setSecret(reply->digest()); + d->saslDigest.setDigestUri(saslResponse.value("digest-uri")); + d->saslDigest.setNc(saslResponse.value("nc")); + d->saslDigest.setCnonce(saslResponse.value("cnonce")); + if (saslResponse.value("response") != d->saslDigest.calculateDigest( + QByteArray("AUTHENTICATE:") + d->saslDigest.digestUri())) + { + sendData("<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><not-authorized/></failure>"); + disconnectFromHost(); + return; + } - return (password == secret) ? NoError : AuthorizationError; + // send new challenge + d->username = username; + d->saslStep = 2; + QMap<QByteArray, QByteArray> challenge; + challenge["rspauth"] = d->saslDigest.calculateDigest( + QByteArray(":") + d->saslDigest.digestUri()); + const QByteArray data = QXmppSaslDigestMd5::serializeMessage(challenge).toBase64(); + sendData("<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" + data +"</challenge>"); } -/// Retrieves the MD5 digest for the given username. -/// -/// Reimplement this method if your backend natively supports -/// retrieving MD5 digests. -/// -/// \param username - -QXmppPasswordChecker::Error QXmppPasswordChecker::getDigest(const QString &username, const QString &domain, QByteArray &digest) +void QXmppIncomingClient::onPasswordReply() { - QString secret; - Error error = getPassword(username, domain, secret); - if (error != NoError) - return error; - - digest = QCryptographicHash::hash( - (username + ":" + domain + ":" + secret).toUtf8(), - QCryptographicHash::Md5); - return NoError; + QXmppPasswordReply *reply = qobject_cast<QXmppPasswordReply*>(sender()); + if (!reply) + return; + reply->deleteLater(); + + const QString username = reply->property("__sasl_username").toString(); + switch (reply->error()) { + case QXmppPasswordReply::NoError: + d->username = username; + info(QString("Authentication succeeded for '%1'").arg(username)); + sendData("<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>"); + break; + case QXmppPasswordReply::AuthorizationError: + warning(QString("Authentication failed for '%1'").arg(username)); + sendData("<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><not-authorized/></failure>"); + disconnectFromHost(); + break; + case QXmppPasswordReply::TemporaryError: + warning(QString("Temporary authentication failure for '%1'").arg(username)); + sendData("<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><temporary-auth-failure/></failure>"); + disconnectFromHost(); + break; + } } -/// Retrieves the password for the given username. -/// -/// The simplest way to write a password checker is to reimplement this method. -/// -/// \param username -/// \param domain -/// \param password - -QXmppPasswordChecker::Error QXmppPasswordChecker::getPassword(const QString &username, const QString &domain, QString &password) +void QXmppIncomingClient::onTimeout() { - Q_UNUSED(username); - Q_UNUSED(domain); - Q_UNUSED(password); - return TemporaryError; + warning(QString("Idle timeout for %1").arg(jid())); + disconnectFromHost(); } -/// Returns true if the getPassword() method is implemented. -/// - -bool QXmppPasswordChecker::hasGetPassword() const -{ - return false; -} diff --git a/src/QXmppIncomingClient.h b/src/QXmppIncomingClient.h index 8e95bd0a..793b8141 100644 --- a/src/QXmppIncomingClient.h +++ b/src/QXmppIncomingClient.h @@ -27,28 +27,11 @@ #include "QXmppStream.h" class QXmppIncomingClientPrivate; +class QXmppPasswordChecker; /// \brief Interface for password checkers. /// -class QXmppPasswordChecker -{ -public: - /// This enum is used to describe authentication errors. - enum Error { - NoError = 0, - AuthorizationError, - TemporaryError, - }; - - virtual Error checkPassword(const QString &username, const QString &domain, const QString &password); - virtual Error getDigest(const QString &username, const QString &domain, QByteArray &digest); - virtual bool hasGetPassword() const; - -protected: - virtual Error getPassword(const QString &username, const QString &domain, QString &password); -}; - /// \brief The QXmppIncomingClient class represents an incoming XMPP stream /// from an XMPP client. /// @@ -78,7 +61,9 @@ protected: /// \endcond private slots: - void slotTimeout(); + void onDigestReply(); + void onPasswordReply(); + void onTimeout(); private: Q_DISABLE_COPY(QXmppIncomingClient) diff --git a/src/QXmppPasswordChecker.cpp b/src/QXmppPasswordChecker.cpp new file mode 100644 index 00000000..0bbf6802 --- /dev/null +++ b/src/QXmppPasswordChecker.cpp @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +#include <QCryptographicHash> +#include <QString> +#include <QTimer> + +#include "QXmppPasswordChecker.h" + +/// Returns the requested domain. + +QString QXmppPasswordRequest::domain() const +{ + return m_domain; +} + +/// Sets the requested \a domain. +/// +/// \param domain + +void QXmppPasswordRequest::setDomain(const QString &domain) +{ + m_domain = domain; +} + +QString QXmppPasswordRequest::password() const +{ + return m_password; +} + +void QXmppPasswordRequest::setPassword(const QString &password) +{ + m_password = password; +} + +/// Returns the requested username. + +QString QXmppPasswordRequest::username() const +{ + return m_username; +} + +/// Sets the requested \a username. +/// +/// \param username + +void QXmppPasswordRequest::setUsername(const QString &username) +{ + m_username = username; +} + +/// Constructs a new QXmppPasswordReply. +/// +/// \param parent + +QXmppPasswordReply::QXmppPasswordReply(QObject *parent) + : QObject(parent), + m_error(QXmppPasswordReply::NoError), + m_isFinished(false) +{ +} + +/// Returns the received MD5 digest. + +QByteArray QXmppPasswordReply::digest() const +{ + return m_digest; +} + +/// Sets the received MD5 digest. +/// +/// \param digest + +void QXmppPasswordReply::setDigest(const QByteArray &digest) +{ + m_digest = digest; +} + +/// Returns the error that was found during the processing of this request. +/// +/// If no error was found, returns NoError. + +QXmppPasswordReply::Error QXmppPasswordReply::error() const +{ + return m_error; +} + +/// Returns the error that was found during the processing of this request. +/// +void QXmppPasswordReply::setError(QXmppPasswordReply::Error error) +{ + m_error = error; +} + +/// Mark reply as finished. + +void QXmppPasswordReply::finish() +{ + m_isFinished = true; + emit finished(); +} + +/// Delay marking reply as finished. + +void QXmppPasswordReply::finishLater() +{ + QTimer::singleShot(0, this, SLOT(finish())); +} + +/// Returns true when the reply has finished. + +bool QXmppPasswordReply::isFinished() const +{ + return m_isFinished; +} + +/// Returns the received password. + +QString QXmppPasswordReply::password() const +{ + return m_password; +} + +/// Sets the received password. +/// +/// \param password + +void QXmppPasswordReply::setPassword(const QString &password) +{ + m_password = password; +} + +/// Checks that the given credentials are valid. +/// +/// The base implementation requires that you reimplement getPassword(). +/// +/// \param request + +QXmppPasswordReply *QXmppPasswordChecker::checkPassword(const QXmppPasswordRequest &request) +{ + QXmppPasswordReply *reply = new QXmppPasswordReply; + + QString secret; + QXmppPasswordReply::Error error = getPassword(request, secret); + if (error == QXmppPasswordReply::NoError) { + if (request.password() != secret) + reply->setError(QXmppPasswordReply::AuthorizationError); + } else { + reply->setError(error); + } + + // reply is finished + reply->finishLater(); + return reply; +} + +/// Retrieves the MD5 digest for the given username. +/// +/// Reimplement this method if your backend natively supports +/// retrieving MD5 digests. +/// +/// \param request + +QXmppPasswordReply *QXmppPasswordChecker::getDigest(const QXmppPasswordRequest &request) +{ + QXmppPasswordReply *reply = new QXmppPasswordReply; + + QString secret; + QXmppPasswordReply::Error error = getPassword(request, secret); + if (error == QXmppPasswordReply::NoError) { + reply->setDigest(QCryptographicHash::hash( + (request.username() + ":" + request.domain() + ":" + secret).toUtf8(), + QCryptographicHash::Md5)); + } else { + reply->setError(error); + } + + // reply is finished + reply->finishLater(); + return reply; +} + +/// Retrieves the password for the given username. +/// +/// The simplest way to write a password checker is to reimplement this method. +/// +/// \param request +/// \param password + +QXmppPasswordReply::Error QXmppPasswordChecker::getPassword(const QXmppPasswordRequest &request, QString &password) +{ + Q_UNUSED(request); + Q_UNUSED(password); + return QXmppPasswordReply::TemporaryError; +} + +/// Returns true if the getPassword() method is implemented. +/// + +bool QXmppPasswordChecker::hasGetPassword() const +{ + return false; +} + diff --git a/src/QXmppPasswordChecker.h b/src/QXmppPasswordChecker.h new file mode 100644 index 00000000..85cef3d4 --- /dev/null +++ b/src/QXmppPasswordChecker.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +#ifndef QXMPPPASSWORDCHECKER_H +#define QXMPPPASSWORDCHECKER_H + +#include <QObject> + +/// \brief The QXmppPasswordRequest class represents a password request. +/// +class QXmppPasswordRequest +{ +public: + // This enum is used to describe request types. + enum Type { + CheckPassword = 0, + }; + + QString domain() const; + void setDomain(const QString &domain); + + QString password() const; + void setPassword(const QString &password); + + QString username() const; + void setUsername(const QString &username); + +private: + QString m_domain; + QString m_password; + QString m_username; +}; + +/// \brief The QXmppPasswordRequest class represents a password reply. +/// +class QXmppPasswordReply : public QObject +{ + Q_OBJECT + +public: + /// This enum is used to describe authentication errors. + enum Error { + NoError = 0, + AuthorizationError, + TemporaryError, + }; + + QXmppPasswordReply(QObject *parent = 0); + + QByteArray digest() const; + void setDigest(const QByteArray &digest); + + QString password() const; + void setPassword(const QString &password); + + QXmppPasswordReply::Error error() const; + void setError(QXmppPasswordReply::Error error); + + bool isFinished() const; + +public slots: + void finish(); + void finishLater(); + +signals: + void finished(); + +private: + QByteArray m_digest; + QString m_password; + QXmppPasswordReply::Error m_error; + bool m_isFinished; +}; + +/// \brief The QXmppPasswordChecker class represents an abstract password checker. +/// + +class QXmppPasswordChecker +{ +public: + virtual QXmppPasswordReply *checkPassword(const QXmppPasswordRequest &request); + virtual QXmppPasswordReply *getDigest(const QXmppPasswordRequest &request); + virtual bool hasGetPassword() const; + +protected: + virtual QXmppPasswordReply::Error getPassword(const QXmppPasswordRequest &request, QString &password); +}; + +#endif diff --git a/src/src.pro b/src/src.pro index 2a78c200..546dd383 100644 --- a/src/src.pro +++ b/src/src.pro @@ -51,6 +51,7 @@ INSTALL_HEADERS = QXmppUtils.h \ QXmppOutgoingClient.h \ QXmppOutgoingServer.h \ QXmppPacket.h \ + QXmppPasswordChecker.h \ QXmppPingIq.h \ QXmppPresence.h \ QXmppPubSubIq.h \ @@ -118,6 +119,7 @@ SOURCES += QXmppUtils.cpp \ QXmppOutgoingClient.cpp \ QXmppOutgoingServer.cpp \ QXmppPacket.cpp \ + QXmppPasswordChecker.cpp \ QXmppPingIq.cpp \ QXmppPresence.cpp \ QXmppPubSubIq.cpp \ |
