source: psi/trunk/src/tools/openpgp/gpgop.cpp

Last change on this file was 86, checked in by dmik, 19 years ago

Psi: OpenPGP: replaced printf() debug statements with qDebug() to redirect debug logs to stderr; added some minor OS/2-related corrections.

File size: 12.3 KB
Line 
1#include"gpgop.h"
2
3#include<qstringlist.h>
4#include<qtimer.h>
5#include<qdatetime.h>
6#include<stdlib.h>
7#include"gpgproc/gpgproc.h"
8
9static QByteArray stringToArray(const QString &str)
10{
11 QCString cs = str.utf8();
12 QByteArray a(cs.length());
13 memcpy(a.data(), cs.data(), a.size());
14 return a;
15}
16
17class GpgOp::Private
18{
19public:
20 Private() {}
21
22 QString bin;
23 int op;
24 GPGProc *proc;
25 bool tryAgent;
26
27 QByteArray outbuf, errbuf;
28 bool didPassphrase;
29 QString sigKeyID;
30 QDateTime sigTS;
31 int verType;
32
33 OpenPGP::KeyList keys;
34 QString keyring;
35 bool badpp;
36
37 QString enc;
38 QByteArray dec;
39};
40
41GpgOp::GpgOp(const QString &bin, QObject *parent)
42:QObject(parent)
43{
44 d = new Private;
45 d->bin = bin;
46 d->proc = 0;
47 d->op = -1;
48 d->tryAgent = true;
49}
50
51GpgOp::~GpgOp()
52{
53 reset();
54 delete d;
55}
56
57void GpgOp::reset()
58{
59 if(d->proc) {
60 d->proc->disconnect(this);
61 d->proc->deleteLater();
62 d->proc = 0;
63 }
64
65 d->sigKeyID = "";
66 d->sigTS = QDateTime();
67 d->didPassphrase = false;
68 d->outbuf.resize(0);
69 d->errbuf.resize(0);
70 d->keys.clear();
71 d->keyring = "";
72 d->verType = OpenPGP::VerifyError;
73 d->badpp = false;
74 d->enc = "";
75 d->dec.resize(0);
76}
77
78void GpgOp::stop()
79{
80 reset();
81}
82
83bool GpgOp::isActive() const
84{
85 return (d->proc ? true: false);
86}
87
88int GpgOp::op() const
89{
90 return d->op;
91}
92
93const OpenPGP::KeyList & GpgOp::keys() const
94{
95 return d->keys;
96}
97
98const QString & GpgOp::keyringFile() const
99{
100 return d->keyring;
101}
102
103const QString & GpgOp::keyID() const
104{
105 return d->sigKeyID;
106}
107
108const QDateTime & GpgOp::timestamp() const
109{
110 return d->sigTS;
111}
112
113int GpgOp::verifyResult() const
114{
115 return d->verType;
116}
117
118bool GpgOp::badPassphrase() const
119{
120 return d->badpp;
121}
122
123const QString & GpgOp::encrypted() const
124{
125 return d->enc;
126}
127
128const QByteArray & GpgOp::decrypted() const
129{
130 return d->dec;
131}
132
133const QString & GpgOp::signature() const
134{
135 return d->enc;
136}
137
138void GpgOp::setTryAgent(bool b)
139{
140 d->tryAgent = b;
141}
142
143void GpgOp::doCheck()
144{
145 reset();
146
147 d->op = Check;
148 QStringList args;
149 args += "--version";
150 if(!launchGPG(args, false)) {
151 QTimer::singleShot(0, this, SLOT(doFail()));
152 return;
153 }
154}
155
156void GpgOp::doSecretKeyringFile()
157{
158 reset();
159
160 d->op = SecretKeyringFile;
161 QStringList args;
162 args += "--list-secret-keys";
163 if(!launchGPG(args, false)) {
164 QTimer::singleShot(0, this, SLOT(doFail()));
165 return;
166 }
167}
168
169void GpgOp::doPublicKeyringFile()
170{
171 reset();
172
173 d->op = PublicKeyringFile;
174 QStringList args;
175 args += "--list-public-keys";
176 if(!launchGPG(args, false)) {
177 QTimer::singleShot(0, this, SLOT(doFail()));
178 return;
179 }
180}
181
182void GpgOp::doSecretKeys()
183{
184 reset();
185
186 d->op = SecretKeys;
187 QStringList args;
188 args += "--fixed-list-mode";
189 args += "--with-colons";
190 args += "--list-secret-keys";
191 if(!launchGPG(args, false)) {
192 QTimer::singleShot(0, this, SLOT(doFail()));
193 return;
194 }
195}
196
197void GpgOp::doPublicKeys()
198{
199 reset();
200
201 d->op = PublicKeys;
202 QStringList args;
203 args += "--fixed-list-mode";
204 args += "--with-colons";
205 args += "--list-public-keys";
206 if(!launchGPG(args, false)) {
207 QTimer::singleShot(0, this, SLOT(doFail()));
208 return;
209 }
210}
211
212void GpgOp::doEncrypt(const QByteArray &in, const QStringList &keys)
213{
214 reset();
215
216 d->op = Encrypt;
217 QStringList args;
218 args += "--armor";
219 args += "--always-trust";
220 args += "--encrypt";
221
222 // recipients
223 for(QStringList::ConstIterator it = keys.begin(); it != keys.end(); ++it) {
224 args += "--recipient";
225 args += QString("0x") + *it;
226 }
227
228 if(!launchGPG(args)) {
229 QTimer::singleShot(0, this, SLOT(doFail()));
230 return;
231 }
232
233 if(!in.isEmpty())
234 d->proc->writeToStdin(in);
235 else
236 d->proc->closeStdin();
237}
238
239void GpgOp::doDecrypt(const QString &in)
240{
241 reset();
242
243 d->op = Decrypt;
244 QStringList args;
245 if(!d->tryAgent)
246 args += "--no-use-agent";
247 args += "--armor";
248 args += "--decrypt";
249 if(!launchGPG(args)) {
250 QTimer::singleShot(0, this, SLOT(doFail()));
251 return;
252 }
253
254 if(!in.isEmpty())
255 d->proc->writeToStdin(stringToArray(fixOutgoingLines(in)));
256 else
257 d->proc->closeStdin();
258}
259
260void GpgOp::doSign(const QByteArray &in, const QString &keyID)
261{
262 reset();
263
264 d->op = Sign;
265 QStringList args;
266 if(!d->tryAgent)
267 args += "--no-use-agent";
268 args += "--armor";
269 args += "--default-key";
270 args += QString("0x") + keyID;
271 args += "--detach-sign";
272 if(!launchGPG(args)) {
273 QTimer::singleShot(0, this, SLOT(doFail()));
274 return;
275 }
276
277 if(!in.isEmpty())
278 d->proc->writeToStdin(in);
279 else
280 d->proc->closeStdin();
281}
282
283void GpgOp::doVerify(const QByteArray &in, const QString &sig)
284{
285 reset();
286
287 d->op = Verify;
288 d->verType = OpenPGP::VerifyError;
289 QStringList args;
290 args += "--armor";
291 args += "--verify";
292 args += "-";
293 args += "-&?";
294
295 if(!launchGPG(args)) {
296 QTimer::singleShot(0, this, SLOT(doFail()));
297 return;
298 }
299
300 // sig goes into stdin
301 if(!sig.isEmpty())
302 d->proc->writeToStdin(stringToArray(fixOutgoingLines(sig)));
303 else
304 d->proc->closeStdin();
305
306 // data goes into aux
307 d->proc->writeToAux(in);
308 d->proc->closeAux();
309}
310
311void GpgOp::submitPassphrase(const QString &pp)
312{
313 QCString cs = pp.local8Bit() + '\n';
314 QByteArray a(cs.length());
315 memcpy(a.data(), cs.data(), a.size());
316 d->proc->writeToCommand(a);
317}
318
319void GpgOp::doFail()
320{
321 finished(false);
322}
323
324bool GpgOp::launchGPG(const QStringList &args, bool useExtra)
325{
326 d->proc = new GPGProc;
327 connect(d->proc, SIGNAL(readyReadStdout()), SLOT(proc_readyReadStdout()));
328 connect(d->proc, SIGNAL(readyReadStderr()), SLOT(proc_readyReadStderr()));
329 connect(d->proc, SIGNAL(processExited()), SLOT(proc_processExited()));
330 connect(d->proc, SIGNAL(wroteToStdin()), SLOT(proc_wroteToStdin()));
331 connect(d->proc, SIGNAL(statusLine(const QString &)), SLOT(proc_statusLine(const QString &)));
332
333 if(!d->proc->start(d->bin, args, useExtra)) {
334 d->proc->disconnect(this);
335 d->proc->deleteLater();
336 d->proc = 0;
337 return false;
338 }
339
340 return true;
341}
342
343void GpgOp::proc_readyReadStdout()
344{
345 QByteArray block = d->proc->readStdout();
346 int oldsize = d->outbuf.size();
347 d->outbuf.resize(oldsize + block.size());
348 memcpy(d->outbuf.data() + oldsize, block.data(), block.size());
349}
350
351void GpgOp::proc_readyReadStderr()
352{
353 QByteArray block = d->proc->readStderr();
354 int oldsize = d->errbuf.size();
355 d->errbuf.resize(oldsize + block.size());
356 memcpy(d->errbuf.data() + oldsize, block.data(), block.size());
357}
358
359void GpgOp::proc_wroteToStdin()
360{
361 d->proc->closeStdin();
362}
363
364void GpgOp::proc_statusLine(const QString &str)
365{
366#ifdef GPG_DEBUG
367 qDebug("### {%s}", str.latin1());
368#endif
369 QString s, rest;
370 int n = str.find(' ');
371 if(n == -1) {
372 s = str;
373 rest = "";
374 }
375 else {
376 s = str.mid(0, n);
377 rest = str.mid(n+1);
378 }
379
380 if(s == "NEED_PASSPHRASE") {
381 if(!(d->tryAgent && getenv("GPG_AGENT_INFO"))) {
382 if(!d->didPassphrase) {
383 d->didPassphrase = true;
384 needPassphrase();
385 }
386 else {
387 QByteArray a(1);
388 a[0] = '\n';
389 d->proc->writeToCommand(a);
390 }
391 }
392 }
393 else if(s == "BAD_PASSPHRASE") {
394 d->badpp = true;
395 }
396 else if(s == "GOOD_PASSPHRASE") {
397 d->badpp = false;
398 }
399 else if(s == "GOODSIG") {
400 QString keyID;
401 int n = rest.find(' ');
402 if(n == -1)
403 keyID = rest;
404 else
405 keyID = rest.mid(0, n);
406 d->sigKeyID = keyID;
407 d->verType = OpenPGP::VerifyGood;
408 }
409 else if(s == "BADSIG") {
410 QString keyID;
411 int n = rest.find(' ');
412 if(n == -1)
413 keyID = rest;
414 else
415 keyID = rest.mid(0, n);
416 d->sigKeyID = keyID;
417 d->verType = OpenPGP::VerifyBad;
418 }
419 else if(s == "ERRSIG") {
420 QStringList list = QStringList::split(' ', rest, false);
421 d->sigKeyID = list[0];
422 d->sigTS.setTime_t(list[4].toInt());
423 d->verType = OpenPGP::VerifyNoKey;
424 }
425 else if(s == "VALIDSIG") {
426 QStringList list = QStringList::split(' ', rest, false);
427 d->sigTS.setTime_t(list[2].toInt());
428 }
429}
430
431void GpgOp::proc_processExited()
432{
433 if(!d->proc)
434 return;
435
436#ifdef GPG_DEBUG
437 qDebug("### GPG Finished: ");
438#endif
439
440 bool clean = true;
441 int exitStatus = -1;
442 if(!d->proc->normalExit()) {
443 clean = false;
444#ifdef GPG_DEBUG
445 qDebug("### bad exit.. crash?");
446#endif
447 }
448 else {
449 exitStatus = d->proc->exitStatus();
450#ifdef GPG_DEBUG
451 qDebug("### exitStatus=%d", exitStatus);
452#endif
453 }
454
455 d->proc->disconnect(this);
456 d->proc->deleteLater();
457 d->proc = 0;
458
459 processResult(clean, exitStatus, d->outbuf, d->errbuf);
460}
461
462void GpgOp::processResult(bool clean, int code, const QByteArray &out, const QByteArray &err)
463{
464 // put stdout and stderr into QStrings
465 QCString cs;
466 cs.resize(out.size()+1);
467 memcpy(cs.data(), out.data(), out.size());
468 QString outstr = QString::fromLatin1(cs);
469 cs.resize(err.size()+1);
470 memcpy(cs.data(), err.data(), err.size());
471 QString errstr = QString::fromLatin1(cs);
472
473#ifdef GPG_DEBUG
474 qDebug("### stdout: [%s]", outstr.latin1());
475 qDebug("### stderr: [%s]", errstr.latin1());
476#endif
477
478 if(d->op == Check) {
479 if(!clean || code != 0) {
480 doFail();
481 return;
482 }
483 finished(true);
484 }
485 else if(d->op == SecretKeyringFile || d->op == PublicKeyringFile) {
486 if(!clean || code != 0) {
487 doFail();
488 return;
489 }
490 if(!findKeyringFilename(fixIncomingLines(outstr), &d->keyring)) {
491 doFail();
492 return;
493 }
494 finished(true);
495 }
496 else if(d->op == SecretKeys || d->op == PublicKeys) {
497 if(!clean || code != 0) {
498 doFail();
499 return;
500 }
501 if(!stringToKeyList(fixIncomingLines(outstr), &d->keys, &d->keyring)) {
502 doFail();
503 return;
504 }
505 finished(true);
506 }
507 else if(d->op == Encrypt) {
508 if(!clean || code != 0) {
509 doFail();
510 return;
511 }
512 d->enc = fixIncomingLines(outstr);
513 finished(true);
514 }
515 else if(d->op == Decrypt) {
516 if(!clean || code != 0) {
517 doFail();
518 return;
519 }
520 d->dec = out;
521 finished(true);
522 }
523 else if(d->op == Sign) {
524 if(!clean || code != 0) {
525 doFail();
526 return;
527 }
528 d->enc = fixIncomingLines(outstr);
529 finished(true);
530 }
531 else if(d->op == Verify) {
532 if(!clean || code != 0) {
533 doFail();
534 return;
535 }
536 finished(true);
537 }
538}
539
540QString GpgOp::fixIncomingLines(const QString &str)
541{
542#if defined(Q_WS_WIN) || defined(Q_OS_OS2)
543 QString out;
544 for(int n = 0; n < (int)str.length(); ++n) {
545 if(str.at(n) == '\r' && (n + 1) < (int)str.length() && str.at(n+1) == '\n') {
546 out += '\n';
547 ++n;
548 }
549 else
550 out += str.at(n);
551 }
552 return out;
553#else
554 return str;
555#endif
556}
557
558QString GpgOp::fixOutgoingLines(const QString &str)
559{
560#if defined(Q_WS_WIN) || defined(Q_OS_OS2)
561 QString out;
562 for(int n = 0; n < (int)str.length(); ++n) {
563 if(str.at(n) == '\n' && (n == 0 || str.at(n-1) != '\r'))
564 out += "\r\n";
565 else
566 out += str.at(n);
567 }
568 return out;
569#else
570 return str;
571#endif
572}
573
574bool GpgOp::stringToKeyList(const QString &outstr, OpenPGP::KeyList *_keylist, QString *_keyring)
575{
576 OpenPGP::KeyList keyList;
577 QStringList lines = QStringList::split('\n', outstr, true);
578
579 if(lines.count() < 1)
580 return false;
581
582 QStringList::ConstIterator it = lines.begin();
583
584 // first line is keyring file
585 QString keyring = *(it++);
586
587 // if the second line isn't a divider, we are dealing
588 // with a new version of gnupg that doesn't give us
589 // the keyring file on gpg --list-keys --with-colons
590 if(it == lines.end() || (*it).at(0) != '-') {
591 // first line wasn't the keyring name...
592 keyring = "";
593 // ...so read the first line again
594 it--;
595 }
596 else {
597 // this was the divider line - skip it
598 it++;
599 }
600
601 OpenPGP::Key *k = 0;
602 for(; it != lines.end(); ++it) {
603 QStringList f = QStringList::split(':', *it, true);
604 QString type = f[0];
605
606 if(type == "pub") {
607 if(k) {
608 keyList += (*k);
609 delete k;
610 k = 0;
611 }
612 k = new OpenPGP::Key;
613
614 k->setKeyID(f[4]);
615 }
616 else if(type == "sec") {
617 if(k) {
618 keyList += (*k);
619 delete k;
620 k = 0;
621 }
622 k = new OpenPGP::Key;
623
624 k->setKeyID(f[4]);
625 }
626 else if(type == "uid") {
627 if(!k)
628 continue;
629 // ignore a uid if we already have one
630 if(!k->userID().isEmpty())
631 continue;
632 QString s = f[9];
633 QCString uid;
634 // convert the "backslash" C-string syntax
635 for(int n = 0; n < (int)s.length(); ++n) {
636 if(s.at(n) == '\\' && n + 1 < (int)s.length()) {
637 ++n;
638 unsigned char c = (unsigned char)s.at(n).latin1();
639 if(c == '\\')
640 uid += '\\';
641 else if(c == 'x' && n + 2 < (int)s.length()) {
642 ++n;
643 QString hex = s.mid(n, 2);
644 bool ok;
645 uint val = hex.toInt(&ok, 16);
646 uid += (unsigned char)val;
647 ++n; // only skip one, the for-loop will skip the next
648 }
649 }
650 else {
651 uid += (unsigned char)s.at(n).latin1();
652 }
653 }
654 k->setUserID(QString::fromUtf8(uid));
655 }
656 }
657 if(k) {
658 keyList += (*k);
659 delete k;
660 k = 0;
661 }
662
663 if(_keylist)
664 *_keylist = keyList;
665 if(_keyring)
666 *_keyring = keyring;
667
668 return true;
669}
670
671bool GpgOp::findKeyringFilename(const QString &outstr, QString *_keyring)
672{
673 QStringList lines = QStringList::split('\n', outstr, true);
674 if(lines.count() < 1)
675 return false;
676
677 *_keyring = lines[0];
678 return true;
679}
680
Note: See TracBrowser for help on using the repository browser.