blob: 8a030601dce9c4e91bafda523bcb12c0934a5539 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtNetwork module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qsocks5socketengine_p.h"
#include "qtcpsocket.h"
#include "qudpsocket.h"
#include "qtcpserver.h"
#include "qdebug.h"
#include "qhash.h"
#include "qqueue.h"
#include "qelapsedtimer.h"
#include "qmutex.h"
#include "qthread.h"
#include "qcoreapplication.h"
#include "qurl.h"
#include "qauthenticator.h"
#include "private/qiodevice_p.h"
#include "private/qringbuffer_p.h"
#include <qendian.h>
#include <qnetworkinterface.h>
QT_BEGIN_NAMESPACE
static const int MaxWriteBufferSize = 128*1024;
//#define QSOCKS5SOCKETLAYER_DEBUG
#define MAX_DATA_DUMP 256
#define SOCKS5_BLOCKING_BIND_TIMEOUT 5000
#define Q_INIT_CHECK(returnValue) do { \
if (!d->data) { \
return returnValue; \
} } while (0)
#define S5_VERSION_5 0x05
#define S5_CONNECT 0x01
#define S5_BIND 0x02
#define S5_UDP_ASSOCIATE 0x03
#define S5_IP_V4 0x01
#define S5_DOMAINNAME 0x03
#define S5_IP_V6 0x04
#define S5_SUCCESS 0x00
#define S5_R_ERROR_SOCKS_FAILURE 0x01
#define S5_R_ERROR_CON_NOT_ALLOWED 0x02
#define S5_R_ERROR_NET_UNREACH 0x03
#define S5_R_ERROR_HOST_UNREACH 0x04
#define S5_R_ERROR_CONN_REFUSED 0x05
#define S5_R_ERROR_TTL 0x06
#define S5_R_ERROR_CMD_NOT_SUPPORTED 0x07
#define S5_R_ERROR_ADD_TYPE_NOT_SUPORTED 0x08
#define S5_AUTHMETHOD_NONE 0x00
#define S5_AUTHMETHOD_PASSWORD 0x02
#define S5_AUTHMETHOD_NOTACCEPTABLE 0xFF
#define S5_PASSWORDAUTH_VERSION 0x01
#ifdef QSOCKS5SOCKETLAYER_DEBUG
# define QSOCKS5_Q_DEBUG qDebug() << this
# define QSOCKS5_D_DEBUG qDebug() << q_ptr
# define QSOCKS5_DEBUG qDebug() << "[QSocks5]"
static QString s5StateToString(QSocks5SocketEnginePrivate::Socks5State s)
{
switch (s) {
case QSocks5SocketEnginePrivate::Uninitialized: return QLatin1String("Uninitialized");
case QSocks5SocketEnginePrivate::ConnectError: return QLatin1String("ConnectError");
case QSocks5SocketEnginePrivate::AuthenticationMethodsSent: return QLatin1String("AuthenticationMethodsSent");
case QSocks5SocketEnginePrivate::Authenticating: return QLatin1String("Authenticating");
case QSocks5SocketEnginePrivate::AuthenticatingError: return QLatin1String("AuthenticatingError");
case QSocks5SocketEnginePrivate::RequestMethodSent: return QLatin1String("RequestMethodSent");
case QSocks5SocketEnginePrivate::RequestError: return QLatin1String("RequestError");
case QSocks5SocketEnginePrivate::Connected: return QLatin1String("Connected");
case QSocks5SocketEnginePrivate::UdpAssociateSuccess: return QLatin1String("UdpAssociateSuccess");
case QSocks5SocketEnginePrivate::BindSuccess: return QLatin1String("BindSuccess");
case QSocks5SocketEnginePrivate::ControlSocketError: return QLatin1String("ControlSocketError");
case QSocks5SocketEnginePrivate::SocksError: return QLatin1String("SocksError");
case QSocks5SocketEnginePrivate::HostNameLookupError: return QLatin1String("HostNameLookupError");
default: break;
}
return QLatin1String("unknown state");
}
static QString dump(const QByteArray &buf)
{
QString data;
for (int i = 0; i < qMin<int>(MAX_DATA_DUMP, buf.size()); ++i) {
if (i) data += QLatin1Char(' ');
uint val = (unsigned char)buf.at(i);
// data += QString("0x%1").arg(val, 3, 16, QLatin1Char('0'));
data += QString::number(val);
}
if (buf.size() > MAX_DATA_DUMP)
data += QLatin1String(" ...");
return QString::fromLatin1("size: %1 data: { %2 }").arg(buf.size()).arg(data);
}
#else
# define QSOCKS5_DEBUG if (0) qDebug()
# define QSOCKS5_Q_DEBUG if (0) qDebug()
# define QSOCKS5_D_DEBUG if (0) qDebug()
static inline QString s5StateToString(QSocks5SocketEnginePrivate::Socks5State) { return QString(); }
static inline QString dump(const QByteArray &) { return QString(); }
#endif
/*
inserts the host address in buf at pos and updates pos.
if the func fails the data in buf and the vallue of pos is undefined
*/
static bool qt_socks5_set_host_address_and_port(const QHostAddress &address, quint16 port, QByteArray *pBuf)
{
QSOCKS5_DEBUG << "setting [" << address << ':' << port << ']';
union {
quint16 port;
quint32 ipv4;
QIPv6Address ipv6;
char ptr;
} data;
// add address
if (address.protocol() == QAbstractSocket::IPv4Protocol) {
data.ipv4 = qToBigEndian<quint32>(address.toIPv4Address());
pBuf->append(S5_IP_V4);
pBuf->append(QByteArray::fromRawData(&data.ptr, sizeof data.ipv4));
} else if (address.protocol() == QAbstractSocket::IPv6Protocol) {
data.ipv6 = address.toIPv6Address();
pBuf->append(S5_IP_V6);
pBuf->append(QByteArray::fromRawData(&data.ptr, sizeof data.ipv6));
} else {
return false;
}
// add port
data.port = qToBigEndian<quint16>(port);
pBuf->append(QByteArray::fromRawData(&data.ptr, sizeof data.port));
return true;
}
/*
like above, but for a hostname
*/
static bool qt_socks5_set_host_name_and_port(const QString &hostname, quint16 port, QByteArray *pBuf)
{
QSOCKS5_DEBUG << "setting [" << hostname << ':' << port << ']';
QByteArray encodedHostName = QUrl::toAce(hostname);
QByteArray &buf = *pBuf;
if (encodedHostName.length() > 255)
return false;
buf.append(S5_DOMAINNAME);
buf.append(uchar(encodedHostName.length()));
buf.append(encodedHostName);
// add port
union {
quint16 port;
char ptr;
} data;
data.port = qToBigEndian<quint16>(port);
buf.append(QByteArray::fromRawData(&data.ptr, sizeof data.port));
return true;
}
/*
retrives the host address in buf at pos and updates pos.
return 1 if OK, 0 if need more data, -1 if error
if the func fails the value of the address and the pos is undefined
*/
static int qt_socks5_get_host_address_and_port(const QByteArray &buf, QHostAddress *pAddress, quint16 *pPort, int *pPos)
{
int ret = -1;
int pos = *pPos;
const unsigned char *pBuf = reinterpret_cast<const unsigned char*>(buf.constData());
QHostAddress address;
quint16 port = 0;
if (buf.size() - pos < 1) {
QSOCKS5_DEBUG << "need more data address/port";
return 0;
}
if (pBuf[pos] == S5_IP_V4) {
pos++;
if (buf.size() - pos < 4) {
QSOCKS5_DEBUG << "need more data for ip4 address";
return 0;
}
address.setAddress(qFromBigEndian<quint32>(&pBuf[pos]));
pos += 4;
ret = 1;
} else if (pBuf[pos] == S5_IP_V6) {
pos++;
if (buf.size() - pos < 16) {
QSOCKS5_DEBUG << "need more data for ip6 address";
return 0;
}
QIPv6Address add;
for (int i = 0; i < 16; ++i)
add[i] = buf[pos++];
address.setAddress(add);
ret = 1;
} else if (pBuf[pos] == S5_DOMAINNAME){
// just skip it
pos++;
qDebug() << "skipping hostname of len" << uint(pBuf[pos]);
pos += uchar(pBuf[pos]);
} else {
QSOCKS5_DEBUG << "invalid address type" << (int)pBuf[pos];
ret = -1;
}
if (ret == 1) {
if (buf.size() - pos < 2) {
QSOCKS5_DEBUG << "need more data for port";
return 0;
}
port = qFromBigEndian<quint16>(&pBuf[pos]);
pos += 2;
}
if (ret == 1) {
QSOCKS5_DEBUG << "got [" << address << ':' << port << ']';
*pAddress = address;
*pPort = port;
*pPos = pos;
}
return ret;
}
struct QSocks5Data
{
QTcpSocket *controlSocket;
QSocks5Authenticator *authenticator;
};
struct QSocks5ConnectData : public QSocks5Data
{
QRingBuffer readBuffer;
};
struct QSocks5BindData : public QSocks5Data
{
QHostAddress localAddress;
quint16 localPort;
QHostAddress peerAddress;
quint16 peerPort;
QElapsedTimer timeStamp;
};
struct QSocks5RevivedDatagram
{
QByteArray data;
QHostAddress address;
quint16 port;
};
#ifndef QT_NO_UDPSOCKET
struct QSocks5UdpAssociateData : public QSocks5Data
{
QUdpSocket *udpSocket;
QHostAddress associateAddress;
quint16 associatePort;
QQueue<QSocks5RevivedDatagram> pendingDatagrams;
};
#endif
// needs to be thread safe
class QSocks5BindStore : public QObject
{
public:
QSocks5BindStore();
~QSocks5BindStore();
void add(qintptr socketDescriptor, QSocks5BindData *bindData);
bool contains(qintptr socketDescriptor);
QSocks5BindData *retrieve(qintptr socketDescriptor);
protected:
void timerEvent(QTimerEvent * event) override;
QRecursiveMutex mutex;
int sweepTimerId = -1;
//socket descriptor, data, timestamp
QHash<int, QSocks5BindData *> store;
};
Q_GLOBAL_STATIC(QSocks5BindStore, socks5BindStore)
QSocks5BindStore::QSocks5BindStore()
{
QCoreApplication *app = QCoreApplication::instance();
if (app && app->thread() != thread())
moveToThread(app->thread());
}
QSocks5BindStore::~QSocks5BindStore()
{
}
void QSocks5BindStore::add(qintptr socketDescriptor, QSocks5BindData *bindData)
{
QMutexLocker lock(&mutex);
if (store.contains(socketDescriptor)) {
// qDebug("delete it");
}
bindData->timeStamp.start();
store.insert(socketDescriptor, bindData);
// start sweep timer if not started
if (sweepTimerId == -1)
sweepTimerId = startTimer(60000);
}
bool QSocks5BindStore::contains(qintptr socketDescriptor)
{
QMutexLocker lock(&mutex);
return store.contains(socketDescriptor);
}
QSocks5BindData *QSocks5BindStore::retrieve(qintptr socketDescriptor)
{
QMutexLocker lock(&mutex);
const auto it = store.constFind(socketDescriptor);
if (it == store.cend())
return 0;
QSocks5BindData *bindData = it.value();
store.erase(it);
if (bindData) {
if (bindData->controlSocket->thread() != QThread::currentThread()) {
qWarning("Cannot access socks5 bind data from different thread");
return 0;
}
} else {
QSOCKS5_DEBUG << "__ERROR__ binddata == 0";
}
// stop the sweep timer if not needed
if (store.isEmpty()) {
killTimer(sweepTimerId);
sweepTimerId = -1;
}
return bindData;
}
void QSocks5BindStore::timerEvent(QTimerEvent * event)
{
QMutexLocker lock(&mutex);
if (event->timerId() == sweepTimerId) {
QSOCKS5_DEBUG << "QSocks5BindStore performing sweep";
for (auto it = store.begin(), end = store.end(); it != end;) {
if (it.value()->timeStamp.hasExpired(350000)) {
QSOCKS5_DEBUG << "QSocks5BindStore removing JJJJ";
it = store.erase(it);
} else {
++it;
}
}
}
}
QSocks5Authenticator::QSocks5Authenticator()
{
}
QSocks5Authenticator::~QSocks5Authenticator()
{
}
char QSocks5Authenticator::methodId()
{
return 0x00;
}
bool QSocks5Authenticator::beginAuthenticate(QTcpSocket *socket, bool *completed)
{
Q_UNUSED(socket);
*completed = true;
return true;
}
bool QSocks5Authenticator::continueAuthenticate(QTcpSocket *socket, bool *completed)
{
Q_UNUSED(socket);
*completed = true;
return true;
}
bool QSocks5Authenticator::seal(const QByteArray &buf, QByteArray *sealedBuf)
{
*sealedBuf = buf;
return true;
}
bool QSocks5Authenticator::unSeal(const QByteArray &sealedBuf, QByteArray *buf)
{
*buf = sealedBuf;
return true;
}
bool QSocks5Authenticator::unSeal(QTcpSocket *sealedSocket, QByteArray *buf)
{
return unSeal(sealedSocket->readAll(), buf);
}
QSocks5PasswordAuthenticator::QSocks5PasswordAuthenticator(const QString &userName, const QString &password)
{
this->userName = userName;
this->password = password;
}
char QSocks5PasswordAuthenticator::methodId()
{
return 0x02;
}
bool QSocks5PasswordAuthenticator::beginAuthenticate(QTcpSocket *socket, bool *completed)
{
*completed = false;
QByteArray uname = userName.toLatin1();
QByteArray passwd = password.toLatin1();
QByteArray dataBuf(3 + uname.size() + passwd.size(), 0);
char *buf = dataBuf.data();
int pos = 0;
buf[pos++] = S5_PASSWORDAUTH_VERSION;
buf[pos++] = uname.size();
memcpy(&buf[pos], uname.data(), uname.size());
pos += uname.size();
buf[pos++] = passwd.size();
memcpy(&buf[pos], passwd.data(), passwd.size());
return socket->write(dataBuf) == dataBuf.size();
}
bool QSocks5PasswordAuthenticator::continueAuthenticate(QTcpSocket *socket, bool *completed)
{
*completed = false;
if (socket->bytesAvailable() < 2)
return true;
QByteArray buf = socket->read(2);
if (buf.at(0) == S5_PASSWORDAUTH_VERSION && buf.at(1) == 0x00) {
*completed = true;
return true;
}
// must disconnect
socket->close();
return false;
}
QString QSocks5PasswordAuthenticator::errorString()
{
return QLatin1String("Socks5 user name or password incorrect");
}
QSocks5SocketEnginePrivate::QSocks5SocketEnginePrivate()
: socks5State(Uninitialized)
, readNotificationEnabled(false)
, writeNotificationEnabled(false)
, exceptNotificationEnabled(false)
, socketDescriptor(-1)
, data(0)
, connectData(0)
#ifndef QT_NO_UDPSOCKET
, udpData(0)
#endif
, bindData(0)
, readNotificationActivated(false)
, writeNotificationActivated(false)
, readNotificationPending(false)
, writeNotificationPending(false)
, connectionNotificationPending(false)
{
mode = NoMode;
}
QSocks5SocketEnginePrivate::~QSocks5SocketEnginePrivate()
{
}
void QSocks5SocketEnginePrivate::initialize(Socks5Mode socks5Mode)
{
Q_Q(QSocks5SocketEngine);
mode = socks5Mode;
if (mode == ConnectMode) {
connectData = new QSocks5ConnectData;
data = connectData;
#ifndef QT_NO_UDPSOCKET
} else if (mode == UdpAssociateMode) {
udpData = new QSocks5UdpAssociateData;
data = udpData;
udpData->udpSocket = new QUdpSocket(q);
#ifndef QT_NO_BEARERMANAGEMENT
udpData->udpSocket->setProperty("_q_networksession", q->property("_q_networksession"));
#endif
udpData->udpSocket->setProxy(QNetworkProxy::NoProxy);
QObject::connect(udpData->udpSocket, SIGNAL(readyRead()),
q, SLOT(_q_udpSocketReadNotification()),
Qt::DirectConnection);
#endif // QT_NO_UDPSOCKET
} else if (mode == BindMode) {
bindData = new QSocks5BindData;
data = bindData;
}
data->controlSocket = new QTcpSocket(q);
#ifndef QT_NO_BEARERMANAGEMENT
data->controlSocket->setProperty("_q_networksession", q->property("_q_networksession"));
#endif
data->controlSocket->setProxy(QNetworkProxy::NoProxy);
QObject::connect(data->controlSocket, SIGNAL(connected()), q, SLOT(_q_controlSocketConnected()),
Qt::DirectConnection);
QObject::connect(data->controlSocket, SIGNAL(readyRead()), q, SLOT(_q_controlSocketReadNotification()),
Qt::DirectConnection);
QObject::connect(data->controlSocket, SIGNAL(bytesWritten(qint64)), q, SLOT(_q_controlSocketBytesWritten()),
Qt::DirectConnection);
QObject::connect(data->controlSocket, SIGNAL(error(QAbstractSocket::SocketError)),
q, SLOT(_q_controlSocketError(QAbstractSocket::SocketError)),
Qt::DirectConnection);
QObject::connect(data->controlSocket, SIGNAL(disconnected()), q, SLOT(_q_controlSocketDisconnected()),
Qt::DirectConnection);
QObject::connect(data->controlSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)),
q, SLOT(_q_controlSocketStateChanged(QAbstractSocket::SocketState)),
Qt::DirectConnection);
if (!proxyInfo.user().isEmpty() || !proxyInfo.password().isEmpty()) {
QSOCKS5_D_DEBUG << "using username/password authentication; user =" << proxyInfo.user();
data->authenticator = new QSocks5PasswordAuthenticator(proxyInfo.user(), proxyInfo.password());
} else {
QSOCKS5_D_DEBUG << "not using authentication";
data->authenticator = new QSocks5Authenticator();
}
}
void QSocks5SocketEnginePrivate::setErrorState(Socks5State state, const QString &extraMessage)
{
Q_Q(QSocks5SocketEngine);
switch (state) {
case Uninitialized:
case Authenticating:
case AuthenticationMethodsSent:
case RequestMethodSent:
case Connected:
case UdpAssociateSuccess:
case BindSuccess:
// these aren't error states
return;
case ConnectError:
case ControlSocketError: {
QAbstractSocket::SocketError controlSocketError = data->controlSocket->error();
if (socks5State != Connected) {
switch (controlSocketError) {
case QAbstractSocket::ConnectionRefusedError:
q->setError(QAbstractSocket::ProxyConnectionRefusedError,
QSocks5SocketEngine::tr("Connection to proxy refused"));
break;
case QAbstractSocket::RemoteHostClosedError:
q->setError(QAbstractSocket::ProxyConnectionClosedError,
QSocks5SocketEngine::tr("Connection to proxy closed prematurely"));
break;
case QAbstractSocket::HostNotFoundError:
q->setError(QAbstractSocket::ProxyNotFoundError,
QSocks5SocketEngine::tr("Proxy host not found"));
break;
case QAbstractSocket::SocketTimeoutError:
if (state == ConnectError) {
q->setError(QAbstractSocket::ProxyConnectionTimeoutError,
QSocks5SocketEngine::tr("Connection to proxy timed out"));
break;
}
Q_FALLTHROUGH();
default:
q->setError(controlSocketError, data->controlSocket->errorString());
break;
}
} else {
q->setError(controlSocketError, data->controlSocket->errorString());
}
break;
}
case AuthenticatingError:
q->setError(QAbstractSocket::ProxyAuthenticationRequiredError,
extraMessage.isEmpty() ?
QSocks5SocketEngine::tr("Proxy authentication failed") :
QSocks5SocketEngine::tr("Proxy authentication failed: %1").arg(extraMessage));
break;
case RequestError:
// error code set by caller (overload)
break;
case SocksError:
q->setError(QAbstractSocket::ProxyProtocolError,
QSocks5SocketEngine::tr("SOCKS version 5 protocol error"));
break;
case HostNameLookupError:
q->setError(QAbstractSocket::HostNotFoundError,
QAbstractSocket::tr("Host not found"));
break;
}
q->setState(QAbstractSocket::UnconnectedState);
socks5State = state;
}
void QSocks5SocketEnginePrivate::setErrorState(Socks5State state, Socks5Error socks5error)
{
Q_Q(QSocks5SocketEngine);
switch (socks5error) {
case SocksFailure:
q->setError(QAbstractSocket::NetworkError,
QSocks5SocketEngine::tr("General SOCKSv5 server failure"));
break;
case ConnectionNotAllowed:
q->setError(QAbstractSocket::SocketAccessError,
QSocks5SocketEngine::tr("Connection not allowed by SOCKSv5 server"));
break;
case NetworkUnreachable:
q->setError(QAbstractSocket::NetworkError,
QAbstractSocket::tr("Network unreachable"));
break;
case HostUnreachable:
q->setError(QAbstractSocket::HostNotFoundError,
QAbstractSocket::tr("Host not found"));
break;
case ConnectionRefused:
q->setError(QAbstractSocket::ConnectionRefusedError,
QAbstractSocket::tr("Connection refused"));
break;
case TTLExpired:
q->setError(QAbstractSocket::NetworkError,
QSocks5SocketEngine::tr("TTL expired"));
break;
case CommandNotSupported:
q->setError(QAbstractSocket::UnsupportedSocketOperationError,
QSocks5SocketEngine::tr("SOCKSv5 command not supported"));
break;
case AddressTypeNotSupported:
q->setError(QAbstractSocket::UnsupportedSocketOperationError,
QSocks5SocketEngine::tr("Address type not supported"));
break;
default:
q->setError(QAbstractSocket::UnknownSocketError,
QSocks5SocketEngine::tr("Unknown SOCKSv5 proxy error code 0x%1").arg(int(socks5error), 16));
break;
}
setErrorState(state, QString());
}
void QSocks5SocketEnginePrivate::reauthenticate()
{
Q_Q(QSocks5SocketEngine);
// we require authentication
QAuthenticator auth;
q->proxyAuthenticationRequired(proxyInfo, &auth);
if (!auth.user().isEmpty() || !auth.password().isEmpty()) {
// we have new credentials, let's try again
QSOCKS5_DEBUG << "authentication failure: retrying connection";
socks5State = QSocks5SocketEnginePrivate::Uninitialized;
delete data->authenticator;
proxyInfo.setUser(auth.user());
proxyInfo.setPassword(auth.password());
data->authenticator = new QSocks5PasswordAuthenticator(proxyInfo.user(), proxyInfo.password());
{
const QSignalBlocker blocker(data->controlSocket);
data->controlSocket->abort();
}
data->controlSocket->connectToHost(proxyInfo.hostName(), proxyInfo.port());
} else {
// authentication failure
setErrorState(AuthenticatingError);
data->controlSocket->close();
emitConnectionNotification();
}
}
void QSocks5SocketEnginePrivate::parseAuthenticationMethodReply()
{
// not enough data to begin
if (data->controlSocket->bytesAvailable() < 2)
return;
QByteArray buf = data->controlSocket->read(2);
if (buf.at(0) != S5_VERSION_5) {
QSOCKS5_D_DEBUG << "Socks5 version incorrect";
setErrorState(SocksError);
data->controlSocket->close();
emitConnectionNotification();
return;
}
bool authComplete = false;
if (uchar(buf.at(1)) == S5_AUTHMETHOD_NONE) {
authComplete = true;
} else if (uchar(buf.at(1)) == S5_AUTHMETHOD_NOTACCEPTABLE) {
reauthenticate();
return;
} else if (buf.at(1) != data->authenticator->methodId()
|| !data->authenticator->beginAuthenticate(data->controlSocket, &authComplete)) {
setErrorState(AuthenticatingError, QLatin1String("Socks5 host did not support authentication method."));
socketError = QAbstractSocket::SocketAccessError; // change the socket error
emitConnectionNotification();
return;
}
if (authComplete)
sendRequestMethod();
else
socks5State = Authenticating;
}
void QSocks5SocketEnginePrivate::parseAuthenticatingReply()
{
bool authComplete = false;
if (!data->authenticator->continueAuthenticate(data->controlSocket, &authComplete)) {
reauthenticate();
return;
}
if (authComplete)
sendRequestMethod();
}
void QSocks5SocketEnginePrivate::sendRequestMethod()
{
QHostAddress address;
quint16 port = 0;
char command = 0;
if (mode == ConnectMode) {
command = S5_CONNECT;
address = peerAddress;
port = peerPort;
} else if (mode == BindMode) {
command = S5_BIND;
address = localAddress;
port = localPort;
} else {
#ifndef QT_NO_UDPSOCKET
command = S5_UDP_ASSOCIATE;
address = localAddress; //data->controlSocket->localAddress();
port = localPort;
#endif
}
QByteArray buf;
buf.reserve(270); // big enough for domain name;
buf.append(char(S5_VERSION_5));
buf.append(command);
buf.append('\0');
if (peerName.isEmpty() && !qt_socks5_set_host_address_and_port(address, port, &buf)) {
QSOCKS5_DEBUG << "error setting address" << address << " : " << port;
//### set error code ....
return;
} else if (!peerName.isEmpty() && !qt_socks5_set_host_name_and_port(peerName, port, &buf)) {
QSOCKS5_DEBUG << "error setting peer name" << peerName << " : " << port;
//### set error code ....
return;
}
QSOCKS5_DEBUG << "sending" << dump(buf);
QByteArray sealedBuf;
if (!data->authenticator->seal(buf, &sealedBuf)) {
// ### Handle this error.
}
data->controlSocket->write(sealedBuf);
data->controlSocket->flush();
socks5State = RequestMethodSent;
}
void QSocks5SocketEnginePrivate::parseRequestMethodReply()
{
Q_Q(QSocks5SocketEngine);
QSOCKS5_DEBUG << "parseRequestMethodReply()";
QByteArray inBuf;
if (!data->authenticator->unSeal(data->controlSocket, &inBuf)) {
// ### check error and not just not enough data
QSOCKS5_DEBUG << "unSeal failed, needs more data";
return;
}
inBuf.prepend(receivedHeaderFragment);
receivedHeaderFragment.clear();
QSOCKS5_DEBUG << dump(inBuf);
if (inBuf.size() < 3) {
QSOCKS5_DEBUG << "need more data for request reply header .. put this data somewhere";
receivedHeaderFragment = inBuf;
return;
}
QHostAddress address;
quint16 port = 0;
if (inBuf.at(0) != S5_VERSION_5 || inBuf.at(2) != 0x00) {
QSOCKS5_DEBUG << "socks protocol error";
setErrorState(SocksError);
} else if (inBuf.at(1) != S5_SUCCESS) {
Socks5Error socks5Error = Socks5Error(inBuf.at(1));
QSOCKS5_DEBUG << "Request error :" << socks5Error;
if ((socks5Error == SocksFailure || socks5Error == ConnectionNotAllowed)
&& !peerName.isEmpty()) {
// Dante seems to use this error code to indicate hostname resolution failure
setErrorState(HostNameLookupError);
} else {
setErrorState(RequestError, socks5Error);
}
} else {
// connection success, retrieve the remote addresses
int pos = 3;
int err = qt_socks5_get_host_address_and_port(inBuf, &address, &port, &pos);
if (err == -1) {
QSOCKS5_DEBUG << "error getting address";
setErrorState(SocksError);
} else if (err == 0) {
//need more data
receivedHeaderFragment = inBuf;
return;
} else {
inBuf.remove(0, pos);
for (int i = inBuf.size() - 1; i >= 0 ; --i)
data->controlSocket->ungetChar(inBuf.at(i));
}
}
if (socks5State == RequestMethodSent) {
// no error
localAddress = address;
localPort = port;
if (mode == ConnectMode) {
inboundStreamCount = outboundStreamCount = 1;
socks5State = Connected;
// notify the upper layer that we're done
q->setState(QAbstractSocket::ConnectedState);
emitConnectionNotification();
} else if (mode == BindMode) {
socks5State = BindSuccess;
q->setState(QAbstractSocket::ListeningState);
} else {
socks5State = UdpAssociateSuccess;
}
} else if (socks5State == BindSuccess) {
// no error and we got a connection
bindData->peerAddress = address;
bindData->peerPort = port;
emitReadNotification();
} else {
// got an error
data->controlSocket->close();
emitConnectionNotification();
}
}
void QSocks5SocketEnginePrivate::_q_emitPendingReadNotification()
{
Q_Q(QSocks5SocketEngine);
readNotificationPending = false;
if (readNotificationEnabled) {
QSOCKS5_D_DEBUG << "emitting readNotification";
QPointer<QSocks5SocketEngine> qq = q;
q->readNotification();
if (!qq)
return;
// check if there needs to be a new zero read notification
if (data && data->controlSocket->state() == QAbstractSocket::UnconnectedState
&& data->controlSocket->error() == QAbstractSocket::RemoteHostClosedError) {
connectData->readBuffer.clear();
emitReadNotification();
}
}
}
void QSocks5SocketEnginePrivate::emitReadNotification()
{
Q_Q(QSocks5SocketEngine);
readNotificationActivated = true;
if (readNotificationEnabled && !readNotificationPending) {
QSOCKS5_D_DEBUG << "queueing readNotification";
readNotificationPending = true;
QMetaObject::invokeMethod(q, "_q_emitPendingReadNotification", Qt::QueuedConnection);
}
}
void QSocks5SocketEnginePrivate::_q_emitPendingWriteNotification()
{
writeNotificationPending = false;
Q_Q(QSocks5SocketEngine);
if (writeNotificationEnabled) {
QSOCKS5_D_DEBUG << "emitting writeNotification";
q->writeNotification();
}
}
void QSocks5SocketEnginePrivate::emitWriteNotification()
{
Q_Q(QSocks5SocketEngine);
writeNotificationActivated = true;
if (writeNotificationEnabled && !writeNotificationPending) {
QSOCKS5_D_DEBUG << "queueing writeNotification";
writeNotificationPending = true;
QMetaObject::invokeMethod(q, "_q_emitPendingWriteNotification", Qt::QueuedConnection);
}
}
void QSocks5SocketEnginePrivate::_q_emitPendingConnectionNotification()
{
connectionNotificationPending = false;
Q_Q(QSocks5SocketEngine);
QSOCKS5_D_DEBUG << "emitting connectionNotification";
q->connectionNotification();
}
void QSocks5SocketEnginePrivate::emitConnectionNotification()
{
Q_Q(QSocks5SocketEngine);
QSOCKS5_D_DEBUG << "queueing connectionNotification";
connectionNotificationPending = true;
QMetaObject::invokeMethod(q, "_q_emitPendingConnectionNotification", Qt::QueuedConnection);
}
QSocks5SocketEngine::QSocks5SocketEngine(QObject *parent)
:QAbstractSocketEngine(*new QSocks5SocketEnginePrivate(), parent)
{
}
QSocks5SocketEngine::~QSocks5SocketEngine()
{
Q_D(QSocks5SocketEngine);
if (d->data) {
delete d->data->authenticator;
delete d->data->controlSocket;
}
if (d->connectData)
delete d->connectData;
#ifndef QT_NO_UDPSOCKET
if (d->udpData) {
delete d->udpData->udpSocket;
delete d->udpData;
}
#endif
if (d->bindData)
delete d->bindData;
}
static int nextDescriptor()
{
static QBasicAtomicInt counter;
return 1 + counter.fetchAndAddRelaxed(1);
}
bool QSocks5SocketEngine::initialize(QAbstractSocket::SocketType type, QAbstractSocket::NetworkLayerProtocol protocol)
{
Q_D(QSocks5SocketEngine);
d->socketDescriptor = nextDescriptor();
d->socketType = type;
d->socketProtocol = protocol;
return true;
}
bool QSocks5SocketEngine::initialize(qintptr socketDescriptor, QAbstractSocket::SocketState socketState)
{
Q_D(QSocks5SocketEngine);
QSOCKS5_Q_DEBUG << "initialize" << socketDescriptor;
// this is only valid for the other side of a bind, nothing else is supported
if (socketState != QAbstractSocket::ConnectedState) {
//### must be connected state ???
return false;
}
QSocks5BindData *bindData = socks5BindStore()->retrieve(socketDescriptor);
if (bindData) {
d->socketState = QAbstractSocket::ConnectedState;
d->socketType = QAbstractSocket::TcpSocket;
d->connectData = new QSocks5ConnectData;
d->data = d->connectData;
d->mode = QSocks5SocketEnginePrivate::ConnectMode;
d->data->controlSocket = bindData->controlSocket;
bindData->controlSocket = 0;
d->data->controlSocket->setParent(this);
d->socketProtocol = d->data->controlSocket->localAddress().protocol();
d->data->authenticator = bindData->authenticator;
bindData->authenticator = 0;
d->localPort = bindData->localPort;
d->localAddress = bindData->localAddress;
d->peerPort = bindData->peerPort;
d->peerAddress = bindData->peerAddress;
d->inboundStreamCount = d->outboundStreamCount = 1;
delete bindData;
QObject::connect(d->data->controlSocket, SIGNAL(connected()), this, SLOT(_q_controlSocketConnected()),
Qt::DirectConnection);
QObject::connect(d->data->controlSocket, SIGNAL(readyRead()), this, SLOT(_q_controlSocketReadNotification()),
Qt::DirectConnection);
QObject::connect(d->data->controlSocket, SIGNAL(bytesWritten(qint64)), this, SLOT(_q_controlSocketBytesWritten()),
Qt::DirectConnection);
QObject::connect(d->data->controlSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(_q_controlSocketError(QAbstractSocket::SocketError)),
Qt::DirectConnection);
QObject::connect(d->data->controlSocket, SIGNAL(disconnected()), this, SLOT(_q_controlSocketDisconnected()),
Qt::DirectConnection);
QObject::connect(d->data->controlSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)),
this, SLOT(_q_controlSocketStateChanged(QAbstractSocket::SocketState)),
Qt::DirectConnection);
d->socks5State = QSocks5SocketEnginePrivate::Connected;
if (d->data->controlSocket->bytesAvailable() != 0)
d->_q_controlSocketReadNotification();
return true;
}
return false;
}
void QSocks5SocketEngine::setProxy(const QNetworkProxy &networkProxy)
{
Q_D(QSocks5SocketEngine);
d->proxyInfo = networkProxy;
}
qintptr QSocks5SocketEngine::socketDescriptor() const
{
Q_D(const QSocks5SocketEngine);
return d->socketDescriptor;
}
bool QSocks5SocketEngine::isValid() const
{
Q_D(const QSocks5SocketEngine);
return d->socketType != QAbstractSocket::UnknownSocketType
&& d->socks5State != QSocks5SocketEnginePrivate::SocksError
&& (d->socketError == QAbstractSocket::UnknownSocketError
|| d->socketError == QAbstractSocket::SocketTimeoutError
|| d->socketError == QAbstractSocket::UnfinishedSocketOperationError);
}
bool QSocks5SocketEngine::connectInternal()
{
Q_D(QSocks5SocketEngine);
if (!d->data) {
if (socketType() == QAbstractSocket::TcpSocket) {
d->initialize(QSocks5SocketEnginePrivate::ConnectMode);
#ifndef QT_NO_UDPSOCKET
} else if (socketType() == QAbstractSocket::UdpSocket) {
d->initialize(QSocks5SocketEnginePrivate::UdpAssociateMode);
// all udp needs to be bound
if (!bind(QHostAddress(QLatin1String("0.0.0.0")), 0))
return false;
setState(QAbstractSocket::ConnectedState);
return true;
#endif
} else {
qFatal("QSocks5SocketEngine::connectToHost: in QTcpServer mode");
return false;
}
}
if (d->socketState != QAbstractSocket::ConnectingState) {
if (d->socks5State == QSocks5SocketEnginePrivate::Uninitialized
// We may have new auth credentials since an earlier failure:
|| d->socks5State == QSocks5SocketEnginePrivate::AuthenticatingError) {
setState(QAbstractSocket::ConnectingState);
//limit buffer in internal socket, data is buffered in the external socket under application control
d->data->controlSocket->setReadBufferSize(65536);
}
d->data->controlSocket->connectToHost(d->proxyInfo.hostName(), d->proxyInfo.port());
}
return false;
}
bool QSocks5SocketEngine::connectToHost(const QHostAddress &address, quint16 port)
{
Q_D(QSocks5SocketEngine);
QSOCKS5_DEBUG << "connectToHost" << address << ':' << port;
setPeerAddress(address);
setPeerPort(port);
d->peerName.clear();
return connectInternal();
}
bool QSocks5SocketEngine::connectToHostByName(const QString &hostname, quint16 port)
{
Q_D(QSocks5SocketEngine);
setPeerAddress(QHostAddress());
setPeerPort(port);
d->peerName = hostname;
return connectInternal();
}
void QSocks5SocketEnginePrivate::_q_controlSocketConnected()
{
QSOCKS5_DEBUG << "_q_controlSocketConnected";
QByteArray buf(3, 0);
buf[0] = S5_VERSION_5;
buf[1] = 0x01;
buf[2] = data->authenticator->methodId();
data->controlSocket->write(buf);
socks5State = AuthenticationMethodsSent;
}
void QSocks5SocketEnginePrivate::_q_controlSocketReadNotification()
{
QSOCKS5_D_DEBUG << "_q_controlSocketReadNotification socks5state" << s5StateToString(socks5State)
<< "bytes available" << data->controlSocket->bytesAvailable();
if (data->controlSocket->bytesAvailable() == 0) {
QSOCKS5_D_DEBUG << "########## bogus read why do we get these ... on windows only";
return;
}
switch (socks5State) {
case AuthenticationMethodsSent:
parseAuthenticationMethodReply();
break;
case Authenticating:
parseAuthenticatingReply();
break;
case RequestMethodSent:
parseRequestMethodReply();
if (socks5State == Connected && data->controlSocket->bytesAvailable())
_q_controlSocketReadNotification();
break;
case Connected: {
QByteArray buf;
if (!data->authenticator->unSeal(data->controlSocket, &buf)) {
// qDebug("unseal error maybe need to wait for more data");
}
if (buf.size()) {
QSOCKS5_DEBUG << dump(buf);
connectData->readBuffer.append(buf);
emitReadNotification();
}
break;
}
case BindSuccess:
// only get here if command is bind
if (mode == BindMode) {
parseRequestMethodReply();
break;
}
Q_FALLTHROUGH();
default:
qWarning("QSocks5SocketEnginePrivate::_q_controlSocketReadNotification: "
"Unexpectedly received data while in state=%d and mode=%d",
socks5State, mode);
break;
};
}
void QSocks5SocketEnginePrivate::_q_controlSocketBytesWritten()
{
QSOCKS5_DEBUG << "_q_controlSocketBytesWritten";
if (socks5State != Connected
|| (mode == ConnectMode
&& data->controlSocket->bytesToWrite()))
return;
if (data->controlSocket->bytesToWrite() < MaxWriteBufferSize) {
emitWriteNotification();
writeNotificationActivated = false;
}
}
void QSocks5SocketEnginePrivate::_q_controlSocketError(QAbstractSocket::SocketError error)
{
QSOCKS5_D_DEBUG << "controlSocketError" << error << data->controlSocket->errorString();
if (error == QAbstractSocket::SocketTimeoutError)
return; // ignore this error -- comes from the waitFor* functions
if (error == QAbstractSocket::RemoteHostClosedError
&& socks5State == Connected) {
// clear the read buffer in connect mode so that bytes available returns 0
// if there already is a read notification pending then this will be processed first
if (!readNotificationPending)
connectData->readBuffer.clear();
emitReadNotification();
data->controlSocket->close();
// cause a disconnect in the outer socket
emitWriteNotification();
} else if (socks5State == Uninitialized
|| socks5State == AuthenticationMethodsSent
|| socks5State == Authenticating
|| socks5State == RequestMethodSent) {
setErrorState(socks5State == Uninitialized ? ConnectError : ControlSocketError);
data->controlSocket->close();
emitConnectionNotification();
} else {
q_func()->setError(data->controlSocket->error(), data->controlSocket->errorString());
emitReadNotification();
emitWriteNotification();
}
}
void QSocks5SocketEnginePrivate::_q_controlSocketDisconnected()
{
QSOCKS5_D_DEBUG << "_q_controlSocketDisconnected";
}
void QSocks5SocketEnginePrivate::_q_controlSocketStateChanged(QAbstractSocket::SocketState state)
{
QSOCKS5_D_DEBUG << "_q_controlSocketStateChanged" << state;
}
#ifndef QT_NO_UDPSOCKET
void QSocks5SocketEnginePrivate::_q_udpSocketReadNotification()
{
QSOCKS5_D_DEBUG << "_q_udpSocketReadNotification()";
// check some state stuff
if (!udpData->udpSocket->hasPendingDatagrams()) {
QSOCKS5_D_DEBUG << "false read ??";
return;
}
while (udpData->udpSocket->hasPendingDatagrams()) {
QByteArray sealedBuf(udpData->udpSocket->pendingDatagramSize(), 0);
QSOCKS5_D_DEBUG << "new datagram";
udpData->udpSocket->readDatagram(sealedBuf.data(), sealedBuf.size());
QByteArray inBuf;
if (!data->authenticator->unSeal(sealedBuf, &inBuf)) {
QSOCKS5_D_DEBUG << "failed unsealing datagram discarding";
return;
}
QSOCKS5_DEBUG << dump(inBuf);
int pos = 0;
const char *buf = inBuf.constData();
if (inBuf.size() < 4) {
QSOCKS5_D_DEBUG << "bugus udp data, discarding";
return;
}
QSocks5RevivedDatagram datagram;
if (buf[pos++] != 0 || buf[pos++] != 0) {
QSOCKS5_D_DEBUG << "invalid datagram discarding";
return;
}
if (buf[pos++] != 0) { //### add fragmentation reading support
QSOCKS5_D_DEBUG << "don't support fragmentation yet disgarding";
return;
}
if (qt_socks5_get_host_address_and_port(inBuf, &datagram.address, &datagram.port, &pos) != 1) {
QSOCKS5_D_DEBUG << "failed to get address from datagram disgarding";
return;
}
datagram.data = QByteArray(&buf[pos], inBuf.size() - pos);
udpData->pendingDatagrams.enqueue(datagram);
}
emitReadNotification();
}
#endif // QT_NO_UDPSOCKET
bool QSocks5SocketEngine::bind(const QHostAddress &addr, quint16 port)
{
Q_D(QSocks5SocketEngine);
// when bind wee will block until the bind is finished as the info from the proxy server is needed
QHostAddress address;
if (addr.protocol() == QAbstractSocket::AnyIPProtocol)
address = QHostAddress::AnyIPv4; //SOCKS5 doesn't support dual stack, and there isn't any implementation of udp on ipv6 yet
else
address = addr;
if (!d->data) {
if (socketType() == QAbstractSocket::TcpSocket) {
d->initialize(QSocks5SocketEnginePrivate::BindMode);
#ifndef QT_NO_UDPSOCKET
} else if (socketType() == QAbstractSocket::UdpSocket) {
d->initialize(QSocks5SocketEnginePrivate::UdpAssociateMode);
#endif
} else {
//### something invalid
return false;
}
}
#ifndef QT_NO_UDPSOCKET
if (d->mode == QSocks5SocketEnginePrivate::UdpAssociateMode) {
if (!d->udpData->udpSocket->bind(address, port)) {
QSOCKS5_Q_DEBUG << "local udp bind failed";
setError(d->udpData->udpSocket->error(), d->udpData->udpSocket->errorString());
return false;
}
d->localAddress = d->udpData->udpSocket->localAddress();
d->localPort = d->udpData->udpSocket->localPort();
} else
#endif
if (d->mode == QSocks5SocketEnginePrivate::BindMode) {
d->localAddress = address;
d->localPort = port;
} else {
//### something invalid
return false;
}
int msecs = SOCKS5_BLOCKING_BIND_TIMEOUT;
QElapsedTimer stopWatch;
stopWatch.start();
d->data->controlSocket->connectToHost(d->proxyInfo.hostName(), d->proxyInfo.port());
if (!d->waitForConnected(msecs, 0) ||
d->data->controlSocket->state() == QAbstractSocket::UnconnectedState) {
// waitForConnected sets the error state and closes the socket
QSOCKS5_Q_DEBUG << "waitForConnected to proxy server" << d->data->controlSocket->errorString();
return false;
}
if (d->socks5State == QSocks5SocketEnginePrivate::BindSuccess) {
setState(QAbstractSocket::BoundState);
return true;
#ifndef QT_NO_UDPSOCKET
} else if (d->socks5State == QSocks5SocketEnginePrivate::UdpAssociateSuccess) {
setState(QAbstractSocket::BoundState);
d->udpData->associateAddress = d->localAddress;
d->localAddress = QHostAddress();
d->udpData->associatePort = d->localPort;
d->localPort = 0;
return true;
#endif // QT_NO_UDPSOCKET
}
// binding timed out
setError(QAbstractSocket::SocketTimeoutError,
QLatin1String(QT_TRANSLATE_NOOP("QSocks5SocketEngine", "Network operation timed out")));
///### delete d->udpSocket;
///### d->udpSocket = 0;
return false;
}
bool QSocks5SocketEngine::listen()
{
Q_D(QSocks5SocketEngine);
QSOCKS5_Q_DEBUG << "listen()";
// check that we are in bound and then go to listening.
if (d->socketState == QAbstractSocket::BoundState) {
d->socketState = QAbstractSocket::ListeningState;
// check if we already have a connection
if (d->socks5State == QSocks5SocketEnginePrivate::BindSuccess)
d->emitReadNotification();
return true;
}
return false;
}
int QSocks5SocketEngine::accept()
{
Q_D(QSocks5SocketEngine);
// check we are listing ---
QSOCKS5_Q_DEBUG << "accept()";
qintptr sd = -1;
switch (d->socks5State) {
case QSocks5SocketEnginePrivate::BindSuccess:
QSOCKS5_Q_DEBUG << "BindSuccess adding" << d->socketDescriptor << "to the bind store";
d->data->controlSocket->disconnect();
d->data->controlSocket->setParent(0);
d->bindData->localAddress = d->localAddress;
d->bindData->localPort = d->localPort;
sd = d->socketDescriptor;
socks5BindStore()->add(sd, d->bindData);
d->data = 0;
d->bindData = 0;
d->socketDescriptor = 0;
//### do something about this socket layer ... set it closed and an error about why ...
// reset state and local port/address
d->socks5State = QSocks5SocketEnginePrivate::Uninitialized; // ..??
d->socketState = QAbstractSocket::UnconnectedState;
break;
case QSocks5SocketEnginePrivate::ControlSocketError:
setError(QAbstractSocket::ProxyProtocolError, QLatin1String("Control socket error"));
break;
default:
setError(QAbstractSocket::ProxyProtocolError, QLatin1String("SOCKS5 proxy error"));
break;
}
return sd;
}
void QSocks5SocketEngine::close()
{
QSOCKS5_Q_DEBUG << "close()";
Q_D(QSocks5SocketEngine);
if (d->data && d->data->controlSocket) {
if (d->data->controlSocket->state() == QAbstractSocket::ConnectedState) {
int msecs = 100;
QElapsedTimer stopWatch;
stopWatch.start();
while (!d->data->controlSocket->bytesToWrite()) {
if (!d->data->controlSocket->waitForBytesWritten(qt_subtract_from_timeout(msecs, stopWatch.elapsed())))
break;
}
}
d->data->controlSocket->close();
}
d->inboundStreamCount = d->outboundStreamCount = 0;
#ifndef QT_NO_UDPSOCKET
if (d->udpData && d->udpData->udpSocket)
d->udpData->udpSocket->close();
#endif
}
qint64 QSocks5SocketEngine::bytesAvailable() const
{
Q_D(const QSocks5SocketEngine);
if (d->mode == QSocks5SocketEnginePrivate::ConnectMode)
return d->connectData->readBuffer.size();
#ifndef QT_NO_UDPSOCKET
else if (d->mode == QSocks5SocketEnginePrivate::UdpAssociateMode
&& !d->udpData->pendingDatagrams.isEmpty())
return d->udpData->pendingDatagrams.first().data.size();
#endif
return 0;
}
qint64 QSocks5SocketEngine::read(char *data, qint64 maxlen)
{
Q_D(QSocks5SocketEngine);
QSOCKS5_Q_DEBUG << "read( , maxlen = " << maxlen << ')';
if (d->mode == QSocks5SocketEnginePrivate::ConnectMode) {
if (d->connectData->readBuffer.isEmpty()) {
if (d->data->controlSocket->state() == QAbstractSocket::UnconnectedState) {
//imitate remote closed
close();
setError(QAbstractSocket::RemoteHostClosedError,
QLatin1String("Remote host closed connection###"));
setState(QAbstractSocket::UnconnectedState);
return -1;
} else {
return 0; // nothing to be read
}
}
const qint64 copy = d->connectData->readBuffer.read(data, maxlen);
QSOCKS5_DEBUG << "read" << dump(QByteArray(data, copy));
return copy;
#ifndef QT_NO_UDPSOCKET
} else if (d->mode == QSocks5SocketEnginePrivate::UdpAssociateMode) {
return readDatagram(data, maxlen);
#endif
}
return 0;
}
qint64 QSocks5SocketEngine::write(const char *data, qint64 len)
{
Q_D(QSocks5SocketEngine);
QSOCKS5_Q_DEBUG << "write" << dump(QByteArray(data, len));
if (d->mode == QSocks5SocketEnginePrivate::ConnectMode) {
// clamp down the amount of bytes to transfer at once
len = qMin<qint64>(len, MaxWriteBufferSize) - d->data->controlSocket->bytesToWrite();
if (len <= 0)
return 0;
QByteArray buf = QByteArray::fromRawData(data, len);
QByteArray sealedBuf;
if (!d->data->authenticator->seal(buf, &sealedBuf)) {
// ### Handle this error.
}
qint64 written = d->data->controlSocket->write(sealedBuf);
if (written <= 0) {
QSOCKS5_Q_DEBUG << "native write returned" << written;
return written;
}
d->data->controlSocket->waitForBytesWritten(0);
//NB: returning len rather than written for the OK case, because the "sealing" may increase the length
return len;
#ifndef QT_NO_UDPSOCKET
} else if (d->mode == QSocks5SocketEnginePrivate::UdpAssociateMode) {
// send to connected address
return writeDatagram(data, len, QIpPacketHeader(d->peerAddress, d->peerPort));
#endif
}
//### set an error ???
return -1;
}
#ifndef QT_NO_UDPSOCKET
#ifndef QT_NO_NETWORKINTERFACE
bool QSocks5SocketEngine::joinMulticastGroup(const QHostAddress &,
const QNetworkInterface &)
{
setError(QAbstractSocket::UnsupportedSocketOperationError,
QLatin1String("Operation on socket is not supported"));
return false;
}
bool QSocks5SocketEngine::leaveMulticastGroup(const QHostAddress &,
const QNetworkInterface &)
{
setError(QAbstractSocket::UnsupportedSocketOperationError,
QLatin1String("Operation on socket is not supported"));
return false;
}
QNetworkInterface QSocks5SocketEngine::multicastInterface() const
{
return QNetworkInterface();
}
bool QSocks5SocketEngine::setMulticastInterface(const QNetworkInterface &)
{
setError(QAbstractSocket::UnsupportedSocketOperationError,
QLatin1String("Operation on socket is not supported"));
return false;
}
#endif // QT_NO_NETWORKINTERFACE
bool QSocks5SocketEngine::hasPendingDatagrams() const
{
Q_D(const QSocks5SocketEngine);
Q_INIT_CHECK(false);
return !d->udpData->pendingDatagrams.isEmpty();
}
qint64 QSocks5SocketEngine::pendingDatagramSize() const
{
Q_D(const QSocks5SocketEngine);
if (!d->udpData->pendingDatagrams.isEmpty())
return d->udpData->pendingDatagrams.head().data.size();
return 0;
}
#endif // QT_NO_UDPSOCKET
qint64 QSocks5SocketEngine::readDatagram(char *data, qint64 maxlen, QIpPacketHeader *header, PacketHeaderOptions)
{
#ifndef QT_NO_UDPSOCKET
Q_D(QSocks5SocketEngine);
if (d->udpData->pendingDatagrams.isEmpty())
return 0;
QSocks5RevivedDatagram datagram = d->udpData->pendingDatagrams.dequeue();
int copyLen = qMin<int>(maxlen, datagram.data.size());
memcpy(data, datagram.data.constData(), copyLen);
if (header) {
header->senderAddress = datagram.address;
header->senderPort = datagram.port;
}
return copyLen;
#else
Q_UNUSED(data)
Q_UNUSED(maxlen)
Q_UNUSED(header)
return -1;
#endif // QT_NO_UDPSOCKET
}
qint64 QSocks5SocketEngine::writeDatagram(const char *data, qint64 len, const QIpPacketHeader &header)
{
#ifndef QT_NO_UDPSOCKET
Q_D(QSocks5SocketEngine);
// it is possible to send with out first binding with udp, but socks5 requires a bind.
if (!d->data) {
d->initialize(QSocks5SocketEnginePrivate::UdpAssociateMode);
// all udp needs to be bound
if (!bind(QHostAddress(QLatin1String("0.0.0.0")), 0)) {
//### set error
return -1;
}
}
QByteArray outBuf;
outBuf.reserve(270 + len);
outBuf[0] = 0x00;
outBuf[1] = 0x00;
outBuf[2] = 0x00;
if (!qt_socks5_set_host_address_and_port(header.destinationAddress, header.destinationPort, &outBuf)) {
}
outBuf += QByteArray(data, len);
QSOCKS5_DEBUG << "sending" << dump(outBuf);
QByteArray sealedBuf;
if (!d->data->authenticator->seal(outBuf, &sealedBuf)) {
QSOCKS5_DEBUG << "sealing data failed";
setError(QAbstractSocket::SocketAccessError, d->data->authenticator->errorString());
return -1;
}
if (d->udpData->udpSocket->writeDatagram(sealedBuf, d->udpData->associateAddress, d->udpData->associatePort) != sealedBuf.size()) {
//### try frgamenting
if (d->udpData->udpSocket->error() == QAbstractSocket::DatagramTooLargeError)
setError(d->udpData->udpSocket->error(), d->udpData->udpSocket->errorString());
//### else maybe more serious error
return -1;
}
return len;
#else
Q_UNUSED(data)
Q_UNUSED(len)
Q_UNUSED(header)
return -1;
#endif // QT_NO_UDPSOCKET
}
qint64 QSocks5SocketEngine::bytesToWrite() const
{
Q_D(const QSocks5SocketEngine);
if (d->data && d->data->controlSocket) {
return d->data->controlSocket->bytesToWrite();
} else {
return 0;
}
}
int QSocks5SocketEngine::option(SocketOption option) const
{
Q_D(const QSocks5SocketEngine);
if (d->data && d->data->controlSocket) {
// convert the enum and call the real socket
if (option == QAbstractSocketEngine::LowDelayOption)
return d->data->controlSocket->socketOption(QAbstractSocket::LowDelayOption).toInt();
if (option == QAbstractSocketEngine::KeepAliveOption)
return d->data->controlSocket->socketOption(QAbstractSocket::KeepAliveOption).toInt();
}
return -1;
}
bool QSocks5SocketEngine::setOption(SocketOption option, int value)
{
Q_D(QSocks5SocketEngine);
if (d->data && d->data->controlSocket) {
// convert the enum and call the real socket
if (option == QAbstractSocketEngine::LowDelayOption)
d->data->controlSocket->setSocketOption(QAbstractSocket::LowDelayOption, value);
if (option == QAbstractSocketEngine::KeepAliveOption)
d->data->controlSocket->setSocketOption(QAbstractSocket::KeepAliveOption, value);
return true;
}
return false;
}
bool QSocks5SocketEnginePrivate::waitForConnected(int msecs, bool *timedOut)
{
if (data->controlSocket->state() == QAbstractSocket::UnconnectedState)
return false;
const Socks5State wantedState =
mode == ConnectMode ? Connected :
mode == BindMode ? BindSuccess :
UdpAssociateSuccess;
QElapsedTimer stopWatch;
stopWatch.start();
while (socks5State != wantedState) {
if (!data->controlSocket->waitForReadyRead(qt_subtract_from_timeout(msecs, stopWatch.elapsed()))) {
if (data->controlSocket->state() == QAbstractSocket::UnconnectedState)
return true;
setErrorState(QSocks5SocketEnginePrivate::ControlSocketError);
if (timedOut && data->controlSocket->error() == QAbstractSocket::SocketTimeoutError)
*timedOut = true;
return false;
}
}
return true;
}
bool QSocks5SocketEngine::waitForRead(int msecs, bool *timedOut)
{
Q_D(QSocks5SocketEngine);
QSOCKS5_DEBUG << "waitForRead" << msecs;
d->readNotificationActivated = false;
QElapsedTimer stopWatch;
stopWatch.start();
// are we connected yet?
if (!d->waitForConnected(msecs, timedOut))
return false;
if (d->data->controlSocket->state() == QAbstractSocket::UnconnectedState)
return true;
if (bytesAvailable() && d->readNotificationPending) {
// We've got some data incoming, but the queued call hasn't been performed yet.
// The data is where we expect it to be already, so just return true.
return true;
}
// we're connected
if (d->mode == QSocks5SocketEnginePrivate::ConnectMode ||
d->mode == QSocks5SocketEnginePrivate::BindMode) {
while (!d->readNotificationActivated) {
if (!d->data->controlSocket->waitForReadyRead(qt_subtract_from_timeout(msecs, stopWatch.elapsed()))) {
if (d->data->controlSocket->state() == QAbstractSocket::UnconnectedState)
return true;
setError(d->data->controlSocket->error(), d->data->controlSocket->errorString());
if (timedOut && d->data->controlSocket->error() == QAbstractSocket::SocketTimeoutError)
*timedOut = true;
return false;
}
}
#ifndef QT_NO_UDPSOCKET
} else {
while (!d->readNotificationActivated) {
if (!d->udpData->udpSocket->waitForReadyRead(qt_subtract_from_timeout(msecs, stopWatch.elapsed()))) {
setError(d->udpData->udpSocket->error(), d->udpData->udpSocket->errorString());
if (timedOut && d->udpData->udpSocket->error() == QAbstractSocket::SocketTimeoutError)
*timedOut = true;
return false;
}
}
#endif // QT_NO_UDPSOCKET
}
bool ret = d->readNotificationActivated;
d->readNotificationActivated = false;
QSOCKS5_DEBUG << "waitForRead returned" << ret;
return ret;
}
bool QSocks5SocketEngine::waitForWrite(int msecs, bool *timedOut)
{
Q_D(QSocks5SocketEngine);
QSOCKS5_DEBUG << "waitForWrite" << msecs;
QElapsedTimer stopWatch;
stopWatch.start();
// are we connected yet?
if (!d->waitForConnected(msecs, timedOut))
return false;
if (d->data->controlSocket->state() == QAbstractSocket::UnconnectedState)
return true;
// we're connected
// flush any bytes we may still have buffered in the time that we have left
if (d->data->controlSocket->bytesToWrite())
d->data->controlSocket->waitForBytesWritten(qt_subtract_from_timeout(msecs, stopWatch.elapsed()));
while ((msecs == -1 || stopWatch.elapsed() < msecs)
&& d->data->controlSocket->state() == QAbstractSocket::ConnectedState
&& d->data->controlSocket->bytesToWrite() >= MaxWriteBufferSize)
d->data->controlSocket->waitForBytesWritten(qt_subtract_from_timeout(msecs, stopWatch.elapsed()));
return d->data->controlSocket->bytesToWrite() < MaxWriteBufferSize;
}
bool QSocks5SocketEngine::waitForReadOrWrite(bool *readyToRead, bool *readyToWrite,
bool checkRead, bool checkWrite,
int msecs, bool *timedOut)
{
Q_UNUSED(checkRead);
if (!checkWrite) {
bool canRead = waitForRead(msecs, timedOut);
if (readyToRead)
*readyToRead = canRead;
return canRead;
}
bool canWrite = waitForWrite(msecs, timedOut);
if (readyToWrite)
*readyToWrite = canWrite;
return canWrite;
}
bool QSocks5SocketEngine::isReadNotificationEnabled() const
{
Q_D(const QSocks5SocketEngine);
return d->readNotificationEnabled;
}
void QSocks5SocketEngine::setReadNotificationEnabled(bool enable)
{
Q_D(QSocks5SocketEngine);
QSOCKS5_Q_DEBUG << "setReadNotificationEnabled(" << enable << ')';
bool emitSignal = false;
if (!d->readNotificationEnabled
&& enable) {
if (d->mode == QSocks5SocketEnginePrivate::ConnectMode)
emitSignal = !d->connectData->readBuffer.isEmpty();
#ifndef QT_NO_UDPSOCKET
else if (d->mode == QSocks5SocketEnginePrivate::UdpAssociateMode)
emitSignal = !d->udpData->pendingDatagrams.isEmpty();
#endif
else if (d->mode == QSocks5SocketEnginePrivate::BindMode
&& d->socketState == QAbstractSocket::ListeningState
&& d->socks5State == QSocks5SocketEnginePrivate::BindSuccess)
emitSignal = true;
}
d->readNotificationEnabled = enable;
if (emitSignal)
d->emitReadNotification();
}
bool QSocks5SocketEngine::isWriteNotificationEnabled() const
{
Q_D(const QSocks5SocketEngine);
return d->writeNotificationEnabled;
}
void QSocks5SocketEngine::setWriteNotificationEnabled(bool enable)
{
Q_D(QSocks5SocketEngine);
d->writeNotificationEnabled = enable;
if (enable && d->socketState == QAbstractSocket::ConnectedState) {
if (d->mode == QSocks5SocketEnginePrivate::ConnectMode && d->data->controlSocket->bytesToWrite())
return; // will be emitted as a result of bytes written
d->emitWriteNotification();
d->writeNotificationActivated = false;
}
}
bool QSocks5SocketEngine::isExceptionNotificationEnabled() const
{
Q_D(const QSocks5SocketEngine);
return d->exceptNotificationEnabled;
}
void QSocks5SocketEngine::setExceptionNotificationEnabled(bool enable)
{
Q_D(QSocks5SocketEngine);
d->exceptNotificationEnabled = enable;
}
QAbstractSocketEngine *
QSocks5SocketEngineHandler::createSocketEngine(QAbstractSocket::SocketType socketType,
const QNetworkProxy &proxy, QObject *parent)
{
Q_UNUSED(socketType);
// proxy type must have been resolved by now
if (proxy.type() != QNetworkProxy::Socks5Proxy) {
QSOCKS5_DEBUG << "not proxying";
return 0;
}
QScopedPointer<QSocks5SocketEngine> engine(new QSocks5SocketEngine(parent));
engine->setProxy(proxy);
return engine.take();
}
QAbstractSocketEngine *QSocks5SocketEngineHandler::createSocketEngine(qintptr socketDescriptor, QObject *parent)
{
QSOCKS5_DEBUG << "createSocketEngine" << socketDescriptor;
if (socks5BindStore()->contains(socketDescriptor)) {
QSOCKS5_DEBUG << "bind store contains" << socketDescriptor;
return new QSocks5SocketEngine(parent);
}
return 0;
}
QT_END_NAMESPACE