diff options
| author | Jeremy Lainé <jeremy.laine@m4x.org> | 2012-02-08 12:33:41 +0000 |
|---|---|---|
| committer | Jeremy Lainé <jeremy.laine@m4x.org> | 2012-02-08 12:33:41 +0000 |
| commit | 21acd67e9b65bea87902032b12709675905aa922 (patch) | |
| tree | ed5ae9066b10400c4fe6e67dfaf2f4c37a09c32e /src/QXmppOutgoingClient.cpp | |
| parent | cea7ae1e702b82d2d0d0a851de1aae58270b14f6 (diff) | |
| download | qxmpp-21acd67e9b65bea87902032b12709675905aa922.tar.gz | |
start moving client-specific code
Diffstat (limited to 'src/QXmppOutgoingClient.cpp')
| -rw-r--r-- | src/QXmppOutgoingClient.cpp | 773 |
1 files changed, 0 insertions, 773 deletions
diff --git a/src/QXmppOutgoingClient.cpp b/src/QXmppOutgoingClient.cpp deleted file mode 100644 index 7953dad2..00000000 --- a/src/QXmppOutgoingClient.cpp +++ /dev/null @@ -1,773 +0,0 @@ -/* - * 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; -} - |
