File indexing completed on 2024-05-19 04:46:24

0001 /*
0002  * Copyright (C) 2003-2007  Justin Karneges <justin@affinix.com>
0003  *
0004  * This library is free software; you can redistribute it and/or
0005  * modify it under the terms of the GNU Lesser General Public
0006  * License as published by the Free Software Foundation; either
0007  * version 2.1 of the License, or (at your option) any later version.
0008  *
0009  * This library is distributed in the hope that it will be useful,
0010  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0012  * Lesser General Public License for more details.
0013  *
0014  * You should have received a copy of the GNU Lesser General Public
0015  * License along with this library; if not, write to the Free Software
0016  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
0017  *
0018  */
0019 
0020 #include "gpgproc_p.h"
0021 
0022 #ifdef Q_OS_MAC
0023 #define QT_PIPE_HACK
0024 #endif
0025 
0026 using namespace QCA;
0027 
0028 namespace gpgQCAPlugin {
0029 
0030 void releaseAndDeleteLater(QObject *owner, QObject *obj)
0031 {
0032     obj->disconnect(owner);
0033     obj->setParent(nullptr);
0034     obj->deleteLater();
0035 }
0036 
0037 GPGProc::Private::Private(GPGProc *_q)
0038     : QObject(_q)
0039     , q(_q)
0040     , pipeAux(this)
0041     , pipeCommand(this)
0042     , pipeStatus(this)
0043     , startTrigger(this)
0044     , doneTrigger(this)
0045 {
0046     qRegisterMetaType<gpgQCAPlugin::GPGProc::Error>("gpgQCAPlugin::GPGProc::Error");
0047 
0048     proc       = nullptr;
0049     proc_relay = nullptr;
0050     startTrigger.setSingleShot(true);
0051     doneTrigger.setSingleShot(true);
0052 
0053     connect(&pipeAux.writeEnd(), &QCA::QPipeEnd::bytesWritten, this, &GPGProc::Private::aux_written);
0054     connect(&pipeAux.writeEnd(), &QCA::QPipeEnd::error, this, &GPGProc::Private::aux_error);
0055     connect(&pipeCommand.writeEnd(), &QCA::QPipeEnd::bytesWritten, this, &GPGProc::Private::command_written);
0056     connect(&pipeCommand.writeEnd(), &QCA::QPipeEnd::error, this, &GPGProc::Private::command_error);
0057     connect(&pipeStatus.readEnd(), &QCA::QPipeEnd::readyRead, this, &GPGProc::Private::status_read);
0058     connect(&pipeStatus.readEnd(), &QCA::QPipeEnd::error, this, &GPGProc::Private::status_error);
0059     connect(&startTrigger, &QCA::SafeTimer::timeout, this, &GPGProc::Private::doStart);
0060     connect(&doneTrigger, &QCA::SafeTimer::timeout, this, &GPGProc::Private::doTryDone);
0061 
0062     reset(ResetSessionAndData);
0063 }
0064 
0065 GPGProc::Private::~Private()
0066 {
0067     reset(ResetSession);
0068 }
0069 
0070 void GPGProc::Private::closePipes()
0071 {
0072 #ifdef QT_PIPE_HACK
0073     pipeAux.readEnd().reset();
0074     pipeCommand.readEnd().reset();
0075     pipeStatus.writeEnd().reset();
0076 #endif
0077 
0078     pipeAux.reset();
0079     pipeCommand.reset();
0080     pipeStatus.reset();
0081 }
0082 
0083 void GPGProc::Private::reset(ResetMode mode)
0084 {
0085 #ifndef QT_PIPE_HACK
0086     closePipes();
0087 #endif
0088 
0089     if (proc) {
0090         proc->disconnect(this);
0091 
0092         if (proc->state() != QProcess::NotRunning) {
0093             // Before try to correct end proccess
0094             // Terminate if failed
0095             proc->close();
0096             bool finished = proc->waitForFinished(5000);
0097             if (!finished)
0098                 proc->terminate();
0099         }
0100 
0101         proc->setParent(nullptr);
0102         releaseAndDeleteLater(this, proc_relay);
0103         proc_relay = nullptr;
0104         delete proc; // should be safe to do thanks to relay
0105         proc = nullptr;
0106     }
0107 
0108 #ifdef QT_PIPE_HACK
0109     closePipes();
0110 #endif
0111 
0112     startTrigger.stop();
0113     doneTrigger.stop();
0114 
0115     pre_stdin.clear();
0116     pre_aux.clear();
0117     pre_command.clear();
0118     pre_stdin_close   = false;
0119     pre_aux_close     = false;
0120     pre_command_close = false;
0121 
0122     need_status = false;
0123     fin_process = false;
0124     fin_status  = false;
0125 
0126     if (mode >= ResetSessionAndData) {
0127         statusBuf.clear();
0128         statusLines.clear();
0129         leftover_stdout.clear();
0130         leftover_stderr.clear();
0131         error    = GPGProc::FailedToStart;
0132         exitCode = -1;
0133     }
0134 }
0135 
0136 bool GPGProc::Private::setupPipes(bool makeAux)
0137 {
0138     if (makeAux && !pipeAux.create()) {
0139         closePipes();
0140         emit q->debug(QStringLiteral("Error creating pipeAux"));
0141         return false;
0142     }
0143 
0144 #ifdef QPIPE_SECURE
0145     if (!pipeCommand.create(true)) // secure
0146 #else
0147     if (!pipeCommand.create())
0148 #endif
0149     {
0150         closePipes();
0151         emit q->debug(QStringLiteral("Error creating pipeCommand"));
0152         return false;
0153     }
0154 
0155     if (!pipeStatus.create()) {
0156         closePipes();
0157         emit q->debug(QStringLiteral("Error creating pipeStatus"));
0158         return false;
0159     }
0160 
0161     return true;
0162 }
0163 
0164 void GPGProc::Private::setupArguments()
0165 {
0166     QStringList fullargs;
0167     fullargs += QStringLiteral("--no-tty");
0168     fullargs += QStringLiteral("--pinentry-mode");
0169     fullargs += QStringLiteral("loopback");
0170 
0171     if (mode == ExtendedMode) {
0172         fullargs += QStringLiteral("--enable-special-filenames");
0173 
0174         fullargs += QStringLiteral("--status-fd");
0175         fullargs += QString::number(pipeStatus.writeEnd().idAsInt());
0176 
0177         fullargs += QStringLiteral("--command-fd");
0178         fullargs += QString::number(pipeCommand.readEnd().idAsInt());
0179     }
0180 
0181     for (int n = 0; n < args.count(); ++n) {
0182         QString a = args[n];
0183         if (mode == ExtendedMode && a == QLatin1String("-&?"))
0184             fullargs += QStringLiteral("-&") + QString::number(pipeAux.readEnd().idAsInt());
0185         else
0186             fullargs += a;
0187     }
0188 
0189     QString fullcmd = fullargs.join(QStringLiteral(" "));
0190     emit    q->debug(QStringLiteral("Running: [") + bin + QLatin1Char(' ') + fullcmd + QLatin1Char(']'));
0191 
0192     args = fullargs;
0193 }
0194 
0195 void GPGProc::Private::doStart()
0196 {
0197 #ifdef Q_OS_WIN
0198     // Note: for unix, inheritability is set in SProcess
0199     if (pipeAux.readEnd().isValid())
0200         pipeAux.readEnd().setInheritable(true);
0201     if (pipeCommand.readEnd().isValid())
0202         pipeCommand.readEnd().setInheritable(true);
0203     if (pipeStatus.writeEnd().isValid())
0204         pipeStatus.writeEnd().setInheritable(true);
0205 #endif
0206 
0207     setupArguments();
0208 
0209     proc->start(bin, args);
0210     proc->waitForStarted();
0211 
0212     pipeAux.readEnd().close();
0213     pipeCommand.readEnd().close();
0214     pipeStatus.writeEnd().close();
0215 }
0216 
0217 void GPGProc::Private::aux_written(int x)
0218 {
0219     emit q->bytesWrittenAux(x);
0220 }
0221 
0222 void GPGProc::Private::aux_error(QCA::QPipeEnd::Error)
0223 {
0224     emit q->debug(QStringLiteral("Aux: Pipe error"));
0225     reset(ResetSession);
0226     emit q->error(GPGProc::ErrorWrite);
0227 }
0228 
0229 void GPGProc::Private::command_written(int x)
0230 {
0231     emit q->bytesWrittenCommand(x);
0232 }
0233 
0234 void GPGProc::Private::command_error(QCA::QPipeEnd::Error)
0235 {
0236     emit q->debug(QStringLiteral("Command: Pipe error"));
0237     reset(ResetSession);
0238     emit q->error(GPGProc::ErrorWrite);
0239 }
0240 
0241 void GPGProc::Private::status_read()
0242 {
0243     if (readAndProcessStatusData())
0244         emit q->readyReadStatusLines();
0245 }
0246 
0247 void GPGProc::Private::status_error(QCA::QPipeEnd::Error e)
0248 {
0249     if (e == QPipeEnd::ErrorEOF)
0250         emit q->debug(QStringLiteral("Status: Closed (EOF)"));
0251     else
0252         emit q->debug(QStringLiteral("Status: Closed (gone)"));
0253 
0254     fin_status = true;
0255     doTryDone();
0256 }
0257 
0258 void GPGProc::Private::proc_started()
0259 {
0260     emit q->debug(QStringLiteral("Process started"));
0261 
0262     // Note: we don't close these here anymore.  instead we
0263     //   do it just after calling proc->start().
0264     // close these, we don't need them
0265     /*pipeAux.readEnd().close();
0266       pipeCommand.readEnd().close();
0267       pipeStatus.writeEnd().close();*/
0268 
0269     // do the pre* stuff
0270     if (!pre_stdin.isEmpty()) {
0271         proc->write(pre_stdin);
0272         pre_stdin.clear();
0273     }
0274     if (!pre_aux.isEmpty()) {
0275         pipeAux.writeEnd().write(pre_aux);
0276         pre_aux.clear();
0277     }
0278     if (!pre_command.isEmpty()) {
0279 #ifdef QPIPE_SECURE
0280         pipeCommand.writeEnd().writeSecure(pre_command);
0281 #else
0282         pipeCommand.writeEnd().write(pre_command);
0283 #endif
0284         pre_command.clear();
0285     }
0286 
0287     if (pre_stdin_close) {
0288         proc->waitForBytesWritten();
0289         proc->closeWriteChannel();
0290     }
0291 
0292     if (pre_aux_close)
0293         pipeAux.writeEnd().close();
0294     if (pre_command_close)
0295         pipeCommand.writeEnd().close();
0296 }
0297 
0298 void GPGProc::Private::proc_readyReadStandardOutput()
0299 {
0300     emit q->readyReadStdout();
0301 }
0302 
0303 void GPGProc::Private::proc_readyReadStandardError()
0304 {
0305     emit q->readyReadStderr();
0306 }
0307 
0308 void GPGProc::Private::proc_bytesWritten(qint64 lx)
0309 {
0310     int  x = (int)lx;
0311     emit q->bytesWrittenStdin(x);
0312 }
0313 
0314 void GPGProc::Private::proc_finished(int x)
0315 {
0316     emit q->debug(QStringLiteral("Process finished: %1").arg(x));
0317     exitCode = x;
0318 
0319     fin_process         = true;
0320     fin_process_success = true;
0321 
0322     if (need_status && !fin_status) {
0323         pipeStatus.readEnd().finalize();
0324         fin_status = true;
0325         if (readAndProcessStatusData()) {
0326             doneTrigger.start();
0327             emit q->readyReadStatusLines();
0328             return;
0329         }
0330     }
0331 
0332     doTryDone();
0333 }
0334 
0335 void GPGProc::Private::proc_error(QProcess::ProcessError x)
0336 {
0337     QMap<int, QString> errmap;
0338     errmap[QProcess::FailedToStart] = QStringLiteral("FailedToStart");
0339     errmap[QProcess::Crashed]       = QStringLiteral("Crashed");
0340     errmap[QProcess::Timedout]      = QStringLiteral("Timedout");
0341     errmap[QProcess::WriteError]    = QStringLiteral("WriteError");
0342     errmap[QProcess::ReadError]     = QStringLiteral("ReadError");
0343     errmap[QProcess::UnknownError]  = QStringLiteral("UnknownError");
0344 
0345     emit q->debug(QStringLiteral("Process error: %1").arg(errmap[x]));
0346 
0347     if (x == QProcess::FailedToStart)
0348         error = GPGProc::FailedToStart;
0349     else if (x == QProcess::WriteError)
0350         error = GPGProc::ErrorWrite;
0351     else
0352         error = GPGProc::UnexpectedExit;
0353 
0354     fin_process         = true;
0355     fin_process_success = false;
0356 
0357 #ifdef QT_PIPE_HACK
0358     // If the process fails to start, then the ends of the pipes
0359     // intended for the child process are still open.  Some Mac
0360     // users experience a lockup if we close our ends of the pipes
0361     // when the child's ends are still open.  If we ensure the
0362     // child's ends are closed, we prevent this lockup.  I have no
0363     // idea why the problem even happens or why this fix should
0364     // work.
0365     pipeAux.readEnd().reset();
0366     pipeCommand.readEnd().reset();
0367     pipeStatus.writeEnd().reset();
0368 #endif
0369 
0370     if (need_status && !fin_status) {
0371         pipeStatus.readEnd().finalize();
0372         fin_status = true;
0373         if (readAndProcessStatusData()) {
0374             doneTrigger.start();
0375             emit q->readyReadStatusLines();
0376             return;
0377         }
0378     }
0379 
0380     doTryDone();
0381 }
0382 
0383 void GPGProc::Private::doTryDone()
0384 {
0385     if (!fin_process)
0386         return;
0387 
0388     if (need_status && !fin_status)
0389         return;
0390 
0391     emit q->debug(QStringLiteral("Done"));
0392 
0393     // get leftover data
0394     proc->setReadChannel(QProcess::StandardOutput);
0395     leftover_stdout = proc->readAll();
0396 
0397     proc->setReadChannel(QProcess::StandardError);
0398     leftover_stderr = proc->readAll();
0399 
0400     reset(ResetSession);
0401     if (fin_process_success)
0402         emit q->finished(exitCode);
0403     else
0404         emit q->error(error);
0405 }
0406 
0407 bool GPGProc::Private::readAndProcessStatusData()
0408 {
0409     const QByteArray buf = pipeStatus.readEnd().read();
0410     if (buf.isEmpty())
0411         return false;
0412 
0413     return processStatusData(buf);
0414 }
0415 
0416 // return true if there are newly parsed lines available
0417 bool GPGProc::Private::processStatusData(const QByteArray &buf)
0418 {
0419     statusBuf.append(buf);
0420 
0421     // extract all lines
0422     QStringList list;
0423     while (true) {
0424         int n = statusBuf.indexOf('\n');
0425         if (n == -1)
0426             break;
0427 
0428         // extract the string from statusbuf
0429         ++n;
0430         char      *p = (char *)statusBuf.data();
0431         QByteArray cs(p, n);
0432         const int  newsize = statusBuf.size() - n;
0433         memmove(p, p + n, newsize);
0434         statusBuf.resize(newsize);
0435 
0436         // convert to string without newline
0437         QString str = QString::fromUtf8(cs);
0438         str.truncate(str.length() - 1);
0439 
0440         // ensure it has a proper header
0441         if (str.left(9) != QLatin1String("[GNUPG:] "))
0442             continue;
0443 
0444         // take it off
0445         str = str.mid(9);
0446 
0447         // add to the list
0448         list += str;
0449     }
0450 
0451     if (list.isEmpty())
0452         return false;
0453 
0454     statusLines += list;
0455     return true;
0456 }
0457 
0458 GPGProc::GPGProc(QObject *parent)
0459     : QObject(parent)
0460 {
0461     d = new Private(this);
0462 }
0463 
0464 GPGProc::~GPGProc()
0465 {
0466     delete d;
0467 }
0468 
0469 void GPGProc::reset()
0470 {
0471     d->reset(ResetAll);
0472 }
0473 
0474 bool GPGProc::isActive() const
0475 {
0476     return (d->proc ? true : false);
0477 }
0478 
0479 void GPGProc::start(const QString &bin, const QStringList &args, Mode mode)
0480 {
0481     if (isActive())
0482         d->reset(ResetSessionAndData);
0483 
0484     if (mode == ExtendedMode) {
0485         if (!d->setupPipes(args.contains(QStringLiteral("-&?")))) {
0486             d->error = FailedToStart;
0487 
0488             // emit later
0489             QMetaObject::invokeMethod(
0490                 this, "error", Qt::QueuedConnection, Q_ARG(gpgQCAPlugin::GPGProc::Error, d->error));
0491             return;
0492         }
0493 
0494         d->need_status = true;
0495 
0496         emit debug(QStringLiteral("Pipe setup complete"));
0497     }
0498 
0499     d->proc = new SProcess(d);
0500 
0501 #ifdef Q_OS_UNIX
0502     QList<int> plist;
0503     if (d->pipeAux.readEnd().isValid())
0504         plist += d->pipeAux.readEnd().id();
0505     if (d->pipeCommand.readEnd().isValid())
0506         plist += d->pipeCommand.readEnd().id();
0507     if (d->pipeStatus.writeEnd().isValid())
0508         plist += d->pipeStatus.writeEnd().id();
0509     d->proc->setInheritPipeList(plist);
0510 #endif
0511 
0512     // enable the pipes we want
0513     if (d->pipeAux.writeEnd().isValid())
0514         d->pipeAux.writeEnd().enable();
0515     if (d->pipeCommand.writeEnd().isValid())
0516         d->pipeCommand.writeEnd().enable();
0517     if (d->pipeStatus.readEnd().isValid())
0518         d->pipeStatus.readEnd().enable();
0519 
0520     d->proc_relay = new QProcessSignalRelay(d->proc, d);
0521     connect(d->proc_relay, &QProcessSignalRelay::started, d, &GPGProc::Private::proc_started);
0522     connect(d->proc_relay,
0523             &QProcessSignalRelay::readyReadStandardOutput,
0524             d,
0525             &GPGProc::Private::proc_readyReadStandardOutput);
0526     connect(
0527         d->proc_relay, &QProcessSignalRelay::readyReadStandardError, d, &GPGProc::Private::proc_readyReadStandardError);
0528     connect(d->proc_relay, &QProcessSignalRelay::bytesWritten, d, &GPGProc::Private::proc_bytesWritten);
0529     connect(d->proc_relay, &QProcessSignalRelay::finished, d, &GPGProc::Private::proc_finished);
0530     connect(d->proc_relay, &QProcessSignalRelay::error, d, &GPGProc::Private::proc_error);
0531 
0532     d->bin  = bin;
0533     d->args = args;
0534     d->mode = mode;
0535     d->startTrigger.start();
0536 }
0537 
0538 QByteArray GPGProc::readStdout()
0539 {
0540     if (d->proc) {
0541         d->proc->setReadChannel(QProcess::StandardOutput);
0542         return d->proc->readAll();
0543     } else {
0544         const QByteArray a = d->leftover_stdout;
0545         d->leftover_stdout.clear();
0546         return a;
0547     }
0548 }
0549 
0550 QByteArray GPGProc::readStderr()
0551 {
0552     if (d->proc) {
0553         d->proc->setReadChannel(QProcess::StandardError);
0554         return d->proc->readAll();
0555     } else {
0556         const QByteArray a = d->leftover_stderr;
0557         d->leftover_stderr.clear();
0558         return a;
0559     }
0560 }
0561 
0562 QStringList GPGProc::readStatusLines()
0563 {
0564     const QStringList out = d->statusLines;
0565     d->statusLines.clear();
0566     return out;
0567 }
0568 
0569 void GPGProc::writeStdin(const QByteArray &a)
0570 {
0571     if (!d->proc || a.isEmpty())
0572         return;
0573 
0574     if (d->proc->state() == QProcess::Running)
0575         d->proc->write(a);
0576     else
0577         d->pre_stdin += a;
0578 }
0579 
0580 void GPGProc::writeAux(const QByteArray &a)
0581 {
0582     if (!d->proc || a.isEmpty())
0583         return;
0584 
0585     if (d->proc->state() == QProcess::Running)
0586         d->pipeAux.writeEnd().write(a);
0587     else
0588         d->pre_aux += a;
0589 }
0590 
0591 #ifdef QPIPE_SECURE
0592 void GPGProc::writeCommand(const SecureArray &a)
0593 #else
0594 void GPGProc::writeCommand(const QByteArray &a)
0595 #endif
0596 {
0597     if (!d->proc || a.isEmpty())
0598         return;
0599 
0600     if (d->proc->state() == QProcess::Running)
0601 #ifdef QPIPE_SECURE
0602         d->pipeCommand.writeEnd().writeSecure(a);
0603 #else
0604         d->pipeCommand.writeEnd().write(a);
0605 #endif
0606     else
0607         d->pre_command += a;
0608 }
0609 
0610 void GPGProc::closeStdin()
0611 {
0612     if (!d->proc)
0613         return;
0614 
0615     if (d->proc->state() == QProcess::Running) {
0616         d->proc->waitForBytesWritten();
0617         d->proc->closeWriteChannel();
0618     } else {
0619         d->pre_stdin_close = true;
0620     }
0621 }
0622 
0623 void GPGProc::closeAux()
0624 {
0625     if (!d->proc)
0626         return;
0627 
0628     if (d->proc->state() == QProcess::Running)
0629         d->pipeAux.writeEnd().close();
0630     else
0631         d->pre_aux_close = true;
0632 }
0633 
0634 void GPGProc::closeCommand()
0635 {
0636     if (!d->proc)
0637         return;
0638 
0639     if (d->proc->state() == QProcess::Running)
0640         d->pipeCommand.writeEnd().close();
0641     else
0642         d->pre_command_close = true;
0643 }
0644 
0645 }