aboutsummaryrefslogtreecommitdiff
path: root/src/client/QXmppOutgoingClient.cpp
diff options
context:
space:
mode:
authorJeremy Lainé <jeremy.laine@m4x.org>2012-02-08 12:33:41 +0000
committerJeremy Lainé <jeremy.laine@m4x.org>2012-02-08 12:33:41 +0000
commit21acd67e9b65bea87902032b12709675905aa922 (patch)
treeed5ae9066b10400c4fe6e67dfaf2f4c37a09c32e /src/client/QXmppOutgoingClient.cpp
parentcea7ae1e702b82d2d0d0a851de1aae58270b14f6 (diff)
downloadqxmpp-21acd67e9b65bea87902032b12709675905aa922.tar.gz
start moving client-specific code
Diffstat (limited to 'src/client/QXmppOutgoingClient.cpp')
-rw-r--r--src/client/QXmppOutgoingClient.cpp773
1 files changed, 773 insertions, 0 deletions
diff --git a/src/client/QXmppOutgoingClient.cpp b/src/client/QXmppOutgoingClient.cpp
new file mode 100644
index 00000000..7953dad2
--- /dev/null
+++ b/src/client/QXmppOutgoingClient.cpp
@@ -0,0 +1,773 @@
+/*
+ * Copyright (C) 2008-2011 The QXmpp developers
+ *
+ * Authors:
+ * Manjeet Dahiya
+ * 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 <QSslSocket>
+#include <QUrl>
+#include "qdnslookup.h"
+
+#include "QXmppConfiguration.h"
+#include "QXmppConstants.h"
+#include "QXmppIq.h"
+#include "QXmppLogger.h"
+#include "QXmppMessage.h"
+#include "QXmppPacket.h"
+#include "QXmppPresence.h"
+#include "QXmppOutgoingClient.h"
+#include "QXmppStreamFeatures.h"
+#include "QXmppNonSASLAuth.h"
+#include "QXmppSaslAuth.h"
+#include "QXmppUtils.h"
+
+// IQ types
+#include "QXmppBindIq.h"
+#include "QXmppPingIq.h"
+#include "QXmppSessionIq.h"
+
+#include <QBuffer>
+#include <QCoreApplication>
+#include <QDomDocument>
+#include <QStringList>
+#include <QRegExp>
+#include <QHostAddress>
+#include <QXmlStreamWriter>
+#include <QTimer>
+
+class QXmppOutgoingClientPrivate
+{
+public:
+ QXmppOutgoingClientPrivate();
+
+ // This object provides the configuration
+ // required for connecting to the XMPP server.
+ QXmppConfiguration config;
+ QXmppStanza::Error::Condition xmppStreamError;
+
+ // DNS
+ QDnsLookup dns;
+
+ // Stream
+ QString streamId;
+ QString streamFrom;
+ QString streamVersion;
+
+ // Session
+ QString bindId;
+ QString sessionId;
+ bool sessionAvailable;
+ bool sessionStarted;
+
+ // Authentication
+ QString nonSASLAuthId;
+ QXmppSaslDigestMd5 saslDigest;
+ int saslDigestStep;
+ int saslMechanism;
+
+ // Timers
+ QTimer *pingTimer;
+ QTimer *timeoutTimer;
+};
+
+QXmppOutgoingClientPrivate::QXmppOutgoingClientPrivate()
+ : sessionAvailable(false),
+ saslDigestStep(0),
+ saslMechanism(-1)
+{
+}
+
+/// Constructs an outgoing client stream.
+///
+/// \param parent
+
+QXmppOutgoingClient::QXmppOutgoingClient(QObject *parent)
+ : QXmppStream(parent),
+ d(new QXmppOutgoingClientPrivate)
+{
+ bool check;
+ Q_UNUSED(check);
+
+ // initialise socket
+ QSslSocket *socket = new QSslSocket(this);
+ setSocket(socket);
+
+ check = connect(socket, SIGNAL(sslErrors(QList<QSslError>)),
+ this, SLOT(socketSslErrors(QList<QSslError>)));
+ Q_ASSERT(check);
+
+ check = connect(socket, SIGNAL(error(QAbstractSocket::SocketError)),
+ this, SLOT(socketError(QAbstractSocket::SocketError)));
+ Q_ASSERT(check);
+
+ // DNS lookups
+ check = connect(&d->dns, SIGNAL(finished()),
+ this, SLOT(_q_dnsLookupFinished()));
+ Q_ASSERT(check);
+
+ // XEP-0199: XMPP Ping
+ d->pingTimer = new QTimer(this);
+ check = connect(d->pingTimer, SIGNAL(timeout()),
+ this, SLOT(pingSend()));
+ Q_ASSERT(check);
+
+ d->timeoutTimer = new QTimer(this);
+ d->timeoutTimer->setSingleShot(true);
+ check = connect(d->timeoutTimer, SIGNAL(timeout()),
+ this, SLOT(pingTimeout()));
+ Q_ASSERT(check);
+
+ check = connect(this, SIGNAL(connected()),
+ this, SLOT(pingStart()));
+ Q_ASSERT(check);
+
+ check = connect(this, SIGNAL(disconnected()),
+ this, SLOT(pingStop()));
+ Q_ASSERT(check);
+}
+
+/// Destroys an outgoing client stream.
+
+QXmppOutgoingClient::~QXmppOutgoingClient()
+{
+ delete d;
+}
+
+/// Returns a reference to the stream's configuration.
+
+QXmppConfiguration& QXmppOutgoingClient::configuration()
+{
+ return d->config;
+}
+
+/// Attempts to connect to the XMPP server.
+
+void QXmppOutgoingClient::connectToHost()
+{
+ const QString host = configuration().host();
+ const quint16 port = configuration().port();
+
+ // override CA certificates if requested
+ if (!configuration().caCertificates().isEmpty()) {
+ socket()->setCaCertificates(configuration().caCertificates());
+ }
+
+ // if an explicit host was provided, connect to it
+ if (!host.isEmpty() && port) {
+ info(QString("Connecting to %1:%2").arg(host, QString::number(port)));
+ socket()->setProxy(configuration().networkProxy());
+ socket()->connectToHost(host, port);
+ return;
+ }
+
+ // otherwise, lookup server
+ const QString domain = configuration().domain();
+ debug(QString("Looking up server for domain %1").arg(domain));
+ d->dns.setName("_xmpp-client._tcp." + domain);
+ d->dns.setType(QDnsLookup::SRV);
+ d->dns.lookup();
+}
+
+void QXmppOutgoingClient::_q_dnsLookupFinished()
+{
+ QString host;
+ quint16 port;
+
+ if (d->dns.error() == QDnsLookup::NoError &&
+ !d->dns.serviceRecords().isEmpty()) {
+ // take the first returned record
+ host = d->dns.serviceRecords().first().target();
+ port = d->dns.serviceRecords().first().port();
+ } else {
+ // as a fallback, use domain as the host name
+ warning(QString("Lookup for domain %1 failed: %2")
+ .arg(d->dns.name(), d->dns.errorString()));
+ host = configuration().domain();
+ port = configuration().port();
+ }
+
+ // connect to server
+ info(QString("Connecting to %1:%2").arg(host, QString::number(port)));
+ socket()->setProxy(configuration().networkProxy());
+ socket()->connectToHost(host, port);
+}
+
+/// Returns true if the socket is connected and a session has been started.
+///
+
+bool QXmppOutgoingClient::isConnected() const
+{
+ return QXmppStream::isConnected() && d->sessionStarted;
+}
+
+void QXmppOutgoingClient::socketSslErrors(const QList<QSslError> & error)
+{
+ warning("SSL errors");
+ for(int i = 0; i< error.count(); ++i)
+ warning(error.at(i).errorString());
+
+ if (configuration().ignoreSslErrors())
+ socket()->ignoreSslErrors();
+}
+
+void QXmppOutgoingClient::socketError(QAbstractSocket::SocketError socketError)
+{
+ Q_UNUSED(socketError);
+ emit error(QXmppClient::SocketError);
+}
+
+void QXmppOutgoingClient::handleStart()
+{
+ QXmppStream::handleStart();
+
+ // reset stream information
+ d->streamId.clear();
+ d->streamFrom.clear();
+ d->streamVersion.clear();
+
+ // reset authentication step
+ d->saslDigestStep = 0;
+ d->saslMechanism = -1;
+
+ // reset session information
+ d->bindId.clear();
+ d->sessionId.clear();
+ d->sessionAvailable = false;
+ d->sessionStarted = false;
+
+ // start stream
+ QByteArray data = "<?xml version='1.0'?><stream:stream to='";
+ data.append(configuration().domain().toUtf8());
+ data.append("' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>");
+ sendData(data);
+}
+
+void QXmppOutgoingClient::handleStream(const QDomElement &streamElement)
+{
+ if(d->streamId.isEmpty())
+ d->streamId = streamElement.attribute("id");
+ if (d->streamFrom.isEmpty())
+ d->streamFrom = streamElement.attribute("from");
+ if(d->streamVersion.isEmpty())
+ {
+ d->streamVersion = streamElement.attribute("version");
+
+ // no version specified, signals XMPP Version < 1.0.
+ // switch to old auth mechanism
+ if(d->streamVersion.isEmpty())
+ sendNonSASLAuthQuery();
+ }
+}
+
+void QXmppOutgoingClient::handleStanza(const QDomElement &nodeRecv)
+{
+ // if we receive any kind of data, stop the timeout timer
+ d->timeoutTimer->stop();
+
+ const QString ns = nodeRecv.namespaceURI();
+
+ // give client opportunity to handle stanza
+ bool handled = false;
+ emit elementReceived(nodeRecv, handled);
+ if (handled)
+ return;
+
+ if(QXmppStreamFeatures::isStreamFeatures(nodeRecv))
+ {
+ QXmppStreamFeatures features;
+ features.parse(nodeRecv);
+
+ if (!socket()->isEncrypted())
+ {
+ // determine TLS mode to use
+ const QXmppConfiguration::StreamSecurityMode localSecurity = configuration().streamSecurityMode();
+ const QXmppStreamFeatures::Mode remoteSecurity = features.tlsMode();
+ if (!socket()->supportsSsl() &&
+ (localSecurity == QXmppConfiguration::TLSRequired ||
+ remoteSecurity == QXmppStreamFeatures::Required))
+ {
+ warning("Disconnecting as TLS is required, but SSL support is not available");
+ disconnectFromHost();
+ return;
+ }
+ if (localSecurity == QXmppConfiguration::TLSRequired &&
+ remoteSecurity == QXmppStreamFeatures::Disabled)
+ {
+ warning("Disconnecting as TLS is required, but not supported by the server");
+ disconnectFromHost();
+ return;
+ }
+
+ if (socket()->supportsSsl() &&
+ localSecurity != QXmppConfiguration::TLSDisabled &&
+ remoteSecurity != QXmppStreamFeatures::Disabled)
+ {
+ // enable TLS as it is support by both parties
+ sendData("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
+ return;
+ }
+ }
+
+ // handle authentication
+ const bool nonSaslAvailable = features.nonSaslAuthMode() != QXmppStreamFeatures::Disabled;
+ const bool saslAvailable = !features.authMechanisms().isEmpty();
+ const bool useSasl = configuration().useSASLAuthentication();
+ if((saslAvailable && nonSaslAvailable && !useSasl) ||
+ (!saslAvailable && nonSaslAvailable))
+ {
+ sendNonSASLAuthQuery();
+ }
+ else if(saslAvailable)
+ {
+ // determine SASL Authentication mechanism to use
+ const QList<QXmppConfiguration::SASLAuthMechanism> mechanisms = features.authMechanisms();
+ if (mechanisms.isEmpty())
+ {
+ warning("No supported SASL Authentication mechanism available");
+ disconnectFromHost();
+ return;
+ }
+ else if (!mechanisms.contains(configuration().sASLAuthMechanism()))
+ {
+ info("Desired SASL Auth mechanism is not available, selecting first available one");
+ d->saslMechanism = mechanisms.first();
+ } else {
+ d->saslMechanism = configuration().sASLAuthMechanism();
+ }
+
+ // send SASL Authentication request
+ switch(d->saslMechanism)
+ {
+ case QXmppConfiguration::SASLPlain:
+ {
+ QString userPass('\0' + configuration().user() +
+ '\0' + configuration().password());
+ QByteArray data = "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>";
+ data += userPass.toUtf8().toBase64();
+ data += "</auth>";
+ sendData(data);
+ }
+ break;
+ case QXmppConfiguration::SASLDigestMD5:
+ sendData("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>");
+ break;
+ case QXmppConfiguration::SASLAnonymous:
+ sendData("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='ANONYMOUS'/>");
+ break;
+ case QXmppConfiguration::SASLXFacebookPlatform:
+ sendData("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='X-FACEBOOK-PLATFORM'/>");
+ break;
+ }
+ }
+
+ // check whether bind is available
+ if (features.bindMode() != QXmppStreamFeatures::Disabled)
+ {
+ QXmppBindIq bind;
+ bind.setType(QXmppIq::Set);
+ bind.setResource(configuration().resource());
+ d->bindId = bind.id();
+ sendPacket(bind);
+ }
+
+ // check whether session is available
+ if (features.sessionMode() != QXmppStreamFeatures::Disabled)
+ d->sessionAvailable = true;
+ }
+ else if(ns == ns_stream && nodeRecv.tagName() == "error")
+ {
+ if (!nodeRecv.firstChildElement("conflict").isNull())
+ d->xmppStreamError = QXmppStanza::Error::Conflict;
+ else
+ d->xmppStreamError = QXmppStanza::Error::UndefinedCondition;
+ emit error(QXmppClient::XmppStreamError);
+ }
+ else if(ns == ns_tls)
+ {
+ if(nodeRecv.tagName() == "proceed")
+ {
+ debug("Starting encryption");
+ socket()->startClientEncryption();
+ return;
+ }
+ }
+ else if(ns == ns_sasl)
+ {
+ if(nodeRecv.tagName() == "success")
+ {
+ debug("Authenticated");
+ handleStart();
+ }
+ else if(nodeRecv.tagName() == "challenge")
+ {
+ switch(d->saslMechanism)
+ {
+ case QXmppConfiguration::SASLDigestMD5:
+ d->saslDigestStep++;
+ switch (d->saslDigestStep)
+ {
+ case 1 :
+ sendAuthDigestMD5ResponseStep1(nodeRecv.text());
+ break;
+ case 2 :
+ sendAuthDigestMD5ResponseStep2(nodeRecv.text());
+ break;
+ default :
+ warning("Too many authentication steps");
+ disconnectFromHost();
+ break;
+ }
+ break;
+ case QXmppConfiguration::SASLXFacebookPlatform:
+ sendAuthXFacebookResponse(nodeRecv.text());
+ break;
+ default:
+ warning("Unexpected SASL challenge");
+ disconnectFromHost();
+ break;
+ }
+ }
+ else if(nodeRecv.tagName() == "failure")
+ {
+ if (!nodeRecv.firstChildElement("not-authorized").isNull())
+ d->xmppStreamError = QXmppStanza::Error::NotAuthorized;
+ else
+ d->xmppStreamError = QXmppStanza::Error::UndefinedCondition;
+ emit error(QXmppClient::XmppStreamError);
+
+ warning("Authentication failure");
+ disconnectFromHost();
+ }
+ }
+ else if(ns == ns_client)
+ {
+
+ if(nodeRecv.tagName() == "iq")
+ {
+ QDomElement element = nodeRecv.firstChildElement();
+ QString id = nodeRecv.attribute("id");
+ QString type = nodeRecv.attribute("type");
+ if(type.isEmpty())
+ warning("QXmppStream: iq type can't be empty");
+
+ if(id == d->sessionId)
+ {
+ QXmppSessionIq session;
+ session.parse(nodeRecv);
+
+ // xmpp connection made
+ d->sessionStarted = true;
+ emit connected();
+ }
+ else if(QXmppBindIq::isBindIq(nodeRecv) && id == d->bindId)
+ {
+ QXmppBindIq bind;
+ bind.parse(nodeRecv);
+
+ // bind result
+ if (bind.type() == QXmppIq::Result)
+ {
+ if (!bind.jid().isEmpty())
+ {
+ QRegExp jidRegex("^([^@/]+)@([^@/]+)/(.+)$");
+ if (jidRegex.exactMatch(bind.jid()))
+ {
+ configuration().setUser(jidRegex.cap(1));
+ configuration().setDomain(jidRegex.cap(2));
+ configuration().setResource(jidRegex.cap(3));
+ } else {
+ warning("Bind IQ received with invalid JID: " + bind.jid());
+ }
+ }
+
+ // start session if it is available
+ if (d->sessionAvailable)
+ {
+ QXmppSessionIq session;
+ session.setType(QXmppIq::Set);
+ session.setTo(configuration().domain());
+ d->sessionId = session.id();
+ sendPacket(session);
+ }
+ }
+ }
+ // extensions
+
+ // XEP-0078: Non-SASL Authentication
+ else if(id == d->nonSASLAuthId && type == "result")
+ {
+ // successful Non-SASL Authentication
+ debug("Authenticated (Non-SASL)");
+
+ // xmpp connection made
+ d->sessionStarted = true;
+ emit connected();
+ }
+ else if(QXmppNonSASLAuthIq::isNonSASLAuthIq(nodeRecv))
+ {
+ if(type == "result")
+ {
+ bool digest = !nodeRecv.firstChildElement("query").
+ firstChildElement("digest").isNull();
+ bool plain = !nodeRecv.firstChildElement("query").
+ firstChildElement("password").isNull();
+ bool plainText = false;
+
+ if(plain && digest)
+ {
+ if(configuration().nonSASLAuthMechanism() ==
+ QXmppConfiguration::NonSASLDigest)
+ plainText = false;
+ else
+ plainText = true;
+ }
+ else if(plain)
+ plainText = true;
+ else if(digest)
+ plainText = false;
+ else
+ {
+ warning("No supported Non-SASL Authentication mechanism available");
+ disconnectFromHost();
+ return;
+ }
+ sendNonSASLAuth(plainText);
+ }
+ }
+ // XEP-0199: XMPP Ping
+ else if(QXmppPingIq::isPingIq(nodeRecv))
+ {
+ QXmppPingIq req;
+ req.parse(nodeRecv);
+
+ QXmppIq iq(QXmppIq::Result);
+ iq.setId(req.id());
+ iq.setTo(req.from());
+ sendPacket(iq);
+ }
+ else
+ {
+ QXmppIq iqPacket;
+ iqPacket.parse(nodeRecv);
+
+ // if we didn't understant the iq, reply with error
+ // except for "result" and "error" iqs
+ if (type != "result" && type != "error")
+ {
+ QXmppIq iq(QXmppIq::Error);
+ iq.setId(iqPacket.id());
+ iq.setTo(iqPacket.from());
+ QXmppStanza::Error error(QXmppStanza::Error::Cancel,
+ QXmppStanza::Error::FeatureNotImplemented);
+ iq.setError(error);
+ sendPacket(iq);
+ } else {
+ emit iqReceived(iqPacket);
+ }
+ }
+ }
+ else if(nodeRecv.tagName() == "presence")
+ {
+ QXmppPresence presence;
+ presence.parse(nodeRecv);
+
+ // emit presence
+ emit presenceReceived(presence);
+ }
+ else if(nodeRecv.tagName() == "message")
+ {
+ QXmppMessage message;
+ message.parse(nodeRecv);
+
+ // emit message
+ emit messageReceived(message);
+ }
+ }
+}
+
+void QXmppOutgoingClient::pingStart()
+{
+ const int interval = configuration().keepAliveInterval();
+ // start ping timer
+ if (interval > 0)
+ {
+ d->pingTimer->setInterval(interval * 1000);
+ d->pingTimer->start();
+ }
+}
+
+void QXmppOutgoingClient::pingStop()
+{
+ // stop all timers
+ d->pingTimer->stop();
+ d->timeoutTimer->stop();
+}
+
+void QXmppOutgoingClient::pingSend()
+{
+ // send ping packet
+ QXmppPingIq ping;
+ ping.setTo(configuration().domain());
+ sendPacket(ping);
+
+ // start timeout timer
+ const int timeout = configuration().keepAliveTimeout();
+ if (timeout > 0)
+ {
+ d->timeoutTimer->setInterval(timeout * 1000);
+ d->timeoutTimer->start();
+ }
+}
+
+void QXmppOutgoingClient::pingTimeout()
+{
+ warning("Ping timeout");
+ disconnectFromHost();
+ emit error(QXmppClient::KeepAliveError);
+}
+
+// challenge is BASE64 encoded string
+void QXmppOutgoingClient::sendAuthDigestMD5ResponseStep1(const QString& challenge)
+{
+ QByteArray ba = QByteArray::fromBase64(challenge.toAscii());
+ QMap<QByteArray, QByteArray> map = QXmppSaslDigestMd5::parseMessage(ba);
+
+ if (!map.contains("nonce"))
+ {
+ warning("sendAuthDigestMD5ResponseStep1: Invalid input");
+ disconnectFromHost();
+ return;
+ }
+
+ d->saslDigest.setAuthzid(map.value("authzid"));
+ d->saslDigest.setCnonce(QXmppSaslDigestMd5::generateNonce());
+ d->saslDigest.setDigestUri(QString("xmpp/%1").arg(configuration().domain()).toUtf8());
+ d->saslDigest.setNc("00000001");
+ d->saslDigest.setNonce(map.value("nonce"));
+ d->saslDigest.setQop("auth");
+ d->saslDigest.setSecret(QCryptographicHash::hash(
+ configuration().user().toUtf8() + ":" + map.value("realm") + ":" + configuration().password().toUtf8(),
+ QCryptographicHash::Md5));
+
+ // Build response
+ QMap<QByteArray, QByteArray> response;
+ response["username"] = configuration().user().toUtf8();
+ if (map.contains("realm"))
+ response["realm"] = map.value("realm");
+ response["nonce"] = d->saslDigest.nonce();
+ response["cnonce"] = d->saslDigest.cnonce();
+ response["nc"] = d->saslDigest.nc();
+ response["qop"] = d->saslDigest.qop();
+ response["digest-uri"] = d->saslDigest.digestUri();
+ response["response"] = d->saslDigest.calculateDigest(
+ QByteArray("AUTHENTICATE:") + d->saslDigest.digestUri());
+
+ if(!d->saslDigest.authzid().isEmpty())
+ response["authzid"] = d->saslDigest.authzid();
+ response["charset"] = "utf-8";
+
+ const QByteArray data = QXmppSaslDigestMd5::serializeMessage(response);
+ QByteArray packet = "<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"
+ + data.toBase64() + "</response>";
+ sendData(packet);
+}
+
+void QXmppOutgoingClient::sendAuthDigestMD5ResponseStep2(const QString &challenge)
+{
+ QByteArray ba = QByteArray::fromBase64(challenge.toAscii());
+ QMap<QByteArray, QByteArray> map = QXmppSaslDigestMd5::parseMessage(ba);
+
+ if (!map.contains("rspauth"))
+ {
+ warning("sendAuthDigestMD5ResponseStep2: Invalid input");
+ disconnectFromHost();
+ return;
+ }
+
+ // check new challenge
+ if (map["rspauth"] !=
+ d->saslDigest.calculateDigest(QByteArray(":") + d->saslDigest.digestUri()))
+ {
+ warning("sendAuthDigestMD5ResponseStep2: Bad challenge");
+ disconnectFromHost();
+ return;
+ }
+
+ sendData("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>");
+}
+
+void QXmppOutgoingClient::sendAuthXFacebookResponse(const QString& challenge)
+{
+ // parse request
+ QUrl request;
+ request.setEncodedQuery(QByteArray::fromBase64(challenge.toAscii()));
+ if (!request.hasQueryItem("method") || !request.hasQueryItem("nonce")) {
+ warning("sendAuthXFacebookResponse: Invalid input");
+ disconnectFromHost();
+ return;
+ }
+
+ // build response
+ QUrl response;
+ response.addQueryItem("access_token", configuration().facebookAccessToken());
+ response.addQueryItem("api_key", configuration().facebookAppId());
+ response.addQueryItem("call_id", 0);
+ response.addQueryItem("method", request.queryItemValue("method"));
+ response.addQueryItem("nonce", request.queryItemValue("nonce"));
+ response.addQueryItem("v", "1.0");
+
+ sendData("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"
+ + response.encodedQuery().toBase64() + "</response>");
+}
+
+void QXmppOutgoingClient::sendNonSASLAuth(bool plainText)
+{
+ QXmppNonSASLAuthIq authQuery;
+ authQuery.setType(QXmppIq::Set);
+ authQuery.setUsername(configuration().user());
+ if (plainText)
+ authQuery.setPassword(configuration().password());
+ else
+ authQuery.setDigest(d->streamId, configuration().password());
+ authQuery.setResource(configuration().resource());
+ d->nonSASLAuthId = authQuery.id();
+ sendPacket(authQuery);
+}
+
+void QXmppOutgoingClient::sendNonSASLAuthQuery()
+{
+ QXmppNonSASLAuthIq authQuery;
+ authQuery.setType(QXmppIq::Get);
+ authQuery.setTo(d->streamFrom);
+ // FIXME : why are we setting the username, XEP-0078 states we should
+ // not attempt to guess the required fields?
+ authQuery.setUsername(configuration().user());
+ sendPacket(authQuery);
+}
+
+/// Returns the type of the last XMPP stream error that occured.
+
+QXmppStanza::Error::Condition QXmppOutgoingClient::xmppStreamError()
+{
+ return d->xmppStreamError;
+}
+