1 /* main.cpp - A Qt dialog for PIN entry.
2  * Copyright (C) 2002, 2008 Klarälvdalens Datakonsult AB (KDAB)
3  * Copyright (C) 2003 g10 Code GmbH
4  * Copyright 2007 Ingo Klöcker
5  *
6  * Written by Steffen Hansen <steffen@klaralvdalens-datakonsult.se>.
7  * Modified by Marcus Brinkmann <marcus@g10code.de>.
8  * Modified by Marc Mutz <marc@kdab.com>
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License as
12  * published by the Free Software Foundation; either version 2 of the
13  * License, or (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful, but
16  * WITHOUT ANY WARRANTY; without even the implied warranty of
Family()17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, see <https://www.gnu.org/licenses/>.
22  * SPDX-License-Identifier: GPL-2.0+
23  */
24 
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
28 
29 #include "pinentryconfirm.h"
30 #include "pinentrydialog.h"
31 #include "pinentry.h"
32 
33 #include <QApplication>
34 #include <QDebug>
35 #include <QIcon>
36 #include <QMessageBox>
37 #include <QPushButton>
38 #include <QString>
39 #include <QWidget>
40 #include <QWindow>
41 
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <errno.h>
45 
46 #include <stdexcept>
47 #include <gpg-error.h>
48 
49 #ifdef FALLBACK_CURSES
50 #include <pinentry-curses.h>
51 #endif
52 
53 #if QT_VERSION >= 0x050000 && defined(QT_STATIC)
54   #include <QtPlugin>
55   #ifdef Q_OS_WIN
56     #include <windows.h>
57     #include <shlobj.h>
58     Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin)
59   #elif defined(Q_OS_MAC)
60     Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin)
61   #else
62     Q_IMPORT_PLUGIN(QXcbIntegrationPlugin)
63   #endif
64 #endif
65 
66 #ifdef Q_OS_WIN
67 #include <windows.h>
68 #endif
69 
70 static QString escape_accel(const QString &s)
71 {
72 
73     QString result;
74     result.reserve(s.size());
75 
76     bool afterUnderscore = false;
77 
78     for (unsigned int i = 0, end = s.size() ; i != end ; ++i) {
79         const QChar ch = s[i];
80         if (ch == QLatin1Char('_')) {
81             if (afterUnderscore) { // escaped _
82                 result += QLatin1Char('_');
83                 afterUnderscore = false;
84             } else { // accel
85                 afterUnderscore = true;
86             }
87         } else {
88             if (afterUnderscore ||  // accel
89                     ch == QLatin1Char('&')) {  // escape & from being interpreted by Qt
90                 result += QLatin1Char('&');
91             }
92             result += ch;
93             afterUnderscore = false;
94         }
95     }
96 
97     if (afterUnderscore)
98         // trailing single underscore: shouldn't happen, but deal with it robustly:
99     {
100         result += QLatin1Char('_');
101     }
102 
103     return result;
104 }
105 
106 namespace
107 {
108 class InvalidUtf8 : public std::invalid_argument
109 {
110 public:
111     InvalidUtf8() : std::invalid_argument("invalid utf8") {}
112     ~InvalidUtf8() throw() {}
113 };
114 }
115 
116 static const bool GPG_AGENT_IS_PORTED_TO_ONLY_SEND_UTF8 = false;
117 
118 static QString from_utf8(const char *s)
119 {
120     const QString result = QString::fromUtf8(s);
121     if (result.contains(QChar::ReplacementCharacter)) {
122         if (GPG_AGENT_IS_PORTED_TO_ONLY_SEND_UTF8) {
123             throw InvalidUtf8();
124         } else {
125             return QString::fromLocal8Bit(s);
126         }
127     }
128 
129     return result;
130 }
131 
132 static void
133 setup_foreground_window(QWidget *widget, WId parentWid)
134 {
135     /* For windows set the desktop window as the transient parent */
136     QWindow *parentWindow = nullptr;
137     if (parentWid) {
138         parentWindow = QWindow::fromWinId(parentWid);
139     }
140 #ifdef Q_OS_WIN
141     if (!parentWindow) {
142         HWND desktop = GetDesktopWindow();
143         if (desktop) {
144             parentWindow = QWindow::fromWinId((WId) desktop);
145         }
146     }
147 #endif
148     if (parentWindow) {
149         // Ensure that we have a native wid
150         widget->winId();
151         QWindow *wndHandle = widget->windowHandle();
152 
153         if (wndHandle) {
154             wndHandle->setTransientParent(parentWindow);
155         }
156     }
157     widget->setWindowFlags(Qt::Window |
158                            Qt::CustomizeWindowHint |
159                            Qt::WindowTitleHint |
160                            Qt::WindowCloseButtonHint |
161                            Qt::WindowStaysOnTopHint |
162                            Qt::WindowMinimizeButtonHint);
163 }
164 
165 static int
166 qt_cmd_handler(pinentry_t pe)
167 {
168     char *str;
169 
170     int want_pass = !!pe->pin;
171 
172     const QString ok =
173         pe->ok             ? escape_accel(from_utf8(pe->ok)) :
174         pe->default_ok     ? escape_accel(from_utf8(pe->default_ok)) :
175         /* else */           QLatin1String("&OK") ;
176     const QString cancel =
177         pe->cancel         ? escape_accel(from_utf8(pe->cancel)) :
178         pe->default_cancel ? escape_accel(from_utf8(pe->default_cancel)) :
179         /* else */           QLatin1String("&Cancel") ;
180 
181     str = pinentry_get_title (pe);
182     const QString title =
183         str       ? from_utf8(str) :
184         /* else */  QLatin1String("pinentry-qt") ;
185     free (str);
186 
187     const QString repeatError =
188         pe->repeat_error_string ? from_utf8(pe->repeat_error_string) :
189                                   QLatin1String("Passphrases do not match");
190     const QString repeatString =
191         pe->repeat_passphrase ? from_utf8(pe->repeat_passphrase) :
192                                 QString();
193     const QString visibilityTT =
194         pe->default_tt_visi ? from_utf8(pe->default_tt_visi) :
195                               QLatin1String("Show passphrase");
196     const QString hideTT =
197         pe->default_tt_hide ? from_utf8(pe->default_tt_hide) :
198                               QLatin1String("Hide passphrase");
199 
200     const QString generateLbl = pe->genpin_label ? from_utf8(pe->genpin_label) :
201                                 QString();
202     const QString generateTT = pe->genpin_tt ? from_utf8(pe->genpin_tt) :
203                                QString();
204 
205 
206     if (want_pass) {
207         char *str;
208 
209         PinEntryDialog pinentry(nullptr, 0, pe->timeout, true, !!pe->quality_bar,
210                                 repeatString, visibilityTT, hideTT);
211         setup_foreground_window(&pinentry, pe->parent_wid);
212         pinentry.setPinentryInfo(pe);
213         pinentry.setPrompt(escape_accel(from_utf8(pe->prompt)));
214         pinentry.setDescription(from_utf8(pe->description));
215         pinentry.setRepeatErrorText(repeatError);
216         pinentry.setGenpinLabel(generateLbl);
217         pinentry.setGenpinTT(generateTT);
218 
219         str = pinentry_get_title (pe);
220         if (str) {
221             pinentry.setWindowTitle(from_utf8(str));
222             free (str);
223         }
224 
225         /* If we reuse the same dialog window.  */
226         pinentry.setPin(QString());
227 
228         pinentry.setOkText(ok);
229         pinentry.setCancelText(cancel);
230         if (pe->error) {
231             pinentry.setError(from_utf8(pe->error));
232         }
233         if (pe->quality_bar) {
234             pinentry.setQualityBar(from_utf8(pe->quality_bar));
235         }
236         if (pe->quality_bar_tt) {
237             pinentry.setQualityBarTT(from_utf8(pe->quality_bar_tt));
238         }
239         bool ret = pinentry.exec();
240         if (!ret) {
241             if (pinentry.timedOut())
242                 pe->specific_err = gpg_error (GPG_ERR_TIMEOUT);
243             return -1;
244         }
245 
246         const QString pinStr = pinentry.pin();
247         QByteArray pin = pinStr.toUtf8();
248 
249         if (!!pe->repeat_passphrase) {
250             /* Should not have been possible to accept
251                the dialog in that case but we do a safety
252                check here */
253             pe->repeat_okay = (pinStr == pinentry.repeatedPin());
254         }
255 
256         int len = strlen(pin.constData());
257         if (len >= 0) {
258             pinentry_setbufferlen(pe, len + 1);
259             if (pe->pin) {
260                 strcpy(pe->pin, pin.constData());
261                 return len;
262             }
263         }
264         return -1;
265     } else {
266         const QString desc  = pe->description ? from_utf8(pe->description) : QString();
267         const QString notok = pe->notok       ? escape_accel(from_utf8(pe->notok)) : QString();
268 
269         const QMessageBox::StandardButtons buttons =
270             pe->one_button ? QMessageBox::Ok :
271             pe->notok      ? QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel :
272             /* else */       QMessageBox::Ok | QMessageBox::Cancel ;
273 
274         PinentryConfirm box(QMessageBox::Information, pe->timeout, title, desc, buttons, nullptr);
275         setup_foreground_window(&box, pe->parent_wid);
276 
277         const struct {
278             QMessageBox::StandardButton button;
279             QString label;
280         } buttonLabels[] = {
281             { QMessageBox::Ok,     ok     },
282             { QMessageBox::Yes,    ok     },
283             { QMessageBox::No,     notok  },
284             { QMessageBox::Cancel, cancel },
285         };
286 
287         for (size_t i = 0 ; i < sizeof buttonLabels / sizeof * buttonLabels ; ++i)
288             if ((buttons & buttonLabels[i].button) && !buttonLabels[i].label.isEmpty()) {
289                 box.button(buttonLabels[i].button)->setText(buttonLabels[i].label);
290 #ifndef QT_NO_ACCESSIBILITY
291                 box.button(buttonLabels[i].button)->setAccessibleDescription(buttonLabels[i].label);
292 #endif
293             }
294 
295         box.setIconPixmap(icon());
296 
297         if (!pe->one_button) {
298             box.setDefaultButton(QMessageBox::Cancel);
299         }
300 
301         box.show();
302         raiseWindow(&box);
303 
304         const int rc = box.exec();
305 
306         if (rc == QMessageBox::Cancel) {
307             pe->canceled = true;
308         }
309         if (box.timedOut()) {
310           pe->specific_err = gpg_error (GPG_ERR_TIMEOUT);
311         }
312 
313         return rc == QMessageBox::Ok || rc == QMessageBox::Yes ;
314 
315     }
316 }
317 
318 static int
319 qt_cmd_handler_ex(pinentry_t pe)
320 {
321     try {
322         return qt_cmd_handler(pe);
323     } catch (const InvalidUtf8 &) {
324         pe->locale_err = true;
325         return pe->pin ? -1 : false ;
326     } catch (...) {
327         pe->canceled = true;
328         return pe->pin ? -1 : false ;
329     }
330 }
331 
332 pinentry_cmd_handler_t pinentry_cmd_handler = qt_cmd_handler_ex;
333 
334 int
335 main(int argc, char *argv[])
336 {
337     pinentry_init("pinentry-qt");
338 
339     QApplication *app = NULL;
340     int new_argc = 0;
341 
342 #ifdef FALLBACK_CURSES
343     if (!pinentry_have_display(argc, argv)) {
344         pinentry_cmd_handler = curses_cmd_handler;
345         pinentry_set_flavor_flag ("curses");
346     } else
347 #endif
348     {
349         /* Qt does only understand -display but not --display; thus we
350            are fixing that here.  The code is pretty simply and may get
351            confused if an argument is called "--display". */
352         char **new_argv, *p;
353         size_t n;
354         int i, done;
355 
356         for (n = 0, i = 0; i < argc; i++) {
357             n += strlen(argv[i]) + 1;
358         }
359         n++;
360         new_argv = (char **)calloc(argc + 1, sizeof * new_argv);
361         if (new_argv) {
362             *new_argv = (char *)malloc(n);
363         }
364         if (!new_argv || !*new_argv) {
365             fprintf(stderr, "pinentry-qt: can't fixup argument list: %s\n",
366                     strerror(errno));
367             exit(EXIT_FAILURE);
368 
369         }
370         for (done = 0, p = *new_argv, i = 0; i < argc; i++)
371             if (!done && !strcmp(argv[i], "--display")) {
372                 new_argv[i] = strcpy(p, argv[i] + 1);
373                 p += strlen(argv[i] + 1) + 1;
374                 done = 1;
375             } else {
376                 new_argv[i] = strcpy(p, argv[i]);
377                 p += strlen(argv[i]) + 1;
378             }
379 
380         /* Note: QApplication uses int &argc so argc has to be valid
381          * for the full lifetime of the application.
382          *
383          * As Qt might modify argc / argv we use copies here so that
384          * we do not loose options that are handled in both. e.g. display.
385          */
386         new_argc = argc;
387         Q_ASSERT (new_argc);
388         app = new QApplication(new_argc, new_argv);
389         app->setWindowIcon(QIcon(QLatin1String(":/document-encrypt.png")));
390     }
391 
392     pinentry_parse_opts(argc, argv);
393 
394     int rc = pinentry_loop();
395     delete app;
396     return rc ? EXIT_FAILURE : EXIT_SUCCESS ;
397 }
398