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