diff options
| author | Jeremy Lainé <jeremy.laine@m4x.org> | 2010-02-25 12:27:19 +0000 |
|---|---|---|
| committer | Jeremy Lainé <jeremy.laine@m4x.org> | 2010-02-25 12:27:19 +0000 |
| commit | 4833759c62915f433ba20910f1cf4ec17baea9c4 (patch) | |
| tree | a8f794f27721a102ace4eacbbc4f13ecb177171a /source/QXmppTransferManager.cpp | |
| parent | 3d9042871ead55f48023f17b0f6f5fbb1bfae259 (diff) | |
| download | qxmpp-4833759c62915f433ba20910f1cf4ec17baea9c4.tar.gz | |
add basic support for SOCKS5 bytestreams
Diffstat (limited to 'source/QXmppTransferManager.cpp')
| -rw-r--r-- | source/QXmppTransferManager.cpp | 392 |
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; +} |
