aboutsummaryrefslogtreecommitdiff
path: root/src/QXmppStream.cpp
diff options
context:
space:
mode:
authorJeremy Lainé <jeremy.laine@m4x.org>2010-08-11 07:31:23 +0000
committerJeremy Lainé <jeremy.laine@m4x.org>2010-08-11 07:31:23 +0000
commit40c39853816cfab113d79682c34bc76a2c79c357 (patch)
treee4d6a184cf565cb87477339ce738299ff9787bc3 /src/QXmppStream.cpp
parent551c284e35280b7b91a939fe7352e496ffea402a (diff)
downloadqxmpp-40c39853816cfab113d79682c34bc76a2c79c357.tar.gz
rename "source" directory to "src"
Diffstat (limited to 'src/QXmppStream.cpp')
-rw-r--r--src/QXmppStream.cpp1120
1 files changed, 1120 insertions, 0 deletions
diff --git a/src/QXmppStream.cpp b/src/QXmppStream.cpp
new file mode 100644
index 00000000..95acb236
--- /dev/null
+++ b/src/QXmppStream.cpp
@@ -0,0 +1,1120 @@
+/*
+ * Copyright (C) 2008-2010 The QXmpp developers
+ *
+ * Author:
+ * Manjeet Dahiya
+ *
+ * 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 "QXmppUtils.h"
+#include "QXmppBind.h"
+#include "QXmppIq.h"
+#include "QXmppLogger.h"
+#include "QXmppMessage.h"
+#include "QXmppPacket.h"
+#include "QXmppPresence.h"
+#include "QXmppSession.h"
+#include "QXmppConstants.h"
+#include "QXmppStream.h"
+#include "QXmppNonSASLAuth.h"
+
+// IQ types
+#include "QXmppArchiveIq.h"
+#include "QXmppByteStreamIq.h"
+#include "QXmppDiscoveryIq.h"
+#include "QXmppIbbIq.h"
+#include "QXmppJingleIq.h"
+#include "QXmppMucIq.h"
+#include "QXmppPingIq.h"
+#include "QXmppRpcIq.h"
+#include "QXmppRosterIq.h"
+#include "QXmppStreamInitiationIq.h"
+#include "QXmppVCard.h"
+#include "QXmppVersionIq.h"
+
+#include <QCoreApplication>
+#include <QDomDocument>
+#include <QStringList>
+#include <QRegExp>
+#include <QHostAddress>
+#include <QXmlStreamWriter>
+#include <QTimer>
+
+static const QString capabilitiesNode = "http://code.google.com/p/qxmpp";
+static const QByteArray streamRootElementStart = "<?xml version=\"1.0\"?><stream:stream xmlns:stream=\"http://etherx.jabber.org/streams\" version=\"1.0\" xmlns=\"jabber:client\" xml:lang=\"en\" xmlns:xml=\"http://www.w3.org/XML/1998/namespace\">\n";
+static const QByteArray streamRootElementEnd = "</stream:stream>";
+
+class QXmppStreamPrivate
+{
+public:
+ QSslSocket socket;
+ QAbstractSocket::SocketError socketError;
+};
+
+QXmppStream::QXmppStream(QObject *parent)
+ : QObject(parent),
+ m_logger(0),
+ m_sessionAvailable(false),
+ m_authStep(0),
+ d(new QXmppStreamPrivate)
+{
+ // Make sure the random number generator is seeded
+ qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));
+
+ // initialise logger
+ setLogger(QXmppLogger::getLogger());
+
+ bool check = QObject::connect(&d->socket, SIGNAL(hostFound()),
+ this, SLOT(socketHostFound()));
+ Q_ASSERT(check);
+ check = QObject::connect(&d->socket, SIGNAL(connected()),
+ this, SLOT(socketConnected()));
+ Q_ASSERT(check);
+ check = QObject::connect(&d->socket, SIGNAL(disconnected()),
+ this, SLOT(socketDisconnected()));
+ Q_ASSERT(check);
+ check = QObject::connect(&d->socket, SIGNAL(readyRead()),
+ this, SLOT(socketReadReady()));
+ Q_ASSERT(check);
+ check = QObject::connect(&d->socket, SIGNAL(encrypted()),
+ this, SLOT(socketEncrypted()));
+ Q_ASSERT(check);
+ check = QObject::connect(&d->socket,
+ SIGNAL(sslErrors(const QList<QSslError>&)), this,
+ SLOT(socketSslErrors(const QList<QSslError>&)));
+ Q_ASSERT(check);
+ check = QObject::connect(&d->socket,
+ SIGNAL(error(QAbstractSocket::SocketError)), this,
+ SLOT(socketError(QAbstractSocket::SocketError)));
+ Q_ASSERT(check);
+
+ // XEP-0199: XMPP Ping
+ m_pingTimer = new QTimer(this);
+ check = QObject::connect(m_pingTimer, SIGNAL(timeout()), this, SLOT(pingSend()));
+ Q_ASSERT(check);
+
+ m_timeoutTimer = new QTimer(this);
+ m_timeoutTimer->setSingleShot(true);
+ check = QObject::connect(m_timeoutTimer, SIGNAL(timeout()), this, SLOT(pingTimeout()));
+ Q_ASSERT(check);
+
+ check = QObject::connect(this, SIGNAL(xmppConnected()), this, SLOT(pingStart()));
+ Q_ASSERT(check);
+
+ check = QObject::connect(this, SIGNAL(disconnected()), this, SLOT(pingStop()));
+ Q_ASSERT(check);
+}
+
+QXmppStream::~QXmppStream()
+{
+ delete d;
+}
+
+QXmppConfiguration& QXmppStream::configuration()
+{
+ return m_config;
+}
+
+void QXmppStream::connect()
+{
+ info(QString("Connecting to: %1:%2").arg(configuration().
+ host()).arg(configuration().port()));
+
+ // prepare for connection
+ m_authStep = 0;
+
+ d->socket.setProxy(configuration().networkProxy());
+ d->socket.connectToHost(configuration().
+ host(), configuration().port());
+}
+
+void QXmppStream::socketSslErrors(const QList<QSslError> & error)
+{
+ warning("SSL errors");
+ for(int i = 0; i< error.count(); ++i)
+ warning(error.at(i).errorString());
+
+ if (configuration().ignoreSslErrors())
+ d->socket.ignoreSslErrors();
+}
+
+void QXmppStream::socketHostFound()
+{
+ debug("Host found");
+ emit hostFound();
+}
+
+void QXmppStream::socketConnected()
+{
+ flushDataBuffer();
+ info("Connected");
+ emit connected();
+ sendStartStream();
+}
+
+void QXmppStream::socketDisconnected()
+{
+ flushDataBuffer();
+ info("Disconnected");
+ emit disconnected();
+}
+
+void QXmppStream::socketEncrypted()
+{
+ debug("Encrypted");
+ sendStartStream();
+}
+
+void QXmppStream::socketError(QAbstractSocket::SocketError ee)
+{
+ d->socketError = ee;
+ emit error(QXmppClient::SocketError);
+ warning(QString("Socket error: " + d->socket.errorString()));
+}
+
+void QXmppStream::socketReadReady()
+{
+ const QByteArray data = d->socket.readAll();
+ //debug("SERVER [COULD BE PARTIAL DATA]:" + data.left(20));
+ parser(data);
+}
+
+void QXmppStream::sendNonSASLAuthQuery( const QString &to )
+{
+ QXmppNonSASLAuthTypesRequestIq authQuery;
+ authQuery.setTo(to);
+ authQuery.setUsername(configuration().user());
+
+ sendPacket(authQuery);
+}
+
+/// Returns the QXmppLogger associated with the current QXmppStream.
+
+QXmppLogger *QXmppStream::logger()
+{
+ return m_logger;
+}
+
+/// Sets the QXmppLogger associated with the current QXmppStream.
+
+void QXmppStream::setLogger(QXmppLogger *logger)
+{
+ if (m_logger)
+ QObject::disconnect(this, SIGNAL(logMessage(QXmppLogger::MessageType, QString)),
+ m_logger, SLOT(log(QXmppLogger::MessageType, QString)));
+ m_logger = logger;
+ if (m_logger)
+ QObject::connect(this, SIGNAL(logMessage(QXmppLogger::MessageType, QString)),
+ m_logger, SLOT(log(QXmppLogger::MessageType, QString)));
+}
+
+void QXmppStream::debug(const QString &data)
+{
+ emit logMessage(QXmppLogger::DebugMessage, data);
+}
+
+void QXmppStream::info(const QString &data)
+{
+ emit logMessage(QXmppLogger::InformationMessage, data);
+}
+
+void QXmppStream::warning(const QString &data)
+{
+ emit logMessage(QXmppLogger::WarningMessage, data);
+}
+
+void QXmppStream::parser(const QByteArray& data)
+{
+ QDomDocument doc;
+ QByteArray completeXml;
+
+ m_dataBuffer = m_dataBuffer + data;
+
+ if(hasStartStreamElement(m_dataBuffer))
+ {
+ completeXml = m_dataBuffer + streamRootElementEnd;
+ }
+ else if(hasEndStreamElement(data))
+ {
+ completeXml = streamRootElementStart + m_dataBuffer;
+ }
+ else
+ {
+ completeXml = streamRootElementStart + m_dataBuffer + streamRootElementEnd;
+ }
+
+ if(doc.setContent(completeXml, true))
+ {
+ emit logMessage(QXmppLogger::ReceivedMessage, QString::fromUtf8(m_dataBuffer));
+ flushDataBuffer();
+
+ QDomElement nodeRecv = doc.documentElement().firstChildElement();
+
+ if(nodeRecv.isNull())
+ {
+ QDomElement streamElement = doc.documentElement();
+ if(m_streamId.isEmpty())
+ m_streamId = streamElement.attribute("id");
+ if(m_XMPPVersion.isEmpty())
+ {
+ m_XMPPVersion = streamElement.attribute("version");
+ if(m_XMPPVersion.isEmpty())
+ {
+ // no version specified, signals XMPP Version < 1.0.
+ // switch to old auth mechanism
+ sendNonSASLAuthQuery(doc.documentElement().attribute("from"));
+ }
+ }
+ }
+ else
+ {
+ //TODO: Make a login error here.
+ }
+ while(!nodeRecv.isNull())
+ {
+
+ QString ns = nodeRecv.namespaceURI();
+
+ // if we receive any kind of data, stop the timeout timer
+ m_timeoutTimer->stop();
+
+ bool handled = false;
+ emit elementReceived(nodeRecv, handled);
+
+ if(handled)
+ {
+ // already handled by client, do nothing
+ }
+ else if(ns == ns_stream && nodeRecv.tagName() == "features")
+ {
+ bool nonSaslAvailable = nodeRecv.firstChildElement("auth").
+ namespaceURI() == ns_authFeature;
+ bool saslAvailable = nodeRecv.firstChildElement("mechanisms").
+ namespaceURI() == ns_sasl;
+ bool useSasl = configuration().useSASLAuthentication();
+
+ if (!d->socket.isEncrypted())
+ {
+ // parse remote TLS mode
+ QXmppConfiguration::StreamSecurityMode remoteSecurity;
+ QDomElement tlsElement = nodeRecv.firstChildElement("starttls");
+ if (tlsElement.namespaceURI() == ns_tls)
+ {
+ if (tlsElement.firstChildElement().tagName() == "required")
+ remoteSecurity = QXmppConfiguration::TLSRequired;
+ else
+ remoteSecurity = QXmppConfiguration::TLSEnabled;
+ } else {
+ remoteSecurity = QXmppConfiguration::TLSDisabled;
+ }
+
+ // determine TLS mode to use
+ const QXmppConfiguration::StreamSecurityMode localSecurity = configuration().streamSecurityMode();
+ if (!d->socket.supportsSsl() &&
+ (localSecurity == QXmppConfiguration::TLSRequired ||
+ remoteSecurity == QXmppConfiguration::TLSRequired))
+ {
+ warning("Disconnecting as TLS is required, but SSL support is not available");
+ disconnect();
+ return;
+ }
+ if (localSecurity == QXmppConfiguration::TLSRequired &&
+ remoteSecurity == QXmppConfiguration::TLSDisabled)
+ {
+ warning("Disconnecting as TLS is required, but not supported by the server");
+ disconnect();
+ return;
+ }
+
+ if (d->socket.supportsSsl() &&
+ (remoteSecurity == QXmppConfiguration::TLSRequired ||
+ localSecurity != QXmppConfiguration::TLSDisabled))
+ {
+ // enable TLS as it is required by the server
+ // or supported by the client
+ sendToServer("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
+ return;
+ }
+ }
+
+ if((saslAvailable && nonSaslAvailable && !useSasl) ||
+ (!saslAvailable && nonSaslAvailable))
+ {
+ sendNonSASLAuthQuery(doc.documentElement().attribute("from"));
+ }
+ else if(saslAvailable)
+ {
+ // parse advertised SASL Authentication mechanisms
+ QList<QXmppConfiguration::SASLAuthMechanism> mechanisms;
+ QDomElement element = nodeRecv.firstChildElement("mechanisms");
+ QDomElement subElement = element.firstChildElement("mechanism");
+ debug("SASL Authentication mechanisms:");
+ while(!subElement.isNull())
+ {
+ debug(subElement.text());
+ if (subElement.text() == QLatin1String("PLAIN"))
+ mechanisms << QXmppConfiguration::SASLPlain;
+ else if (subElement.text() == QLatin1String("DIGEST-MD5"))
+ mechanisms << QXmppConfiguration::SASLDigestMD5;
+ else if (subElement.text() == QLatin1String("ANONYMOUS"))
+ mechanisms << QXmppConfiguration::SASLAnonymous;
+ subElement = subElement.nextSiblingElement("mechanism");
+ }
+
+ // determine SASL Authentication mechanism to use
+ QXmppConfiguration::SASLAuthMechanism mechanism = configuration().sASLAuthMechanism();
+ if (mechanisms.isEmpty())
+ {
+ warning("No supported SASL Authentication mechanism available");
+ disconnect();
+ return;
+ }
+ else if (!mechanisms.contains(mechanism))
+ {
+ info("Desired SASL Auth mechanism is not available, selecting first available one");
+ mechanism = mechanisms.first();
+ }
+
+ // send SASL Authentication request
+ switch(mechanism)
+ {
+ case QXmppConfiguration::SASLPlain:
+ {
+ QString userPass('\0' + configuration().user() +
+ '\0' + configuration().passwd());
+ QByteArray data = "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>";
+ data += userPass.toUtf8().toBase64();
+ data += "</auth>";
+ sendToServer(data);
+ }
+ break;
+ case QXmppConfiguration::SASLDigestMD5:
+ sendToServer("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>");
+ break;
+ case QXmppConfiguration::SASLAnonymous:
+ sendToServer("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='ANONYMOUS'/>");
+ break;
+ }
+ }
+
+ if(nodeRecv.firstChildElement("bind").
+ namespaceURI() == ns_bind)
+ {
+ sendBindIQ();
+ }
+
+ if(nodeRecv.firstChildElement("session").
+ namespaceURI() == ns_session)
+ {
+ m_sessionAvailable = true;
+ }
+ }
+ else if(ns == ns_stream && nodeRecv.tagName() == "error")
+ {
+ if (!nodeRecv.firstChildElement("conflict").isNull())
+ m_xmppStreamError = QXmppStanza::Error::Conflict;
+ else
+ m_xmppStreamError = QXmppStanza::Error::UndefinedCondition;
+ emit error(QXmppClient::XmppStreamError);
+ }
+ else if(ns == ns_tls)
+ {
+ if(nodeRecv.tagName() == "proceed")
+ {
+ debug("Starting encryption");
+ d->socket.startClientEncryption();
+ return;
+ }
+ }
+ else if(ns == ns_sasl)
+ {
+ if(nodeRecv.tagName() == "success")
+ {
+ debug("Authenticated");
+ sendStartStream();
+ }
+ else if(nodeRecv.tagName() == "challenge")
+ {
+ // TODO: Track which mechanism was used for when other SASL protocols which use challenges are supported
+ m_authStep++;
+ switch (m_authStep)
+ {
+ case 1 :
+ sendAuthDigestMD5ResponseStep1(nodeRecv.text());
+ break;
+ case 2 :
+ sendAuthDigestMD5ResponseStep2();
+ break;
+ default :
+ warning("Too many authentication steps");
+ disconnect();
+ break;
+ }
+ }
+ else if(nodeRecv.tagName() == "failure")
+ {
+ if (!nodeRecv.firstChildElement("not-authorized").isNull())
+ m_xmppStreamError = QXmppStanza::Error::NotAuthorized;
+ else
+ m_xmppStreamError = QXmppStanza::Error::UndefinedCondition;
+ emit error(QXmppClient::XmppStreamError);
+
+ warning("Authentication failure");
+ disconnect();
+ }
+ }
+ 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 == m_sessionId)
+ {
+ QXmppSession session;
+ session.parse(nodeRecv);
+
+ // get back add configuration whether to send
+ // roster and intial presence in beginning
+ // process SessionIq
+
+ // xmpp connection made
+ emit xmppConnected();
+ }
+ else if(QXmppBind::isBind(nodeRecv) && id == m_bindId)
+ {
+ QXmppBind 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());
+ }
+ }
+ if (m_sessionAvailable)
+ sendSessionIQ();
+ }
+ }
+ else if(QXmppRosterIq::isRosterIq(nodeRecv))
+ {
+ QXmppRosterIq rosterIq;
+ rosterIq.parse(nodeRecv);
+ emit rosterIqReceived(rosterIq);
+ }
+ // extensions
+
+ // XEP-0009: Jabber-RPC
+ else if(QXmppRpcInvokeIq::isRpcInvokeIq(nodeRecv))
+ {
+ QXmppRpcInvokeIq rpcIqPacket;
+ rpcIqPacket.parse(nodeRecv);
+ emit rpcCallInvoke(rpcIqPacket);
+ }
+ else if(QXmppRpcResponseIq::isRpcResponseIq(nodeRecv))
+ {
+ QXmppRpcResponseIq rpcResponseIq;
+ rpcResponseIq.parse(nodeRecv);
+ emit rpcCallResponse(rpcResponseIq);
+ }
+ else if(QXmppRpcErrorIq::isRpcErrorIq(nodeRecv))
+ {
+ QXmppRpcErrorIq rpcErrorIq;
+ rpcErrorIq.parse(nodeRecv);
+ emit rpcCallError(rpcErrorIq);
+ }
+
+ // XEP-0030: Service Discovery
+ else if(QXmppDiscoveryIq::isDiscoveryIq(nodeRecv))
+ {
+ QXmppDiscoveryIq discoIq;
+ discoIq.parse(nodeRecv);
+
+ if (discoIq.type() == QXmppIq::Get &&
+ discoIq.queryType() == QXmppDiscoveryIq::InfoQuery &&
+ (discoIq.queryNode().isEmpty() || discoIq.queryNode().startsWith(capabilitiesNode)))
+ {
+ // respond to info query
+ QXmppDiscoveryIq qxmppFeatures = capabilities();
+ qxmppFeatures.setId(discoIq.id());
+ qxmppFeatures.setTo(discoIq.from());
+ qxmppFeatures.setQueryNode(discoIq.queryNode());
+ sendPacket(qxmppFeatures);
+ } else {
+ emit discoveryIqReceived(discoIq);
+ }
+ }
+ // XEP-0045: Multi-User Chat
+ else if (QXmppMucAdminIq::isMucAdminIq(nodeRecv))
+ {
+ QXmppMucAdminIq mucIq;
+ mucIq.parse(nodeRecv);
+ emit mucAdminIqReceived(mucIq);
+ }
+ else if (QXmppMucOwnerIq::isMucOwnerIq(nodeRecv))
+ {
+ QXmppMucOwnerIq mucIq;
+ mucIq.parse(nodeRecv);
+ emit mucOwnerIqReceived(mucIq);
+ }
+ // XEP-0047 In-Band Bytestreams
+ else if(QXmppIbbCloseIq::isIbbCloseIq(nodeRecv))
+ {
+ QXmppIbbCloseIq ibbCloseIq;
+ ibbCloseIq.parse(nodeRecv);
+ emit ibbCloseIqReceived(ibbCloseIq);
+ }
+ else if(QXmppIbbDataIq::isIbbDataIq(nodeRecv))
+ {
+ QXmppIbbDataIq ibbDataIq;
+ ibbDataIq.parse(nodeRecv);
+ emit ibbDataIqReceived(ibbDataIq);
+ }
+ else if(QXmppIbbOpenIq::isIbbOpenIq(nodeRecv))
+ {
+ QXmppIbbOpenIq ibbOpenIq;
+ ibbOpenIq.parse(nodeRecv);
+ emit ibbOpenIqReceived(ibbOpenIq);
+ }
+ // XEP-0054: vcard-temp
+ else if(nodeRecv.firstChildElement("vCard").
+ namespaceURI() == ns_vcard)
+ {
+ QXmppVCard vcardIq;
+ vcardIq.parse(nodeRecv);
+ emit vCardIqReceived(vcardIq);
+ }
+ // XEP-0065: SOCKS5 Bytestreams
+ else if(QXmppByteStreamIq::isByteStreamIq(nodeRecv))
+ {
+ QXmppByteStreamIq byteStreamIq;
+ byteStreamIq.parse(nodeRecv);
+ emit byteStreamIqReceived(byteStreamIq);
+ }
+ // XEP-0078: Non-SASL Authentication
+ else if(id == m_nonSASLAuthId && type == "result")
+ {
+ // successful Non-SASL Authentication
+ debug("Authenticated (Non-SASL)");
+
+ // xmpp connection made
+ emit xmppConnected();
+ }
+ else if(nodeRecv.firstChildElement("query").
+ namespaceURI() == ns_auth)
+ {
+ 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
+ {
+ //TODO Login error
+ return;
+ }
+ sendNonSASLAuth(plainText);
+ }
+ }
+ // XEP-0092: Software Version
+ else if(QXmppVersionIq::isVersionIq(nodeRecv))
+ {
+ QXmppVersionIq versionIq;
+ versionIq.parse(nodeRecv);
+
+ if (versionIq.type() == QXmppIq::Get)
+ {
+ // respond to query
+ QXmppVersionIq responseIq;
+ responseIq.setType(QXmppIq::Result);
+ responseIq.setId(versionIq.id());
+ responseIq.setTo(versionIq.from());
+ responseIq.setName(qApp->applicationName());
+ responseIq.setVersion(qApp->applicationVersion());
+ sendPacket(responseIq);
+ } else {
+ emit iqReceived(versionIq);
+ }
+
+ }
+ // XEP-0095: Stream Initiation
+ else if(QXmppStreamInitiationIq::isStreamInitiationIq(nodeRecv))
+ {
+ QXmppStreamInitiationIq siIq;
+ siIq.parse(nodeRecv);
+ emit streamInitiationIqReceived(siIq);
+ }
+ // XEP-0136: Message Archiving
+ else if(QXmppArchiveChatIq::isArchiveChatIq(nodeRecv))
+ {
+ QXmppArchiveChatIq archiveIq;
+ archiveIq.parse(nodeRecv);
+ emit archiveChatIqReceived(archiveIq);
+ }
+ else if(QXmppArchiveListIq::isArchiveListIq(nodeRecv))
+ {
+ QXmppArchiveListIq archiveIq;
+ archiveIq.parse(nodeRecv);
+ emit archiveListIqReceived(archiveIq);
+ }
+ else if(QXmppArchivePrefIq::isArchivePrefIq(nodeRecv))
+ {
+ QXmppArchivePrefIq archiveIq;
+ archiveIq.parse(nodeRecv);
+ emit archivePrefIqReceived(archiveIq);
+ }
+ // XEP-0166: Jingle
+ else if(QXmppJingleIq::isJingleIq(nodeRecv))
+ {
+ QXmppJingleIq jingleIq;
+ jingleIq.parse(nodeRecv);
+ emit jingleIqReceived(jingleIq);
+ }
+ // 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());
+ iq.setFrom(req.to());
+ 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());
+ iq.setFrom(iqPacket.to());
+ 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);
+ }
+ }
+ nodeRecv = nodeRecv.nextSiblingElement();
+ }
+ }
+ else
+ {
+ //wait for complete packet
+ }
+}
+
+
+void QXmppStream::sendStartStream()
+{
+ QByteArray data = "<?xml version='1.0'?><stream:stream to='";
+ data.append(configuration().domain());
+ data.append("' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>");
+ sendToServer(data);
+}
+
+bool QXmppStream::sendToServer(const QByteArray& packet)
+{
+ emit logMessage(QXmppLogger::SentMessage, QString::fromUtf8(packet));
+ if (!isConnected())
+ return false;
+ return d->socket.write( packet ) == packet.size();
+}
+
+bool QXmppStream::hasStartStreamElement(const QByteArray& data)
+{
+ QString str(data);
+ QRegExp regex("^(<\\?xml.*\\?>)?\\s*<stream:stream.*>");
+ regex.setMinimal(true);
+ return str.contains(regex);
+}
+
+bool QXmppStream::hasEndStreamElement(const QByteArray& data)
+{
+ QString str(data);
+ QRegExp regex("</stream:stream>$");
+ regex.setMinimal(true);
+ return str.contains(regex);
+}
+
+void QXmppStream::sendNonSASLAuth(bool plainText)
+{
+ QXmppNonSASLAuthIq authQuery;
+ authQuery.setUsername(configuration().user());
+ authQuery.setPassword(configuration().passwd());
+ authQuery.setResource(configuration().resource());
+ authQuery.setStreamId(m_streamId);
+ authQuery.setUsePlainText(plainText);
+ m_nonSASLAuthId = authQuery.id();
+ sendPacket(authQuery);
+}
+
+// challenge is BASE64 encoded string
+void QXmppStream::sendAuthDigestMD5ResponseStep1(const QString& challenge)
+{
+ QByteArray ba = QByteArray::fromBase64(challenge.toUtf8());
+
+ QMap<QByteArray, QByteArray> map;
+
+ QByteArray key;
+ QByteArray value;
+ bool parsingValue = false;
+ int startindex = 0;
+ for(int i = 0; i < ba.length(); i++)
+ {
+ char next = ba.at(i);
+ switch (next) {
+ case '=':
+ if (!parsingValue)
+ {
+ // Trim the key, but do not trim the value as it is in delimiters
+ key = ba.mid(startindex, i - startindex).trimmed();
+ // Skip the equals and delimiter
+ startindex = i + 2;
+ }
+ break;
+ case '"':
+ // Ignore the opening delimiter
+ if (startindex != (i + 1))
+ {
+ value = ba.mid(startindex, i - startindex);
+ map[key] = value;
+ debug(key + ":" + value);
+ // Skip the comma
+ i += 2;
+ startindex = i;
+ parsingValue = false;
+ }
+ else {
+ parsingValue = true;
+ }
+
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (!map.contains("nonce"))
+ {
+ warning("sendAuthDigestMD5ResponseStep1: Invalid input");
+ disconnect();
+ return;
+ }
+
+ QByteArray user = configuration().user().toUtf8();
+ QByteArray passwd = configuration().passwd().toUtf8();
+ QByteArray domain = configuration().domain().toUtf8();
+ QByteArray realm;
+ if(map.contains("realm"))
+ realm = map["realm"];
+
+ QByteArray response;
+
+ QByteArray cnonce(32, 'm');
+ for(int n = 0; n < cnonce.size(); ++n)
+ cnonce[n] = (char)(256.0*qrand()/(RAND_MAX+1.0));
+
+ // The random data can the '=' char is not valid as it is a delimiter,
+ // so to be safe, base64 the nonce
+ cnonce = cnonce.toBase64();
+
+ QByteArray nc = "00000001";
+ QByteArray digest_uri = "xmpp/" + domain;
+
+ QByteArray a1 = user + ':' + realm + ':' + passwd;
+ QByteArray ha1 = QCryptographicHash::hash(a1, QCryptographicHash::Md5);
+ ha1 += ':' + map["nonce"] + ':' + cnonce;
+
+ if(map.contains("authzid"))
+ ha1 += ':' + map["authzid"];
+
+ QByteArray A1(ha1);
+ QByteArray A2 = "AUTHENTICATE:" + digest_uri;
+ QByteArray HA1 = QCryptographicHash::hash(A1, QCryptographicHash::Md5).toHex();
+ QByteArray HA2 = QCryptographicHash::hash(A2, QCryptographicHash::Md5).toHex();
+ QByteArray KD = HA1 + ':' + map["nonce"] + ':' + nc + ':' + cnonce + ':'
+ + "auth" + ':' + HA2;
+ QByteArray Z = QCryptographicHash::hash(KD, QCryptographicHash::Md5).toHex();
+
+ response += "username=\"" + user + "\",";
+
+ if(!realm.isEmpty())
+ response += "realm=\"" + realm + "\",";
+
+ response += "nonce=\"" + map["nonce"] + "\",";
+ response += "cnonce=\"" + cnonce + "\",";
+ response += "nc=" + nc + ",";
+ response += "qop=auth,";
+ response += "digest-uri=\"" + digest_uri + "\",";
+ response += "response=" + Z + ",";
+ if(map.contains("authzid"))
+ response += "authzid=\"" + map["authzid"] + "\",";
+ response += "charset=utf-8";
+
+ debug(response);
+ QByteArray packet = "<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"
+ + response.toBase64() + "</response>";
+ sendToServer(packet);
+}
+
+void QXmppStream::sendAuthDigestMD5ResponseStep2()
+{
+ sendToServer("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>");
+}
+
+void QXmppStream::sendBindIQ()
+{
+ QXmppBind bind(QXmppIq::Set);
+ bind.setResource(configuration().resource());
+ m_bindId = bind.id();
+ sendPacket(bind);
+}
+
+void QXmppStream::sendSessionIQ()
+{
+ QXmppSession session(QXmppIq::Set);
+ session.setTo(configuration().domain());
+ m_sessionId = session.id();
+ sendPacket(session);
+}
+
+void QXmppStream::acceptSubscriptionRequest(const QString& from, bool accept)
+{
+ QXmppPresence presence;
+ presence.setTo(from);
+ if(accept)
+ presence.setType(QXmppPresence::Subscribed);
+ else
+ presence.setType(QXmppPresence::Unsubscribed);
+
+ sendPacket(presence);
+}
+
+void QXmppStream::sendSubscriptionRequest(const QString& to)
+{
+ if(to.isEmpty())
+ return;
+
+ QXmppPresence presence;
+ presence.setTo(to);
+ presence.setType(QXmppPresence::Subscribe);
+ sendPacket(presence);
+}
+
+void QXmppStream::disconnect()
+{
+ m_authStep = 0;
+ sendEndStream();
+ d->socket.flush();
+ d->socket.disconnectFromHost();
+}
+
+bool QXmppStream::isConnected() const
+{
+ return d->socket.state() == QAbstractSocket::ConnectedState;
+}
+
+bool QXmppStream::sendPacket(const QXmppPacket& packet)
+{
+ // prepare packet
+ QByteArray data;
+ QXmlStreamWriter xmlStream(&data);
+ packet.toXml(&xmlStream);
+
+ // send packet
+ return sendToServer(data);
+}
+
+void QXmppStream::sendEndStream()
+{
+ sendToServer(streamRootElementEnd);
+}
+
+void QXmppStream::pingStart()
+{
+ const int interval = configuration().keepAliveInterval();
+ // start ping timer
+ if (interval > 0)
+ {
+ m_pingTimer->setInterval(interval * 1000);
+ m_pingTimer->start();
+ }
+}
+
+void QXmppStream::pingStop()
+{
+ // stop all timers
+ m_pingTimer->stop();
+ m_timeoutTimer->stop();
+}
+
+void QXmppStream::pingSend()
+{
+ // send ping packet
+ QXmppPingIq ping;
+ ping.setFrom(configuration().jid());
+ ping.setTo(configuration().domain());
+ sendPacket(ping);
+
+ // start timeout timer
+ const int timeout = configuration().keepAliveTimeout();
+ if (timeout > 0)
+ {
+ m_timeoutTimer->setInterval(timeout * 1000);
+ m_timeoutTimer->start();
+ }
+}
+
+void QXmppStream::pingTimeout()
+{
+ warning("Ping timeout");
+ disconnect();
+ emit error(QXmppClient::KeepAliveError);
+}
+
+QAbstractSocket::SocketError QXmppStream::socketError()
+{
+ return d->socketError;
+}
+
+QXmppStanza::Error::Condition QXmppStream::xmppStreamError()
+{
+ return m_xmppStreamError;
+}
+
+void QXmppStream::flushDataBuffer()
+{
+ m_dataBuffer.clear();
+}
+
+QXmppDiscoveryIq QXmppStream::capabilities() const
+{
+ QXmppDiscoveryIq iq;
+ iq.setType(QXmppIq::Result);
+ iq.setQueryType(QXmppDiscoveryIq::InfoQuery);
+
+ // features
+ QStringList features;
+ features
+ << ns_rpc // XEP-0009: Jabber-RPC
+ << ns_disco_info // XEP-0030: Service Discovery
+ << ns_ibb // XEP-0047: In-Band Bytestreams
+ << ns_vcard // XEP-0054: vcard-temp
+ << ns_bytestreams // XEP-0065: SOCKS5 Bytestreams
+ << ns_chat_states // XEP-0085: Chat State Notifications
+ << ns_version // XEP-0092: Software Version
+ << ns_stream_initiation // XEP-0095: Stream Initiation
+ << ns_stream_initiation_file_transfer // XEP-0096: SI File Transfer
+ << ns_capabilities // XEP-0115 : Entity Capabilities
+ << ns_jingle // XEP-0166 : Jingle
+ << ns_jingle_rtp // XEP-0167 : Jingle RTP Sessions
+ << ns_jingle_rtp_audio
+ << ns_jingle_ice_udp // XEP-0176 : Jingle ICE-UDP Transport Method
+ << ns_ping; // XEP-0199: XMPP Ping
+ iq.setFeatures(features);
+
+ // identities
+ QList<QXmppDiscoveryIq::Identity> identities;
+ QXmppDiscoveryIq::Identity identity;
+
+ identity.setCategory("automation");
+ identity.setType("rpc");
+ identities.append(identity);
+
+ identity.setCategory("client");
+ identity.setType("pc");
+ identity.setName(QString("%1 %2").arg(qApp->applicationName(), qApp->applicationVersion()));
+ identities.append(identity);
+
+ iq.setIdentities(identities);
+ return iq;
+}
+
+QXmppElementList QXmppStream::presenceExtensions() const
+{
+ QXmppElementList extensions;
+
+ QXmppElement caps;
+ caps.setTagName("c");
+ caps.setAttribute("xmlns", ns_capabilities);
+ caps.setAttribute("hash", "sha-1");
+ caps.setAttribute("node", capabilitiesNode);
+ caps.setAttribute("ver", capabilities().verificationString().toBase64());
+ extensions << caps;
+
+ return extensions;
+}
+