Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

646 lines
16 KiB
C++
Raw Permalink Normal View History

/*
* Copyright (C) 2003-2007 Justin Karneges <justin@affinix.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*
*/
2014-10-07 02:49:27 +06:00
#include "gpgproc_p.h"
#ifdef Q_OS_MAC
#define QT_PIPE_HACK
#endif
using namespace QCA;
namespace gpgQCAPlugin {
void releaseAndDeleteLater(QObject *owner, QObject *obj)
{
obj->disconnect(owner);
obj->setParent(nullptr);
obj->deleteLater();
}
2014-10-07 02:49:27 +06:00
GPGProc::Private::Private(GPGProc *_q)
: QObject(_q)
, q(_q)
, pipeAux(this)
, pipeCommand(this)
, pipeStatus(this)
, startTrigger(this)
, doneTrigger(this)
{
qRegisterMetaType<gpgQCAPlugin::GPGProc::Error>("gpgQCAPlugin::GPGProc::Error");
proc = nullptr;
proc_relay = nullptr;
2014-10-07 02:49:27 +06:00
startTrigger.setSingleShot(true);
doneTrigger.setSingleShot(true);
connect(&pipeAux.writeEnd(), &QCA::QPipeEnd::bytesWritten, this, &GPGProc::Private::aux_written);
connect(&pipeAux.writeEnd(), &QCA::QPipeEnd::error, this, &GPGProc::Private::aux_error);
connect(&pipeCommand.writeEnd(), &QCA::QPipeEnd::bytesWritten, this, &GPGProc::Private::command_written);
connect(&pipeCommand.writeEnd(), &QCA::QPipeEnd::error, this, &GPGProc::Private::command_error);
connect(&pipeStatus.readEnd(), &QCA::QPipeEnd::readyRead, this, &GPGProc::Private::status_read);
connect(&pipeStatus.readEnd(), &QCA::QPipeEnd::error, this, &GPGProc::Private::status_error);
connect(&startTrigger, &QCA::SafeTimer::timeout, this, &GPGProc::Private::doStart);
connect(&doneTrigger, &QCA::SafeTimer::timeout, this, &GPGProc::Private::doTryDone);
2014-10-07 02:49:27 +06:00
reset(ResetSessionAndData);
}
2014-10-07 02:49:27 +06:00
GPGProc::Private::~Private()
{
reset(ResetSession);
}
2014-10-07 02:49:27 +06:00
void GPGProc::Private::closePipes()
{
#ifdef QT_PIPE_HACK
2014-10-07 02:49:27 +06:00
pipeAux.readEnd().reset();
pipeCommand.readEnd().reset();
pipeStatus.writeEnd().reset();
#endif
2014-10-07 02:49:27 +06:00
pipeAux.reset();
pipeCommand.reset();
pipeStatus.reset();
}
2014-10-07 02:49:27 +06:00
void GPGProc::Private::reset(ResetMode mode)
{
#ifndef QT_PIPE_HACK
2014-10-07 02:49:27 +06:00
closePipes();
#endif
2014-10-07 02:49:27 +06:00
if (proc) {
proc->disconnect(this);
2014-10-07 02:49:27 +06:00
if (proc->state() != QProcess::NotRunning) {
// Before try to correct end proccess
// Terminate if failed
proc->close();
bool finished = proc->waitForFinished(5000);
if (!finished)
proc->terminate();
}
proc->setParent(nullptr);
2014-10-07 02:49:27 +06:00
releaseAndDeleteLater(this, proc_relay);
proc_relay = nullptr;
2014-10-07 02:49:27 +06:00
delete proc; // should be safe to do thanks to relay
proc = nullptr;
2014-10-07 02:49:27 +06:00
}
#ifdef QT_PIPE_HACK
2014-10-07 02:49:27 +06:00
closePipes();
#endif
2014-10-07 02:49:27 +06:00
startTrigger.stop();
doneTrigger.stop();
2014-10-07 02:49:27 +06:00
pre_stdin.clear();
pre_aux.clear();
pre_command.clear();
pre_stdin_close = false;
pre_aux_close = false;
pre_command_close = false;
2014-10-07 02:49:27 +06:00
need_status = false;
fin_process = false;
fin_status = false;
2014-10-07 02:49:27 +06:00
if (mode >= ResetSessionAndData) {
statusBuf.clear();
statusLines.clear();
leftover_stdout.clear();
leftover_stderr.clear();
error = GPGProc::FailedToStart;
exitCode = -1;
}
2014-10-07 02:49:27 +06:00
}
2014-10-07 02:49:27 +06:00
bool GPGProc::Private::setupPipes(bool makeAux)
{
if (makeAux && !pipeAux.create()) {
closePipes();
emit q->debug(QStringLiteral("Error creating pipeAux"));
2014-10-07 02:49:27 +06:00
return false;
}
#ifdef QPIPE_SECURE
2014-10-07 02:49:27 +06:00
if (!pipeCommand.create(true)) // secure
#else
if (!pipeCommand.create())
#endif
{
closePipes();
emit q->debug(QStringLiteral("Error creating pipeCommand"));
return false;
}
2014-10-07 02:49:27 +06:00
if (!pipeStatus.create()) {
closePipes();
emit q->debug(QStringLiteral("Error creating pipeStatus"));
2014-10-07 02:49:27 +06:00
return false;
}
2014-10-07 02:49:27 +06:00
return true;
}
2014-10-07 02:49:27 +06:00
void GPGProc::Private::setupArguments()
{
QStringList fullargs;
fullargs += QStringLiteral("--no-tty");
fullargs += QStringLiteral("--pinentry-mode");
fullargs += QStringLiteral("loopback");
2014-10-07 02:49:27 +06:00
if (mode == ExtendedMode) {
fullargs += QStringLiteral("--enable-special-filenames");
fullargs += QStringLiteral("--status-fd");
2014-10-07 02:49:27 +06:00
fullargs += QString::number(pipeStatus.writeEnd().idAsInt());
fullargs += QStringLiteral("--command-fd");
2014-10-07 02:49:27 +06:00
fullargs += QString::number(pipeCommand.readEnd().idAsInt());
}
2014-10-07 02:49:27 +06:00
for (int n = 0; n < args.count(); ++n) {
QString a = args[n];
if (mode == ExtendedMode && a == QLatin1String("-&?"))
fullargs += QStringLiteral("-&") + QString::number(pipeAux.readEnd().idAsInt());
2014-10-07 02:49:27 +06:00
else
fullargs += a;
}
QString fullcmd = fullargs.join(QStringLiteral(" "));
2020-02-13 00:59:09 +01:00
emit q->debug(QStringLiteral("Running: [") + bin + QLatin1Char(' ') + fullcmd + QLatin1Char(']'));
2014-10-07 02:49:27 +06:00
args = fullargs;
}
void GPGProc::Private::doStart()
{
#ifdef Q_OS_WIN
2014-10-07 02:49:27 +06:00
// Note: for unix, inheritability is set in SProcess
if (pipeAux.readEnd().isValid())
pipeAux.readEnd().setInheritable(true);
if (pipeCommand.readEnd().isValid())
pipeCommand.readEnd().setInheritable(true);
if (pipeStatus.writeEnd().isValid())
pipeStatus.writeEnd().setInheritable(true);
#endif
2014-10-07 02:49:27 +06:00
setupArguments();
2014-10-07 02:49:27 +06:00
proc->start(bin, args);
proc->waitForStarted();
2014-10-07 02:49:27 +06:00
pipeAux.readEnd().close();
pipeCommand.readEnd().close();
pipeStatus.writeEnd().close();
}
2014-10-07 02:49:27 +06:00
void GPGProc::Private::aux_written(int x)
{
emit q->bytesWrittenAux(x);
}
2014-10-07 02:49:27 +06:00
void GPGProc::Private::aux_error(QCA::QPipeEnd::Error)
{
emit q->debug(QStringLiteral("Aux: Pipe error"));
2014-10-07 02:49:27 +06:00
reset(ResetSession);
emit q->error(GPGProc::ErrorWrite);
}
2014-10-07 02:49:27 +06:00
void GPGProc::Private::command_written(int x)
{
emit q->bytesWrittenCommand(x);
}
2014-10-07 02:49:27 +06:00
void GPGProc::Private::command_error(QCA::QPipeEnd::Error)
{
emit q->debug(QStringLiteral("Command: Pipe error"));
2014-10-07 02:49:27 +06:00
reset(ResetSession);
emit q->error(GPGProc::ErrorWrite);
}
2014-10-07 02:49:27 +06:00
void GPGProc::Private::status_read()
{
if (readAndProcessStatusData())
emit q->readyReadStatusLines();
}
2014-10-07 02:49:27 +06:00
void GPGProc::Private::status_error(QCA::QPipeEnd::Error e)
{
if (e == QPipeEnd::ErrorEOF)
emit q->debug(QStringLiteral("Status: Closed (EOF)"));
2014-10-07 02:49:27 +06:00
else
emit q->debug(QStringLiteral("Status: Closed (gone)"));
2014-10-07 02:49:27 +06:00
fin_status = true;
doTryDone();
}
2014-10-07 02:49:27 +06:00
void GPGProc::Private::proc_started()
{
emit q->debug(QStringLiteral("Process started"));
2014-10-07 02:49:27 +06:00
// Note: we don't close these here anymore. instead we
// do it just after calling proc->start().
// close these, we don't need them
/*pipeAux.readEnd().close();
pipeCommand.readEnd().close();
pipeStatus.writeEnd().close();*/
2014-10-07 02:49:27 +06:00
// do the pre* stuff
if (!pre_stdin.isEmpty()) {
proc->write(pre_stdin);
pre_stdin.clear();
}
if (!pre_aux.isEmpty()) {
pipeAux.writeEnd().write(pre_aux);
pre_aux.clear();
}
if (!pre_command.isEmpty()) {
#ifdef QPIPE_SECURE
2014-10-07 02:49:27 +06:00
pipeCommand.writeEnd().writeSecure(pre_command);
#else
2014-10-07 02:49:27 +06:00
pipeCommand.writeEnd().write(pre_command);
#endif
2014-10-07 02:49:27 +06:00
pre_command.clear();
}
2014-10-07 02:49:27 +06:00
if (pre_stdin_close) {
proc->waitForBytesWritten();
proc->closeWriteChannel();
}
2014-10-07 02:49:27 +06:00
if (pre_aux_close)
pipeAux.writeEnd().close();
if (pre_command_close)
pipeCommand.writeEnd().close();
}
2014-10-07 02:49:27 +06:00
void GPGProc::Private::proc_readyReadStandardOutput()
{
emit q->readyReadStdout();
}
2014-10-07 02:49:27 +06:00
void GPGProc::Private::proc_readyReadStandardError()
{
emit q->readyReadStderr();
}
2014-10-07 02:49:27 +06:00
void GPGProc::Private::proc_bytesWritten(qint64 lx)
{
int x = (int)lx;
emit q->bytesWrittenStdin(x);
}
void GPGProc::Private::proc_finished(int x)
{
emit q->debug(QStringLiteral("Process finished: %1").arg(x));
2014-10-07 02:49:27 +06:00
exitCode = x;
2014-10-07 02:49:27 +06:00
fin_process = true;
fin_process_success = true;
if (need_status && !fin_status) {
pipeStatus.readEnd().finalize();
fin_status = true;
if (readAndProcessStatusData()) {
doneTrigger.start();
emit q->readyReadStatusLines();
return;
}
}
2014-10-07 02:49:27 +06:00
doTryDone();
}
void GPGProc::Private::proc_error(QProcess::ProcessError x)
{
QMap<int, QString> errmap;
errmap[QProcess::FailedToStart] = QStringLiteral("FailedToStart");
errmap[QProcess::Crashed] = QStringLiteral("Crashed");
errmap[QProcess::Timedout] = QStringLiteral("Timedout");
errmap[QProcess::WriteError] = QStringLiteral("WriteError");
errmap[QProcess::ReadError] = QStringLiteral("ReadError");
errmap[QProcess::UnknownError] = QStringLiteral("UnknownError");
2014-10-07 02:49:27 +06:00
emit q->debug(QStringLiteral("Process error: %1").arg(errmap[x]));
2014-10-07 02:49:27 +06:00
if (x == QProcess::FailedToStart)
error = GPGProc::FailedToStart;
else if (x == QProcess::WriteError)
error = GPGProc::ErrorWrite;
else
error = GPGProc::UnexpectedExit;
2014-10-07 02:49:27 +06:00
fin_process = true;
fin_process_success = false;
#ifdef QT_PIPE_HACK
2014-10-07 02:49:27 +06:00
// If the process fails to start, then the ends of the pipes
// intended for the child process are still open. Some Mac
// users experience a lockup if we close our ends of the pipes
// when the child's ends are still open. If we ensure the
// child's ends are closed, we prevent this lockup. I have no
// idea why the problem even happens or why this fix should
// work.
pipeAux.readEnd().reset();
pipeCommand.readEnd().reset();
pipeStatus.writeEnd().reset();
#endif
2014-10-07 02:49:27 +06:00
if (need_status && !fin_status) {
pipeStatus.readEnd().finalize();
fin_status = true;
if (readAndProcessStatusData()) {
doneTrigger.start();
emit q->readyReadStatusLines();
return;
}
}
2014-10-07 02:49:27 +06:00
doTryDone();
}
2014-10-07 02:49:27 +06:00
void GPGProc::Private::doTryDone()
{
if (!fin_process)
return;
2014-10-07 02:49:27 +06:00
if (need_status && !fin_status)
return;
emit q->debug(QStringLiteral("Done"));
2014-10-07 02:49:27 +06:00
// get leftover data
proc->setReadChannel(QProcess::StandardOutput);
leftover_stdout = proc->readAll();
2014-10-07 02:49:27 +06:00
proc->setReadChannel(QProcess::StandardError);
leftover_stderr = proc->readAll();
2014-10-07 02:49:27 +06:00
reset(ResetSession);
if (fin_process_success)
emit q->finished(exitCode);
else
emit q->error(error);
}
2014-10-07 02:49:27 +06:00
bool GPGProc::Private::readAndProcessStatusData()
{
2020-02-17 17:39:40 +01:00
const QByteArray buf = pipeStatus.readEnd().read();
2014-10-07 02:49:27 +06:00
if (buf.isEmpty())
return false;
2014-10-07 02:49:27 +06:00
return processStatusData(buf);
}
// return true if there are newly parsed lines available
bool GPGProc::Private::processStatusData(const QByteArray &buf)
{
statusBuf.append(buf);
// extract all lines
QStringList list;
while (true) {
2014-10-07 02:49:27 +06:00
int n = statusBuf.indexOf('\n');
if (n == -1)
break;
2014-10-07 02:49:27 +06:00
// extract the string from statusbuf
++n;
char * p = (char *)statusBuf.data();
QByteArray cs(p, n);
2020-02-24 16:24:46 +01:00
const int newsize = statusBuf.size() - n;
2014-10-07 02:49:27 +06:00
memmove(p, p + n, newsize);
statusBuf.resize(newsize);
2014-10-07 02:49:27 +06:00
// convert to string without newline
QString str = QString::fromUtf8(cs);
str.truncate(str.length() - 1);
2014-10-07 02:49:27 +06:00
// ensure it has a proper header
if (str.left(9) != QLatin1String("[GNUPG:] "))
2014-10-07 02:49:27 +06:00
continue;
// take it off
str = str.mid(9);
// add to the list
list += str;
}
2014-10-07 02:49:27 +06:00
if (list.isEmpty())
return false;
statusLines += list;
return true;
}
GPGProc::GPGProc(QObject *parent)
: QObject(parent)
{
d = new Private(this);
}
GPGProc::~GPGProc()
{
delete d;
}
void GPGProc::reset()
{
d->reset(ResetAll);
}
bool GPGProc::isActive() const
{
return (d->proc ? true : false);
}
void GPGProc::start(const QString &bin, const QStringList &args, Mode mode)
{
if (isActive())
d->reset(ResetSessionAndData);
if (mode == ExtendedMode) {
if (!d->setupPipes(args.contains(QStringLiteral("-&?")))) {
d->error = FailedToStart;
// emit later
QMetaObject::invokeMethod(
this, "error", Qt::QueuedConnection, Q_ARG(gpgQCAPlugin::GPGProc::Error, d->error));
return;
}
d->need_status = true;
emit debug(QStringLiteral("Pipe setup complete"));
}
d->proc = new SProcess(d);
#ifdef Q_OS_UNIX
QList<int> plist;
if (d->pipeAux.readEnd().isValid())
plist += d->pipeAux.readEnd().id();
if (d->pipeCommand.readEnd().isValid())
plist += d->pipeCommand.readEnd().id();
if (d->pipeStatus.writeEnd().isValid())
plist += d->pipeStatus.writeEnd().id();
d->proc->setInheritPipeList(plist);
#endif
// enable the pipes we want
if (d->pipeAux.writeEnd().isValid())
d->pipeAux.writeEnd().enable();
if (d->pipeCommand.writeEnd().isValid())
d->pipeCommand.writeEnd().enable();
if (d->pipeStatus.readEnd().isValid())
d->pipeStatus.readEnd().enable();
d->proc_relay = new QProcessSignalRelay(d->proc, d);
connect(d->proc_relay, &QProcessSignalRelay::started, d, &GPGProc::Private::proc_started);
connect(d->proc_relay,
&QProcessSignalRelay::readyReadStandardOutput,
d,
&GPGProc::Private::proc_readyReadStandardOutput);
connect(
d->proc_relay, &QProcessSignalRelay::readyReadStandardError, d, &GPGProc::Private::proc_readyReadStandardError);
connect(d->proc_relay, &QProcessSignalRelay::bytesWritten, d, &GPGProc::Private::proc_bytesWritten);
connect(d->proc_relay, &QProcessSignalRelay::finished, d, &GPGProc::Private::proc_finished);
connect(d->proc_relay, &QProcessSignalRelay::error, d, &GPGProc::Private::proc_error);
d->bin = bin;
d->args = args;
d->mode = mode;
d->startTrigger.start();
}
QByteArray GPGProc::readStdout()
{
if (d->proc) {
d->proc->setReadChannel(QProcess::StandardOutput);
return d->proc->readAll();
} else {
2020-02-17 17:39:40 +01:00
const QByteArray a = d->leftover_stdout;
d->leftover_stdout.clear();
return a;
}
}
QByteArray GPGProc::readStderr()
{
if (d->proc) {
d->proc->setReadChannel(QProcess::StandardError);
return d->proc->readAll();
} else {
2020-02-17 17:39:40 +01:00
const QByteArray a = d->leftover_stderr;
d->leftover_stderr.clear();
return a;
}
}
QStringList GPGProc::readStatusLines()
{
2020-02-16 12:11:57 +01:00
const QStringList out = d->statusLines;
d->statusLines.clear();
return out;
}
void GPGProc::writeStdin(const QByteArray &a)
{
if (!d->proc || a.isEmpty())
return;
if (d->proc->state() == QProcess::Running)
d->proc->write(a);
else
d->pre_stdin += a;
}
void GPGProc::writeAux(const QByteArray &a)
{
if (!d->proc || a.isEmpty())
return;
if (d->proc->state() == QProcess::Running)
d->pipeAux.writeEnd().write(a);
else
d->pre_aux += a;
}
#ifdef QPIPE_SECURE
void GPGProc::writeCommand(const SecureArray &a)
#else
void GPGProc::writeCommand(const QByteArray &a)
#endif
{
if (!d->proc || a.isEmpty())
return;
if (d->proc->state() == QProcess::Running)
#ifdef QPIPE_SECURE
d->pipeCommand.writeEnd().writeSecure(a);
#else
d->pipeCommand.writeEnd().write(a);
#endif
else
d->pre_command += a;
}
void GPGProc::closeStdin()
{
if (!d->proc)
return;
if (d->proc->state() == QProcess::Running) {
d->proc->waitForBytesWritten();
d->proc->closeWriteChannel();
} else {
d->pre_stdin_close = true;
}
}
void GPGProc::closeAux()
{
if (!d->proc)
return;
if (d->proc->state() == QProcess::Running)
d->pipeAux.writeEnd().close();
else
d->pre_aux_close = true;
}
void GPGProc::closeCommand()
{
if (!d->proc)
return;
if (d->proc->state() == QProcess::Running)
d->pipeCommand.writeEnd().close();
else
d->pre_command_close = true;
}
}