aboutsummaryrefslogtreecommitdiff
path: root/source/QXmppTransferManager.cpp
diff options
context:
space:
mode:
authorJeremy Lainé <jeremy.laine@m4x.org>2010-02-25 12:27:19 +0000
committerJeremy Lainé <jeremy.laine@m4x.org>2010-02-25 12:27:19 +0000
commit4833759c62915f433ba20910f1cf4ec17baea9c4 (patch)
treea8f794f27721a102ace4eacbbc4f13ecb177171a /source/QXmppTransferManager.cpp
parent3d9042871ead55f48023f17b0f6f5fbb1bfae259 (diff)
downloadqxmpp-4833759c62915f433ba20910f1cf4ec17baea9c4.tar.gz
add basic support for SOCKS5 bytestreams
Diffstat (limited to 'source/QXmppTransferManager.cpp')
-rw-r--r--source/QXmppTransferManager.cpp392
1 files changed, 357 insertions, 35 deletions
diff --git a/source/QXmppTransferManager.cpp b/source/QXmppTransferManager.cpp
index 511933b8..554820f9 100644
--- a/source/QXmppTransferManager.cpp
+++ b/source/QXmppTransferManager.cpp
@@ -21,25 +21,42 @@
*
*/
+#include <QCryptographicHash>
#include <QDomElement>
#include <QFile>
#include <QFileInfo>
+#include <QNetworkInterface>
+#include "QXmppByteStreamIq.h"
#include "QXmppClient.h"
#include "QXmppConstants.h"
#include "QXmppIbbIqs.h"
+#include "QXmppSocks.h"
#include "QXmppStreamInitiationIq.h"
#include "QXmppTransferManager.h"
#include "QXmppUtils.h"
+static QString streamHash(const QString &sid, const QString &initiatorJid, const QString &targetJid)
+{
+ QCryptographicHash hash(QCryptographicHash::Sha1);
+ QString str = sid + initiatorJid + targetJid;
+ hash.addData(str.toAscii());
+ return hash.result().toHex();
+}
+
QXmppTransferJob::QXmppTransferJob(const QString &jid, QXmppTransferManager *manager)
: QObject(manager),
+ m_blockSize(16384),
m_done(0),
m_error(NoError),
m_iodevice(0),
m_jid(jid),
+ m_method(NoMethod),
+ m_state(StartState),
m_fileSize(0),
- m_ibbSequence(0)
+ m_ibbSequence(0),
+ m_socksClient(0),
+ m_socksServer(0)
{
}
@@ -79,11 +96,33 @@ int QXmppTransferJob::fileSize() const
return m_fileSize;
}
+QXmppTransferJob::Method QXmppTransferJob::method() const
+{
+ return m_method;
+}
+
+QXmppTransferJob::State QXmppTransferJob::state() const
+{
+ return m_state;
+}
+
+void QXmppTransferJob::setState(QXmppTransferJob::State state)
+{
+ if (m_state != state)
+ {
+ m_state = state;
+ emit stateChanged(m_state);
+ }
+}
+
void QXmppTransferJob::terminate(QXmppTransferJob::Error cause)
{
// close IO device
m_iodevice->close();
+ // change state
+ setState(FinishedState);
+
// emit signal
m_error = cause;
if (cause == NoError)
@@ -93,8 +132,115 @@ void QXmppTransferJob::terminate(QXmppTransferJob::Error cause)
}
QXmppTransferManager::QXmppTransferManager(QXmppClient *client)
- : m_client(client), m_ibbBlockSize(4096)
+ : m_client(client),
+ m_ibbBlockSize(4096),
+ m_supportedMethods(QXmppTransferJob::AnyMethod)
+{
+}
+
+void QXmppTransferManager::byteStreamIqReceived(const QXmppByteStreamIq &iq)
+{
+ if (iq.getType() == QXmppIq::Result)
+ byteStreamResultReceived(iq);
+ else if (iq.getType() == QXmppIq::Set)
+ byteStreamSetReceived(iq);
+}
+
+void QXmppTransferManager::byteStreamResponseReceived(const QXmppIq &iq)
{
+ QXmppTransferJob *job = getJobByRequestId(iq.getFrom(), iq.getId());
+ if (!job ||
+ job->method() != QXmppTransferJob::SocksMethod ||
+ job->state() != QXmppTransferJob::StartState)
+ return;
+
+ if (iq.getType() == QXmppIq::Error)
+ {
+ // FIXME : close sockets?
+ job->terminate(QXmppTransferJob::ProtocolError);
+ }
+}
+
+/// Handle a bytestream result, i.e. after the remote party has connected to our stream host.
+void QXmppTransferManager::byteStreamResultReceived(const QXmppByteStreamIq &iq)
+{
+ QXmppTransferJob *job = getJobByRequestId(iq.getFrom(), iq.getId());
+ if (!job ||
+ job->method() != QXmppTransferJob::SocksMethod ||
+ job->state() != QXmppTransferJob::StartState)
+ return;
+
+ // start sending data
+ job->setState(QXmppTransferJob::TransferState);
+ connect(job->m_socksServer, SIGNAL(bytesWritten(qint64)), this, SLOT(socksServerDataSent()));
+ connect(job->m_socksServer, SIGNAL(disconnected()), this, SLOT(socksServerDisconnected()));
+ socksServerSendData(job);
+}
+
+/// Handle a bytestream set, i.e. an invitation from the remote party to connect to their stream host.
+void QXmppTransferManager::byteStreamSetReceived(const QXmppByteStreamIq &iq)
+{
+ QXmppIq response;
+ response.setId(iq.getId());
+ response.setTo(iq.getFrom());
+
+ QXmppTransferJob *job = getJobBySid(iq.getFrom(), iq.sid());
+ if (!job ||
+ job->method() != QXmppTransferJob::SocksMethod ||
+ job->state() != QXmppTransferJob::StartState)
+ {
+ // the stream is unknown
+ QXmppStanza::Error error(QXmppStanza::Error::Auth, QXmppStanza::Error::NotAcceptable);
+ error.setCode(406);
+ response.setType(QXmppIq::Error);
+ response.setError(error);
+ m_client->sendPacket(response);
+ return;
+ }
+
+ // try connecting to the offered stream hosts
+ foreach (const QXmppByteStreamIq::StreamHost &streamHost, iq.streamHosts())
+ {
+ qDebug() << "Connecting to streamhost" << streamHost.jid();
+ qDebug() << " host:" << streamHost.host().toString();
+ qDebug() << " port:" << streamHost.port();
+
+ QString hostName = streamHash(job->m_sid,
+ streamHost.jid(),
+ m_client->getConfiguration().getJid());
+
+ // try to connect to stream host
+ job->m_socksClient = new QXmppSocksClient(streamHost.host(), streamHost.port(), job);
+ job->m_socksClient->connectToHost(hostName, 0);
+ if (job->m_socksClient->waitForConnected())
+ {
+ job->setState(QXmppTransferJob::TransferState);
+ connect(job->m_socksClient, SIGNAL(readyRead()), this, SLOT(socksClientDataReceived()));
+ connect(job->m_socksClient, SIGNAL(disconnected()), this, SLOT(socksClientDisconnected()));
+
+ QXmppByteStreamIq ackIq;
+ ackIq.setId(iq.getId());
+ ackIq.setTo(iq.getFrom());
+ ackIq.setType(QXmppIq::Result);
+ ackIq.setSid(job->m_sid);
+ ackIq.setStreamHostUsed(streamHost.jid());
+ m_client->sendPacket(ackIq);
+ return;
+ } else {
+ qWarning() << "Failed to connect to" << streamHost.host().toString() << streamHost.port() << ":" << job->m_socksClient->errorString();
+ delete job->m_socksClient;
+ job->m_socksClient = 0;
+ }
+ }
+
+ // could not connect to any stream host
+ QXmppStanza::Error error(QXmppStanza::Error::Cancel, QXmppStanza::Error::ItemNotFound);
+ error.setCode(404);
+ response.setType(QXmppIq::Error);
+ response.setError(error);
+ m_client->sendPacket(response);
+
+ job->terminate(QXmppTransferJob::ProtocolError);
}
QXmppTransferJob* QXmppTransferManager::getJobByRequestId(const QString &jid, const QString &id)
@@ -113,6 +259,22 @@ QXmppTransferJob* QXmppTransferManager::getJobBySid(const QString &jid, const QS
return 0;
}
+QXmppTransferJob* QXmppTransferManager::getJobBySocksClient(QXmppSocksClient *socksClient)
+{
+ foreach (QXmppTransferJob *job, m_jobs)
+ if (job->m_socksClient == socksClient)
+ return job;
+ return 0;
+}
+
+QXmppTransferJob* QXmppTransferManager::getJobBySocksServer(QXmppSocksServer *socksServer)
+{
+ foreach (QXmppTransferJob *job, m_jobs)
+ if (job->m_socksServer == socksServer)
+ return job;
+ return 0;
+}
+
void QXmppTransferManager::ibbCloseIqReceived(const QXmppIbbCloseIq &iq)
{
QXmppIq response;
@@ -120,7 +282,7 @@ void QXmppTransferManager::ibbCloseIqReceived(const QXmppIbbCloseIq &iq)
response.setId(iq.getId());
QXmppTransferJob *job = getJobBySid(iq.getFrom(), iq.getSid());
- if (!job)
+ if (!job || job->method() != QXmppTransferJob::InBandMethod)
{
// the job is unknown, cancel it
QXmppStanza::Error error(QXmppStanza::Error::Cancel, QXmppStanza::Error::ItemNotFound);
@@ -148,7 +310,7 @@ void QXmppTransferManager::ibbDataIqReceived(const QXmppIbbDataIq &iq)
response.setId(iq.getId());
QXmppTransferJob *job = getJobBySid(iq.getFrom(), iq.getSid());
- if (!job)
+ if (!job || job->method() != QXmppTransferJob::InBandMethod)
{
// the job is unknown, cancel it
QXmppStanza::Error error(QXmppStanza::Error::Cancel, QXmppStanza::Error::ItemNotFound);
@@ -187,7 +349,7 @@ void QXmppTransferManager::ibbOpenIqReceived(const QXmppIbbOpenIq &iq)
response.setId(iq.getId());
QXmppTransferJob *job = getJobBySid(iq.getFrom(), iq.getSid());
- if (!job)
+ if (!job || job->method() != QXmppTransferJob::InBandMethod)
{
// the job is unknown, cancel it
QXmppStanza::Error error(QXmppStanza::Error::Cancel, QXmppStanza::Error::ItemNotFound);
@@ -205,6 +367,8 @@ void QXmppTransferManager::ibbOpenIqReceived(const QXmppIbbOpenIq &iq)
response.setError(error);
m_client->sendPacket(response);
return;
+ } else {
+ job->m_blockSize = iq.getBlockSize();
}
// accept transfer
@@ -212,10 +376,12 @@ void QXmppTransferManager::ibbOpenIqReceived(const QXmppIbbOpenIq &iq)
m_client->sendPacket(response);
}
-void QXmppTransferManager::iqReceived(const QXmppIq &iq)
+void QXmppTransferManager::ibbResponseReceived(const QXmppIq &iq)
{
QXmppTransferJob *job = getJobByRequestId(iq.getFrom(), iq.getId());
- if (!job)
+ if (!job ||
+ job->method() != QXmppTransferJob::InBandMethod ||
+ job->state() == QXmppTransferJob::FinishedState)
return;
// if the IO device is closed, do nothing
@@ -224,8 +390,8 @@ void QXmppTransferManager::iqReceived(const QXmppIq &iq)
if (iq.getType() == QXmppIq::Result)
{
- const QByteArray buffer = job->m_iodevice->read(m_ibbBlockSize);
-
+ const QByteArray buffer = job->m_iodevice->read(job->m_blockSize);
+ job->setState(QXmppTransferJob::TransferState);
if (buffer.size())
{
// send next data block
@@ -261,20 +427,34 @@ void QXmppTransferManager::iqReceived(const QXmppIq &iq)
job->m_requestId = closeIq.getId();
job->terminate(QXmppTransferJob::ProtocolError);
-
}
}
+void QXmppTransferManager::iqReceived(const QXmppIq &iq)
+{
+ QXmppTransferJob *job = getJobByRequestId(iq.getFrom(), iq.getId());
+ if (!job)
+ return;
+
+ if (job->method() == QXmppTransferJob::InBandMethod)
+ ibbResponseReceived(iq);
+ else if (job->method() == QXmppTransferJob::SocksMethod)
+ byteStreamResponseReceived(iq);
+}
+
QXmppTransferJob *QXmppTransferManager::sendFile(const QString &jid, const QString &fileName)
{
+
+ // create job
+ QXmppTransferJob *job = new QXmppTransferJob(jid, this);
+
// open file
- QFile *fileIo = new QFile(fileName, this);
+ QFile *fileIo = new QFile(fileName, job);
fileIo->open(QIODevice::ReadOnly);
QFileInfo info(*fileIo);
- // create job
- QXmppTransferJob *job = new QXmppTransferJob(jid, this);
job->m_iodevice = fileIo;
+ job->m_methods = m_supportedMethods;
job->m_sid = generateStanzaHash();
job->m_fileDate = info.lastModified();
job->m_fileName = info.fileName();
@@ -308,14 +488,30 @@ QXmppTransferJob *QXmppTransferManager::sendFile(const QString &jid, const QStri
field.setAttribute("type", "list-single");
x.appendChild(field);
- QXmppElement option;
- option.setTagName("option");
- field.appendChild(option);
+ // add supported stream methods
+ if (job->m_methods & QXmppTransferJob::InBandMethod)
+ {
+ QXmppElement option;
+ option.setTagName("option");
+ field.appendChild(option);
+
+ QXmppElement value;
+ value.setTagName("value");
+ value.setValue(ns_ibb);
+ option.appendChild(value);
+ }
+ if (job->m_methods & QXmppTransferJob::SocksMethod)
+ {
+ QXmppElement option;
+ option.setTagName("option");
+ field.appendChild(option);
+
+ QXmppElement value;
+ value.setTagName("value");
+ value.setValue(ns_bytestreams);
+ option.appendChild(value);
+ }
- QXmppElement value;
- value.setTagName("value");
- value.setValue(ns_ibb);
- option.appendChild(value);
items.append(feature);
QXmppStreamInitiationIq request;
@@ -330,6 +526,65 @@ QXmppTransferJob *QXmppTransferManager::sendFile(const QString &jid, const QStri
return job;
}
+void QXmppTransferManager::socksClientDataReceived()
+{
+ QXmppSocksClient *socks = qobject_cast<QXmppSocksClient*>(sender());
+ QXmppTransferJob *job = getJobBySocksClient(socks);
+ if (!job)
+ return;
+
+ QByteArray data = job->m_socksClient->readAll();
+ job->m_iodevice->write(data);
+ job->m_done += data.size();
+ job->progress(job->m_done, job->fileSize());
+}
+
+void QXmppTransferManager::socksClientDisconnected()
+{
+ QXmppSocksClient *socks = qobject_cast<QXmppSocksClient*>(sender());
+ QXmppTransferJob *job = getJobBySocksClient(socks);
+ if (!job)
+ return;
+
+ // terminate the transfer
+ if (job->fileSize() && job->m_done != job->fileSize())
+ job->terminate(QXmppTransferJob::FileCorruptError);
+ else
+ job->terminate(QXmppTransferJob::NoError);
+}
+
+void QXmppTransferManager::socksServerDataSent()
+{
+ QXmppSocksServer *socksServer = qobject_cast<QXmppSocksServer*>(sender());
+ QXmppTransferJob *job = getJobBySocksServer(socksServer);
+ if (!job ||
+ job->state() != QXmppTransferJob::TransferState)
+ return;
+
+ // send next data block
+ socksServerSendData(job);
+}
+
+void QXmppTransferManager::socksServerDisconnected()
+{
+ qWarning("Socks server disconnected");
+}
+
+void QXmppTransferManager::socksServerSendData(QXmppTransferJob *job)
+{
+ const QByteArray buffer = job->m_iodevice->read(job->m_blockSize);
+ if (buffer.size())
+ {
+ job->m_socksServer->write(buffer);
+
+ job->m_done += buffer.size();
+ job->progress(job->m_done, job->fileSize());
+ } else {
+ // FIXME : close socket
+ job->terminate(QXmppTransferJob::NoError);
+ }
+}
+
void QXmppTransferManager::streamInitiationIqReceived(const QXmppStreamInitiationIq &iq)
{
if (iq.getType() == QXmppIq::Result)
@@ -344,7 +599,6 @@ void QXmppTransferManager::streamInitiationResultReceived(const QXmppStreamIniti
if (!job)
return;
- int method = 0;
foreach (const QXmppElement &item, iq.getSiItems())
{
if (item.tagName() == "feature" && item.attribute("xmlns") == ns_feature_negotiation)
@@ -354,25 +608,79 @@ void QXmppTransferManager::streamInitiationResultReceived(const QXmppStreamIniti
{
if (field.attribute("var") == "stream-method")
{
- if (field.firstChildElement("value").value() == ns_ibb)
- method = QXmppTransferJob::InBandByteStream;
-/*
- else if (field.firstChildElement("value").value() == ns_bytestreams)
- method = QXmppTransferJob::SocksByteStream;
-*/
+ if ((field.firstChildElement("value").value() == ns_ibb) &&
+ (job->m_methods & QXmppTransferJob::InBandMethod))
+ job->m_method = QXmppTransferJob::InBandMethod;
+ else if ((field.firstChildElement("value").value() == ns_bytestreams) &&
+ (job->m_methods & QXmppTransferJob::SocksMethod))
+ job->m_method = QXmppTransferJob::SocksMethod;
}
field = field.nextSiblingElement("field");
}
}
}
- if (method == QXmppTransferJob::InBandByteStream)
+ if (job->method() == QXmppTransferJob::InBandMethod)
{
+ // lower block size for IBB
+ job->m_blockSize = m_ibbBlockSize;
+
QXmppIbbOpenIq openIq;
openIq.setTo(job->m_jid);
openIq.setSid(job->m_sid);
- openIq.setBlockSize(m_ibbBlockSize);
+ openIq.setBlockSize(job->m_blockSize);
m_client->sendPacket(openIq);
+ } else if (job->method() == QXmppTransferJob::SocksMethod) {
+ QXmppByteStreamIq streamIq;
+ streamIq.setType(QXmppIq::Set);
+ streamIq.setTo(job->m_jid);
+ streamIq.setSid(job->m_sid);
+
+ quint16 port = 40123;
+ const QString ownJid = m_client->getConfiguration().getJid();
+ QList<QXmppByteStreamIq::StreamHost> streamHosts;
+
+ // find interface to bind to
+ job->m_socksServer = new QXmppSocksServer(this);
+ job->m_socksServer->setHostName(streamHash(job->m_sid, ownJid, job->jid()));
+ job->m_socksServer->setHostPort(0);
+ foreach (const QNetworkInterface &interface, QNetworkInterface::allInterfaces())
+ {
+ if (!(interface.flags() & QNetworkInterface::IsRunning) ||
+ interface.flags() & QNetworkInterface::IsLoopBack)
+ continue;
+
+ foreach (const QNetworkAddressEntry &entry, interface.addressEntries())
+ {
+ if (entry.ip().protocol() != QAbstractSocket::IPv4Protocol ||
+ entry.netmask().isNull() ||
+ entry.netmask() == QHostAddress::Broadcast)
+ continue;
+
+ if (!job->m_socksServer->listen(entry.ip(), port))
+ {
+ qWarning() << "QXmppSocksServer could not listen on address" << entry.ip();
+ continue;
+ }
+
+ qDebug() << "QXmppSocksServer listening on" << job->m_socksServer->serverAddress() << job->m_socksServer->serverPort();
+
+ QXmppByteStreamIq::StreamHost streamHost;
+ streamHost.setHost(job->m_socksServer->serverAddress());
+ streamHost.setPort(job->m_socksServer->serverPort());
+ streamHost.setJid(ownJid);
+ streamHosts.append(streamHost);
+ streamIq.setStreamHosts(streamHosts);
+ job->m_requestId = streamIq.getId();
+ m_client->sendPacket(streamIq);
+ return;
+ }
+ }
+ qWarning("Could not determine public IP");
+ job->terminate(QXmppTransferJob::ProtocolError);
+ } else {
+ qWarning("We received an unsupported method");
+ job->terminate(QXmppTransferJob::ProtocolError);
}
}
@@ -398,7 +706,7 @@ void QXmppTransferManager::streamInitiationSetReceived(const QXmppStreamInitiati
// check the stream type
QXmppTransferJob *job = new QXmppTransferJob(iq.getFrom(), this);
- job->m_methods = 0;
+ job->m_methods = QXmppTransferJob::NoMethod;
job->m_sid = iq.getSiId();
job->m_mimeType = iq.getMimeType();
foreach (const QXmppElement &item, iq.getSiItems())
@@ -414,11 +722,9 @@ void QXmppTransferManager::streamInitiationSetReceived(const QXmppStreamInitiati
while (!option.isNull())
{
if (option.firstChildElement("value").value() == ns_ibb)
- job->m_methods = job->m_methods | QXmppTransferJob::InBandByteStream;
-/*
+ job->m_methods = job->m_methods | QXmppTransferJob::InBandMethod;
else if (option.firstChildElement("value").value() == ns_bytestreams)
- job->m_methods = job->m_methods | QXmppTransferJob::SocksByteStream;
-*/
+ job->m_methods = job->m_methods | QXmppTransferJob::SocksMethod;
option = option.nextSiblingElement("option");
}
}
@@ -434,6 +740,10 @@ void QXmppTransferManager::streamInitiationSetReceived(const QXmppStreamInitiati
}
}
+ if (job->m_methods & QXmppTransferJob::SocksMethod)
+ job->m_method = QXmppTransferJob::SocksMethod;
+ else if (job->m_methods & QXmppTransferJob::InBandMethod)
+ job->m_method = QXmppTransferJob::InBandMethod;
if (!job->m_methods)
{
// FIXME : we should add:
@@ -469,7 +779,10 @@ void QXmppTransferManager::streamInitiationSetReceived(const QXmppStreamInitiati
QXmppElement value;
value.setTagName("value");
- value.setValue(ns_ibb);
+ if (job->method() == QXmppTransferJob::InBandMethod)
+ value.setValue(ns_ibb);
+ else if (job->method() == QXmppTransferJob::SocksMethod)
+ value.setValue(ns_bytestreams);
QXmppElement field;
field.setTagName("field");
@@ -494,3 +807,12 @@ void QXmppTransferManager::streamInitiationSetReceived(const QXmppStreamInitiati
m_client->sendPacket(response);
}
+int QXmppTransferManager::supportedMethods() const
+{
+ return m_supportedMethods;
+}
+
+void QXmppTransferManager::setSupportedMethods(int methods)
+{
+ m_supportedMethods = methods;
+}