2017-12-21 04:43:28 -05:00
|
|
|
/* Ricochet - https://ricochet.im/
|
|
|
|
* Copyright (C) 2014, John Brooks <john.brooks@dereferenced.net>
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions are
|
|
|
|
* met:
|
|
|
|
*
|
|
|
|
* * Redistributions of source code must retain the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer.
|
|
|
|
*
|
|
|
|
* * Redistributions in binary form must reproduce the above
|
|
|
|
* copyright notice, this list of conditions and the following disclaimer
|
|
|
|
* in the documentation and/or other materials provided with the
|
|
|
|
* distribution.
|
|
|
|
*
|
|
|
|
* * Neither the names of the copyright owners nor the names of its
|
|
|
|
* contributors may be used to endorse or promote products derived from
|
|
|
|
* this software without specific prior written permission.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*/
|
|
|
|
|
2018-01-18 16:40:28 -05:00
|
|
|
#include <time.h>
|
|
|
|
|
2017-12-21 04:43:28 -05:00
|
|
|
#include "TorControl.h"
|
|
|
|
#include "TorControlSocket.h"
|
|
|
|
#include "HiddenService.h"
|
|
|
|
#include "ProtocolInfoCommand.h"
|
|
|
|
#include "AuthenticateCommand.h"
|
|
|
|
#include "SetConfCommand.h"
|
|
|
|
#include "GetConfCommand.h"
|
|
|
|
#include "AddOnionCommand.h"
|
2018-01-18 16:13:24 -05:00
|
|
|
#include "StrUtil.h"
|
2017-12-21 04:43:28 -05:00
|
|
|
#include "Settings.h"
|
|
|
|
#include "PendingOperation.h"
|
|
|
|
#include <QHostAddress>
|
|
|
|
#include <QDir>
|
|
|
|
#include <QNetworkProxy>
|
|
|
|
//#include <QQmlEngine>
|
|
|
|
#include <QTimer>
|
|
|
|
#include <QSaveFile>
|
|
|
|
#include <QRegularExpression>
|
|
|
|
#include <QDebug>
|
|
|
|
|
|
|
|
Tor::TorControl *torControl = 0;
|
|
|
|
|
2017-12-26 11:21:57 -05:00
|
|
|
class nullstream: public std::ostream {};
|
|
|
|
|
|
|
|
static std::ostream& torctrldebug()
|
|
|
|
{
|
|
|
|
static nullstream null ;
|
|
|
|
|
|
|
|
if(true)
|
|
|
|
return std::cerr << time(NULL) << ":TOR CONTROL: " ;
|
|
|
|
else
|
|
|
|
return null ;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define torCtrlDebug torctrldebug
|
|
|
|
|
|
|
|
|
2017-12-21 04:43:28 -05:00
|
|
|
using namespace Tor;
|
|
|
|
|
|
|
|
namespace Tor {
|
|
|
|
|
|
|
|
class TorControlPrivate : public QObject
|
|
|
|
{
|
|
|
|
Q_OBJECT
|
|
|
|
|
|
|
|
public:
|
|
|
|
TorControl *q;
|
|
|
|
|
|
|
|
TorControlSocket *socket;
|
|
|
|
QHostAddress torAddress;
|
|
|
|
QString errorMessage;
|
|
|
|
QString torVersion;
|
|
|
|
QByteArray authPassword;
|
|
|
|
QHostAddress socksAddress;
|
|
|
|
QList<HiddenService*> services;
|
|
|
|
quint16 controlPort, socksPort;
|
|
|
|
TorControl::Status status;
|
|
|
|
TorControl::TorStatus torStatus;
|
|
|
|
QVariantMap bootstrapStatus;
|
|
|
|
bool hasOwnership;
|
|
|
|
|
|
|
|
TorControlPrivate(TorControl *parent);
|
|
|
|
|
|
|
|
void setStatus(TorControl::Status status);
|
|
|
|
void setTorStatus(TorControl::TorStatus status);
|
|
|
|
|
|
|
|
void getTorInfo();
|
|
|
|
void publishServices();
|
|
|
|
|
|
|
|
public slots:
|
|
|
|
void socketConnected();
|
|
|
|
void socketDisconnected();
|
|
|
|
void socketError();
|
|
|
|
|
|
|
|
void authenticateReply();
|
|
|
|
void protocolInfoReply();
|
|
|
|
void getTorInfoReply();
|
|
|
|
void setError(const QString &message);
|
|
|
|
|
|
|
|
void statusEvent(int code, const QByteArray &data);
|
|
|
|
void updateBootstrap(const QList<QByteArray> &data);
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
TorControl::TorControl(QObject *parent)
|
|
|
|
: QObject(parent), d(new TorControlPrivate(this))
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
TorControlPrivate::TorControlPrivate(TorControl *parent)
|
|
|
|
: QObject(parent), q(parent), controlPort(0), socksPort(0),
|
|
|
|
status(TorControl::NotConnected), torStatus(TorControl::TorUnknown),
|
|
|
|
hasOwnership(false)
|
|
|
|
{
|
|
|
|
socket = new TorControlSocket(this);
|
|
|
|
QObject::connect(socket, SIGNAL(connected()), this, SLOT(socketConnected()));
|
|
|
|
QObject::connect(socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected()));
|
|
|
|
QObject::connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError()));
|
|
|
|
QObject::connect(socket, SIGNAL(error(QString)), this, SLOT(setError(QString)));
|
|
|
|
}
|
|
|
|
|
|
|
|
QNetworkProxy TorControl::connectionProxy()
|
|
|
|
{
|
|
|
|
return QNetworkProxy(QNetworkProxy::Socks5Proxy, d->socksAddress.toString(), d->socksPort);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorControlPrivate::setStatus(TorControl::Status n)
|
|
|
|
{
|
|
|
|
if (n == status)
|
|
|
|
return;
|
|
|
|
|
|
|
|
TorControl::Status old = status;
|
|
|
|
status = n;
|
|
|
|
|
|
|
|
if (old == TorControl::Error)
|
|
|
|
errorMessage.clear();
|
|
|
|
|
|
|
|
emit q->statusChanged(status, old);
|
|
|
|
|
|
|
|
if (status == TorControl::Connected && old < TorControl::Connected)
|
|
|
|
emit q->connected();
|
|
|
|
else if (status < TorControl::Connected && old >= TorControl::Connected)
|
|
|
|
emit q->disconnected();
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorControlPrivate::setTorStatus(TorControl::TorStatus n)
|
|
|
|
{
|
|
|
|
if (n == torStatus)
|
|
|
|
return;
|
|
|
|
|
|
|
|
TorControl::TorStatus old = torStatus;
|
|
|
|
torStatus = n;
|
|
|
|
emit q->torStatusChanged(torStatus, old);
|
|
|
|
emit q->connectivityChanged();
|
|
|
|
|
|
|
|
if (torStatus == TorControl::TorReady && socksAddress.isNull()) {
|
|
|
|
// Request info again to read the SOCKS port
|
|
|
|
getTorInfo();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorControlPrivate::setError(const QString &message)
|
|
|
|
{
|
|
|
|
errorMessage = message;
|
|
|
|
setStatus(TorControl::Error);
|
|
|
|
|
|
|
|
qWarning() << "torctrl: Error:" << errorMessage;
|
|
|
|
|
|
|
|
socket->abort();
|
|
|
|
|
|
|
|
QTimer::singleShot(15000, q, SLOT(reconnect()));
|
|
|
|
}
|
|
|
|
|
|
|
|
TorControl::Status TorControl::status() const
|
|
|
|
{
|
|
|
|
return d->status;
|
|
|
|
}
|
|
|
|
|
|
|
|
TorControl::TorStatus TorControl::torStatus() const
|
|
|
|
{
|
|
|
|
return d->torStatus;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString TorControl::torVersion() const
|
|
|
|
{
|
|
|
|
return d->torVersion;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString TorControl::errorMessage() const
|
|
|
|
{
|
|
|
|
return d->errorMessage;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool TorControl::hasConnectivity() const
|
|
|
|
{
|
|
|
|
return torStatus() == TorReady && !d->socksAddress.isNull();
|
|
|
|
}
|
|
|
|
|
|
|
|
QHostAddress TorControl::socksAddress() const
|
|
|
|
{
|
|
|
|
return d->socksAddress;
|
|
|
|
}
|
|
|
|
|
|
|
|
quint16 TorControl::socksPort() const
|
|
|
|
{
|
|
|
|
return d->socksPort;
|
|
|
|
}
|
|
|
|
|
|
|
|
QList<HiddenService*> TorControl::hiddenServices() const
|
|
|
|
{
|
|
|
|
return d->services;
|
|
|
|
}
|
|
|
|
|
|
|
|
QVariantMap TorControl::bootstrapStatus() const
|
|
|
|
{
|
|
|
|
return d->bootstrapStatus;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorControl::setAuthPassword(const QByteArray &password)
|
|
|
|
{
|
|
|
|
d->authPassword = password;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorControl::connect(const QHostAddress &address, quint16 port)
|
|
|
|
{
|
|
|
|
if (status() > Connecting)
|
|
|
|
{
|
2017-12-26 11:21:57 -05:00
|
|
|
torCtrlDebug() << "Ignoring TorControl::connect due to existing connection" << std::endl;
|
2017-12-21 04:43:28 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
d->torAddress = address;
|
|
|
|
d->controlPort = port;
|
|
|
|
d->setTorStatus(TorUnknown);
|
|
|
|
|
|
|
|
bool b = d->socket->blockSignals(true);
|
|
|
|
d->socket->abort();
|
|
|
|
d->socket->blockSignals(b);
|
|
|
|
|
|
|
|
d->setStatus(Connecting);
|
|
|
|
d->socket->connectToHost(address, port);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorControl::reconnect()
|
|
|
|
{
|
|
|
|
Q_ASSERT(!d->torAddress.isNull() && d->controlPort);
|
|
|
|
if (d->torAddress.isNull() || !d->controlPort || status() >= Connecting)
|
|
|
|
return;
|
|
|
|
|
|
|
|
d->setStatus(Connecting);
|
|
|
|
d->socket->connectToHost(d->torAddress, d->controlPort);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorControlPrivate::authenticateReply()
|
|
|
|
{
|
|
|
|
AuthenticateCommand *command = qobject_cast<AuthenticateCommand*>(sender());
|
|
|
|
Q_ASSERT(command);
|
|
|
|
Q_ASSERT(status == TorControl::Authenticating);
|
|
|
|
if (!command)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!command->isSuccessful()) {
|
|
|
|
setError(command->errorMessage());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-12-26 11:21:57 -05:00
|
|
|
torCtrlDebug() << "torctrl: Authentication successful" << std::endl;
|
2017-12-21 04:43:28 -05:00
|
|
|
setStatus(TorControl::Connected);
|
|
|
|
|
|
|
|
setTorStatus(TorControl::TorUnknown);
|
|
|
|
|
|
|
|
TorControlCommand *clientEvents = new TorControlCommand;
|
|
|
|
connect(clientEvents, &TorControlCommand::replyLine, this, &TorControlPrivate::statusEvent);
|
|
|
|
socket->registerEvent("STATUS_CLIENT", clientEvents);
|
|
|
|
|
|
|
|
getTorInfo();
|
|
|
|
publishServices();
|
|
|
|
|
|
|
|
// XXX Fix old configurations that would store unwanted options in torrc.
|
|
|
|
// This can be removed some suitable amount of time after 1.0.4.
|
|
|
|
if (hasOwnership)
|
|
|
|
q->saveConfiguration();
|
|
|
|
}
|
|
|
|
|
2017-12-26 11:21:57 -05:00
|
|
|
|
2017-12-21 04:43:28 -05:00
|
|
|
void TorControlPrivate::socketConnected()
|
|
|
|
{
|
|
|
|
Q_ASSERT(status == TorControl::Connecting);
|
|
|
|
|
2017-12-26 11:21:57 -05:00
|
|
|
torCtrlDebug() << "torctrl: Connected socket; querying information" << std::endl;
|
2017-12-21 04:43:28 -05:00
|
|
|
setStatus(TorControl::Authenticating);
|
|
|
|
|
|
|
|
ProtocolInfoCommand *command = new ProtocolInfoCommand(q);
|
|
|
|
connect(command, &TorControlCommand::finished, this, &TorControlPrivate::protocolInfoReply);
|
|
|
|
socket->sendCommand(command, command->build());
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorControlPrivate::socketDisconnected()
|
|
|
|
{
|
|
|
|
/* Clear some internal state */
|
|
|
|
torVersion.clear();
|
|
|
|
socksAddress.clear();
|
|
|
|
socksPort = 0;
|
|
|
|
setTorStatus(TorControl::TorUnknown);
|
|
|
|
|
|
|
|
/* This emits the disconnected() signal as well */
|
|
|
|
setStatus(TorControl::NotConnected);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorControlPrivate::socketError()
|
|
|
|
{
|
|
|
|
setError(QStringLiteral("Connection failed: %1").arg(socket->errorString()));
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorControlPrivate::protocolInfoReply()
|
|
|
|
{
|
|
|
|
ProtocolInfoCommand *info = qobject_cast<ProtocolInfoCommand*>(sender());
|
|
|
|
if (!info)
|
|
|
|
return;
|
|
|
|
|
|
|
|
torVersion = info->torVersion();
|
|
|
|
|
|
|
|
if (status == TorControl::Authenticating)
|
|
|
|
{
|
|
|
|
AuthenticateCommand *auth = new AuthenticateCommand;
|
|
|
|
connect(auth, &TorControlCommand::finished, this, &TorControlPrivate::authenticateReply);
|
|
|
|
|
|
|
|
QByteArray data;
|
|
|
|
ProtocolInfoCommand::AuthMethods methods = info->authMethods();
|
|
|
|
|
|
|
|
if (methods.testFlag(ProtocolInfoCommand::AuthNull))
|
|
|
|
{
|
2017-12-26 11:21:57 -05:00
|
|
|
torCtrlDebug() << "torctrl: Using null authentication" << std::endl;
|
2017-12-21 04:43:28 -05:00
|
|
|
data = auth->build();
|
|
|
|
}
|
|
|
|
else if (methods.testFlag(ProtocolInfoCommand::AuthCookie) && !info->cookieFile().isEmpty())
|
|
|
|
{
|
|
|
|
QString cookieFile = info->cookieFile();
|
|
|
|
QString cookieError;
|
2017-12-26 11:21:57 -05:00
|
|
|
torCtrlDebug() << "torctrl: Using cookie authentication with file" << cookieFile.toStdString() << std::endl;
|
2017-12-21 04:43:28 -05:00
|
|
|
|
|
|
|
QFile file(cookieFile);
|
|
|
|
if (file.open(QIODevice::ReadOnly))
|
|
|
|
{
|
|
|
|
QByteArray cookie = file.readAll();
|
|
|
|
file.close();
|
|
|
|
|
|
|
|
/* Simple test to avoid a vulnerability where any process listening on what we think is
|
|
|
|
* the control port could trick us into sending the contents of an arbitrary file */
|
|
|
|
if (cookie.size() == 32)
|
|
|
|
data = auth->build(cookie);
|
|
|
|
else
|
|
|
|
cookieError = QStringLiteral("Unexpected file size");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
cookieError = file.errorString();
|
|
|
|
|
|
|
|
if (!cookieError.isNull() || data.isNull())
|
|
|
|
{
|
|
|
|
/* If we know a password and password authentication is allowed, try using that instead.
|
|
|
|
* This is a strange corner case that will likely never happen in a normal configuration,
|
|
|
|
* but it has happened. */
|
|
|
|
if (methods.testFlag(ProtocolInfoCommand::AuthHashedPassword) && !authPassword.isEmpty())
|
|
|
|
{
|
2017-12-26 11:21:57 -05:00
|
|
|
torCtrlDebug() << "torctrl: Unable to read authentication cookie file:" << cookieError.toStdString() << std::endl;
|
2017-12-21 04:43:28 -05:00
|
|
|
goto usePasswordAuth;
|
|
|
|
}
|
|
|
|
|
|
|
|
setError(QStringLiteral("Unable to read authentication cookie file: %1").arg(cookieError));
|
|
|
|
delete auth;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (methods.testFlag(ProtocolInfoCommand::AuthHashedPassword) && !authPassword.isEmpty())
|
|
|
|
{
|
|
|
|
usePasswordAuth:
|
2017-12-26 11:21:57 -05:00
|
|
|
torCtrlDebug() << "torctrl: Using hashed password authentication" << std::endl;
|
2017-12-21 04:43:28 -05:00
|
|
|
data = auth->build(authPassword);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (methods.testFlag(ProtocolInfoCommand::AuthHashedPassword))
|
|
|
|
setError(QStringLiteral("Tor requires a control password to connect, but no password is configured."));
|
|
|
|
else
|
|
|
|
setError(QStringLiteral("Tor is not configured to accept any supported authentication methods."));
|
|
|
|
delete auth;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
socket->sendCommand(auth, data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorControlPrivate::getTorInfo()
|
|
|
|
{
|
|
|
|
Q_ASSERT(q->isConnected());
|
|
|
|
|
|
|
|
GetConfCommand *command = new GetConfCommand(GetConfCommand::GetInfo);
|
|
|
|
connect(command, &TorControlCommand::finished, this, &TorControlPrivate::getTorInfoReply);
|
|
|
|
|
|
|
|
QList<QByteArray> keys;
|
|
|
|
keys << QByteArray("status/circuit-established") << QByteArray("status/bootstrap-phase");
|
|
|
|
|
|
|
|
/* If these are set in the config, they override the automatic behavior. */
|
|
|
|
SettingsObject settings(QStringLiteral("tor"));
|
|
|
|
QHostAddress forceAddress(settings.read("socksAddress").toString());
|
|
|
|
quint16 port = (quint16)settings.read("socksPort").toInt();
|
|
|
|
|
|
|
|
if (!forceAddress.isNull() && port) {
|
2017-12-26 11:21:57 -05:00
|
|
|
torCtrlDebug() << "torctrl: Using manually specified SOCKS connection settings";
|
2017-12-21 04:43:28 -05:00
|
|
|
socksAddress = forceAddress;
|
|
|
|
socksPort = port;
|
|
|
|
emit q->connectivityChanged();
|
|
|
|
} else
|
|
|
|
keys << QByteArray("net/listeners/socks");
|
|
|
|
|
|
|
|
socket->sendCommand(command, command->build(keys));
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorControlPrivate::getTorInfoReply()
|
|
|
|
{
|
|
|
|
GetConfCommand *command = qobject_cast<GetConfCommand*>(sender());
|
|
|
|
if (!command || !q->isConnected())
|
|
|
|
return;
|
|
|
|
|
|
|
|
QList<QByteArray> listenAddresses = splitQuotedStrings(command->get(QByteArray("net/listeners/socks")).toString().toLatin1(), ' ');
|
|
|
|
for (QList<QByteArray>::Iterator it = listenAddresses.begin(); it != listenAddresses.end(); ++it) {
|
|
|
|
QByteArray value = unquotedString(*it);
|
|
|
|
int sepp = value.indexOf(':');
|
|
|
|
QHostAddress address(QString::fromLatin1(value.mid(0, sepp)));
|
|
|
|
quint16 port = (quint16)value.mid(sepp+1).toUInt();
|
|
|
|
|
|
|
|
/* Use the first address that matches the one used for this control connection. If none do,
|
|
|
|
* just use the first address and rely on the user to reconfigure if necessary (not a problem;
|
|
|
|
* their setup is already very customized) */
|
|
|
|
if (socksAddress.isNull() || address == socket->peerAddress()) {
|
|
|
|
socksAddress = address;
|
|
|
|
socksPort = port;
|
|
|
|
if (address == socket->peerAddress())
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* It is not immediately an error to have no SOCKS address; when DisableNetwork is set there won't be a
|
|
|
|
* listener yet. To handle that situation, we'll try to read the socks address again when TorReady state
|
|
|
|
* is reached. */
|
|
|
|
if (!socksAddress.isNull()) {
|
2017-12-26 11:21:57 -05:00
|
|
|
torCtrlDebug() << "torctrl: SOCKS address is " << socksAddress.toString().toStdString() << ":" << socksPort << std::endl;
|
2017-12-21 04:43:28 -05:00
|
|
|
emit q->connectivityChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (command->get(QByteArray("status/circuit-established")).toInt() == 1) {
|
2017-12-26 11:21:57 -05:00
|
|
|
torCtrlDebug() << "torctrl: Tor indicates that circuits have been established; state is TorReady" << std::endl;
|
2017-12-21 04:43:28 -05:00
|
|
|
setTorStatus(TorControl::TorReady);
|
|
|
|
} else {
|
|
|
|
setTorStatus(TorControl::TorOffline);
|
|
|
|
}
|
|
|
|
|
|
|
|
QByteArray bootstrap = command->get(QByteArray("status/bootstrap-phase")).toString().toLatin1();
|
|
|
|
if (!bootstrap.isEmpty())
|
|
|
|
updateBootstrap(splitQuotedStrings(bootstrap, ' '));
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorControl::addHiddenService(HiddenService *service)
|
|
|
|
{
|
|
|
|
if (d->services.contains(service))
|
|
|
|
return;
|
|
|
|
|
|
|
|
d->services.append(service);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorControlPrivate::publishServices()
|
|
|
|
{
|
2017-12-26 11:21:57 -05:00
|
|
|
torCtrlDebug() << "Publish Services... " ;
|
|
|
|
|
2017-12-21 04:43:28 -05:00
|
|
|
Q_ASSERT(q->isConnected());
|
|
|
|
if (services.isEmpty())
|
2017-12-26 11:21:57 -05:00
|
|
|
{
|
|
|
|
std::cerr << "No service regstered!" << std::endl;
|
2017-12-21 04:43:28 -05:00
|
|
|
return;
|
2017-12-26 11:21:57 -05:00
|
|
|
}
|
|
|
|
std::cerr << std::endl;
|
2017-12-21 04:43:28 -05:00
|
|
|
|
|
|
|
SettingsObject settings(QStringLiteral("tor"));
|
|
|
|
if (settings.read("neverPublishServices").toBool())
|
|
|
|
{
|
2017-12-26 11:21:57 -05:00
|
|
|
torCtrlDebug() << "torctrl: Skipping service publication because neverPublishService is enabled" << std::endl;
|
2017-12-21 04:43:28 -05:00
|
|
|
|
|
|
|
/* Call servicePublished under the assumption that they're published externally. */
|
|
|
|
for (QList<HiddenService*>::Iterator it = services.begin(); it != services.end(); ++it)
|
|
|
|
(*it)->servicePublished();
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (q->torVersionAsNewAs(QStringLiteral("0.2.7"))) {
|
|
|
|
foreach (HiddenService *service, services) {
|
|
|
|
if (service->hostname().isEmpty())
|
2017-12-26 11:21:57 -05:00
|
|
|
torCtrlDebug() << "torctrl: Creating a new hidden service" << std::endl;
|
2017-12-21 04:43:28 -05:00
|
|
|
else
|
2017-12-26 11:21:57 -05:00
|
|
|
torCtrlDebug() << "torctrl: Publishing hidden service: " << service->hostname().toStdString() << std::endl;
|
2017-12-21 04:43:28 -05:00
|
|
|
AddOnionCommand *onionCommand = new AddOnionCommand(service);
|
|
|
|
QObject::connect(onionCommand, &AddOnionCommand::succeeded, service, &HiddenService::servicePublished);
|
|
|
|
socket->sendCommand(onionCommand, onionCommand->build());
|
|
|
|
}
|
|
|
|
} else {
|
2017-12-26 11:21:57 -05:00
|
|
|
torCtrlDebug() << "torctrl: Using legacy SETCONF hidden service configuration for tor" << torVersion.toStdString() << std::endl;
|
2017-12-21 04:43:28 -05:00
|
|
|
SetConfCommand *command = new SetConfCommand;
|
|
|
|
QList<QPair<QByteArray,QByteArray> > torConfig;
|
|
|
|
|
|
|
|
foreach (HiddenService *service, services)
|
|
|
|
{
|
|
|
|
if (service->dataPath().isEmpty())
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (service->privateKey().isLoaded() && !QFile::exists(service->dataPath() + QStringLiteral("/private_key"))) {
|
|
|
|
// This case can happen if tor is downgraded after the profile is created
|
|
|
|
qWarning() << "Cannot publish ephemeral hidden services with this version of tor; skipping";
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2017-12-26 11:21:57 -05:00
|
|
|
torCtrlDebug() << "torctrl: Configuring hidden service at" << service->dataPath().toStdString() << std::endl;
|
2017-12-21 04:43:28 -05:00
|
|
|
|
|
|
|
QDir dir(service->dataPath());
|
|
|
|
torConfig.append(qMakePair(QByteArray("HiddenServiceDir"), dir.absolutePath().toLocal8Bit()));
|
|
|
|
|
|
|
|
const QList<HiddenService::Target> &targets = service->targets();
|
|
|
|
for (QList<HiddenService::Target>::ConstIterator tit = targets.begin(); tit != targets.end(); ++tit)
|
|
|
|
{
|
|
|
|
QString target = QString::fromLatin1("%1 %2:%3").arg(tit->servicePort)
|
|
|
|
.arg(tit->targetAddress.toString())
|
|
|
|
.arg(tit->targetPort);
|
|
|
|
torConfig.append(qMakePair(QByteArray("HiddenServicePort"), target.toLatin1()));
|
|
|
|
}
|
|
|
|
|
|
|
|
QObject::connect(command, &SetConfCommand::setConfSucceeded, service, &HiddenService::servicePublished);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!torConfig.isEmpty())
|
|
|
|
socket->sendCommand(command, command->build(torConfig));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorControl::shutdown()
|
|
|
|
{
|
|
|
|
if (!hasOwnership()) {
|
|
|
|
qWarning() << "torctrl: Ignoring shutdown command for a tor instance I don't own";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
d->socket->sendCommand("SIGNAL SHUTDOWN\r\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorControl::shutdownSync()
|
|
|
|
{
|
|
|
|
if (!hasOwnership()) {
|
|
|
|
qWarning() << "torctrl: Ignoring shutdown command for a tor instance I don't own";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
shutdown();
|
|
|
|
while (d->socket->bytesToWrite())
|
|
|
|
{
|
|
|
|
if (!d->socket->waitForBytesWritten(5000))
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorControlPrivate::statusEvent(int code, const QByteArray &data)
|
|
|
|
{
|
|
|
|
Q_UNUSED(code);
|
|
|
|
|
|
|
|
QList<QByteArray> tokens = splitQuotedStrings(data.trimmed(), ' ');
|
|
|
|
if (tokens.size() < 3)
|
|
|
|
return;
|
|
|
|
|
2017-12-26 11:21:57 -05:00
|
|
|
torCtrlDebug() << "torctrl: status event:" << QString(data.trimmed()).toStdString() << std::endl;
|
2017-12-21 04:43:28 -05:00
|
|
|
|
|
|
|
if (tokens[2] == "CIRCUIT_ESTABLISHED") {
|
|
|
|
setTorStatus(TorControl::TorReady);
|
|
|
|
} else if (tokens[2] == "CIRCUIT_NOT_ESTABLISHED") {
|
|
|
|
setTorStatus(TorControl::TorOffline);
|
|
|
|
} else if (tokens[2] == "BOOTSTRAP") {
|
|
|
|
tokens.takeFirst();
|
|
|
|
updateBootstrap(tokens);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorControlPrivate::updateBootstrap(const QList<QByteArray> &data)
|
|
|
|
{
|
|
|
|
bootstrapStatus.clear();
|
|
|
|
// WARN or NOTICE
|
|
|
|
bootstrapStatus[QStringLiteral("severity")] = data.value(0);
|
|
|
|
for (int i = 1; i < data.size(); i++) {
|
|
|
|
int equals = data[i].indexOf('=');
|
|
|
|
QString key = QString::fromLatin1(data[i].mid(0, equals));
|
|
|
|
QString value;
|
|
|
|
if (equals >= 0)
|
|
|
|
value = QString::fromLatin1(unquotedString(data[i].mid(equals + 1)));
|
|
|
|
bootstrapStatus[key.toLower()] = value;
|
|
|
|
}
|
|
|
|
|
2017-12-26 11:21:57 -05:00
|
|
|
//torCtrlDebug() << bootstrapStatus << std::endl;
|
|
|
|
|
2017-12-21 04:43:28 -05:00
|
|
|
emit q->bootstrapStatusChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
QObject *TorControl::getConfiguration(const QString &options)
|
|
|
|
{
|
|
|
|
GetConfCommand *command = new GetConfCommand(GetConfCommand::GetConf);
|
|
|
|
d->socket->sendCommand(command, command->build(options.toLatin1()));
|
|
|
|
|
|
|
|
//QQmlEngine::setObjectOwnership(command, QQmlEngine::CppOwnership);
|
|
|
|
return command;
|
|
|
|
}
|
|
|
|
|
|
|
|
QObject *TorControl::setConfiguration(const QVariantMap &options)
|
|
|
|
{
|
|
|
|
SetConfCommand *command = new SetConfCommand;
|
|
|
|
command->setResetMode(true);
|
|
|
|
d->socket->sendCommand(command, command->build(options));
|
|
|
|
|
|
|
|
//QQmlEngine::setObjectOwnership(command, QQmlEngine::CppOwnership);
|
|
|
|
return command;
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace Tor {
|
|
|
|
|
|
|
|
class SaveConfigOperation : public PendingOperation
|
|
|
|
{
|
|
|
|
Q_OBJECT
|
|
|
|
|
|
|
|
public:
|
|
|
|
SaveConfigOperation(QObject *parent)
|
|
|
|
: PendingOperation(parent), command(0)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void start(TorControlSocket *socket)
|
|
|
|
{
|
|
|
|
Q_ASSERT(!command);
|
|
|
|
command = new GetConfCommand(GetConfCommand::GetInfo);
|
|
|
|
QObject::connect(command, &TorControlCommand::finished, this, &SaveConfigOperation::configTextReply);
|
|
|
|
socket->sendCommand(command, command->build(QList<QByteArray>() << "config-text" << "config-file"));
|
|
|
|
}
|
|
|
|
|
|
|
|
private slots:
|
|
|
|
void configTextReply()
|
|
|
|
{
|
|
|
|
Q_ASSERT(command);
|
|
|
|
if (!command)
|
|
|
|
return;
|
|
|
|
|
|
|
|
QString path = QFile::decodeName(command->get("config-file").toByteArray());
|
|
|
|
if (path.isEmpty()) {
|
|
|
|
finishWithError(QStringLiteral("Cannot write torrc without knowing its path"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Out of paranoia, refuse to write any file not named 'torrc', or if the
|
|
|
|
// file doesn't exist
|
|
|
|
QFileInfo fileInfo(path);
|
|
|
|
if (fileInfo.fileName() != QStringLiteral("torrc") || !fileInfo.exists()) {
|
|
|
|
finishWithError(QStringLiteral("Refusing to write torrc to unacceptable path %1").arg(path));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
QSaveFile file(path);
|
|
|
|
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
|
|
|
finishWithError(QStringLiteral("Failed opening torrc file for writing: %1").arg(file.errorString()));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove these keys when writing torrc; they are set at runtime and contain
|
|
|
|
// absolute paths or port numbers
|
|
|
|
static const char *bannedKeys[] = {
|
|
|
|
"ControlPortWriteToFile",
|
|
|
|
"DataDirectory",
|
|
|
|
"HiddenServiceDir",
|
|
|
|
"HiddenServicePort",
|
|
|
|
0
|
|
|
|
};
|
|
|
|
|
|
|
|
QVariantList configText = command->get("config-text").toList();
|
|
|
|
foreach (const QVariant &value, configText) {
|
|
|
|
QByteArray line = value.toByteArray();
|
|
|
|
|
|
|
|
bool skip = false;
|
|
|
|
for (const char **key = bannedKeys; *key; key++) {
|
|
|
|
if (line.startsWith(*key)) {
|
|
|
|
skip = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (skip)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
file.write(line);
|
|
|
|
file.write("\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!file.commit()) {
|
|
|
|
finishWithError(QStringLiteral("Failed writing torrc: %1").arg(file.errorString()));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-12-26 11:21:57 -05:00
|
|
|
torCtrlDebug() << "torctrl: Wrote torrc file" << std::endl;
|
2017-12-21 04:43:28 -05:00
|
|
|
finishWithSuccess();
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
GetConfCommand *command;
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
PendingOperation *TorControl::saveConfiguration()
|
|
|
|
{
|
|
|
|
if (!hasOwnership()) {
|
|
|
|
qWarning() << "torctrl: Ignoring save configuration command for a tor instance I don't own";
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
SaveConfigOperation *operation = new SaveConfigOperation(this);
|
|
|
|
QObject::connect(operation, &PendingOperation::finished, operation, &QObject::deleteLater);
|
|
|
|
operation->start(d->socket);
|
|
|
|
|
|
|
|
//QQmlEngine::setObjectOwnership(operation, QQmlEngine::CppOwnership);
|
|
|
|
return operation;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool TorControl::hasOwnership() const
|
|
|
|
{
|
|
|
|
return d->hasOwnership;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorControl::takeOwnership()
|
|
|
|
{
|
|
|
|
d->hasOwnership = true;
|
|
|
|
d->socket->sendCommand("TAKEOWNERSHIP\r\n");
|
|
|
|
|
|
|
|
// Reset PID-based polling
|
|
|
|
QVariantMap options;
|
|
|
|
options[QStringLiteral("__OwningControllerProcess")] = QVariant();
|
|
|
|
setConfiguration(options);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool TorControl::torVersionAsNewAs(const QString &match) const
|
|
|
|
{
|
|
|
|
QRegularExpression r(QStringLiteral("[.-]"));
|
|
|
|
QStringList split = torVersion().split(r);
|
|
|
|
QStringList matchSplit = match.split(r);
|
|
|
|
|
|
|
|
for (int i = 0; i < matchSplit.size(); i++) {
|
|
|
|
if (i >= split.size())
|
|
|
|
return false;
|
|
|
|
bool ok1 = false, ok2 = false;
|
|
|
|
int currentVal = split[i].toInt(&ok1);
|
|
|
|
int matchVal = matchSplit[i].toInt(&ok2);
|
|
|
|
if (!ok1 || !ok2)
|
|
|
|
return false;
|
|
|
|
if (currentVal > matchVal)
|
|
|
|
return true;
|
|
|
|
if (currentVal < matchVal)
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Versions are equal, up to the length of match
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
#include "TorControl.moc"
|
|
|
|
|