1 | /* |
2 | * Copyright (C) 2003-2005 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 St, Fifth Floor, Boston, MA 02110-1301, USA |
17 | * |
18 | */ |
19 | |
20 | // #define GPGOP_DEBUG |
21 | |
22 | #include "gpgaction.h" |
23 | |
24 | #ifdef GPGOP_DEBUG |
25 | #include "stdio.h" |
26 | #endif |
27 | |
28 | namespace gpgQCAPlugin { |
29 | |
30 | static QDateTime getTimestamp(const QString &s) |
31 | { |
32 | if (s.isEmpty()) |
33 | return QDateTime(); |
34 | |
35 | if (s.contains(c: QLatin1Char('T'))) { |
36 | return QDateTime::fromString(string: s, format: Qt::ISODate); |
37 | } else { |
38 | return QDateTime::fromSecsSinceEpoch(secs: s.toInt()); |
39 | } |
40 | } |
41 | |
42 | static QByteArray getCString(const QByteArray &a) |
43 | { |
44 | QByteArray out; |
45 | |
46 | // convert the "backslash" C-string syntax |
47 | for (int n = 0; n < a.size(); ++n) { |
48 | if (a[n] == '\\' && n + 1 < a.size()) { |
49 | ++n; |
50 | unsigned char c = (unsigned char)a[n]; |
51 | if (c == '\\') { |
52 | out += '\\'; |
53 | } else if (c == 'x' && n + 2 < a.size()) { |
54 | ++n; |
55 | const QByteArray hex = a.mid(index: n, len: 2); |
56 | ++n; // only skip one, loop will skip the next |
57 | |
58 | bool ok; |
59 | uint val = hex.toInt(ok: &ok, base: 16); |
60 | if (ok) { |
61 | out += (unsigned char)val; |
62 | } else { |
63 | out += "\\x" ; |
64 | out += hex; |
65 | } |
66 | } |
67 | } else { |
68 | out += a[n]; |
69 | } |
70 | } |
71 | |
72 | return out; |
73 | } |
74 | |
75 | static bool stringToKeyList(const QString &outstr, GpgOp::KeyList *_keylist, QString *_keyring) |
76 | { |
77 | GpgOp::KeyList keyList; |
78 | const QStringList lines = outstr.split(sep: QLatin1Char('\n')); |
79 | |
80 | if (lines.count() < 1) |
81 | return false; |
82 | |
83 | QStringList::ConstIterator it = lines.constBegin(); |
84 | |
85 | // first line is keyring file |
86 | QString keyring = *(it++); |
87 | |
88 | // if the second line isn't a divider, we are dealing |
89 | // with a new version of gnupg that doesn't give us |
90 | // the keyring file on gpg --list-keys --with-colons |
91 | if (it == lines.constEnd() || (*it).isEmpty() || (*it).at(i: 0) != QLatin1Char('-')) { |
92 | // first line wasn't the keyring name... |
93 | keyring.clear(); |
94 | // ...so read the first line again |
95 | it--; |
96 | } else { |
97 | // this was the divider line - skip it |
98 | it++; |
99 | } |
100 | |
101 | for (; it != lines.constEnd(); ++it) { |
102 | const QStringList f = (*it).split(sep: QLatin1Char(':')); |
103 | if (f.count() < 1) |
104 | continue; |
105 | const QString &type = f[0]; |
106 | |
107 | bool key = false; // key or not |
108 | bool primary = false; // primary key or sub key |
109 | // bool sec = false; // private key or not |
110 | |
111 | if (type == QLatin1String("pub" )) { |
112 | key = true; |
113 | primary = true; |
114 | } else if (type == QLatin1String("sec" )) { |
115 | key = true; |
116 | primary = true; |
117 | // sec = true; |
118 | } else if (type == QLatin1String("sub" )) { |
119 | key = true; |
120 | } else if (type == QLatin1String("ssb" )) { |
121 | key = true; |
122 | // sec = true; |
123 | } |
124 | |
125 | if (key) { |
126 | if (primary) { |
127 | keyList += GpgOp::Key(); |
128 | |
129 | const QString &trust = f[1]; |
130 | if (trust == QLatin1String("f" ) || trust == QLatin1String("u" )) |
131 | keyList.last().isTrusted = true; |
132 | } |
133 | |
134 | const int key_type = f[3].toInt(); |
135 | const QString &caps = f[11]; |
136 | |
137 | GpgOp::KeyItem item; |
138 | item.bits = f[2].toInt(); |
139 | if (key_type == 1) |
140 | item.type = GpgOp::KeyItem::RSA; |
141 | else if (key_type == 16) |
142 | item.type = GpgOp::KeyItem::ElGamal; |
143 | else if (key_type == 17) |
144 | item.type = GpgOp::KeyItem::DSA; |
145 | else |
146 | item.type = GpgOp::KeyItem::Unknown; |
147 | item.id = f[4]; |
148 | item.creationDate = getTimestamp(s: f[5]); |
149 | item.expirationDate = getTimestamp(s: f[6]); |
150 | if (caps.contains(c: QLatin1Char('e'))) |
151 | item.caps |= GpgOp::KeyItem::Encrypt; |
152 | if (caps.contains(c: QLatin1Char('s'))) |
153 | item.caps |= GpgOp::KeyItem::Sign; |
154 | if (caps.contains(c: QLatin1Char('c'))) |
155 | item.caps |= GpgOp::KeyItem::Certify; |
156 | if (caps.contains(c: QLatin1Char('a'))) |
157 | item.caps |= GpgOp::KeyItem::Auth; |
158 | |
159 | keyList.last().keyItems += item; |
160 | } else if (type == QLatin1String("uid" )) { |
161 | const QByteArray uid = getCString(a: f[9].toUtf8()); |
162 | keyList.last().userIds.append(t: QString::fromUtf8(ba: uid)); |
163 | } else if (type == QLatin1String("fpr" )) { |
164 | const QString &s = f[9]; |
165 | keyList.last().keyItems.last().fingerprint = s; |
166 | } |
167 | } |
168 | |
169 | if (_keylist) |
170 | *_keylist = keyList; |
171 | if (_keyring) |
172 | *_keyring = keyring; |
173 | |
174 | return true; |
175 | } |
176 | |
177 | static bool findKeyringFilename(const QString &outstr, QString *_keyring) |
178 | { |
179 | const QStringList lines = outstr.split(sep: QLatin1Char('\n')); |
180 | if (lines.count() < 1) |
181 | return false; |
182 | |
183 | *_keyring = lines[0]; |
184 | return true; |
185 | } |
186 | |
187 | GpgAction::GpgAction(QObject *parent) |
188 | : QObject(parent) |
189 | , proc(this) |
190 | , dtextTimer(this) |
191 | , utf8Output(false) |
192 | { |
193 | dtextTimer.setSingleShot(true); |
194 | |
195 | connect(sender: &proc, signal: &GPGProc::error, context: this, slot: &GpgAction::proc_error); |
196 | connect(sender: &proc, signal: &GPGProc::finished, context: this, slot: &GpgAction::proc_finished); |
197 | connect(sender: &proc, signal: &GPGProc::readyReadStdout, context: this, slot: &GpgAction::proc_readyReadStdout); |
198 | connect(sender: &proc, signal: &GPGProc::readyReadStderr, context: this, slot: &GpgAction::proc_readyReadStderr); |
199 | connect(sender: &proc, signal: &GPGProc::readyReadStatusLines, context: this, slot: &GpgAction::proc_readyReadStatusLines); |
200 | connect(sender: &proc, signal: &GPGProc::bytesWrittenStdin, context: this, slot: &GpgAction::proc_bytesWrittenStdin); |
201 | connect(sender: &proc, signal: &GPGProc::bytesWrittenAux, context: this, slot: &GpgAction::proc_bytesWrittenAux); |
202 | connect(sender: &proc, signal: &GPGProc::bytesWrittenCommand, context: this, slot: &GpgAction::proc_bytesWrittenCommand); |
203 | connect(sender: &proc, signal: &GPGProc::debug, context: this, slot: &GpgAction::proc_debug); |
204 | connect(sender: &dtextTimer, signal: &QCA::SafeTimer::timeout, context: this, slot: &GpgAction::t_dtext); |
205 | |
206 | reset(); |
207 | } |
208 | |
209 | GpgAction::~GpgAction() |
210 | { |
211 | reset(); |
212 | } |
213 | |
214 | void GpgAction::reset() |
215 | { |
216 | collectOutput = true; |
217 | allowInput = false; |
218 | readConv.setup(LineConverter::Read); |
219 | writeConv.setup(LineConverter::Write); |
220 | readText = false; |
221 | writeText = false; |
222 | useAux = false; |
223 | passphraseKeyId = QString(); |
224 | signing = false; |
225 | decryptGood = false; |
226 | signGood = false; |
227 | curError = GpgOp::ErrorUnknown; |
228 | badPassphrase = false; |
229 | need_submitPassphrase = false; |
230 | need_cardOkay = false; |
231 | diagnosticText = QString(); |
232 | dtextTimer.stop(); |
233 | |
234 | output = Output(); |
235 | |
236 | proc.reset(); |
237 | } |
238 | |
239 | void GpgAction::start() |
240 | { |
241 | reset(); |
242 | |
243 | QStringList args; |
244 | bool = false; |
245 | |
246 | if (input.opt_ascii) |
247 | args += QStringLiteral("--armor" ); |
248 | |
249 | if (input.opt_noagent) |
250 | args += QStringLiteral("--no-use-agent" ); |
251 | |
252 | if (input.opt_alwaystrust) |
253 | args += QStringLiteral("--always-trust" ); |
254 | |
255 | if (!input.opt_pubfile.isEmpty() && !input.opt_secfile.isEmpty()) { |
256 | args += QStringLiteral("--no-default-keyring" ); |
257 | args += QStringLiteral("--keyring" ); |
258 | args += input.opt_pubfile; |
259 | args += QStringLiteral("--secret-keyring" ); |
260 | args += input.opt_secfile; |
261 | } |
262 | |
263 | switch (input.op) { |
264 | case GpgOp::Check: |
265 | { |
266 | args += QStringLiteral("--version" ); |
267 | readText = true; |
268 | break; |
269 | } |
270 | case GpgOp::SecretKeyringFile: |
271 | { |
272 | #ifndef Q_OS_WIN |
273 | args += QStringLiteral("--display-charset=utf-8" ); |
274 | #endif |
275 | args += QStringLiteral("--list-secret-keys" ); |
276 | readText = true; |
277 | break; |
278 | } |
279 | case GpgOp::PublicKeyringFile: |
280 | { |
281 | #ifndef Q_OS_WIN |
282 | args += QStringLiteral("--display-charset=utf-8" ); |
283 | #endif |
284 | args += QStringLiteral("--list-public-keys" ); |
285 | readText = true; |
286 | break; |
287 | } |
288 | case GpgOp::SecretKeys: |
289 | { |
290 | args += QStringLiteral("--fixed-list-mode" ); |
291 | args += QStringLiteral("--with-colons" ); |
292 | args += QStringLiteral("--with-fingerprint" ); |
293 | args += QStringLiteral("--with-fingerprint" ); |
294 | args += QStringLiteral("--list-secret-keys" ); |
295 | utf8Output = true; |
296 | readText = true; |
297 | break; |
298 | } |
299 | case GpgOp::PublicKeys: |
300 | { |
301 | args += QStringLiteral("--fixed-list-mode" ); |
302 | args += QStringLiteral("--with-colons" ); |
303 | args += QStringLiteral("--with-fingerprint" ); |
304 | args += QStringLiteral("--with-fingerprint" ); |
305 | args += QStringLiteral("--list-public-keys" ); |
306 | utf8Output = true; |
307 | readText = true; |
308 | break; |
309 | } |
310 | case GpgOp::Encrypt: |
311 | { |
312 | args += QStringLiteral("--encrypt" ); |
313 | |
314 | // recipients |
315 | for (QStringList::ConstIterator it = input.recip_ids.constBegin(); it != input.recip_ids.constEnd(); ++it) { |
316 | args += QStringLiteral("--recipient" ); |
317 | args += QStringLiteral("0x" ) + *it; |
318 | } |
319 | extra = true; |
320 | collectOutput = false; |
321 | allowInput = true; |
322 | if (input.opt_ascii) |
323 | readText = true; |
324 | break; |
325 | } |
326 | case GpgOp::Decrypt: |
327 | { |
328 | args += QStringLiteral("--decrypt" ); |
329 | extra = true; |
330 | collectOutput = false; |
331 | allowInput = true; |
332 | if (input.opt_ascii) |
333 | writeText = true; |
334 | break; |
335 | } |
336 | case GpgOp::Sign: |
337 | { |
338 | args += QStringLiteral("--default-key" ); |
339 | args += QStringLiteral("0x" ) + input.signer_id; |
340 | args += QStringLiteral("--sign" ); |
341 | extra = true; |
342 | collectOutput = false; |
343 | allowInput = true; |
344 | if (input.opt_ascii) |
345 | readText = true; |
346 | signing = true; |
347 | break; |
348 | } |
349 | case GpgOp::SignAndEncrypt: |
350 | { |
351 | args += QStringLiteral("--default-key" ); |
352 | args += QStringLiteral("0x" ) + input.signer_id; |
353 | args += QStringLiteral("--sign" ); |
354 | args += QStringLiteral("--encrypt" ); |
355 | |
356 | // recipients |
357 | for (QStringList::ConstIterator it = input.recip_ids.constBegin(); it != input.recip_ids.constEnd(); ++it) { |
358 | args += QStringLiteral("--recipient" ); |
359 | args += QStringLiteral("0x" ) + *it; |
360 | } |
361 | extra = true; |
362 | collectOutput = false; |
363 | allowInput = true; |
364 | if (input.opt_ascii) |
365 | readText = true; |
366 | signing = true; |
367 | break; |
368 | } |
369 | case GpgOp::SignClearsign: |
370 | { |
371 | args += QStringLiteral("--default-key" ); |
372 | args += QStringLiteral("0x" ) + input.signer_id; |
373 | args += QStringLiteral("--clearsign" ); |
374 | extra = true; |
375 | collectOutput = false; |
376 | allowInput = true; |
377 | if (input.opt_ascii) |
378 | readText = true; |
379 | signing = true; |
380 | break; |
381 | } |
382 | case GpgOp::SignDetached: |
383 | { |
384 | args += QStringLiteral("--default-key" ); |
385 | args += QStringLiteral("0x" ) + input.signer_id; |
386 | args += QStringLiteral("--detach-sign" ); |
387 | extra = true; |
388 | collectOutput = false; |
389 | allowInput = true; |
390 | if (input.opt_ascii) |
391 | readText = true; |
392 | signing = true; |
393 | break; |
394 | } |
395 | case GpgOp::Verify: |
396 | { |
397 | args += QStringLiteral("--verify" ); |
398 | args += QStringLiteral("-" ); // krazy:exclude=doublequote_chars |
399 | extra = true; |
400 | allowInput = true; |
401 | if (input.opt_ascii) |
402 | writeText = true; |
403 | break; |
404 | } |
405 | case GpgOp::VerifyDetached: |
406 | { |
407 | args += QStringLiteral("--verify" ); |
408 | args += QStringLiteral("-" ); // krazy:exclude=doublequote_chars |
409 | args += QStringLiteral("-&?" ); |
410 | extra = true; |
411 | allowInput = true; |
412 | useAux = true; |
413 | break; |
414 | } |
415 | case GpgOp::Import: |
416 | { |
417 | args += QStringLiteral("--import" ); |
418 | readText = true; |
419 | if (input.opt_ascii) |
420 | writeText = true; |
421 | break; |
422 | } |
423 | case GpgOp::Export: |
424 | { |
425 | args += QStringLiteral("--export" ); |
426 | args += QStringLiteral("0x" ) + input.export_key_id; |
427 | collectOutput = false; |
428 | if (input.opt_ascii) |
429 | readText = true; |
430 | break; |
431 | } |
432 | case GpgOp::DeleteKey: |
433 | { |
434 | args += QStringLiteral("--batch" ); |
435 | args += QStringLiteral("--delete-key" ); |
436 | args += QStringLiteral("0x" ) + input.delete_key_fingerprint; |
437 | break; |
438 | } |
439 | } |
440 | |
441 | #ifdef GPG_PROFILE |
442 | timer.start(); |
443 | printf("<< launch >>\n" ); |
444 | #endif |
445 | proc.start(bin: input.bin, args, m: extra ? GPGProc::ExtendedMode : GPGProc::NormalMode); |
446 | |
447 | // detached sig |
448 | if (input.op == GpgOp::VerifyDetached) { |
449 | QByteArray a = input.sig; |
450 | if (input.opt_ascii) { |
451 | LineConverter conv; |
452 | conv.setup(LineConverter::Write); |
453 | a = conv.process(buf: a); |
454 | } |
455 | proc.writeStdin(a); |
456 | proc.closeStdin(); |
457 | } |
458 | |
459 | // import |
460 | if (input.op == GpgOp::Import) { |
461 | QByteArray a = input.inkey; |
462 | if (writeText) { |
463 | LineConverter conv; |
464 | conv.setup(LineConverter::Write); |
465 | a = conv.process(buf: a); |
466 | } |
467 | proc.writeStdin(a); |
468 | proc.closeStdin(); |
469 | } |
470 | } |
471 | |
472 | #ifdef QPIPE_SECURE |
473 | void GpgAction::submitPassphrase(const QCA::SecureArray &a) |
474 | #else |
475 | void GpgAction::submitPassphrase(const QByteArray &a) |
476 | #endif |
477 | { |
478 | if (!need_submitPassphrase) |
479 | return; |
480 | |
481 | need_submitPassphrase = false; |
482 | |
483 | #ifdef QPIPE_SECURE |
484 | QCA::SecureArray b; |
485 | #else |
486 | QByteArray b; |
487 | #endif |
488 | // filter out newlines, since that's the delimiter used |
489 | // to indicate a submitted passphrase |
490 | b.resize(size: a.size()); |
491 | int at = 0; |
492 | for (int n = 0; n < a.size(); ++n) { |
493 | if (a[n] != '\n') |
494 | b[at++] = a[n]; |
495 | } |
496 | b.resize(size: at); |
497 | |
498 | // append newline |
499 | b.resize(size: b.size() + 1); |
500 | b[b.size() - 1] = '\n'; |
501 | proc.writeCommand(a: b); |
502 | } |
503 | |
504 | QByteArray GpgAction::read() |
505 | { |
506 | if (collectOutput) |
507 | return QByteArray(); |
508 | |
509 | QByteArray a = proc.readStdout(); |
510 | if (readText) |
511 | a = readConv.update(buf: a); |
512 | if (!proc.isActive()) |
513 | a += readConv.final(); |
514 | return a; |
515 | } |
516 | |
517 | void GpgAction::write(const QByteArray &in) |
518 | { |
519 | if (!allowInput) |
520 | return; |
521 | |
522 | QByteArray a = in; |
523 | if (writeText) |
524 | a = writeConv.update(buf: in); |
525 | |
526 | if (useAux) |
527 | proc.writeAux(a); |
528 | else |
529 | proc.writeStdin(a); |
530 | } |
531 | |
532 | void GpgAction::endWrite() |
533 | { |
534 | if (!allowInput) |
535 | return; |
536 | |
537 | if (useAux) |
538 | proc.closeAux(); |
539 | else |
540 | proc.closeStdin(); |
541 | } |
542 | |
543 | void GpgAction::cardOkay() |
544 | { |
545 | if (need_cardOkay) { |
546 | need_cardOkay = false; |
547 | submitCommand(a: "\n" ); |
548 | } |
549 | } |
550 | |
551 | QString GpgAction::readDiagnosticText() |
552 | { |
553 | QString s = diagnosticText; |
554 | diagnosticText = QString(); |
555 | return s; |
556 | } |
557 | |
558 | void GpgAction::submitCommand(const QByteArray &a) |
559 | { |
560 | proc.writeCommand(a); |
561 | } |
562 | |
563 | // since str is taken as a value, it is ok to use the same variable for 'rest' |
564 | QString GpgAction::nextArg(QString str, QString *rest) |
565 | { |
566 | int n = str.indexOf(c: QLatin1Char(' ')); |
567 | if (n == -1) { |
568 | if (rest) |
569 | *rest = QString(); |
570 | return str; |
571 | } else { |
572 | if (rest) |
573 | *rest = str.mid(position: n + 1); |
574 | return str.mid(position: 0, n); |
575 | } |
576 | } |
577 | |
578 | void GpgAction::processStatusLine(const QString &line) |
579 | { |
580 | appendDiagnosticText(QStringLiteral("{" ) + line + QStringLiteral("}" )); |
581 | ensureDTextEmit(); |
582 | |
583 | if (!proc.isActive()) |
584 | return; |
585 | |
586 | QString s, rest; |
587 | s = nextArg(str: line, rest: &rest); |
588 | |
589 | if (s == QLatin1String("NODATA" )) { |
590 | // only set this if it'll make it better |
591 | if (curError == GpgOp::ErrorUnknown) |
592 | curError = GpgOp::ErrorFormat; |
593 | } else if (s == QLatin1String("UNEXPECTED" )) { |
594 | if (curError == GpgOp::ErrorUnknown) |
595 | curError = GpgOp::ErrorFormat; |
596 | } else if (s == QLatin1String("EXPKEYSIG" )) { |
597 | curError = GpgOp::ErrorSignerExpired; |
598 | } else if (s == QLatin1String("REVKEYSIG" )) { |
599 | curError = GpgOp::ErrorSignerRevoked; |
600 | } else if (s == QLatin1String("EXPSIG" )) { |
601 | curError = GpgOp::ErrorSignatureExpired; |
602 | } else if (s == QLatin1String("INV_RECP" )) { |
603 | const int r = nextArg(str: rest).toInt(); |
604 | |
605 | if (curError == GpgOp::ErrorUnknown) { |
606 | if (r == 10) |
607 | curError = GpgOp::ErrorEncryptUntrusted; |
608 | else if (r == 4) |
609 | curError = GpgOp::ErrorEncryptRevoked; |
610 | else if (r == 5) |
611 | curError = GpgOp::ErrorEncryptExpired; |
612 | else |
613 | // due to GnuPG bug #1650 |
614 | // <https://bugs.g10code.com/gnupg/issue1650> |
615 | // encrypting to expired and revoked keys will |
616 | // not specify any reason for failing, |
617 | // defaulting to this |
618 | curError = GpgOp::ErrorEncryptInvalid; |
619 | } |
620 | } else if (s == QLatin1String("NO_SECKEY" )) { |
621 | output.encryptedToId = nextArg(str: rest); |
622 | |
623 | if (curError == GpgOp::ErrorUnknown) |
624 | curError = GpgOp::ErrorDecryptNoKey; |
625 | } else if (s == QLatin1String("DECRYPTION_OKAY" )) { |
626 | decryptGood = true; |
627 | |
628 | // message could be encrypted with several keys |
629 | if (curError == GpgOp::ErrorDecryptNoKey) |
630 | curError = GpgOp::ErrorUnknown; |
631 | } else if (s == QLatin1String("SIG_CREATED" )) { |
632 | signGood = true; |
633 | } else if (s == QLatin1String("USERID_HINT" )) { |
634 | passphraseKeyId = nextArg(str: rest); |
635 | } else if (s == QLatin1String("GET_HIDDEN" )) { |
636 | QString arg = nextArg(str: rest); |
637 | if (arg == QLatin1String("passphrase.enter" ) || arg == QLatin1String("passphrase.pin.ask" )) { |
638 | need_submitPassphrase = true; |
639 | |
640 | // for signal-safety, emit later |
641 | QMetaObject::invokeMethod(obj: this, member: "needPassphrase" , c: Qt::QueuedConnection, Q_ARG(QString, passphraseKeyId)); |
642 | } |
643 | } else if (s == QLatin1String("GET_LINE" )) { |
644 | QString arg = nextArg(str: rest); |
645 | if (arg == QLatin1String("cardctrl.insert_card.okay" )) { |
646 | need_cardOkay = true; |
647 | |
648 | QMetaObject::invokeMethod(obj: this, member: "needCard" , c: Qt::QueuedConnection); |
649 | } |
650 | } else if (s == QLatin1String("GET_BOOL" )) { |
651 | QString arg = nextArg(str: rest); |
652 | if (arg == QLatin1String("untrusted_key.override" )) |
653 | submitCommand(a: "no\n" ); |
654 | } else if (s == QLatin1String("GOOD_PASSPHRASE" )) { |
655 | badPassphrase = false; |
656 | } else if (s == QLatin1String("BAD_PASSPHRASE" )) { |
657 | badPassphrase = true; |
658 | } else if (s == QLatin1String("GOODSIG" )) { |
659 | output.wasSigned = true; |
660 | output.signerId = nextArg(str: rest); |
661 | output.verifyResult = GpgOp::VerifyGood; |
662 | } else if (s == QLatin1String("BADSIG" )) { |
663 | output.wasSigned = true; |
664 | output.signerId = nextArg(str: rest); |
665 | output.verifyResult = GpgOp::VerifyBad; |
666 | } else if (s == QLatin1String("ERRSIG" )) { |
667 | output.wasSigned = true; |
668 | const QStringList list = rest.split(sep: QLatin1Char(' '), behavior: Qt::SkipEmptyParts); |
669 | output.signerId = list[0]; |
670 | output.timestamp = getTimestamp(s: list[4]); |
671 | output.verifyResult = GpgOp::VerifyNoKey; |
672 | } else if (s == QLatin1String("VALIDSIG" )) { |
673 | const QStringList list = rest.split(sep: QLatin1Char(' '), behavior: Qt::SkipEmptyParts); |
674 | output.timestamp = getTimestamp(s: list[2]); |
675 | } |
676 | } |
677 | |
678 | void GpgAction::processResult(int code) |
679 | { |
680 | #ifdef GPG_PROFILE |
681 | printf("<< launch: %d >>\n" , timer.elapsed()); |
682 | #endif |
683 | |
684 | // put stdout and stderr into QStrings |
685 | |
686 | QString outstr; |
687 | QString errstr; |
688 | |
689 | #ifdef Q_OS_WIN |
690 | if (!utf8Output) { |
691 | outstr = QString::fromLocal8Bit(buf_stdout); |
692 | errstr = QString::fromLocal8Bit(buf_stderr); |
693 | } else { |
694 | #endif |
695 | outstr = QString::fromUtf8(ba: buf_stdout); |
696 | errstr = QString::fromUtf8(ba: buf_stderr); |
697 | #ifdef Q_OS_WIN |
698 | } |
699 | #endif |
700 | |
701 | if (collectOutput) |
702 | appendDiagnosticText(QStringLiteral("stdout: [%1]" ).arg(a: outstr)); |
703 | appendDiagnosticText(QStringLiteral("stderr: [%1]" ).arg(a: errstr)); |
704 | ensureDTextEmit(); |
705 | |
706 | if (badPassphrase) { |
707 | output.errorCode = GpgOp::ErrorPassphrase; |
708 | } else if (curError != GpgOp::ErrorUnknown) { |
709 | output.errorCode = curError; |
710 | } else if (code == 0) { |
711 | if (input.op == GpgOp::Check) { |
712 | const QStringList strList = outstr.split(QStringLiteral("\n" )); |
713 | foreach (const QString &str, strList) { |
714 | if (!str.startsWith(s: QLatin1String("Home: " ))) |
715 | continue; |
716 | |
717 | output.homeDir = str.section(asep: QLatin1Char(' '), astart: 1); |
718 | break; |
719 | } |
720 | output.success = true; |
721 | } else if (input.op == GpgOp::SecretKeyringFile || input.op == GpgOp::PublicKeyringFile) { |
722 | if (findKeyringFilename(outstr, keyring: &output.keyringFile)) |
723 | output.success = true; |
724 | } else if (input.op == GpgOp::SecretKeys || input.op == GpgOp::PublicKeys) { |
725 | if (stringToKeyList(outstr, keylist: &output.keys, keyring: &output.keyringFile)) |
726 | output.success = true; |
727 | } else |
728 | output.success = true; |
729 | } else { |
730 | // decrypt and sign success based on status only. |
731 | // this is mainly because gpg uses fatal return |
732 | // values if there is trouble with gpg-agent, even |
733 | // though the operation otherwise works. |
734 | |
735 | if (input.op == GpgOp::Decrypt && decryptGood) |
736 | output.success = true; |
737 | if (signing && signGood) |
738 | output.success = true; |
739 | |
740 | // gpg will indicate failure for bad sigs, but we don't |
741 | // consider this to be operation failure. |
742 | |
743 | bool signedMakesItGood = false; |
744 | if (input.op == GpgOp::Verify || input.op == GpgOp::VerifyDetached) |
745 | signedMakesItGood = true; |
746 | |
747 | if (signedMakesItGood && output.wasSigned) |
748 | output.success = true; |
749 | } |
750 | |
751 | emit finished(); |
752 | } |
753 | |
754 | void GpgAction::ensureDTextEmit() |
755 | { |
756 | if (!dtextTimer.isActive()) |
757 | dtextTimer.start(); |
758 | } |
759 | |
760 | void GpgAction::t_dtext() |
761 | { |
762 | emit readyReadDiagnosticText(); |
763 | } |
764 | |
765 | void GpgAction::proc_error(gpgQCAPlugin::GPGProc::Error e) |
766 | { |
767 | QString str; |
768 | if (e == GPGProc::FailedToStart) |
769 | str = QStringLiteral("FailedToStart" ); |
770 | else if (e == GPGProc::UnexpectedExit) |
771 | str = QStringLiteral("UnexpectedExit" ); |
772 | else if (e == GPGProc::ErrorWrite) |
773 | str = QStringLiteral("ErrorWrite" ); |
774 | |
775 | appendDiagnosticText(QStringLiteral("GPG Process Error: %1" ).arg(a: str)); |
776 | ensureDTextEmit(); |
777 | |
778 | output.errorCode = GpgOp::ErrorProcess; |
779 | emit finished(); |
780 | } |
781 | |
782 | void GpgAction::proc_finished(int exitCode) |
783 | { |
784 | appendDiagnosticText(QStringLiteral("GPG Process Finished: exitStatus=%1" ).arg(a: exitCode)); |
785 | ensureDTextEmit(); |
786 | |
787 | processResult(code: exitCode); |
788 | } |
789 | |
790 | void GpgAction::proc_readyReadStdout() |
791 | { |
792 | if (collectOutput) { |
793 | QByteArray a = proc.readStdout(); |
794 | if (readText) |
795 | a = readConv.update(buf: a); |
796 | buf_stdout.append(a); |
797 | } else |
798 | emit readyRead(); |
799 | } |
800 | |
801 | void GpgAction::proc_readyReadStderr() |
802 | { |
803 | buf_stderr.append(a: proc.readStderr()); |
804 | } |
805 | |
806 | void GpgAction::proc_readyReadStatusLines() |
807 | { |
808 | const QStringList lines = proc.readStatusLines(); |
809 | for (int n = 0; n < lines.count(); ++n) |
810 | processStatusLine(line: lines[n]); |
811 | } |
812 | |
813 | void GpgAction::proc_bytesWrittenStdin(int bytes) |
814 | { |
815 | if (!useAux) { |
816 | int actual = writeConv.writtenToActual(bytes); |
817 | emit bytesWritten(bytes: actual); |
818 | } |
819 | } |
820 | |
821 | void GpgAction::proc_bytesWrittenAux(int bytes) |
822 | { |
823 | if (useAux) { |
824 | int actual = writeConv.writtenToActual(bytes); |
825 | emit bytesWritten(bytes: actual); |
826 | } |
827 | } |
828 | |
829 | void GpgAction::proc_bytesWrittenCommand(int) |
830 | { |
831 | // don't care about this |
832 | } |
833 | |
834 | void GpgAction::proc_debug(const QString &str) |
835 | { |
836 | appendDiagnosticText(QStringLiteral("GPGProc: " ) + str); |
837 | ensureDTextEmit(); |
838 | } |
839 | |
840 | void GpgAction::appendDiagnosticText(const QString &line) |
841 | { |
842 | #ifdef GPGOP_DEBUG |
843 | printf("%s\n" , qPrintable(line)); |
844 | #endif |
845 | diagnosticText += line; |
846 | } |
847 | |
848 | } // end namespace gpgQCAPlugin |
849 | |