source: psi/trunk/src/tools/openpgp/gnupg.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: 9.3 KB
Line 
1/*
2 * gnupg.cpp - OpenPGP interface to GnuPG
3 * Copyright (C) 2003 Justin Karneges
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this library; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 *
19 */
20
21#include"gnupg.h"
22
23#include<qptrlist.h>
24#include<qstringlist.h>
25#include<qapplication.h>
26#include<qtimer.h>
27#include<qguardedptr.h>
28#include<qfileinfo.h>
29#include"gpgop.h"
30#include"dirwatch.h"
31
32#ifdef Q_WS_WIN
33#include<windows.h>
34
35static QString find_reg_gpgProgram()
36{
37 HKEY root;
38 root = HKEY_CURRENT_USER;
39
40 HKEY hkey;
41 const char *path = "Software\\GNU\\GnuPG";
42 if(RegOpenKeyExA(HKEY_CURRENT_USER, path, 0, KEY_QUERY_VALUE, &hkey) != ERROR_SUCCESS) {
43 if(RegOpenKeyExA(HKEY_LOCAL_MACHINE, path, 0, KEY_QUERY_VALUE, &hkey) != ERROR_SUCCESS)
44 return QString::null;
45 }
46
47 char szValue[256];
48 DWORD dwLen = 256;
49 if(RegQueryValueExA(hkey, "gpgProgram", NULL, NULL, (LPBYTE)szValue, &dwLen) != ERROR_SUCCESS) {
50 RegCloseKey(hkey);
51 return QString::null;
52 }
53 RegCloseKey(hkey);
54
55 return QString::fromLatin1(szValue);
56}
57#endif
58
59using namespace OpenPGP;
60
61//----------------------------------------------------------------------------
62// GnuPG
63//----------------------------------------------------------------------------
64class GnuPG::Item
65{
66public:
67 Item() {}
68
69 QGuardedPtr<Request> r;
70 Request *r_ng;
71
72 int op;
73 QByteArray data;
74 QString verSig, keyID;
75 QStringList keys;
76 QString str;
77};
78
79class GnuPG::Private
80{
81public:
82 Private(const QString &bin) : gpg(bin) {}
83
84 bool available, initialized;
85 QString name;
86 GpgOp gpg;
87
88 // item list
89 QPtrList<Item> list;
90
91 KeyList secretKeys, publicKeys;
92 QString secretRing, publicRing;
93 FileWatch secretWatch, publicWatch;
94 bool secretDirty, publicDirty;
95};
96
97GnuPG::GnuPG(QObject *parent)
98:Engine(parent)
99{
100 QString bin = "gpg";
101#ifdef Q_WS_WIN
102 QString s = find_reg_gpgProgram();
103 if(!s.isNull())
104 bin = s;
105#endif
106
107#ifdef Q_OS_MAC
108 // mac-gpg
109 QFileInfo fi("/usr/local/bin/gpg");
110 if(fi.exists())
111 bin = fi.filePath();
112#endif
113
114#ifdef GPG_DEBUG
115 qDebug("### gpg bin=[%s]", bin.latin1());
116#endif
117 d = new Private(bin);
118 d->available = false;
119 d->initialized = false;
120 d->name = "GNU Privacy Guard (GPG)";
121
122 connect(&d->gpg, SIGNAL(finished(bool)), SLOT(gpg_finished(bool)));
123 connect(&d->gpg, SIGNAL(needPassphrase()), SLOT(gpg_needPassphrase()));
124
125 connect(&d->secretWatch, SIGNAL(changed()), SLOT(fileChanged()));
126 connect(&d->publicWatch, SIGNAL(changed()), SLOT(fileChanged()));
127}
128
129GnuPG::~GnuPG()
130{
131 d->gpg.stop();
132
133 // unhook all requests (send errors)
134 QPtrListIterator<Item> it(d->list);
135 Item *i;
136 while((i = it.current())) {
137 if(i->op == Sign)
138 signFinished(i->r, false, "");
139 else if(i->op == Verify)
140 verifyFinished(i->r, VerifyError, "", QDateTime());
141 unhook(i);
142 }
143
144 delete d;
145}
146
147void GnuPG::setTryAgent(bool b)
148{
149 d->gpg.setTryAgent(b);
150}
151
152bool GnuPG::checkAvailability()
153{
154 if(d->available)
155 return true;
156
157 d->gpg.doCheck();
158
159 // loop here until we are done executing
160 while(d->gpg.isActive())
161 qApp->processEvents();
162
163 return d->available;
164}
165
166QString GnuPG::id() const
167{
168 return "gpg";
169}
170
171QString GnuPG::name() const
172{
173 return d->name;
174}
175
176void GnuPG::init()
177{
178 if(d->initialized)
179 return;
180
181 d->available = true;
182
183 // first step: get secret keys
184 d->gpg.doSecretKeys();
185}
186
187KeyList GnuPG::secretKeys() const
188{
189 return d->secretKeys;
190}
191
192KeyList GnuPG::publicKeys() const
193{
194 return d->publicKeys;
195}
196
197void GnuPG::encrypt(Request *r, const QByteArray &in, const QStringList &keys)
198{
199 if(!d->initialized)
200 return;
201
202 Item *i = new Item;
203 i->r = r;
204 i->r_ng = r;
205 i->op = Encrypt;
206 i->data = in;
207 i->keys = keys;
208 hook(i);
209
210 if(d->list.count() == 1)
211 tryNext();
212}
213
214void GnuPG::decrypt(Request *r, const QString &in)
215{
216 if(!d->initialized)
217 return;
218
219 Item *i = new Item;
220 i->r = r;
221 i->r_ng = r;
222 i->op = Decrypt;
223 i->str = in;
224 hook(i);
225
226 if(d->list.count() == 1)
227 tryNext();
228}
229
230void GnuPG::sign(Request *r, const QByteArray &in, const QString &keyID)
231{
232 if(!d->initialized)
233 return;
234
235 Item *i = new Item;
236 i->r = r;
237 i->r_ng = r;
238 i->op = Sign;
239 i->data = in;
240 i->keyID = keyID;
241 hook(i);
242
243 if(d->list.count() == 1)
244 tryNext();
245}
246
247void GnuPG::verify(Request *r, const QByteArray &in, const QString &sig)
248{
249 if(!d->initialized)
250 return;
251
252 Item *i = new Item;
253 i->r = r;
254 i->r_ng = r;
255 i->op = Verify;
256 i->data = in;
257 i->verSig = sig;
258 hook(i);
259
260 if(d->list.count() == 1)
261 tryNext();
262}
263
264void GnuPG::submitPassphrase(Request *, const QString &pp)
265{
266 d->gpg.submitPassphrase(pp);
267}
268
269void GnuPG::tryNext()
270{
271 QTimer::singleShot(0, this, SLOT(doNext()));
272}
273
274void GnuPG::doNext()
275{
276 // busy? don't do anything then
277 if(d->gpg.isActive())
278 return;
279 if(d->list.isEmpty()) {
280 handleDirtyRings();
281 return;
282 }
283
284 Item *i = d->list.getFirst();
285 if(i->op == Encrypt)
286 d->gpg.doEncrypt(i->data, i->keys);
287 else if(i->op == Decrypt)
288 d->gpg.doDecrypt(i->str);
289 else if(i->op == Sign)
290 d->gpg.doSign(i->data, i->keyID);
291 else if(i->op == Verify)
292 d->gpg.doVerify(i->data, i->verSig);
293}
294
295void GnuPG::doInitFinished(bool b, const QString &err)
296{
297#ifdef GPG_DEBUG
298 qDebug("### GnuPG initialization complete");
299 qDebug("### Secret Keyring: [%s]", d->secretWatch.fileName().latin1());
300 qDebug("### Public Keyring: [%s]", d->publicWatch.fileName().latin1());
301#endif
302 initFinished(b, err);
303}
304
305void GnuPG::gpg_finished(bool b)
306{
307 GpgOp *gpg = (GpgOp *)sender();
308 int op = gpg->op();
309
310 if(op == GpgOp::Check)
311 d->available = b;
312 else if(op == GpgOp::SecretKeyringFile) {
313 if(!gpg->keyringFile().isEmpty())
314 d->secretWatch.setFileName(gpg->keyringFile());
315
316 if(!d->initialized) {
317 // get public keys
318 d->gpg.doPublicKeys();
319 }
320 else
321 keysUpdated();
322
323 handleDirtyRings();
324 }
325 else if(op == GpgOp::PublicKeyringFile) {
326 if(!gpg->keyringFile().isEmpty())
327 d->publicWatch.setFileName(gpg->keyringFile());
328
329 if(!d->initialized) {
330 // init finished
331 d->initialized = true;
332 doInitFinished(true, "");
333 }
334 else
335 keysUpdated();
336
337 handleDirtyRings();
338 }
339 else if(op == GpgOp::SecretKeys) {
340 if(!b) {
341 doInitFinished(false, tr("Unable to retrieve secret key list."));
342 return;
343 }
344
345 d->secretKeys = gpg->keys();
346 d->secretDirty = false;
347 // We may need to do a second request to obtain the keyring
348 // filename, but only do this once.
349 if(!d->initialized && gpg->keyringFile().isEmpty()) {
350 d->gpg.doSecretKeyringFile();
351 return;
352 }
353 d->secretWatch.setFileName(gpg->keyringFile());
354
355 if(!d->initialized) {
356 // get public keys
357 d->gpg.doPublicKeys();
358 }
359 else
360 keysUpdated();
361
362 handleDirtyRings();
363 }
364 else if(op == GpgOp::PublicKeys) {
365 if(!b) {
366 doInitFinished(false, tr("Unable to retrieve public key list."));
367 return;
368 }
369
370 d->publicKeys = gpg->keys();
371 d->publicDirty = false;
372 // We may need to do a second request to obtain the keyring
373 // filename, but only do this once.
374 if(!d->initialized && gpg->keyringFile().isEmpty()) {
375 d->gpg.doPublicKeyringFile();
376 return;
377 }
378 d->publicWatch.setFileName(gpg->keyringFile());
379
380 if(!d->initialized) {
381 // init finished
382 d->initialized = true;
383 doInitFinished(true, "");
384 }
385 else
386 keysUpdated();
387
388 handleDirtyRings();
389 }
390 else {
391 Request *r = unhookFirst();
392 setBadPassphrase(r, gpg->badPassphrase());
393
394 if(op == GpgOp::Encrypt)
395 encryptFinished(r, b, gpg->encrypted());
396 else if(op == GpgOp::Decrypt)
397 decryptFinished(r, b, gpg->decrypted());
398 else if(op == GpgOp::Sign)
399 signFinished(r, b, gpg->signature());
400 else if(op == GpgOp::Verify)
401 verifyFinished(r, gpg->verifyResult(), gpg->keyID(), gpg->timestamp());
402
403 tryNext();
404 }
405}
406
407void GnuPG::gpg_needPassphrase()
408{
409 needPassphrase(d->list.getFirst()->r);
410}
411
412void GnuPG::req_destroyed()
413{
414 Request *r = (Request *)sender();
415 Item *i = find(r);
416 if(i) {
417 bool first = (r == d->list.getFirst()->r_ng) ? true: false;
418 unhook(i);
419 if(first) {
420 d->gpg.stop();
421 tryNext();
422 }
423 }
424}
425
426GnuPG::Item *GnuPG::find(Request *r) const
427{
428 QPtrListIterator<Item> it(d->list);
429 for(Item *i; (i = it.current()); ++it) {
430 if((Request *)i->r_ng == r)
431 return i;
432 }
433 return 0;
434}
435
436void GnuPG::hook(Item *i)
437{
438 connect(i->r, SIGNAL(destroyed()), this, SLOT(req_destroyed()));
439 d->list.append(i);
440}
441
442Request *GnuPG::unhook(Item *i)
443{
444 Request *r = i->r;
445 if(r)
446 disconnect(r, SIGNAL(destroyed()), this, SLOT(req_destroyed()));
447 d->list.removeRef(i);
448 delete i;
449 return r;
450}
451
452Request *GnuPG::unhookFirst()
453{
454 return unhook(d->list.getFirst());
455}
456
457void GnuPG::fileChanged()
458{
459 FileWatch *w = (FileWatch *)sender();
460 if(w == &d->secretWatch) {
461 d->secretDirty = true;
462#ifdef GPG_DEBUG
463 qDebug("### Secret keys updated!");
464#endif
465 }
466 else if(w == &d->publicWatch) {
467 d->publicDirty = true;
468#ifdef GPG_DEBUG
469 qDebug("### Public keys updated!");
470#endif
471 }
472 handleDirtyRings();
473}
474
475void GnuPG::handleDirtyRings()
476{
477 if(!d->initialized || d->gpg.isActive())
478 return;
479
480 if(d->secretDirty)
481 d->gpg.doSecretKeys();
482 else if(d->publicDirty)
483 d->gpg.doPublicKeys();
484}
Note: See TracBrowser for help on using the repository browser.