2020-10-07 10:36:04 +00:00
// SPDX-License-Identifier: BSD-3-Clause
2020-12-26 19:56:06 +00:00
// Copyright (c) 2020-2021, The Monero Project.
2020-10-07 10:36:04 +00:00
# include <QScreen>
# include <QDesktopWidget>
# include <QDesktopServices>
2021-02-05 10:53:18 +00:00
# include <QRegularExpression>
2020-10-07 10:36:04 +00:00
# include "utils/utils.h"
# include "utils/tor.h"
# include "appcontext.h"
2021-02-05 10:53:18 +00:00
# include "config-feather.h"
2020-10-07 10:36:04 +00:00
QString Tor : : torHost = " 127.0.0.1 " ;
quint16 Tor : : torPort = 9050 ;
Tor : : Tor ( AppContext * ctx , QObject * parent )
: QObject ( parent )
, m_ctx ( ctx )
, m_checkConnectionTimer ( new QTimer ( this ) )
{
connect ( m_checkConnectionTimer , & QTimer : : timeout , this , & Tor : : checkConnection ) ;
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-04-04 22:46:48 +00:00
if ( m_ctx - > cmdargs - > isSet ( " tor-port " ) | | m_ctx - > cmdargs - > isSet ( " tor-host " ) ) {
if ( m_ctx - > cmdargs - > isSet ( " tor-host " ) )
Tor : : torHost = m_ctx - > cmdargs - > value ( " tor-host " ) ;
if ( m_ctx - > cmdargs - > isSet ( " tor-port " ) )
Tor : : torPort = m_ctx - > cmdargs - > value ( " tor-port " ) . toUShort ( ) ;
2020-10-07 10:36:04 +00:00
this - > localTor = true ;
if ( ! Utils : : portOpen ( Tor : : torHost , Tor : : torPort ) ) {
2021-04-04 22:46:48 +00:00
this - > errorMsg = QString ( " --tor-host || --tor-port were specified but no running Tor instance was found on %1:%2. " ) . arg ( Tor : : torHost , QString : : number ( Tor : : torPort ) ) ;
2020-10-07 10:36:04 +00:00
}
return ;
}
// Assume Tor is already running
this - > localTor = m_ctx - > cmdargs - > isSet ( " use-local-tor " ) ;
if ( this - > localTor & & ! Utils : : portOpen ( Tor : : torHost , Tor : : torPort ) ) {
this - > errorMsg = " --use-local-tor was specified but no running Tor instance found. " ;
}
if ( m_ctx - > isTorSocks | | m_ctx - > isTails | | m_ctx - > isWhonix | | Utils : : portOpen ( Tor : : torHost , Tor : : torPort ) )
this - > localTor = true ;
if ( this - > localTor ) {
return ;
}
2020-12-11 13:36:08 +00:00
# ifndef HAS_TOR_BIN
2020-10-17 21:14:56 +00:00
qCritical ( ) < < " Feather built without embedded Tor. Assuming --use-local-tor " ;
this - > localTor = true ;
return ;
# endif
2020-10-07 10:36:04 +00:00
bool unpacked = this - > unpackBins ( ) ;
if ( ! unpacked ) {
2020-10-17 21:14:56 +00:00
qCritical ( ) < < " Error unpacking embedded Tor. Assuming --use-local-tor " ;
2020-10-07 10:36:04 +00:00
this - > localTor = true ;
return ;
}
2020-10-09 00:13:08 +00:00
// Don't spawn Tor on default port to avoid conflicts
Tor : : torPort = 19450 ;
if ( Utils : : portOpen ( Tor : : torHost , Tor : : torPort ) ) {
this - > localTor = true ;
return ;
}
2020-10-07 10:36:04 +00:00
qDebug ( ) < < " Using embedded tor instance " ;
m_process . setProcessChannelMode ( QProcess : : MergedChannels ) ;
connect ( & m_process , & QProcess : : readyReadStandardOutput , this , & Tor : : handleProcessOutput ) ;
connect ( & m_process , & QProcess : : errorOccurred , this , & Tor : : handleProcessError ) ;
connect ( & m_process , & QProcess : : stateChanged , this , & Tor : : stateChanged ) ;
}
2020-10-09 00:13:08 +00:00
void Tor : : stop ( ) {
m_process . kill ( ) ;
}
2020-10-07 10:36:04 +00:00
void Tor : : start ( ) {
if ( this - > localTor ) {
this - > checkConnection ( ) ;
m_checkConnectionTimer - > start ( 5000 ) ;
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 ;
}
if ( Utils : : portOpen ( Tor : : torHost , Tor : : torPort ) ) {
this - > errorMsg = QString ( " Unable to start Tor on %1:%2. Port already in use. " ) . arg ( Tor : : torHost , Tor : : torPort ) ;
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 " ;
arguments < < " --SocksPort " < < QString ( " %1:%2 " ) . arg ( Tor : : torHost , QString : : number ( Tor : : torPort ) ) ;
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 ) ;
}
void Tor : : checkConnection ( ) {
// We might not be able to connect to localhost if torsocks is used to start feather
if ( m_ctx - > isTorSocks )
this - > setConnectionState ( true ) ;
else if ( m_ctx - > isWhonix )
this - > setConnectionState ( true ) ;
else if ( m_ctx - > isTails ) {
QStringList args = QStringList ( ) < < " --quiet " < < " is-active " < < " tails-tor-has-bootstrapped.target " ;
int code = QProcess : : execute ( " /bin/systemctl " , args ) ;
this - > setConnectionState ( code = = 0 ) ;
}
else if ( Utils : : portOpen ( Tor : : torHost , Tor : : torPort ) )
this - > setConnectionState ( true ) ;
else
this - > setConnectionState ( false ) ;
}
void Tor : : setConnectionState ( bool connected ) {
this - > torConnected = connected ;
emit connectionStateChanged ( connected ) ;
}
void Tor : : stateChanged ( QProcess : : ProcessState state ) {
if ( state = = QProcess : : ProcessState : : Running )
qWarning ( ) < < " Tor started, awaiting bootstrap " ;
else if ( state = = QProcess : : ProcessState : : NotRunning ) {
this - > setConnectionState ( false ) ;
if ( m_stopRetries )
return ;
QTimer : : singleShot ( 1000 , [ = ] {
this - > start ( ) ;
} ) ;
}
}
void Tor : : 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 ;
}
void Tor : : 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 ;
}
}
bool Tor : : unpackBins ( ) {
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
TorVersion embeddedVersion = this - > stringToVersion ( QString ( TOR_VERSION ) ) ;
TorVersion filesystemVersion = this - > getVersion ( torPath ) ;
qDebug ( ) < < QString ( " Tor versions: embedded %1, filesystem %2 " ) . arg ( embeddedVersion . toString ( ) , filesystemVersion . toString ( ) ) ;
2021-02-05 18:43:14 +00:00
if ( TorVersion : : isValid ( filesystemVersion ) & & ( embeddedVersion > filesystemVersion ) ) {
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 ) ;
torBin . setPermissions ( QFile : : ExeGroup | QFile : : ExeOther | QFile : : ExeOther | QFile : : ExeUser ) ;
# endif
return true ;
}
2021-02-05 10:53:18 +00:00
TorVersion Tor : : 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-02-05 10:53:18 +00:00
return TorVersion ( ) ;
2020-10-07 10:36:04 +00:00
}
2021-02-05 10:53:18 +00:00
return this - > stringToVersion ( output ) ;
}
TorVersion Tor : : stringToVersion ( const QString & version ) {
QRegularExpression re ( " (?<major> \\ d) \ \ . ( ? < minor > \ \ d ) \ \ . ( ? < patch > \ \ d ) \ \ . ( ? < release > \ \ d ) " ) ;
QRegularExpressionMatch match = re . match ( version ) ;
if ( ! match . hasMatch ( ) ) {
2020-10-07 10:36:04 +00:00
qWarning ( ) < < " Could not parse Tor version " ;
2021-02-05 10:53:18 +00:00
return TorVersion ( ) ;
2020-10-07 10:36:04 +00:00
}
2021-02-05 10:53:18 +00:00
return TorVersion ( match . captured ( " major " ) . toInt ( ) ,
match . captured ( " minor " ) . toInt ( ) ,
match . captured ( " patch " ) . toInt ( ) ,
match . captured ( " release " ) . toInt ( ) ) ;
}