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 | |
26 | using namespace QCA; |
27 | |
28 | namespace gpgQCAPlugin { |
29 | |
30 | void releaseAndDeleteLater(QObject *owner, QObject *obj) |
31 | { |
32 | obj->disconnect(receiver: owner); |
33 | obj->setParent(nullptr); |
34 | obj->deleteLater(); |
35 | } |
36 | |
37 | GPGProc::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 | |
65 | GPGProc::Private::~Private() |
66 | { |
67 | reset(mode: ResetSession); |
68 | } |
69 | |
70 | void 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 | |
83 | void 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 | |
136 | bool 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 | |
164 | void 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 | |
195 | void 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 | |
217 | void GPGProc::Private::aux_written(int x) |
218 | { |
219 | emit q->bytesWrittenAux(bytes: x); |
220 | } |
221 | |
222 | void 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 | |
229 | void GPGProc::Private::command_written(int x) |
230 | { |
231 | emit q->bytesWrittenCommand(bytes: x); |
232 | } |
233 | |
234 | void 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 | |
241 | void GPGProc::Private::status_read() |
242 | { |
243 | if (readAndProcessStatusData()) |
244 | emit q->readyReadStatusLines(); |
245 | } |
246 | |
247 | void 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 | |
258 | void 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 | |
298 | void GPGProc::Private::proc_readyReadStandardOutput() |
299 | { |
300 | emit q->readyReadStdout(); |
301 | } |
302 | |
303 | void GPGProc::Private::proc_readyReadStandardError() |
304 | { |
305 | emit q->readyReadStderr(); |
306 | } |
307 | |
308 | void GPGProc::Private::proc_bytesWritten(qint64 lx) |
309 | { |
310 | int x = (int)lx; |
311 | emit q->bytesWrittenStdin(bytes: x); |
312 | } |
313 | |
314 | void 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 | |
335 | void 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 | |
383 | void 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 | |
407 | bool 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 |
417 | bool 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 | |
458 | GPGProc::GPGProc(QObject *parent) |
459 | : QObject(parent) |
460 | { |
461 | d = new Private(this); |
462 | } |
463 | |
464 | GPGProc::~GPGProc() |
465 | { |
466 | delete d; |
467 | } |
468 | |
469 | void GPGProc::reset() |
470 | { |
471 | d->reset(mode: ResetAll); |
472 | } |
473 | |
474 | bool GPGProc::isActive() const |
475 | { |
476 | return (d->proc ? true : false); |
477 | } |
478 | |
479 | void 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 | |
538 | QByteArray 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 | |
550 | QByteArray 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 | |
562 | QStringList GPGProc::readStatusLines() |
563 | { |
564 | const QStringList out = d->statusLines; |
565 | d->statusLines.clear(); |
566 | return out; |
567 | } |
568 | |
569 | void 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 | |
580 | void 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 |
592 | void GPGProc::writeCommand(const SecureArray &a) |
593 | #else |
594 | void 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 | |
610 | void 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 | |
623 | void 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 | |
634 | void 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 | |