aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJeremy Lainé <jeremy.laine@m4x.org>2011-04-07 18:27:51 +0000
committerJeremy Lainé <jeremy.laine@m4x.org>2011-04-07 18:27:51 +0000
commit194762951e6a59808350032f38e05cd0e5d03807 (patch)
treec766a97835dad98838906710be77a2197099e1e9 /src
parentfafebd0f90dd8b6fe311e0384898653632072539 (diff)
downloadqxmpp-194762951e6a59808350032f38e05cd0e5d03807.tar.gz
make password checking full asynchronous
Diffstat (limited to 'src')
-rw-r--r--src/QXmppIncomingClient.cpp189
-rw-r--r--src/QXmppIncomingClient.h23
-rw-r--r--src/QXmppPasswordChecker.cpp224
-rw-r--r--src/QXmppPasswordChecker.h109
-rw-r--r--src/src.pro2
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 \