2020-10-07 10:36:04 +00:00
|
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
2022-02-10 10:26:41 +00:00
|
|
|
// SPDX-FileCopyrightText: 2020-2022 The Monero Project
|
2020-10-07 10:36:04 +00:00
|
|
|
|
2021-05-02 18:22:38 +00:00
|
|
|
#include "utils/TorManager.h"
|
|
|
|
|
2020-10-07 10:36:04 +00:00
|
|
|
#include <QScreen>
|
|
|
|
#include <QDesktopWidget>
|
|
|
|
#include <QDesktopServices>
|
2021-02-05 10:53:18 +00:00
|
|
|
#include <QRegularExpression>
|
2021-05-02 18:22:38 +00:00
|
|
|
|
2021-07-06 19:36:27 +00:00
|
|
|
#include "utils/Utils.h"
|
2021-05-25 13:06:38 +00:00
|
|
|
#include "utils/os/tails.h"
|
2020-10-07 10:36:04 +00:00
|
|
|
#include "appcontext.h"
|
2021-02-05 10:53:18 +00:00
|
|
|
#include "config-feather.h"
|
2020-10-07 10:36:04 +00:00
|
|
|
|
2021-05-02 18:22:38 +00:00
|
|
|
TorManager::TorManager(QObject *parent)
|
|
|
|
: QObject(parent)
|
|
|
|
, m_checkConnectionTimer(new QTimer(this))
|
2020-10-07 10:36:04 +00:00
|
|
|
{
|
2021-05-02 18:22:38 +00:00
|
|
|
connect(m_checkConnectionTimer, &QTimer::timeout, this, &TorManager::checkConnection);
|
2020-10-07 10:36:04 +00:00
|
|
|
|
2021-03-12 18:26:48 +00:00
|
|
|
this->torDir = Config::defaultConfigDir().filePath("tor");
|
2020-10-07 10:36:04 +00:00
|
|
|
this->torDataPath = QDir(this->torDir).filePath("data");
|
|
|
|
|
2021-05-02 18:22:38 +00:00
|
|
|
m_process.setProcessChannelMode(QProcess::MergedChannels);
|
2020-10-07 10:36:04 +00:00
|
|
|
|
2021-05-02 18:22:38 +00:00
|
|
|
connect(&m_process, &QProcess::readyReadStandardOutput, this, &TorManager::handleProcessOutput);
|
|
|
|
connect(&m_process, &QProcess::errorOccurred, this, &TorManager::handleProcessError);
|
|
|
|
connect(&m_process, &QProcess::stateChanged, this, &TorManager::stateChanged);
|
|
|
|
}
|
2020-10-07 10:36:04 +00:00
|
|
|
|
2021-05-02 18:22:38 +00:00
|
|
|
QPointer<TorManager> TorManager::m_instance(nullptr);
|
2020-10-17 21:14:56 +00:00
|
|
|
|
2021-05-02 18:22:38 +00:00
|
|
|
void TorManager::init() {
|
|
|
|
m_localTor = !shouldStartTorDaemon();
|
2020-10-07 10:36:04 +00:00
|
|
|
|
2021-05-02 18:22:38 +00:00
|
|
|
auto state = m_process.state();
|
|
|
|
if (m_localTor && (state == QProcess::ProcessState::Running || state == QProcess::ProcessState::Starting)) {
|
|
|
|
m_process.kill();
|
2020-10-09 00:13:08 +00:00
|
|
|
}
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
|
2021-05-02 18:22:38 +00:00
|
|
|
void TorManager::stop() {
|
2020-10-09 00:13:08 +00:00
|
|
|
m_process.kill();
|
|
|
|
}
|
|
|
|
|
2021-05-02 18:22:38 +00:00
|
|
|
void TorManager::start() {
|
2021-05-04 23:09:19 +00:00
|
|
|
m_checkConnectionTimer->start(5000);
|
|
|
|
|
2021-05-02 18:22:38 +00:00
|
|
|
if (m_localTor) {
|
2020-10-07 10:36:04 +00:00
|
|
|
this->checkConnection();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto state = m_process.state();
|
|
|
|
if (state == QProcess::ProcessState::Running || state == QProcess::ProcessState::Starting) {
|
|
|
|
this->errorMsg = "Can't start Tor, already running or starting";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-05-02 18:22:38 +00:00
|
|
|
if (Utils::portOpen(featherTorHost, featherTorPort)) {
|
|
|
|
this->errorMsg = QString("Unable to start Tor on %1:%2. Port already in use.").arg(featherTorHost, QString::number(featherTorPort));
|
2020-10-07 10:36:04 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
qDebug() << QString("Start process: %1").arg(this->torPath);
|
|
|
|
|
|
|
|
m_restarts += 1;
|
|
|
|
if (m_restarts > 4) {
|
|
|
|
this->errorMsg = "Tor failed to start: maximum retries exceeded";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
QStringList arguments;
|
|
|
|
|
|
|
|
arguments << "--ignore-missing-torrc";
|
2021-05-02 18:22:38 +00:00
|
|
|
arguments << "--SocksPort" << QString("%1:%2").arg(featherTorHost, QString::number(featherTorPort));
|
2020-10-07 10:36:04 +00:00
|
|
|
arguments << "--TruncateLogFile" << "1";
|
|
|
|
arguments << "--DataDirectory" << this->torDataPath;
|
|
|
|
arguments << "--Log" << "notice";
|
|
|
|
arguments << "--pidfile" << QDir(this->torDataPath).filePath("tor.pid");
|
|
|
|
|
|
|
|
qDebug() << QString("%1 %2").arg(this->torPath, arguments.join(" "));
|
|
|
|
|
|
|
|
m_process.start(this->torPath, arguments);
|
2021-05-02 18:22:38 +00:00
|
|
|
m_started = true;
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
|
2021-05-02 18:22:38 +00:00
|
|
|
void TorManager::checkConnection() {
|
2020-10-07 10:36:04 +00:00
|
|
|
// We might not be able to connect to localhost if torsocks is used to start feather
|
2021-05-02 18:22:38 +00:00
|
|
|
if (Utils::isTorsocks()) {
|
2020-10-07 10:36:04 +00:00
|
|
|
this->setConnectionState(true);
|
2021-05-02 18:22:38 +00:00
|
|
|
}
|
2020-10-07 10:36:04 +00:00
|
|
|
|
2021-05-02 18:22:38 +00:00
|
|
|
else if (WhonixOS::detect()) {
|
2020-10-07 10:36:04 +00:00
|
|
|
this->setConnectionState(true);
|
2021-05-02 18:22:38 +00:00
|
|
|
}
|
2020-10-07 10:36:04 +00:00
|
|
|
|
2021-05-02 18:22:38 +00:00
|
|
|
else if (TailsOS::detect()) {
|
2020-10-07 10:36:04 +00:00
|
|
|
QStringList args = QStringList() << "--quiet" << "is-active" << "tails-tor-has-bootstrapped.target";
|
|
|
|
int code = QProcess::execute("/bin/systemctl", args);
|
|
|
|
|
|
|
|
this->setConnectionState(code == 0);
|
|
|
|
}
|
|
|
|
|
2021-05-02 18:22:38 +00:00
|
|
|
else if (m_localTor) {
|
|
|
|
QString host = config()->get(Config::socks5Host).toString();
|
|
|
|
quint16 port = config()->get(Config::socks5Port).toString().toUShort();
|
|
|
|
this->setConnectionState(Utils::portOpen(host, port));
|
|
|
|
}
|
2020-10-07 10:36:04 +00:00
|
|
|
|
2021-05-02 18:22:38 +00:00
|
|
|
else {
|
|
|
|
this->setConnectionState(Utils::portOpen(featherTorHost, featherTorPort));
|
|
|
|
}
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
|
2021-05-02 18:22:38 +00:00
|
|
|
void TorManager::setConnectionState(bool connected) {
|
2020-10-07 10:36:04 +00:00
|
|
|
this->torConnected = connected;
|
|
|
|
emit connectionStateChanged(connected);
|
|
|
|
}
|
|
|
|
|
2021-05-02 18:22:38 +00:00
|
|
|
void TorManager::stateChanged(QProcess::ProcessState state) {
|
|
|
|
if (state == QProcess::ProcessState::Running) {
|
2020-10-07 10:36:04 +00:00
|
|
|
qWarning() << "Tor started, awaiting bootstrap";
|
2021-05-02 18:22:38 +00:00
|
|
|
}
|
2020-10-07 10:36:04 +00:00
|
|
|
else if (state == QProcess::ProcessState::NotRunning) {
|
|
|
|
this->setConnectionState(false);
|
|
|
|
|
|
|
|
if (m_stopRetries)
|
|
|
|
return;
|
|
|
|
|
|
|
|
QTimer::singleShot(1000, [=] {
|
|
|
|
this->start();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-02 18:22:38 +00:00
|
|
|
void TorManager::handleProcessOutput() {
|
2020-10-07 10:36:04 +00:00
|
|
|
QByteArray output = m_process.readAllStandardOutput();
|
|
|
|
this->torLogs.append(Utils::barrayToString(output));
|
|
|
|
emit logsUpdated();
|
|
|
|
if(output.contains(QByteArray("Bootstrapped 100%"))) {
|
|
|
|
qDebug() << "Tor OK";
|
|
|
|
this->setConnectionState(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
qDebug() << output;
|
|
|
|
}
|
|
|
|
|
2021-05-02 18:22:38 +00:00
|
|
|
void TorManager::handleProcessError(QProcess::ProcessError error) {
|
2020-10-07 10:36:04 +00:00
|
|
|
if (error == QProcess::ProcessError::Crashed)
|
|
|
|
qWarning() << "Tor crashed or killed";
|
|
|
|
else if (error == QProcess::ProcessError::FailedToStart) {
|
|
|
|
this->errorMsg = "Tor binary failed to start: " + this->torPath;
|
|
|
|
this->m_stopRetries = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-02 18:22:38 +00:00
|
|
|
bool TorManager::unpackBins() {
|
2020-10-07 10:36:04 +00:00
|
|
|
QString torFile;
|
2020-10-09 21:22:02 +00:00
|
|
|
|
2020-10-17 21:14:56 +00:00
|
|
|
// On MacOS write libevent to disk
|
|
|
|
#if defined(Q_OS_MAC)
|
|
|
|
QString libEvent = ":/assets/exec/libevent-2.1.7.dylib";
|
2020-10-09 21:22:02 +00:00
|
|
|
if (Utils::fileExists(libEvent)) {
|
|
|
|
QFile e(libEvent);
|
|
|
|
QFileInfo eventInfo(e);
|
|
|
|
auto libEventPath = QDir(this->torDir).filePath(eventInfo.fileName());
|
|
|
|
qDebug() << libEventPath;
|
|
|
|
e.copy(libEventPath);
|
|
|
|
e.close();
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2020-10-17 21:14:56 +00:00
|
|
|
torFile = ":/assets/exec/tor";
|
2020-10-07 10:36:04 +00:00
|
|
|
if (!Utils::fileExists(torFile))
|
|
|
|
return false;
|
2020-10-17 21:14:56 +00:00
|
|
|
|
|
|
|
// write to disk
|
2020-10-07 10:36:04 +00:00
|
|
|
QFile f(torFile);
|
|
|
|
QFileInfo fileInfo(f);
|
|
|
|
this->torPath = QDir(this->torDir).filePath(fileInfo.fileName());
|
2021-02-05 10:53:18 +00:00
|
|
|
|
2020-10-21 15:56:39 +00:00
|
|
|
#if defined(Q_OS_WIN)
|
|
|
|
if(!this->torPath.endsWith(".exe"))
|
|
|
|
this->torPath += ".exe";
|
|
|
|
#endif
|
2021-02-05 10:53:18 +00:00
|
|
|
|
2021-05-02 18:22:38 +00:00
|
|
|
SemanticVersion embeddedVersion = SemanticVersion::fromString(QString(TOR_VERSION));
|
|
|
|
SemanticVersion filesystemVersion = this->getVersion(torPath);
|
2021-02-05 10:53:18 +00:00
|
|
|
qDebug() << QString("Tor versions: embedded %1, filesystem %2").arg(embeddedVersion.toString(), filesystemVersion.toString());
|
2021-05-02 18:22:38 +00:00
|
|
|
if (SemanticVersion::isValid(filesystemVersion) && (embeddedVersion > filesystemVersion)) {
|
2021-02-05 18:43:14 +00:00
|
|
|
qInfo() << "Embedded version is newer, overwriting.";
|
|
|
|
QFile::setPermissions(torPath, QFile::ReadOther | QFile::WriteOther);
|
|
|
|
if (!QFile::remove(torPath)) {
|
|
|
|
qWarning() << "Unable to remove old Tor binary";
|
|
|
|
};
|
2021-02-05 10:53:18 +00:00
|
|
|
}
|
|
|
|
|
2020-10-17 21:14:56 +00:00
|
|
|
qDebug() << "Writing Tor executable to " << this->torPath;
|
2020-10-07 10:36:04 +00:00
|
|
|
f.copy(torPath);
|
|
|
|
f.close();
|
|
|
|
|
|
|
|
#if defined(Q_OS_UNIX)
|
|
|
|
QFile torBin(this->torPath);
|
2021-05-02 18:22:38 +00:00
|
|
|
torBin.setPermissions(QFile::ExeUser | QFile::ExeGroup | QFile::ExeOther
|
|
|
|
| QFile::ReadOwner | QFile::ReadGroup | QFile::ReadOther);
|
2020-10-07 10:36:04 +00:00
|
|
|
#endif
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-05-02 18:22:38 +00:00
|
|
|
bool TorManager::isLocalTor() {
|
|
|
|
return m_localTor;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool TorManager::isStarted() {
|
|
|
|
return m_started;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool TorManager::shouldStartTorDaemon() {
|
|
|
|
QString torHost = config()->get(Config::socks5Host).toString();
|
|
|
|
quint16 torPort = config()->get(Config::socks5Port).toString().toUShort();
|
|
|
|
QString torHostPort = QString("%1:%2").arg(torHost, QString::number(torPort));
|
|
|
|
|
|
|
|
// Don't start a Tor daemon if Feather is run with Torsocks
|
|
|
|
if (Utils::isTorsocks()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Don't start a Tor daemon on privacy OSes
|
|
|
|
if (TailsOS::detect() || WhonixOS::detect()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Don't start a Tor daemon if we don't have one
|
|
|
|
#ifndef HAS_TOR_BIN
|
|
|
|
qWarning() << "Feather built without embedded Tor. Assuming --use-local-tor";
|
|
|
|
return false;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// Don't start a Tor daemon if --use-local-tor is specified
|
|
|
|
if (config()->get(Config::useLocalTor).toBool()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Don't start a Tor daemon if one is already running
|
|
|
|
if (Utils::portOpen(torHost, torPort)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool unpacked = this->unpackBins();
|
|
|
|
if (!unpacked) {
|
|
|
|
// Don't try to start a Tor daemon if unpacking failed
|
|
|
|
qWarning() << "Error unpacking embedded Tor. Assuming --use-local-tor";
|
|
|
|
this->errorMsg = "Error unpacking embedded Tor. Assuming --use-local-tor";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Tor daemon (or other service) is already running on our port (19450)
|
|
|
|
if (Utils::portOpen(featherTorHost, featherTorPort)) {
|
2021-05-04 23:09:19 +00:00
|
|
|
// TODO: this is a hack, fix it later
|
|
|
|
config()->set(Config::socks5Host, featherTorHost);
|
|
|
|
config()->set(Config::socks5Port, featherTorPort);
|
2021-05-02 18:22:38 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
SemanticVersion TorManager::getVersion(const QString &fileName) {
|
2020-10-07 10:36:04 +00:00
|
|
|
QProcess process;
|
|
|
|
process.setProcessChannelMode(QProcess::MergedChannels);
|
|
|
|
process.start(this->torPath, QStringList() << "--version");
|
|
|
|
process.waitForFinished(-1);
|
|
|
|
QString output = process.readAllStandardOutput();
|
|
|
|
|
|
|
|
if(output.isEmpty()) {
|
|
|
|
qWarning() << "Could not grab Tor version";
|
2021-05-02 18:22:38 +00:00
|
|
|
return SemanticVersion();
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
2021-02-05 10:53:18 +00:00
|
|
|
|
2021-05-02 18:22:38 +00:00
|
|
|
return SemanticVersion::fromString(output);
|
2021-02-05 10:53:18 +00:00
|
|
|
}
|
|
|
|
|
2021-05-02 18:22:38 +00:00
|
|
|
TorManager* TorManager::instance()
|
|
|
|
{
|
|
|
|
if (!m_instance) {
|
|
|
|
m_instance = new TorManager(QCoreApplication::instance());
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
2021-02-05 10:53:18 +00:00
|
|
|
|
2021-05-02 18:22:38 +00:00
|
|
|
return m_instance;
|
2021-02-05 10:53:18 +00:00
|
|
|
}
|