1/*
2 * Copyright (C) 2003-2007 Justin Karneges <justin@affinix.com>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
17 *
18 */
19
20#include "gpgproc_p.h"
21
22#ifdef Q_OS_MAC
23#define QT_PIPE_HACK
24#endif
25
26using namespace QCA;
27
28namespace gpgQCAPlugin {
29
30void releaseAndDeleteLater(QObject *owner, QObject *obj)
31{
32 obj->disconnect(receiver: owner);
33 obj->setParent(nullptr);
34 obj->deleteLater();
35}
36
37GPGProc::Private::Private(GPGProc *_q)
38 : QObject(_q)
39 , q(_q)
40 , pipeAux(this)
41 , pipeCommand(this)
42 , pipeStatus(this)
43 , startTrigger(this)
44 , doneTrigger(this)
45{
46 qRegisterMetaType<gpgQCAPlugin::GPGProc::Error>(typeName: "gpgQCAPlugin::GPGProc::Error");
47
48 proc = nullptr;
49 proc_relay = nullptr;
50 startTrigger.setSingleShot(true);
51 doneTrigger.setSingleShot(true);
52
53 connect(sender: &pipeAux.writeEnd(), signal: &QCA::QPipeEnd::bytesWritten, context: this, slot: &GPGProc::Private::aux_written);
54 connect(sender: &pipeAux.writeEnd(), signal: &QCA::QPipeEnd::error, context: this, slot: &GPGProc::Private::aux_error);
55 connect(sender: &pipeCommand.writeEnd(), signal: &QCA::QPipeEnd::bytesWritten, context: this, slot: &GPGProc::Private::command_written);
56 connect(sender: &pipeCommand.writeEnd(), signal: &QCA::QPipeEnd::error, context: this, slot: &GPGProc::Private::command_error);
57 connect(sender: &pipeStatus.readEnd(), signal: &QCA::QPipeEnd::readyRead, context: this, slot: &GPGProc::Private::status_read);
58 connect(sender: &pipeStatus.readEnd(), signal: &QCA::QPipeEnd::error, context: this, slot: &GPGProc::Private::status_error);
59 connect(sender: &startTrigger, signal: &QCA::SafeTimer::timeout, context: this, slot: &GPGProc::Private::doStart);
60 connect(sender: &doneTrigger, signal: &QCA::SafeTimer::timeout, context: this, slot: &GPGProc::Private::doTryDone);
61
62 reset(mode: ResetSessionAndData);
63}
64
65GPGProc::Private::~Private()
66{
67 reset(mode: ResetSession);
68}
69
70void GPGProc::Private::closePipes()
71{
72#ifdef QT_PIPE_HACK
73 pipeAux.readEnd().reset();
74 pipeCommand.readEnd().reset();
75 pipeStatus.writeEnd().reset();
76#endif
77
78 pipeAux.reset();
79 pipeCommand.reset();
80 pipeStatus.reset();
81}
82
83void GPGProc::Private::reset(ResetMode mode)
84{
85#ifndef QT_PIPE_HACK
86 closePipes();
87#endif
88
89 if (proc) {
90 proc->disconnect(receiver: this);
91
92 if (proc->state() != QProcess::NotRunning) {
93 // Before try to correct end proccess
94 // Terminate if failed
95 proc->close();
96 bool finished = proc->waitForFinished(msecs: 5000);
97 if (!finished)
98 proc->terminate();
99 }
100
101 proc->setParent(nullptr);
102 releaseAndDeleteLater(owner: this, obj: proc_relay);
103 proc_relay = nullptr;
104 delete proc; // should be safe to do thanks to relay
105 proc = nullptr;
106 }
107
108#ifdef QT_PIPE_HACK
109 closePipes();
110#endif
111
112 startTrigger.stop();
113 doneTrigger.stop();
114
115 pre_stdin.clear();
116 pre_aux.clear();
117 pre_command.clear();
118 pre_stdin_close = false;
119 pre_aux_close = false;
120 pre_command_close = false;
121
122 need_status = false;
123 fin_process = false;
124 fin_status = false;
125
126 if (mode >= ResetSessionAndData) {
127 statusBuf.clear();
128 statusLines.clear();
129 leftover_stdout.clear();
130 leftover_stderr.clear();
131 error = GPGProc::FailedToStart;
132 exitCode = -1;
133 }
134}
135
136bool GPGProc::Private::setupPipes(bool makeAux)
137{
138 if (makeAux && !pipeAux.create()) {
139 closePipes();
140 emit q->debug(QStringLiteral("Error creating pipeAux"));
141 return false;
142 }
143
144#ifdef QPIPE_SECURE
145 if (!pipeCommand.create(secure: true)) // secure
146#else
147 if (!pipeCommand.create())
148#endif
149 {
150 closePipes();
151 emit q->debug(QStringLiteral("Error creating pipeCommand"));
152 return false;
153 }
154
155 if (!pipeStatus.create()) {
156 closePipes();
157 emit q->debug(QStringLiteral("Error creating pipeStatus"));
158 return false;
159 }
160
161 return true;
162}
163
164void GPGProc::Private::setupArguments()
165{
166 QStringList fullargs;
167 fullargs += QStringLiteral("--no-tty");
168 fullargs += QStringLiteral("--pinentry-mode");
169 fullargs += QStringLiteral("loopback");
170
171 if (mode == ExtendedMode) {
172 fullargs += QStringLiteral("--enable-special-filenames");
173
174 fullargs += QStringLiteral("--status-fd");
175 fullargs += QString::number(pipeStatus.writeEnd().idAsInt());
176
177 fullargs += QStringLiteral("--command-fd");
178 fullargs += QString::number(pipeCommand.readEnd().idAsInt());
179 }
180
181 for (int n = 0; n < args.count(); ++n) {
182 QString a = args[n];
183 if (mode == ExtendedMode && a == QLatin1String("-&?"))
184 fullargs += QStringLiteral("-&") + QString::number(pipeAux.readEnd().idAsInt());
185 else
186 fullargs += a;
187 }
188
189 QString fullcmd = fullargs.join(QStringLiteral(" "));
190 emit q->debug(QStringLiteral("Running: [") + bin + QLatin1Char(' ') + fullcmd + QLatin1Char(']'));
191
192 args = fullargs;
193}
194
195void GPGProc::Private::doStart()
196{
197#ifdef Q_OS_WIN
198 // Note: for unix, inheritability is set in SProcess
199 if (pipeAux.readEnd().isValid())
200 pipeAux.readEnd().setInheritable(true);
201 if (pipeCommand.readEnd().isValid())
202 pipeCommand.readEnd().setInheritable(true);
203 if (pipeStatus.writeEnd().isValid())
204 pipeStatus.writeEnd().setInheritable(true);
205#endif
206
207 setupArguments();
208
209 proc->start(program: bin, arguments: args);
210 proc->waitForStarted();
211
212 pipeAux.readEnd().close();
213 pipeCommand.readEnd().close();
214 pipeStatus.writeEnd().close();
215}
216
217void GPGProc::Private::aux_written(int x)
218{
219 emit q->bytesWrittenAux(bytes: x);
220}
221
222void GPGProc::Private::aux_error(QCA::QPipeEnd::Error)
223{
224 emit q->debug(QStringLiteral("Aux: Pipe error"));
225 reset(mode: ResetSession);
226 emit q->error(error: GPGProc::ErrorWrite);
227}
228
229void GPGProc::Private::command_written(int x)
230{
231 emit q->bytesWrittenCommand(bytes: x);
232}
233
234void GPGProc::Private::command_error(QCA::QPipeEnd::Error)
235{
236 emit q->debug(QStringLiteral("Command: Pipe error"));
237 reset(mode: ResetSession);
238 emit q->error(error: GPGProc::ErrorWrite);
239}
240
241void GPGProc::Private::status_read()
242{
243 if (readAndProcessStatusData())
244 emit q->readyReadStatusLines();
245}
246
247void GPGProc::Private::status_error(QCA::QPipeEnd::Error e)
248{
249 if (e == QPipeEnd::ErrorEOF)
250 emit q->debug(QStringLiteral("Status: Closed (EOF)"));
251 else
252 emit q->debug(QStringLiteral("Status: Closed (gone)"));
253
254 fin_status = true;
255 doTryDone();
256}
257
258void GPGProc::Private::proc_started()
259{
260 emit q->debug(QStringLiteral("Process started"));
261
262 // Note: we don't close these here anymore. instead we
263 // do it just after calling proc->start().
264 // close these, we don't need them
265 /*pipeAux.readEnd().close();
266 pipeCommand.readEnd().close();
267 pipeStatus.writeEnd().close();*/
268
269 // do the pre* stuff
270 if (!pre_stdin.isEmpty()) {
271 proc->write(data: pre_stdin);
272 pre_stdin.clear();
273 }
274 if (!pre_aux.isEmpty()) {
275 pipeAux.writeEnd().write(a: pre_aux);
276 pre_aux.clear();
277 }
278 if (!pre_command.isEmpty()) {
279#ifdef QPIPE_SECURE
280 pipeCommand.writeEnd().writeSecure(a: pre_command);
281#else
282 pipeCommand.writeEnd().write(pre_command);
283#endif
284 pre_command.clear();
285 }
286
287 if (pre_stdin_close) {
288 proc->waitForBytesWritten();
289 proc->closeWriteChannel();
290 }
291
292 if (pre_aux_close)
293 pipeAux.writeEnd().close();
294 if (pre_command_close)
295 pipeCommand.writeEnd().close();
296}
297
298void GPGProc::Private::proc_readyReadStandardOutput()
299{
300 emit q->readyReadStdout();
301}
302
303void GPGProc::Private::proc_readyReadStandardError()
304{
305 emit q->readyReadStderr();
306}
307
308void GPGProc::Private::proc_bytesWritten(qint64 lx)
309{
310 int x = (int)lx;
311 emit q->bytesWrittenStdin(bytes: x);
312}
313
314void GPGProc::Private::proc_finished(int x)
315{
316 emit q->debug(QStringLiteral("Process finished: %1").arg(a: x));
317 exitCode = x;
318
319 fin_process = true;
320 fin_process_success = true;
321
322 if (need_status && !fin_status) {
323 pipeStatus.readEnd().finalize();
324 fin_status = true;
325 if (readAndProcessStatusData()) {
326 doneTrigger.start();
327 emit q->readyReadStatusLines();
328 return;
329 }
330 }
331
332 doTryDone();
333}
334
335void GPGProc::Private::proc_error(QProcess::ProcessError x)
336{
337 QMap<int, QString> errmap;
338 errmap[QProcess::FailedToStart] = QStringLiteral("FailedToStart");
339 errmap[QProcess::Crashed] = QStringLiteral("Crashed");
340 errmap[QProcess::Timedout] = QStringLiteral("Timedout");
341 errmap[QProcess::WriteError] = QStringLiteral("WriteError");
342 errmap[QProcess::ReadError] = QStringLiteral("ReadError");
343 errmap[QProcess::UnknownError] = QStringLiteral("UnknownError");
344
345 emit q->debug(QStringLiteral("Process error: %1").arg(a: errmap[x]));
346
347 if (x == QProcess::FailedToStart)
348 error = GPGProc::FailedToStart;
349 else if (x == QProcess::WriteError)
350 error = GPGProc::ErrorWrite;
351 else
352 error = GPGProc::UnexpectedExit;
353
354 fin_process = true;
355 fin_process_success = false;
356
357#ifdef QT_PIPE_HACK
358 // If the process fails to start, then the ends of the pipes
359 // intended for the child process are still open. Some Mac
360 // users experience a lockup if we close our ends of the pipes
361 // when the child's ends are still open. If we ensure the
362 // child's ends are closed, we prevent this lockup. I have no
363 // idea why the problem even happens or why this fix should
364 // work.
365 pipeAux.readEnd().reset();
366 pipeCommand.readEnd().reset();
367 pipeStatus.writeEnd().reset();
368#endif
369
370 if (need_status && !fin_status) {
371 pipeStatus.readEnd().finalize();
372 fin_status = true;
373 if (readAndProcessStatusData()) {
374 doneTrigger.start();
375 emit q->readyReadStatusLines();
376 return;
377 }
378 }
379
380 doTryDone();
381}
382
383void GPGProc::Private::doTryDone()
384{
385 if (!fin_process)
386 return;
387
388 if (need_status && !fin_status)
389 return;
390
391 emit q->debug(QStringLiteral("Done"));
392
393 // get leftover data
394 proc->setReadChannel(QProcess::StandardOutput);
395 leftover_stdout = proc->readAll();
396
397 proc->setReadChannel(QProcess::StandardError);
398 leftover_stderr = proc->readAll();
399
400 reset(mode: ResetSession);
401 if (fin_process_success)
402 emit q->finished(exitCode);
403 else
404 emit q->error(error);
405}
406
407bool GPGProc::Private::readAndProcessStatusData()
408{
409 const QByteArray buf = pipeStatus.readEnd().read();
410 if (buf.isEmpty())
411 return false;
412
413 return processStatusData(buf);
414}
415
416// return true if there are newly parsed lines available
417bool GPGProc::Private::processStatusData(const QByteArray &buf)
418{
419 statusBuf.append(a: buf);
420
421 // extract all lines
422 QStringList list;
423 while (true) {
424 int n = statusBuf.indexOf(c: '\n');
425 if (n == -1)
426 break;
427
428 // extract the string from statusbuf
429 ++n;
430 char *p = (char *)statusBuf.data();
431 QByteArray cs(p, n);
432 const int newsize = statusBuf.size() - n;
433 memmove(dest: p, src: p + n, n: newsize);
434 statusBuf.resize(size: newsize);
435
436 // convert to string without newline
437 QString str = QString::fromUtf8(ba: cs);
438 str.truncate(pos: str.length() - 1);
439
440 // ensure it has a proper header
441 if (str.left(n: 9) != QLatin1String("[GNUPG:] "))
442 continue;
443
444 // take it off
445 str = str.mid(position: 9);
446
447 // add to the list
448 list += str;
449 }
450
451 if (list.isEmpty())
452 return false;
453
454 statusLines += list;
455 return true;
456}
457
458GPGProc::GPGProc(QObject *parent)
459 : QObject(parent)
460{
461 d = new Private(this);
462}
463
464GPGProc::~GPGProc()
465{
466 delete d;
467}
468
469void GPGProc::reset()
470{
471 d->reset(mode: ResetAll);
472}
473
474bool GPGProc::isActive() const
475{
476 return (d->proc ? true : false);
477}
478
479void GPGProc::start(const QString &bin, const QStringList &args, Mode mode)
480{
481 if (isActive())
482 d->reset(mode: ResetSessionAndData);
483
484 if (mode == ExtendedMode) {
485 if (!d->setupPipes(args.contains(QStringLiteral("-&?")))) {
486 d->error = FailedToStart;
487
488 // emit later
489 QMetaObject::invokeMethod(
490 obj: this, member: "error", c: Qt::QueuedConnection, Q_ARG(gpgQCAPlugin::GPGProc::Error, d->error));
491 return;
492 }
493
494 d->need_status = true;
495
496 emit debug(QStringLiteral("Pipe setup complete"));
497 }
498
499 d->proc = new SProcess(d);
500
501#ifdef Q_OS_UNIX
502 QList<int> plist;
503 if (d->pipeAux.readEnd().isValid())
504 plist += d->pipeAux.readEnd().id();
505 if (d->pipeCommand.readEnd().isValid())
506 plist += d->pipeCommand.readEnd().id();
507 if (d->pipeStatus.writeEnd().isValid())
508 plist += d->pipeStatus.writeEnd().id();
509 d->proc->setInheritPipeList(plist);
510#endif
511
512 // enable the pipes we want
513 if (d->pipeAux.writeEnd().isValid())
514 d->pipeAux.writeEnd().enable();
515 if (d->pipeCommand.writeEnd().isValid())
516 d->pipeCommand.writeEnd().enable();
517 if (d->pipeStatus.readEnd().isValid())
518 d->pipeStatus.readEnd().enable();
519
520 d->proc_relay = new QProcessSignalRelay(d->proc, d);
521 connect(sender: d->proc_relay, signal: &QProcessSignalRelay::started, context: d, slot: &GPGProc::Private::proc_started);
522 connect(sender: d->proc_relay,
523 signal: &QProcessSignalRelay::readyReadStandardOutput,
524 context: d,
525 slot: &GPGProc::Private::proc_readyReadStandardOutput);
526 connect(
527 sender: d->proc_relay, signal: &QProcessSignalRelay::readyReadStandardError, context: d, slot: &GPGProc::Private::proc_readyReadStandardError);
528 connect(sender: d->proc_relay, signal: &QProcessSignalRelay::bytesWritten, context: d, slot: &GPGProc::Private::proc_bytesWritten);
529 connect(sender: d->proc_relay, signal: &QProcessSignalRelay::finished, context: d, slot: &GPGProc::Private::proc_finished);
530 connect(sender: d->proc_relay, signal: &QProcessSignalRelay::error, context: d, slot: &GPGProc::Private::proc_error);
531
532 d->bin = bin;
533 d->args = args;
534 d->mode = mode;
535 d->startTrigger.start();
536}
537
538QByteArray GPGProc::readStdout()
539{
540 if (d->proc) {
541 d->proc->setReadChannel(QProcess::StandardOutput);
542 return d->proc->readAll();
543 } else {
544 const QByteArray a = d->leftover_stdout;
545 d->leftover_stdout.clear();
546 return a;
547 }
548}
549
550QByteArray GPGProc::readStderr()
551{
552 if (d->proc) {
553 d->proc->setReadChannel(QProcess::StandardError);
554 return d->proc->readAll();
555 } else {
556 const QByteArray a = d->leftover_stderr;
557 d->leftover_stderr.clear();
558 return a;
559 }
560}
561
562QStringList GPGProc::readStatusLines()
563{
564 const QStringList out = d->statusLines;
565 d->statusLines.clear();
566 return out;
567}
568
569void GPGProc::writeStdin(const QByteArray &a)
570{
571 if (!d->proc || a.isEmpty())
572 return;
573
574 if (d->proc->state() == QProcess::Running)
575 d->proc->write(data: a);
576 else
577 d->pre_stdin += a;
578}
579
580void GPGProc::writeAux(const QByteArray &a)
581{
582 if (!d->proc || a.isEmpty())
583 return;
584
585 if (d->proc->state() == QProcess::Running)
586 d->pipeAux.writeEnd().write(a);
587 else
588 d->pre_aux += a;
589}
590
591#ifdef QPIPE_SECURE
592void GPGProc::writeCommand(const SecureArray &a)
593#else
594void GPGProc::writeCommand(const QByteArray &a)
595#endif
596{
597 if (!d->proc || a.isEmpty())
598 return;
599
600 if (d->proc->state() == QProcess::Running)
601#ifdef QPIPE_SECURE
602 d->pipeCommand.writeEnd().writeSecure(a);
603#else
604 d->pipeCommand.writeEnd().write(a);
605#endif
606 else
607 d->pre_command += a;
608}
609
610void GPGProc::closeStdin()
611{
612 if (!d->proc)
613 return;
614
615 if (d->proc->state() == QProcess::Running) {
616 d->proc->waitForBytesWritten();
617 d->proc->closeWriteChannel();
618 } else {
619 d->pre_stdin_close = true;
620 }
621}
622
623void GPGProc::closeAux()
624{
625 if (!d->proc)
626 return;
627
628 if (d->proc->state() == QProcess::Running)
629 d->pipeAux.writeEnd().close();
630 else
631 d->pre_aux_close = true;
632}
633
634void GPGProc::closeCommand()
635{
636 if (!d->proc)
637 return;
638
639 if (d->proc->state() == QProcess::Running)
640 d->pipeCommand.writeEnd().close();
641 else
642 d->pre_command_close = true;
643}
644
645}
646

source code of qca/plugins/qca-gnupg/gpgproc/gpgproc.cpp