feather/src/utils/TorManager.cpp

281 lines
8.6 KiB
C++
Raw Normal View History

// SPDX-License-Identifier: BSD-3-Clause
2022-02-10 10:26:41 +00:00
// SPDX-FileCopyrightText: 2020-2022 The Monero Project
2021-05-02 18:22:38 +00:00
#include "utils/TorManager.h"
#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"
#include "appcontext.h"
2021-02-05 10:53:18 +00:00
#include "config-feather.h"
2021-05-02 18:22:38 +00:00
TorManager::TorManager(QObject *parent)
: QObject(parent)
, m_checkConnectionTimer(new QTimer(this))
{
2021-05-02 18:22:38 +00:00
connect(m_checkConnectionTimer, &QTimer::timeout, this, &TorManager::checkConnection);
2021-03-12 18:26:48 +00:00
this->torDir = Config::defaultConfigDir().filePath("tor");
this->torDataPath = QDir(this->torDir).filePath("data");
2021-05-02 18:22:38 +00:00
m_process.setProcessChannelMode(QProcess::MergedChannels);
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);
}
2021-05-02 18:22:38 +00:00
QPointer<TorManager> TorManager::m_instance(nullptr);
2021-05-02 18:22:38 +00:00
void TorManager::init() {
m_localTor = !shouldStartTorDaemon();
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
}
}
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) {
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));
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));
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;
}
2021-05-02 18:22:38 +00:00
void TorManager::checkConnection() {
// 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()) {
this->setConnectionState(true);
2021-05-02 18:22:38 +00:00
}
2021-05-02 18:22:38 +00:00
else if (WhonixOS::detect()) {
this->setConnectionState(true);
2021-05-02 18:22:38 +00:00
}
2021-05-02 18:22:38 +00:00
else if (TailsOS::detect()) {
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));
}
2021-05-02 18:22:38 +00:00
else {
this->setConnectionState(Utils::portOpen(featherTorHost, featherTorPort));
}
}
2021-05-02 18:22:38 +00:00
void TorManager::setConnectionState(bool connected) {
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) {
qWarning() << "Tor started, awaiting bootstrap";
2021-05-02 18:22:38 +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() {
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) {
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() {
2022-02-23 16:04:23 +00:00
QString torBin = "tor";
#if defined(Q_OS_WIN)
2022-02-23 16:04:23 +00:00
torBin += ".exe";
#endif
2021-02-05 10:53:18 +00:00
2022-02-23 16:04:23 +00:00
this->torPath = QDir(this->torDir).filePath(torBin);
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";
2022-02-23 16:04:23 +00:00
return false;
}
2021-02-05 10:53:18 +00:00
}
2022-02-23 16:04:23 +00:00
if (embeddedVersion > filesystemVersion) {
QDirIterator it(":/assets/tor", QDirIterator::Subdirectories);
while (it.hasNext()) {
QString assetFile = it.next();
QFileInfo assetFileInfo = QFileInfo(assetFile);
QFile f(assetFile);
QString filePath = QDir(this->torDir).filePath(assetFileInfo.fileName());
f.copy(filePath);
f.close();
}
qInfo() << "Wrote Tor binaries to: " << this->torDir;
}
#if defined(Q_OS_UNIX)
2022-02-23 16:04:23 +00:00
QFile tor(this->torPath);
tor.setPermissions(QFile::ExeUser | QFile::ExeGroup | QFile::ExeOther
| QFile::ReadOwner | QFile::ReadGroup | QFile::ReadOther);
#endif
2022-02-23 16:04:23 +00:00
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) {
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();
}
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());
}
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
}