diff options
Diffstat (limited to 'src/pinentry/pinentry_main.cpp')
-rw-r--r-- | src/pinentry/pinentry_main.cpp | 413 |
1 files changed, 413 insertions, 0 deletions
diff --git a/src/pinentry/pinentry_main.cpp b/src/pinentry/pinentry_main.cpp new file mode 100644 index 00000000..fcea7c31 --- /dev/null +++ b/src/pinentry/pinentry_main.cpp @@ -0,0 +1,413 @@ +/* main.cpp - A Qt dialog for PIN entry. + * Copyright (C) 2002, 2008 Klarälvdalens Datakonsult AB (KDAB) + * Copyright (C) 2003, 2021 g10 Code GmbH + * Copyright 2007 Ingo Klöcker + * + * Written by Steffen Hansen <[email protected]>. + * Modified by Marcus Brinkmann <[email protected]>. + * Modified by Marc Mutz <[email protected]> + * Software engineering by Ingo Klöcker <[email protected]> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <QApplication> +#include <QDebug> +#include <QIcon> +#include <QMessageBox> +#include <QPushButton> +#include <QString> +#include <QWidget> + +#include "accessibility.h" +#include "keyboardfocusindication.h" +#include "pinentry.h" +#include "pinentryconfirm.h" +#include "pinentrydialog.h" +#include "util.h" +#if QT_VERSION >= 0x050000 +#include <QWindow> +#endif + +#include <errno.h> +#include <gpg-error.h> +#include <stdio.h> + +#include <stdexcept> + +#ifdef FALLBACK_CURSES +#include <pinentry-curses.h> +#endif + +#if QT_VERSION >= 0x050000 && defined(QT_STATIC) +#include <QtPlugin> +#ifdef Q_OS_WIN +#include <shlobj.h> +#include <windows.h> +Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin) +#elif defined(Q_OS_MAC) +Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin) +#else +Q_IMPORT_PLUGIN(QXcbIntegrationPlugin) +#endif +#endif + +#ifdef Q_OS_WIN +#include <windows.h> +#endif + +#include "pinentry_debug.h" + +static QString escape_accel(const QString &s) { + QString result; + result.reserve(s.size()); + + bool afterUnderscore = false; + + for (unsigned int i = 0, end = s.size(); i != end; ++i) { + const QChar ch = s[i]; + if (ch == QLatin1Char('_')) { + if (afterUnderscore) { // escaped _ + result += QLatin1Char('_'); + afterUnderscore = false; + } else { // accel + afterUnderscore = true; + } + } else { + if (afterUnderscore || // accel + ch == QLatin1Char('&')) { // escape & from being interpreted by Qt + result += QLatin1Char('&'); + } + result += ch; + afterUnderscore = false; + } + } + + if (afterUnderscore) + // trailing single underscore: shouldn't happen, but deal with it robustly: + { + result += QLatin1Char('_'); + } + + return result; +} + +namespace { +class InvalidUtf8 : public std::invalid_argument { + public: + InvalidUtf8() : std::invalid_argument("invalid utf8") {} + ~InvalidUtf8() throw() {} +}; +} // namespace + +static const bool GPG_AGENT_IS_PORTED_TO_ONLY_SEND_UTF8 = false; + +static QString from_utf8(const char *s) { + const QString result = QString::fromUtf8(s); + if (result.contains(QChar::ReplacementCharacter)) { + if (GPG_AGENT_IS_PORTED_TO_ONLY_SEND_UTF8) { + throw InvalidUtf8(); + } else { + return QString::fromLocal8Bit(s); + } + } + + return result; +} + +static void setup_foreground_window(QWidget *widget, WId parentWid) { +#if QT_VERSION >= 0x050000 + /* For windows set the desktop window as the transient parent */ + QWindow *parentWindow = nullptr; + if (parentWid) { + parentWindow = QWindow::fromWinId(parentWid); + } +#ifdef Q_OS_WIN + if (!parentWindow) { + HWND desktop = GetDesktopWindow(); + if (desktop) { + parentWindow = QWindow::fromWinId((WId)desktop); + } + } +#endif + if (parentWindow) { + // Ensure that we have a native wid + widget->winId(); + QWindow *wndHandle = widget->windowHandle(); + + if (wndHandle) { + wndHandle->setTransientParent(parentWindow); + } + } +#endif + widget->setWindowFlags(Qt::Window | Qt::CustomizeWindowHint | + Qt::WindowTitleHint | Qt::WindowCloseButtonHint | + Qt::WindowStaysOnTopHint | + Qt::WindowMinimizeButtonHint); +} + +static int qt_cmd_handler(pinentry_t pe) { + int want_pass = !!pe->pin; + + const QString ok = pe->ok ? escape_accel(from_utf8(pe->ok)) + : pe->default_ok ? escape_accel(from_utf8(pe->default_ok)) + : + /* else */ QLatin1String("&OK"); + const QString cancel = pe->cancel ? escape_accel(from_utf8(pe->cancel)) + : pe->default_cancel + ? escape_accel(from_utf8(pe->default_cancel)) + : + /* else */ QLatin1String("&Cancel"); + + unique_malloced_ptr<char> str{pinentry_get_title(pe)}; + const QString title = str ? from_utf8(str.get()) : + /* else */ QLatin1String("pinentry-qt"); + + const QString repeatError = pe->repeat_error_string + ? from_utf8(pe->repeat_error_string) + : QLatin1String("Passphrases do not match"); + const QString repeatString = + pe->repeat_passphrase ? from_utf8(pe->repeat_passphrase) : QString(); + const QString visibilityTT = pe->default_tt_visi + ? from_utf8(pe->default_tt_visi) + : QLatin1String("Show passphrase"); + const QString hideTT = pe->default_tt_hide ? from_utf8(pe->default_tt_hide) + : QLatin1String("Hide passphrase"); + + const QString capsLockHint = pe->default_capshint + ? from_utf8(pe->default_capshint) + : QLatin1String("Caps Lock is on"); + + const QString generateLbl = + pe->genpin_label ? from_utf8(pe->genpin_label) : QString(); + const QString generateTT = + pe->genpin_tt ? from_utf8(pe->genpin_tt) : QString(); + + if (want_pass) { + PinEntryDialog pinentry(nullptr, 0, pe->timeout, true, !!pe->quality_bar, + repeatString, visibilityTT, hideTT); + setup_foreground_window(&pinentry, pe->parent_wid); + pinentry.setPinentryInfo(pe); + pinentry.setPrompt(escape_accel(from_utf8(pe->prompt))); + pinentry.setDescription(from_utf8(pe->description)); + pinentry.setRepeatErrorText(repeatError); + pinentry.setGenpinLabel(generateLbl); + pinentry.setGenpinTT(generateTT); + pinentry.setCapsLockHint(capsLockHint); + pinentry.setFormattedPassphrase({bool(pe->formatted_passphrase), + from_utf8(pe->formatted_passphrase_hint)}); + pinentry.setConstraintsOptions({bool(pe->constraints_enforce), + from_utf8(pe->constraints_hint_short), + from_utf8(pe->constraints_hint_long), + from_utf8(pe->constraints_error_title)}); + + if (!title.isEmpty()) { + pinentry.setWindowTitle(title); + } + + /* If we reuse the same dialog window. */ + pinentry.setPin(QString()); + + pinentry.setOkText(ok); + pinentry.setCancelText(cancel); + if (pe->error) { + pinentry.setError(from_utf8(pe->error)); + } + if (pe->quality_bar) { + pinentry.setQualityBar(from_utf8(pe->quality_bar)); + } + if (pe->quality_bar_tt) { + pinentry.setQualityBarTT(from_utf8(pe->quality_bar_tt)); + } + bool ret = pinentry.exec(); + if (!ret) { + if (pinentry.timedOut()) pe->specific_err = gpg_error(GPG_ERR_TIMEOUT); + return -1; + } + + const QString pinStr = pinentry.pin(); + QByteArray pin = pinStr.toUtf8(); + + if (!!pe->repeat_passphrase) { + /* Should not have been possible to accept + the dialog in that case but we do a safety + check here */ + pe->repeat_okay = (pinStr == pinentry.repeatedPin()); + } + + int len = strlen(pin.constData()); + if (len >= 0) { + pinentry_setbufferlen(pe, len + 1); + if (pe->pin) { + strcpy(pe->pin, pin.constData()); + return len; + } + } + return -1; + } else { + const QString desc = + pe->description ? from_utf8(pe->description) : QString(); + const QString notok = + pe->notok ? escape_accel(from_utf8(pe->notok)) : QString(); + + const QMessageBox::StandardButtons buttons = + pe->one_button ? QMessageBox::Ok + : pe->notok ? QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel + : + /* else */ QMessageBox::Ok | QMessageBox::Cancel; + + PinentryConfirm box{QMessageBox::Information, title, desc, buttons}; + box.setTextFormat(Qt::PlainText); + box.setTextInteractionFlags(Qt::TextSelectableByMouse); + box.setTimeout(std::chrono::seconds{pe->timeout}); + setup_foreground_window(&box, pe->parent_wid); + + const struct { + QMessageBox::StandardButton button; + QString label; + } buttonLabels[] = { + {QMessageBox::Ok, ok}, + {QMessageBox::Yes, ok}, + {QMessageBox::No, notok}, + {QMessageBox::Cancel, cancel}, + }; + + for (size_t i = 0; i < sizeof buttonLabels / sizeof *buttonLabels; ++i) + if ((buttons & buttonLabels[i].button) && + !buttonLabels[i].label.isEmpty()) { + box.button(buttonLabels[i].button)->setText(buttonLabels[i].label); + Accessibility::setDescription(box.button(buttonLabels[i].button), + buttonLabels[i].label); + } + + box.setIconPixmap(applicationIconPixmap()); + + if (!pe->one_button) { + box.setDefaultButton(QMessageBox::Cancel); + } + + box.show(); + raiseWindow(&box); + + const int rc = box.exec(); + + if (rc == QMessageBox::Cancel) { + pe->canceled = true; + } + if (box.timedOut()) { + pe->specific_err = gpg_error(GPG_ERR_TIMEOUT); + } + + return rc == QMessageBox::Ok || rc == QMessageBox::Yes; + } +} + +static int qt_cmd_handler_ex(pinentry_t pe) { + try { + return qt_cmd_handler(pe); + } catch (const InvalidUtf8 &) { + pe->locale_err = true; + return pe->pin ? -1 : false; + } catch (...) { + pe->canceled = true; + return pe->pin ? -1 : false; + } +} + +pinentry_cmd_handler_t pinentry_cmd_handler = qt_cmd_handler_ex; + +int pinentry_main(int argc, char *argv[]) { + pinentry_init("pinentry-qt"); + + QApplication *app = NULL; + int new_argc = 0; + +#ifdef FALLBACK_CURSES +#if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) + // check a few environment variables that are usually set on X11 or Wayland + // sessions + const bool hasWaylandDisplay = qEnvironmentVariableIsSet("WAYLAND_DISPLAY"); + const bool isWaylandSessionType = qgetenv("XDG_SESSION_TYPE") == "wayland"; + const bool hasX11Display = pinentry_have_display(argc, argv); + const bool isX11SessionType = qgetenv("XDG_SESSION_TYPE") == "x11"; + const bool isGUISession = hasWaylandDisplay || isWaylandSessionType || + hasX11Display || isX11SessionType; + qCDebug(PINENTRY_LOG) << "hasWaylandDisplay:" << hasWaylandDisplay; + qCDebug(PINENTRY_LOG) << "isWaylandSessionType:" << isWaylandSessionType; + qCDebug(PINENTRY_LOG) << "hasX11Display:" << hasX11Display; + qCDebug(PINENTRY_LOG) << "isX11SessionType:" << isX11SessionType; + qCDebug(PINENTRY_LOG) << "isGUISession:" << isGUISession; +#else + const bool isGUISession = pinentry_have_display(argc, argv); +#endif + if (!isGUISession) { + pinentry_cmd_handler = curses_cmd_handler; + pinentry_set_flavor_flag("curses"); + } else +#endif + { + /* Qt does only understand -display but not --display; thus we + are fixing that here. The code is pretty simply and may get + confused if an argument is called "--display". */ + char **new_argv, *p; + size_t n; + int i, done; + + for (n = 0, i = 0; i < argc; i++) { + n += strlen(argv[i]) + 1; + } + n++; + new_argv = (char **)calloc(argc + 1, sizeof *new_argv); + if (new_argv) { + *new_argv = (char *)malloc(n); + } + if (!new_argv || !*new_argv) { + fprintf(stderr, "pinentry-qt: can't fixup argument list: %s\n", + strerror(errno)); + exit(EXIT_FAILURE); + } + for (done = 0, p = *new_argv, i = 0; i < argc; i++) + if (!done && !strcmp(argv[i], "--display")) { + new_argv[i] = strcpy(p, argv[i] + 1); + p += strlen(argv[i] + 1) + 1; + done = 1; + } else { + new_argv[i] = strcpy(p, argv[i]); + p += strlen(argv[i]) + 1; + } + + /* Note: QApplication uses int &argc so argc has to be valid + * for the full lifetime of the application. + * + * As Qt might modify argc / argv we use copies here so that + * we do not loose options that are handled in both. e.g. display. + */ + new_argc = argc; + Q_ASSERT(new_argc); + app = new QApplication(new_argc, new_argv); + app->setWindowIcon(QIcon(QLatin1String(":/document-encrypt.png"))); + (void)new KeyboardFocusIndication{app}; + } + + pinentry_parse_opts(argc, argv); + + int rc = pinentry_loop(); + delete app; + return rc ? EXIT_FAILURE : EXIT_SUCCESS; +} |