turned TorProcess into a non-Qt object, using built-in popen3 system

This commit is contained in:
csoler 2021-12-02 23:15:06 +01:00
parent d7afbea1dd
commit b03802fa1b
8 changed files with 174 additions and 187 deletions

View File

@ -742,7 +742,6 @@ HEADERS += tor/AddOnionCommand.h \
tor/TorControlSocket.h \ tor/TorControlSocket.h \
tor/TorManager.h \ tor/TorManager.h \
tor/TorProcess.h \ tor/TorProcess.h \
tor/TorProcess_p.h \
tor/Useful.h tor/Useful.h
SOURCES += tor/AddOnionCommand.cpp \ SOURCES += tor/AddOnionCommand.cpp \

View File

@ -179,8 +179,8 @@ int RsFdBinInterface::readline(void *data, int len)
int n=0; int n=0;
for(auto p:in_buffer) for(auto p:in_buffer)
for(int i=0;i<p.second && n<len;++i,++n) for(int i=0;i<p.second;++i,++n)
if(static_cast<unsigned char*>(p.first)[i] == '\n') if((n+1==len) || static_cast<unsigned char*>(p.first)[i] == '\n')
return readdata(data,n+1); return readdata(data,n+1);
return 0; return 0;

View File

@ -44,7 +44,6 @@
#include "GetConfCommand.h" #include "GetConfCommand.h"
#include "AddOnionCommand.h" #include "AddOnionCommand.h"
#include "StrUtil.h" #include "StrUtil.h"
#include "Settings.h"
#include "PendingOperation.h" #include "PendingOperation.h"
#include <QHostAddress> #include <QHostAddress>
#include <QDir> #include <QDir>

View File

@ -208,4 +208,6 @@ int TorControlSocket::tick()
if(!rw) if(!rw)
std::this_thread::sleep_for(std::chrono::milliseconds(1000)); // temporisation when nothing happens std::this_thread::sleep_for(std::chrono::milliseconds(1000)); // temporisation when nothing happens
return 0; // not sure about what we should return here.
} }

View File

@ -49,7 +49,6 @@
#include "CryptoKey.h" #include "CryptoKey.h"
#include "HiddenService.h" #include "HiddenService.h"
#include "GetConfCommand.h" #include "GetConfCommand.h"
#include "Settings.h"
#include <QFile> #include <QFile>
#include <QDir> #include <QDir>
#include <QCoreApplication> #include <QCoreApplication>

View File

@ -31,22 +31,21 @@
*/ */
#include <sys/types.h> #include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h> #include <unistd.h>
#include <stdio.h> #include <stdio.h>
#include "util/rsdir.h" #include "util/rsdir.h"
#include "pqi/pqifdbin.h"
#include "TorProcess_p.h" #include "TorProcess_p.h"
#include "CryptoKey.h" #include "CryptoKey.h"
#include "SecureRNG.h" #include "SecureRNG.h"
#include <QDir>
#include <QDebug>
#include <QCoreApplication>
using namespace Tor; using namespace Tor;
TorProcess::TorProcess(TorProcessClient *client,QObject *parent) TorProcess::TorProcess(TorProcessClient *client)
: d(new TorProcessPrivate(this)),m_client(client) : m_client(client)
{ {
} }
@ -56,78 +55,63 @@ TorProcess::~TorProcess()
stop(); stop();
} }
TorProcessPrivate::TorProcessPrivate(TorProcess *q)
: q(q), state(TorProcess::NotStarted), controlPort(0), controlPortAttempts(0)
{
connect(&process, &QProcess::started, this, &TorProcessPrivate::processStarted);
connect(&process, (void (QProcess::*)(int, QProcess::ExitStatus))&QProcess::finished,
this, &TorProcessPrivate::processFinished);
connect(&process, (void (QProcess::*)(QProcess::ProcessError))&QProcess::error,
this, &TorProcessPrivate::processError);
connect(&process, &QProcess::readyRead, this, &TorProcessPrivate::processReadable);
controlPortTimer.setInterval(500);
connect(&controlPortTimer, &QTimer::timeout, this, &TorProcessPrivate::tryReadControlPort);
}
std::string TorProcess::executable() const std::string TorProcess::executable() const
{ {
return d->executable; return mExecutable;
} }
void TorProcess::setExecutable(const std::string &path) void TorProcess::setExecutable(const std::string &path)
{ {
d->executable = path; mExecutable = path;
} }
std::string TorProcess::dataDir() const std::string TorProcess::dataDir() const
{ {
return d->dataDir; return mDataDir;
} }
void TorProcess::setDataDir(const std::string &path) void TorProcess::setDataDir(const std::string &path)
{ {
d->dataDir = path; mDataDir = path;
} }
std::string TorProcess::defaultTorrc() const std::string TorProcess::defaultTorrc() const
{ {
return d->defaultTorrc; return mDefaultTorrc;
} }
void TorProcess::setDefaultTorrc(const std::string &path) void TorProcess::setDefaultTorrc(const std::string &path)
{ {
d->defaultTorrc = path; mDefaultTorrc = path;
} }
std::list<std::string> TorProcess::extraSettings() const std::list<std::string> TorProcess::extraSettings() const
{ {
return d->extraSettings; return mExtraSettings;
} }
void TorProcess::setExtraSettings(const std::list<std::string> &settings) void TorProcess::setExtraSettings(const std::list<std::string> &settings)
{ {
d->extraSettings = settings; mExtraSettings = settings;
} }
TorProcess::State TorProcess::state() const TorProcess::State TorProcess::state() const
{ {
return d->state; return mState;
} }
std::string TorProcess::errorMessage() const std::string TorProcess::errorMessage() const
{ {
return d->errorMessage; return mErrorMessage;
} }
// Does a fopen, but dup all file descriptors (STDIN STDOUT and STDERR) to the // Does a fopen, but dup all file descriptors (STDIN STDOUT and STDERR) to the
// FDs supplied by the parent process // FDs supplied by the parent process
int popen3(int fd[3],const char **const cmd) int popen3(int fd[3],const char **const cmd,pid_t& pid)
{ {
int i, e; int i, e;
int p[3][2]; int p[3][2];
pid_t pid;
// set all the FDs to invalid // set all the FDs to invalid
for(i=0; i<3; i++) for(i=0; i<3; i++)
p[i][0] = p[i][1] = -1; p[i][0] = p[i][1] = -1;
@ -140,7 +124,8 @@ int popen3(int fd[3],const char **const cmd)
if(-1 == pid) if(-1 == pid)
goto error; goto error;
// in the parent? // in the parent?
if(pid) { if(pid)
{
// parent // parent
fd[STDIN_FILENO] = p[STDIN_FILENO][1]; fd[STDIN_FILENO] = p[STDIN_FILENO][1];
close(p[STDIN_FILENO][0]); close(p[STDIN_FILENO][0]);
@ -150,7 +135,9 @@ int popen3(int fd[3],const char **const cmd)
close(p[STDERR_FILENO][1]); close(p[STDERR_FILENO][1]);
// success // success
return 0; return 0;
} else { }
else
{
// child // child
dup2(p[STDIN_FILENO][0],STDIN_FILENO); dup2(p[STDIN_FILENO][0],STDIN_FILENO);
close(p[STDIN_FILENO][1]); close(p[STDIN_FILENO][1]);
@ -163,7 +150,6 @@ int popen3(int fd[3],const char **const cmd)
// if we are there, then we failed to launch our program // if we are there, then we failed to launch our program
perror("Could not launch"); perror("Could not launch");
fprintf(stderr," \"%s\"\n",*cmd); fprintf(stderr," \"%s\"\n",*cmd);
_exit(EXIT_FAILURE);
} }
error: error:
@ -182,73 +168,130 @@ void TorProcess::start()
if (state() > NotStarted) if (state() > NotStarted)
return; return;
d->errorMessage.clear(); mErrorMessage.clear();
if (d->executable.empty() || d->dataDir.empty()) { if (mExecutable.empty() || mDataDir.empty()) {
d->errorMessage = "Tor executable and data directory not specified"; mErrorMessage = "Tor executable and data directory not specified";
d->state = Failed; mState = Failed;
if(m_client) m_client->processStateChanged(d->state); // emit stateChanged(d->state); if(m_client) m_client->processStateChanged(mState); // emit stateChanged(d->state);
if(m_client) m_client->processErrorChanged(d->errorMessage); // emit errorMessageChanged(d->errorMessage); if(m_client) m_client->processErrorChanged(mErrorMessage); // emit errorMessageChanged(d->errorMessage);
return; return;
} }
if (!d->ensureFilesExist()) { if (!ensureFilesExist()) {
d->state = Failed; mState = Failed;
if(m_client) m_client->processErrorChanged(d->errorMessage);// emit errorMessageChanged(d->errorMessage); if(m_client) m_client->processErrorChanged(mErrorMessage);// emit errorMessageChanged(d->errorMessage);
if(m_client) m_client->processStateChanged(d->state);// emit stateChanged(d->state); if(m_client) m_client->processStateChanged(mState);// emit stateChanged(d->state);
return; return;
} }
ByteArray password = controlPassword(); ByteArray password = controlPassword();
ByteArray hashedPassword = torControlHashedPassword(password); ByteArray hashedPassword = torControlHashedPassword(password);
if (password.empty() || hashedPassword.empty()) { if (password.empty() || hashedPassword.empty()) {
d->errorMessage = "Random password generation failed"; mErrorMessage = "Random password generation failed";
d->state = Failed; mState = Failed;
if(m_client) m_client->processErrorChanged(d->errorMessage);// emit errorMessageChanged(d->errorMessage); if(m_client) m_client->processErrorChanged(mErrorMessage);// emit errorMessageChanged(d->errorMessage);
if(m_client) m_client->processStateChanged(d->state); // emit stateChanged(d->state); if(m_client) m_client->processStateChanged(mState); // emit stateChanged(d->state);
} }
std::list<std::string> args; mState = Starting;
if (!d->defaultTorrc.empty())
if(m_client) m_client->processStateChanged(mState);// emit stateChanged(d->state);
if (RsDirUtil::fileExists(controlPortFilePath()))
RsDirUtil::removeFile(controlPortFilePath());
mControlPort = 0;
mControlHost.clear();
RsThread::start("TorControl");
}
void TorProcess::run()
{
// We're inside the process control thread: launch the process,
std::vector<std::string> args;
args.push_back(mExecutable);
if (!mDefaultTorrc.empty())
{ {
args.push_back("--defaults-torrc"); args.push_back("--defaults-torrc");
args.push_back(d->defaultTorrc); args.push_back(mDefaultTorrc);
} }
args.push_back("-f"); args.push_back("-f");
args.push_back(d->torrcPath()); args.push_back(torrcPath());
args.push_back("DataDirectory") ; args.push_back("DataDirectory") ;
args.push_back(d->dataDir); args.push_back(mDataDir);
args.push_back("HashedControlPassword") ; args.push_back("HashedControlPassword") ;
args.push_back(hashedPassword.toString()); args.push_back(torControlHashedPassword(mControlPassword).toString());
args.push_back("ControlPort") ; args.push_back("ControlPort") ;
args.push_back("auto"); args.push_back("auto");
args.push_back("ControlPortWriteToFile"); args.push_back("ControlPortWriteToFile");
args.push_back(d->controlPortFilePath()); args.push_back(controlPortFilePath());
args.push_back("__OwningControllerProcess") ; args.push_back("__OwningControllerProcess") ;
args.push_back(RsUtil::NumberToString(getpid())); args.push_back(RsUtil::NumberToString(getpid()));
for(auto s:d->extraSettings) for(auto s:mExtraSettings)
args.push_back(s); args.push_back(s);
d->state = Starting; const char *arguments[args.size()+1];
int n=0;
if(m_client) m_client->processStateChanged(d->state);// emit stateChanged(d->state); // We first pushed everything into a vector of strings to save the pointers obtained from string returning methods
// by the time the process is launched.
if (RsDirUtil::fileExists(d->controlPortFilePath())) for(auto s:args)
RsDirUtil::removeFile(d->controlPortFilePath()); arguments[n++]= s.c_str();
d->controlPort = 0; arguments[n] = nullptr;
d->controlHost.clear();
d->process.setProcessChannelMode(QProcess::MergedChannels); int fd[3]; // File descriptors array
d->process.start(d->executable, args, QIODevice::ReadOnly);
if(popen3(fd,arguments,mTorProcessId))
{
RsErr() << "Could not start Tor process. errno=" << errno ;
mState = Failed;
return; // stop the control thread
}
RsFdBinInterface stdout_FD(fd[STDOUT_FILENO]);
RsFdBinInterface stderr_FD(fd[STDERR_FILENO]);
unsigned char buff[1024];
while(!shouldStop())
{
stdout_FD.tick();
stderr_FD.tick();
int s;
if((s=stdout_FD.readline(buff,1024))) logMessage(std::string((char*)buff,s));
if((s=stderr_FD.readline(buff,1024))) logMessage(std::string((char*)buff,s));
if(!stdout_FD.isactive() || !stderr_FD.isactive())
{
RsErr() << "Tor process died. Exiting TorControl process." ;
return;
}
}
// Kill the Tor process since we've been asked to stop.
kill(mTorProcessId,SIGTERM);
int status=0;
wait(&status);
RsInfo() << "Tor process has been normally terminated. Exiting.";
} }
void TorProcess::stop() void TorProcess::stop()
@ -256,28 +299,14 @@ void TorProcess::stop()
if (state() < Starting) if (state() < Starting)
return; return;
d->controlPortTimer.stop(); while(mState == Starting)
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (d->process.state() == QProcess::Starting) fullstop();
d->process.waitForStarted(2000);
d->state = NotStarted; mState = NotStarted;
// Windows can't terminate the process well, but Tor will clean itself up if(m_client) m_client->processStateChanged(mState);// emit stateChanged(d->state);
#ifndef Q_OS_WIN
if (d->process.state() == QProcess::Running) {
d->process.terminate();
if (!d->process.waitForFinished(5000)) {
qWarning() << "Tor process" << d->process.pid() << "did not respond to terminate, killing...";
d->process.kill();
if (!d->process.waitForFinished(2000)) {
qCritical() << "Tor process" << d->process.pid() << "did not respond to kill!";
}
}
}
#endif
if(m_client) m_client->processStateChanged(d->state);// emit stateChanged(d->state);
} }
void TorProcess::stateChanged(int newState) void TorProcess::stateChanged(int newState)
@ -298,27 +327,27 @@ void TorProcess::logMessage(const std::string& message)
ByteArray TorProcess::controlPassword() ByteArray TorProcess::controlPassword()
{ {
if (d->controlPassword.empty()) if (mControlPassword.empty())
d->controlPassword = RsRandom::printable(16); mControlPassword = RsRandom::printable(16);
return d->controlPassword; return mControlPassword;
} }
QHostAddress TorProcess::controlHost() QHostAddress TorProcess::controlHost()
{ {
return d->controlHost; return mControlHost;
} }
quint16 TorProcess::controlPort() quint16 TorProcess::controlPort()
{ {
return d->controlPort; return mControlPort;
} }
bool TorProcessPrivate::ensureFilesExist() bool TorProcess::ensureFilesExist()
{ {
if(!RsDirUtil::checkCreateDirectory(dataDir)) if(!RsDirUtil::checkCreateDirectory(mDataDir))
{ {
errorMessage = "Cannot create Tor data directory: " + dataDir; mErrorMessage = "Cannot create Tor data directory: " + mDataDir;
return false; return false;
} }
@ -328,7 +357,7 @@ bool TorProcessPrivate::ensureFilesExist()
if(!f) if(!f)
{ {
errorMessage = "Cannot create Tor configuration file: " + torrcPath(); mErrorMessage = "Cannot create Tor configuration file: " + torrcPath();
return false; return false;
} }
else else
@ -338,18 +367,19 @@ bool TorProcessPrivate::ensureFilesExist()
return true; return true;
} }
std::string TorProcessPrivate::torrcPath() const std::string TorProcess::torrcPath() const
{ {
//return QDir::toNativeSeparators(dataDir) + QDir::separator() + QStringLiteral("torrc"); //return QDir::toNativeSeparators(dataDir) + QDir::separator() + QStringLiteral("torrc");
return dataDir + "/" + "torrc"; return mDataDir + "/" + "torrc";
} }
std::string TorProcessPrivate::controlPortFilePath() const std::string TorProcess::controlPortFilePath() const
{ {
//return QDir::toNativeSeparators(dataDir) + QDir::separator() + QStringLiteral("control-port"); //return QDir::toNativeSeparators(dataDir) + QDir::separator() + QStringLiteral("control-port");
return dataDir + "/" + "control-port"; return mDataDir + "/" + "control-port";
} }
#ifdef TO_REMOVE
void TorProcessPrivate::processStarted() void TorProcessPrivate::processStarted()
{ {
state = TorProcess::Connecting; state = TorProcess::Connecting;
@ -427,8 +457,19 @@ void TorProcessPrivate::tryReadControlPort()
/*emit*/ q->stateChanged(state); /*emit*/ q->stateChanged(state);
} }
} }
TorProcessPrivate::TorProcessPrivate(TorProcess *q)
void TorProcess::run() : q(q), state(TorProcess::NotStarted), controlPort(0), controlPortAttempts(0)
{ {
// connect(&process, &QProcess::started, this, &TorProcessPrivate::processStarted);
connect(&process, (void (QProcess::*)(int, QProcess::ExitStatus))&QProcess::finished,
this, &TorProcessPrivate::processFinished);
connect(&process, (void (QProcess::*)(QProcess::ProcessError))&QProcess::error,
this, &TorProcessPrivate::processError);
connect(&process, &QProcess::readyRead, this, &TorProcessPrivate::processReadable);
controlPortTimer.setInterval(500);
connect(&controlPortTimer, &QTimer::timeout, this, &TorProcessPrivate::tryReadControlPort);
} }
#endif

View File

@ -33,7 +33,6 @@
#ifndef TORPROCESS_H #ifndef TORPROCESS_H
#define TORPROCESS_H #define TORPROCESS_H
#include <QObject>
#include <QHostAddress> #include <QHostAddress>
#include "bytearray.h" #include "bytearray.h"
@ -56,7 +55,7 @@ public:
/* Launches and controls a Tor instance with behavior suitable for bundling /* Launches and controls a Tor instance with behavior suitable for bundling
* an instance with the application. */ * an instance with the application. */
class TorProcess: public RsTickingThread class TorProcess: public RsThread
{ {
//Q_OBJECT //Q_OBJECT
//Q_ENUMS(State) //Q_ENUMS(State)
@ -73,7 +72,7 @@ public:
Ready Ready
}; };
explicit TorProcess(TorProcessClient *client,QObject *parent = 0); explicit TorProcess(TorProcessClient *client);
virtual ~TorProcess(); virtual ~TorProcess();
std::string executable() const; std::string executable() const;
@ -103,12 +102,37 @@ public:
void start(); void start();
void stop(); void stop();
// implements RsThread / RsTickingThread // Implements RsThread
virtual void run() override; void run() override ;
// Keeps reading the output of the tor process and so on.
void threadTick();
private: private:
TorProcessPrivate *d;
TorProcessClient *m_client; TorProcessClient *m_client;
std::string mExecutable;
std::string mDataDir;
std::string mDefaultTorrc;
std::list<std::string> mExtraSettings;
TorProcess::State mState;
std::string mErrorMessage;
QHostAddress mControlHost;
quint16 mControlPort;
ByteArray mControlPassword;
int controlPortAttempts;
std::string torrcPath() const;
std::string controlPortFilePath() const;
bool ensureFilesExist();
pid_t mTorProcessId;
public slots:
void processStarted();
void processFinished();
void processError(std::string error);
void processReadable();
void tryReadControlPort();
}; };
} }

View File

@ -1,77 +0,0 @@
/* 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.
*/
#ifndef TORPROCESS_P_H
#define TORPROCESS_P_H
#include "TorProcess.h"
namespace Tor {
class TorProcessPrivate : public RsTickingThread
{
Q_OBJECT
public:
TorProcess *q;
QProcess process;
std::string executable;
std::string dataDir;
std::string defaultTorrc;
std::list<std::string> extraSettings;
TorProcess::State state;
std::string errorMessage;
QHostAddress controlHost;
quint16 controlPort;
ByteArray controlPassword;
QTimer controlPortTimer;
int controlPortAttempts;
TorProcessPrivate(TorProcess *q);
std::string torrcPath() const;
std::string controlPortFilePath() const;
bool ensureFilesExist();
public slots:
void processStarted();
void processFinished();
void processError(QProcess::ProcessError error);
void processReadable();
void tryReadControlPort();
};
}
#endif