1 /***************************************************************************
2 * Copyright (C) 2006 by Petri Damsten *
3 * damu@iki.fi *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (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 program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
19 ***************************************************************************/
20
21 #include "config.h"
22
23 #ifdef HAVE_LIBGPGME
24
25 #include "kgpgme.h"
26 #include "global.h"
27 #include "debugwindow.h"
28
29 #include <QtCore/QPointer>
30 #include <QTreeWidget>
31 #include <QLabel>
32 #include <QtGui/QPixmap>
33 #include <QDialogButtonBox>
34 #include <QPushButton>
35 #include <QVBoxLayout>
36
37 #include <QApplication>
38 #include <KLocalizedString>
39 #include <KMessageBox>
40 #include <KPasswordDialog>
41 #include <KConfigGroup>
42
43 #include <locale.h> //For LC_ALL, etc.
44 #include <errno.h> //For errno
45 #include <unistd.h> //For write
46
47 // KGpgSelKey class based on class in KGpg with the same name
48
49 class KGpgSelKey : public QDialog
50 {
51 private:
52 QTreeWidget* keysListpr;
53
54 public:
55
KGpgSelKey(QWidget * parent,const char * name,QString preselected,const KGpgMe & gpg)56 KGpgSelKey(QWidget *parent, const char *name, QString preselected,
57 const KGpgMe& gpg):
58 QDialog(parent) {
59
60 // Dialog options
61 setObjectName(name);
62 setModal(true);
63 setWindowTitle(i18n("Private Key List"));
64
65 QWidget *mainWidget = new QWidget(this);
66 QVBoxLayout *mainLayout = new QVBoxLayout;
67 setLayout(mainLayout);
68 mainLayout->addWidget(mainWidget);
69
70 QString keyname;
71 QVBoxLayout* vbox;
72 QWidget* page = new QWidget(this);
73 QLabel* labeltxt;
74 QPixmap keyPair = QIcon::fromTheme("kgpg_key2").pixmap(20, 20);
75
76 setMinimumSize(350, 100);
77 keysListpr = new QTreeWidget(page);
78 keysListpr->setRootIsDecorated(true);
79 keysListpr->setColumnCount(3);
80 QStringList headers;
81 headers << i18n("Name") << i18n("Email") << i18n("ID");
82 keysListpr->setHeaderLabels(headers);
83 keysListpr->setAllColumnsShowFocus(true);
84
85 labeltxt = new QLabel(i18n("Choose a secret key:"), page);
86 vbox = new QVBoxLayout(page);
87
88 KGpgKeyList list = gpg.keys(true);
89
90 for (KGpgKeyList::iterator it = list.begin(); it != list.end(); ++it) {
91 QString name = gpg.checkForUtf8((*it).name);
92 QStringList values;
93 values << name << (*it).email << (*it).id;
94 QTreeWidgetItem *item = new
95 QTreeWidgetItem(keysListpr, values, 3);
96 item->setIcon(0, keyPair);
97 if (preselected == (*it).id) {
98 item->setSelected(true);
99 keysListpr->setCurrentItem(item);
100 }
101 }
102 if (!keysListpr->currentItem() && keysListpr->topLevelItemCount() > 0) {
103 keysListpr->topLevelItem(0)->setSelected(true);
104 keysListpr->setCurrentItem(keysListpr->topLevelItem(0));
105 }
106 vbox->addWidget(labeltxt);
107 vbox->addWidget(keysListpr);
108 mainLayout->addWidget(page);
109
110 QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel, this);
111 QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
112 okButton->setDefault(true);
113 okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
114 connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
115 connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
116 mainLayout->addWidget(buttonBox);
117 };
118
key()119 QString key() {
120 QTreeWidgetItem* item = keysListpr->currentItem();
121
122 if (item)
123 return item->text(2);
124 return "";
125 }
126 };
127
KGpgMe()128 KGpgMe::KGpgMe() : m_ctx(0), m_useGnuPGAgent(true)
129 {
130 init(GPGME_PROTOCOL_OpenPGP);
131 if (gpgme_new(&m_ctx) == GPG_ERR_NO_ERROR) {
132 gpgme_set_armor(m_ctx, 1);
133 setPassphraseCb();
134
135 //Set gpg version
136 gpgme_engine_info_t info;
137 gpgme_get_engine_info (&info);
138 while (info != NULL && info->protocol != gpgme_get_protocol(m_ctx)) {
139 info = info->next;
140 }
141
142 if (info != NULL) {
143 QByteArray gpgPath = info->file_name;
144 gpgPath.replace("gpg2", "gpg"); //require GnuPG v1
145 gpgme_ctx_set_engine_info(m_ctx, GPGME_PROTOCOL_OpenPGP, gpgPath.data(), NULL);
146 }
147
148 } else {
149 m_ctx = 0;
150 }
151 }
152
~KGpgMe()153 KGpgMe::~KGpgMe()
154 {
155 if (m_ctx)
156 gpgme_release(m_ctx);
157 clearCache();
158 }
159
clearCache()160 void KGpgMe::clearCache()
161 {
162 if (m_cache.size() > 0) {
163 m_cache.fill('\0');
164 m_cache.truncate(0);
165 }
166 }
167
init(gpgme_protocol_t proto)168 void KGpgMe::init(gpgme_protocol_t proto)
169 {
170 gpgme_error_t err;
171
172 gpgme_check_version("1.0.0"); //require GnuPG v1
173 setlocale(LC_ALL, "");
174 gpgme_set_locale(NULL, LC_CTYPE, setlocale(LC_CTYPE, NULL));
175 #ifndef Q_OS_WIN
176 gpgme_set_locale(NULL, LC_MESSAGES, setlocale(LC_MESSAGES, NULL));
177 #endif
178 err = gpgme_engine_check_version(proto);
179 if (err) {
180 static QString lastErrorText;
181
182 QString text = QString("%1: %2").arg(gpgme_strsource(err), gpgme_strerror(err));
183 if (text != lastErrorText) {
184 KMessageBox::error(qApp->activeWindow(), text);
185 lastErrorText = text;
186 }
187 }
188 }
189
checkForUtf8(QString txt)190 QString KGpgMe::checkForUtf8(QString txt)
191 {
192 // code borrowed from KGpg which borrowed it from gpa
193 const char *s;
194
195 // Make sure the encoding is UTF-8.
196 // Test structure suggested by Werner Koch
197 if (txt.isEmpty())
198 return QString::null;
199
200 for (s = txt.toLatin1(); *s && !(*s & 0x80); s++)
201 ;
202 if (*s && !strchr(txt.toLatin1(), 0xc3) && (txt.indexOf("\\x") == -1))
203 return txt;
204
205 // The string is not in UTF-8
206 //if (strchr (txt.toLatin1(), 0xc3)) return (txt+" +++");
207 if (txt.indexOf("\\x") == -1)
208 return QString::fromUtf8(txt.toLatin1());
209 // if (!strchr (txt.toLatin1(), 0xc3) || (txt.indexOf("\\x")!=-1)) {
210 for (int idx = 0 ; (idx = txt.indexOf("\\x", idx)) >= 0 ; ++idx) {
211 char str[2] = "x";
212 str[0] = (char)QString(txt.mid(idx + 2, 2)).toShort(0, 16);
213 txt.replace(idx, 4, str);
214 }
215 if (!strchr(txt.toLatin1(), 0xc3))
216 return QString::fromUtf8(txt.toLatin1());
217 else
218 return QString::fromUtf8(QString::fromUtf8(txt.toLatin1()).toLatin1());
219 // perform Utf8 twice, or some keys display badly
220 return txt;
221 }
222
selectKey(QString previous)223 QString KGpgMe::selectKey(QString previous)
224 {
225 QPointer<KGpgSelKey> dlg = new KGpgSelKey(qApp->activeWindow(), "", previous, *this);
226
227 if (dlg->exec())
228 return dlg->key();
229 return "";
230 }
231
232 // Rest of the code is mainly based in gpgme examples
233
keys(bool privateKeys) const234 KGpgKeyList KGpgMe::keys(bool privateKeys /* = false */) const
235 {
236 KGpgKeyList keys;
237 gpgme_error_t err = 0, err2 = 0;
238 gpgme_key_t key = 0;
239 gpgme_keylist_result_t result = 0;
240
241 if (m_ctx) {
242 err = gpgme_op_keylist_start(m_ctx, NULL, privateKeys);
243 if (!err) {
244 while (!(err = gpgme_op_keylist_next(m_ctx, &key))) {
245 KGpgKey gpgkey;
246
247 if (!key->subkeys)
248 continue;
249 gpgkey.id = key->subkeys->keyid;
250 if (key->uids) {
251 gpgkey.name = key->uids->name;
252 gpgkey.email = key->uids->email;
253 }
254 keys.append(gpgkey);
255 gpgme_key_unref(key);
256 }
257
258 if (gpg_err_code(err) == GPG_ERR_EOF)
259 err = 0;
260 err2 = gpgme_op_keylist_end(m_ctx);
261 if (!err)
262 err = err2;
263 }
264 }
265
266 if (err) {
267 KMessageBox::error(qApp->activeWindow(), QString("%1: %2")
268 .arg(gpgme_strsource(err)).arg(gpgme_strerror(err)));
269 } else {
270 result = gpgme_op_keylist_result(m_ctx);
271 if (result->truncated) {
272 KMessageBox::error(qApp->activeWindow(),
273 i18n("Key listing unexpectedly truncated."));
274 }
275 }
276 return keys;
277 }
278
encrypt(const QByteArray & inBuffer,unsigned long length,QByteArray * outBuffer,QString keyid)279 bool KGpgMe::encrypt(const QByteArray& inBuffer, unsigned long length,
280 QByteArray* outBuffer, QString keyid /* = QString::null */)
281 {
282 gpgme_error_t err = 0;
283 gpgme_data_t in = 0, out = 0;
284 gpgme_key_t keys[2] = { NULL, NULL };
285 gpgme_key_t* key = NULL;
286 gpgme_encrypt_result_t result = 0;
287
288 outBuffer->resize(0);
289 if (m_ctx) {
290 err = gpgme_data_new_from_mem(&in, inBuffer.data(), length, 1);
291 if (!err) {
292 err = gpgme_data_new(&out);
293 if (!err) {
294 if (keyid.isNull()) {
295 key = NULL;
296 } else {
297 err = gpgme_get_key(m_ctx, keyid.toLatin1(), &keys[0], 0);
298 key = keys;
299 }
300
301 if (!err) {
302 err = gpgme_op_encrypt(m_ctx, key, GPGME_ENCRYPT_ALWAYS_TRUST,
303 in, out);
304 if (!err) {
305 result = gpgme_op_encrypt_result(m_ctx);
306 if (result->invalid_recipients) {
307 KMessageBox::error(qApp->activeWindow(), QString("%1: %2")
308 .arg(i18n("That public key is not meant for encryption"))
309 .arg(result->invalid_recipients->fpr));
310 } else {
311 err = readToBuffer(out, outBuffer);
312 }
313 }
314 }
315 }
316 }
317 }
318 if (err != GPG_ERR_NO_ERROR && err != GPG_ERR_CANCELED) {
319 KMessageBox::error(qApp->activeWindow(), QString("%1: %2")
320 .arg(gpgme_strsource(err)).arg(gpgme_strerror(err)));
321 }
322 if (err != GPG_ERR_NO_ERROR) {
323 DEBUG_WIN << "KGpgMe::encrypt error: " + QString::number(err);
324 clearCache();
325 }
326 if (keys[0])
327 gpgme_key_unref(keys[0]);
328 if (in)
329 gpgme_data_release(in);
330 if (out)
331 gpgme_data_release(out);
332 return (err == GPG_ERR_NO_ERROR);
333 }
334
decrypt(const QByteArray & inBuffer,QByteArray * outBuffer)335 bool KGpgMe::decrypt(const QByteArray& inBuffer, QByteArray* outBuffer)
336 {
337 gpgme_error_t err = 0;
338 gpgme_data_t in = 0, out = 0;
339 gpgme_decrypt_result_t result = 0;
340
341 outBuffer->resize(0);
342 if (m_ctx) {
343 err = gpgme_data_new_from_mem(&in, inBuffer.data(), inBuffer.size(), 1);
344 if (!err) {
345 err = gpgme_data_new(&out);
346 if (!err) {
347 err = gpgme_op_decrypt(m_ctx, in, out);
348 if (!err) {
349 result = gpgme_op_decrypt_result(m_ctx);
350 if (result->unsupported_algorithm) {
351 KMessageBox::error(qApp->activeWindow(), QString("%1: %2")
352 .arg(i18n("Unsupported algorithm"))
353 .arg(result->unsupported_algorithm));
354 } else {
355 err = readToBuffer(out, outBuffer);
356 }
357 }
358 }
359 }
360 }
361 if (err != GPG_ERR_NO_ERROR && err != GPG_ERR_CANCELED) {
362 KMessageBox::error(qApp->activeWindow(), QString("%1: %2")
363 .arg(gpgme_strsource(err)).arg(gpgme_strerror(err)));
364 }
365 if (err != GPG_ERR_NO_ERROR)
366 clearCache();
367 if (in)
368 gpgme_data_release(in);
369 if (out)
370 gpgme_data_release(out);
371 return (err == GPG_ERR_NO_ERROR);
372 }
373
374 #define BUF_SIZE (32 * 1024)
375
readToBuffer(gpgme_data_t in,QByteArray * outBuffer) const376 gpgme_error_t KGpgMe::readToBuffer(gpgme_data_t in, QByteArray* outBuffer) const
377 {
378 int ret;
379 gpgme_error_t err = GPG_ERR_NO_ERROR;
380
381 ret = gpgme_data_seek(in, 0, SEEK_SET);
382 if (ret) {
383 err = gpgme_err_code_from_errno(errno);
384 } else {
385 char* buf = new char[BUF_SIZE + 2];
386
387 if (buf) {
388 while ((ret = gpgme_data_read(in, buf, BUF_SIZE)) > 0) {
389 uint size = outBuffer->size();
390 outBuffer->resize(size + ret);
391 memcpy(outBuffer->data() + size, buf, ret);
392 }
393 if (ret < 0)
394 err = gpgme_err_code_from_errno(errno);
395 delete[] buf;
396 }
397 }
398 return err;
399 }
400
isGnuPGAgentAvailable()401 bool KGpgMe::isGnuPGAgentAvailable()
402 {
403 QString agent_info = qgetenv("GPG_AGENT_INFO");
404
405 if (agent_info.indexOf(':') > 0)
406 return true;
407 return false;
408 }
409
setPassphraseCb()410 void KGpgMe::setPassphraseCb()
411 {
412 bool agent = false;
413 QString agent_info;
414
415 agent_info = qgetenv("GPG_AGENT_INFO");
416
417 if (m_useGnuPGAgent) {
418 if (agent_info.indexOf(':'))
419 agent = true;
420 if (agent_info.startsWith(QLatin1String("disable:")))
421 setenv("GPG_AGENT_INFO", agent_info.mid(8).toLatin1(), 1);
422 } else {
423 if (!agent_info.startsWith(QLatin1String("disable:")))
424 setenv("GPG_AGENT_INFO", "disable:" + agent_info.toLatin1(), 1);
425 }
426 if (agent)
427 gpgme_set_passphrase_cb(m_ctx, 0, 0);
428 else
429 gpgme_set_passphrase_cb(m_ctx, passphraseCb, this);
430 }
431
passphraseCb(void * hook,const char * uid_hint,const char * passphrase_info,int last_was_bad,int fd)432 gpgme_error_t KGpgMe::passphraseCb(void* hook, const char* uid_hint,
433 const char* passphrase_info,
434 int last_was_bad, int fd)
435 {
436 KGpgMe* gpg = static_cast<KGpgMe*>(hook);
437 return gpg->passphrase(uid_hint, passphrase_info, last_was_bad, fd);
438 }
439
passphrase(const char * uid_hint,const char *,int last_was_bad,int fd)440 gpgme_error_t KGpgMe::passphrase(const char* uid_hint,
441 const char* /*passphrase_info*/,
442 int last_was_bad, int fd)
443 {
444 QString s;
445 QString gpg_hint = checkForUtf8(uid_hint);
446 bool canceled = false;
447
448 if (last_was_bad) {
449 s += "<b>" + i18n("Wrong password.") + "</b><br><br>\n\n";
450 clearCache();
451 }
452
453 if (!m_text.isEmpty())
454 s += m_text + "<br>";
455
456 if (!gpg_hint.isEmpty())
457 s += gpg_hint;
458
459 if (m_cache.isEmpty()) {
460 KPasswordDialog dlg;
461 dlg.setPrompt(s);
462
463 if (m_saving)
464 dlg.setWindowTitle(i18n("Please enter a new password:"));
465
466 if (dlg.exec())
467 m_cache = dlg.password();
468 else
469 canceled = true;
470 }
471
472 if (!canceled)
473 write(fd, m_cache.data(), m_cache.length());
474 write(fd, "\n", 1);
475 return canceled ? GPG_ERR_CANCELED : GPG_ERR_NO_ERROR;
476 }
477 #endif // HAVE_LIBGPGME
478