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