diff --git a/src/gpgolconfig/gpgolconfigpage.cpp b/src/gpgolconfig/gpgolconfigpage.cpp index 5ffc6c2..1ebaff6 100644 --- a/src/gpgolconfig/gpgolconfigpage.cpp +++ b/src/gpgolconfig/gpgolconfigpage.cpp @@ -1,270 +1,405 @@ /* Copyright (C) 2018 by Intevation GmbH * * This file is Free Software under the GNU GPL (v>=2) * and comes with ABSOLUTELY NO WARRANTY! * See LICENSE.txt for details. */ #include "gpgolconfigpage.h" #include "w32-gettext.h" #include "w32-util.h" #include #include #include #include #include #include #include #include +#include +#include +#include +#include +#include + +/* See gpgol/src/debug.h */ +#define DBG_OOM (1<<1) +#define DBG_MEMORY (1<<2) +#define DBG_TRACE (1<<3) +#define DBG_DATA (1<<4) /* class ExplainingChkBox: public QWidget { Q_OBJECT public: explicit ExplainingChkBox(const QString &text, const QString &explanation): mChkBox(new QCheckBox(text)), mExplanation(explanation) { auto hBox = new QHBoxLayout(this); hBox->addWidget(mChkBox); auto infoBtn = new QPushButton; infoBtn->setIcon(QIcon::fromTheme("help-contextual")); hBox->addWidget(infoBtn); hBox->addStretch(1); connect(infoBtn, &QPushButton::clicked, this, [this, infoBtn] () { QToolTip::showText(infoBtn->mapToGlobal(QPoint()), mExplanation, infoBtn); }); } void setChecked(bool value) { mChkBox->setChecked(value); } private: QCheckBox *mChkBox; QString mExplanation; }; */ GpgOLConfigPage::GpgOLConfigPage(QWidget *parent): QWidget(parent) { setupGUI(); load(); } /* Helper to build an "About" style layout. static QLayout *buildAboutLayout(const QString &version) { auto hLay = new QHBoxLayout; auto vLay = new QVBoxLayout; hLay->addLayout(vLay); hLay->addStretch(1); auto iconLbl = new QLabel; iconLbl->setPixmap(QIcon(":/gpgol-logo.png").pixmap(128, 80)); auto versionLbl = new QLabel(QStringLiteral(" ") + QString::fromUtf8(_("Version ")) + version); vLay->addWidget(iconLbl); vLay->addWidget(versionLbl); return hLay; } */ void GpgOLConfigPage::setupGUI() { auto baseLay = new QVBoxLayout(this); mSMIMEGrp = new QGroupBox(_("Enable the S/MIME support")); mSMIMEGrp->setCheckable(true); mSMIMEGrp->setAlignment(Qt::AlignLeft); auto smimeLay = new QVBoxLayout(mSMIMEGrp); mPreferSMIMEChk = new QCheckBox(_("&Prefer S/MIME when automatically resolving recipients")); mPreferSMIMEChk->setToolTip(_("If automatic resolution is enabled, prefer S/MIME over OpenPGP if both are possible.")); smimeLay->addWidget(mPreferSMIMEChk); baseLay->addWidget(mSMIMEGrp); // The general group auto generalGrp = new QGroupBox(_("General")); auto generalLay = new QVBoxLayout(generalGrp); generalGrp->setAlignment(Qt::AlignLeft); mAlwaysSigChk = new QCheckBox(_("&Sign new messages by default")); mAlwaysSigChk->setToolTip(_("Toggles the sign option for all new mails.")); mAlwaysEncChk = new QCheckBox(_("&Encrypt new messages by default")); mAlwaysSigChk->setToolTip(_("Toggles the encrypt option for all new mails.")); mReplyCryptChk = new QCheckBox(_("S&elect crypto settings automatically " "for reply and forward")); mReplyCryptChk->setToolTip(_("Toggles sign, encrypt options if the original mail was signed or encrypted.")); mInlinePGPChk = new QCheckBox(_("&Send OpenPGP mails without attachments as PGP/Inline")); mInlinePGPChk->setToolTip(_("Instead of using the PGP/MIME format, " "which properly handles attachments and encoding, " "the deprecated PGP/Inline is used.\n" "This can be useful for compatibility but should generally not " "be used.")); generalLay->addWidget(mAlwaysSigChk); generalLay->addWidget(mAlwaysEncChk); generalLay->addWidget(mReplyCryptChk); generalLay->addWidget(mInlinePGPChk); baseLay->addWidget(generalGrp); // The automation checkboxes mAutomationGrp = new QGroupBox(_("Automation")); mAutomationGrp->setToolTip(_("Enable or disable any automated key handling.")); auto autoLayout = new QVBoxLayout(mAutomationGrp); mAutomationGrp->setCheckable(true); mAutoResolveChk = new QCheckBox(_("&Resolve recipient keys automatically")); autoLayout->addWidget(mAutoResolveChk); auto subLay = new QHBoxLayout; mAutoSecureChk = new QCheckBox(_("Automatically secure &messages")); mAutoSecureChk->setToolTip(_("Automatically toggles secure if keys with at least level 1 trust were found for all recipients.")); subLay->addSpacing(20); subLay->addWidget(mAutoSecureChk); autoLayout->addLayout(subLay); mAutoTrustChk = new QCheckBox(QStringLiteral("%1 (%2)").arg(_("Include OpenPGP &trust based on communication history")).arg(_("experimental"))); mAutoTrustChk->setToolTip(_("This changes the trust model to \"tofu+pgp\" which tracks the history of key usage. " "Automated trust can never exceed level 2.")); /* Dsiabled for now */ mAutoTrustChk->setVisible(false); autoLayout->addWidget(mAutoTrustChk); baseLay->addWidget(mAutomationGrp); + // The debugging group + mDbgGrp = new QGroupBox(_("Enable Logging")); + mDbgGrp->setCheckable(true); + + mDbgCombo = new QComboBox; + mDbgCombo->addItem(_("Default"), 1); + mDbgCombo->addItem(_("+Outlook API calls"), (DBG_OOM)); + mDbgCombo->addItem(_("+Memory analysis"), (DBG_OOM | DBG_MEMORY)); + mDbgCombo->addItem(_("+Call tracing"), (DBG_OOM | DBG_MEMORY | DBG_TRACE)); + + mDbgVerboseWarningLabel = new QLabel(_("Warning: Decreased performance. Huge logs!")); + + mDbgComboLabel = new QLabel(_("Log level:")); + + mDbgLogFileLabel = new QLabel(_("Log File (required):")); + + mDbgDataChk = new QCheckBox(_("Include Mail contents (decrypted!) and meta information.")); + + mDbgLogFileName = new QLineEdit; + + auto dbgLay = new QVBoxLayout(mDbgGrp); + + auto logFileLay = new QHBoxLayout; + + mDbgLogFileBtn = new QPushButton; + mDbgLogFileBtn->setIcon(style()->standardIcon(QStyle::SP_FileDialogStart)); + + logFileLay->addWidget(mDbgLogFileLabel, 0); + logFileLay->addWidget(mDbgLogFileName, 1); + logFileLay->addWidget(mDbgLogFileBtn, 0); + dbgLay->addLayout(logFileLay); + + auto dbgComboLay = new QHBoxLayout; + dbgLay->addLayout(dbgComboLay); + + dbgComboLay->addWidget(mDbgComboLabel); + dbgComboLay->addWidget(mDbgCombo); + dbgComboLay->addWidget(mDbgVerboseWarningLabel); + dbgComboLay->addStretch(1); + + dbgLay->addWidget(mDbgDataChk); + + baseLay->addWidget(mDbgGrp); + + // End debugging group + // baseLay->addLayout(buildAboutLayout(mVersion)); baseLay->addStretch(1); connect(mAutoResolveChk, &QCheckBox::toggled, [this] (bool on) { mAutoSecureChk->setEnabled(on); mPreferSMIMEChk->setEnabled(mSMIMEGrp->isChecked() && on); }); connect(mSMIMEGrp, &QGroupBox::toggled, [this] (bool on) { mPreferSMIMEChk->setEnabled(mAutoSecureChk->isChecked() && on); }); + connect(mDbgGrp, &QGroupBox::toggled, [this] (bool) { + enableDisableDbgWidgets(); + }); + + connect(mDbgCombo, &QComboBox::currentTextChanged, [this] (QString) { + mDbgVerboseWarningLabel->setVisible((mDbgCombo->currentData().toInt() & DBG_TRACE)); + }); + + connect(mDbgLogFileBtn, &QPushButton::clicked, [this] () { + const auto fileName = QFileDialog::getSaveFileName(this, _("Select log file"), + mDbgLogFileName->text(), + "(*.txt)"); + if (!fileName.isEmpty()) { + mDbgLogFileName->setText(QDir::toNativeSeparators(fileName)); + } + }); + enableDisableDbgWidgets(); } static bool strToBool(const std::string &str, bool defaultVal = false) { if (str.empty()) { return defaultVal; } if (str == "1") { return true; } if (str == "0") { return false; } qDebug() << "Unknown bool val" << str.c_str(); return defaultVal; } static bool loadBool(const char *name, bool defaultVal) { return strToBool(W32::readRegStr(nullptr, GPGOL_REG_PATH, name), defaultVal); } /* Bump this if you remove a config value */ #define CONFIG_VERSION "1" static const QMap defaultMap { { QStringLiteral("enableSmime"), false }, { QStringLiteral("encryptDefault"), false }, { QStringLiteral("signDefault"), false }, { QStringLiteral("inlinePGP"), false }, { QStringLiteral("replyCrypt"), true }, { QStringLiteral("preferSmime"), false }, { QStringLiteral("debugGPGME"), false }, { QStringLiteral("automation"), true }, { QStringLiteral("autoresolve"), true }, { QStringLiteral("autosecure"), true }, { QStringLiteral("autotrust"), false }, { QStringLiteral("automation"), true }, { QStringLiteral("syncEnc"), false }, }; void GpgOLConfigPage::updateGUI(const QMap &values) { bool smimeEnabled = values["enableSmime"]; mSMIMEGrp->setChecked(smimeEnabled); mPreferSMIMEChk->setChecked(values["preferSmime"]); mAlwaysEncChk->setChecked(values["encryptDefault"]); mAlwaysSigChk->setChecked(values["signDefault"]); mInlinePGPChk->setChecked(values["inlinePGP"]); mReplyCryptChk->setChecked(values["replyCrypt"]); mAutomationGrp->setChecked(values["automation"]); mAutoSecureChk->setChecked(values["autosecure"]); mAutoTrustChk->setChecked(values["autotrust"]); mAutoResolveChk->setChecked(values["autoresolve"]); mAutoSecureChk->setEnabled(mAutoResolveChk->isChecked() && mAutomationGrp->isChecked()); mPreferSMIMEChk->setEnabled(mAutoResolveChk->isChecked() && smimeEnabled); } void GpgOLConfigPage::load() { QMap confValues; for (const auto &key: defaultMap.keys()) { confValues[key] = loadBool(key.toLocal8Bit().constData(), defaultMap[key]); } updateGUI(confValues); const std::string version = W32::readRegStr(nullptr, GPGOL_REG_PATH, "config-version"); if (version != CONFIG_VERSION) { qDebug() << "Config update. Cleaning old values"; } + + const auto logFile = W32::readRegStr(nullptr, GPGOL_REG_PATH, "logFile"); + mDbgLogFileName->setText(logFile.empty() ? + QDir::toNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/gpgol.txt") : + QString::fromStdString(logFile)); + + const auto logLevelS = W32::readRegStr(nullptr, GPGOL_REG_PATH, "enableDebug"); + + bool ok; + int logLevel = QString::fromStdString(logLevelS).toInt(&ok); + if (!ok) { + logLevel = 0; + } + + mDbgGrp->setChecked(logLevel > 0); + + int idx = 0; + if ((logLevel & DBG_OOM)) { + idx++; + } + if ((logLevel & DBG_MEMORY)) { + idx++; + } + if ((logLevel & DBG_TRACE)) { + idx++; + } + + mDbgCombo->setCurrentIndex(idx); + + mDbgDataChk->setChecked((logLevel & DBG_DATA)); } void GpgOLConfigPage::defaults() { updateGUI(defaultMap); } static void saveBool(const char *name, bool value) { const char *val = value ? "1" : "0"; if (!W32::writeRegStr(nullptr, GPGOL_REG_PATH, name, val)) { qWarning() << "Failed to write registry value for" << name; } } +static void saveInt(const char *name, int value) +{ + const std::string val = std::to_string(value); + + if (!W32::writeRegStr(nullptr, GPGOL_REG_PATH, name, val.c_str())) { + qWarning() << "Failed to write registry value for" << name; + } +} + void GpgOLConfigPage::save() const { saveBool("enableSmime", mSMIMEGrp->isChecked()); saveBool("preferSmime", mPreferSMIMEChk->isChecked()); saveBool("encryptDefault", mAlwaysEncChk->isChecked()); saveBool("signDefault", mAlwaysSigChk->isChecked()); saveBool("inlinePGP", mInlinePGPChk->isChecked()); saveBool("replyCrypt", mReplyCryptChk->isChecked()); saveBool("automation", mAutomationGrp->isChecked()); saveBool("autosecure", mAutoSecureChk->isChecked()); saveBool("autotrust", mAutoTrustChk->isChecked()); saveBool("autoresolve", mAutoResolveChk->isChecked()); + int logLevel = mDbgCombo->currentData().toInt(); + logLevel |= mDbgDataChk->isChecked() ? DBG_DATA : 0; + + saveInt("enableDebug", logLevel); + W32::writeRegStr(nullptr, GPGOL_REG_PATH, "logFile", QDir::toNativeSeparators( + mDbgLogFileName->text()).toLocal8Bit().constData()); + W32::writeRegStr(nullptr, GPGOL_REG_PATH, "config-version", CONFIG_VERSION); } +void GpgOLConfigPage::enableDisableDbgWidgets() +{ + bool vis = mDbgGrp->isChecked(); + + mDbgDataChk->setVisible(vis); + mDbgCombo->setVisible(vis); + mDbgComboLabel->setVisible(vis); + + mDbgLogFileName->setVisible(vis); + mDbgLogFileLabel->setVisible(vis); + mDbgLogFileBtn->setVisible(vis); + + mDbgVerboseWarningLabel->setVisible(vis && (mDbgCombo->currentData().toInt() & DBG_TRACE)); +} + #include "gpgolconfigpage.moc" diff --git a/src/gpgolconfig/gpgolconfigpage.h b/src/gpgolconfig/gpgolconfigpage.h index 5209c78..be7b90f 100644 --- a/src/gpgolconfig/gpgolconfigpage.h +++ b/src/gpgolconfig/gpgolconfigpage.h @@ -1,46 +1,57 @@ #ifndef GPGOLCONFIGPAGE_H #define GPGOLCONFIGPAGE_H /* Copyright (C) 2018 by Intevation GmbH * * This file is Free Software under the GNU GPL (v>=2) * and comes with ABSOLUTELY NO WARRANTY! * See LICENSE.txt for details. */ #include #include #include class QGroupBox; class QCheckBox; class QLabel; -class ExplainingChkBox; +class QLineEdit; +class QComboBox; +class QPushButton; class GpgOLConfigPage: public QWidget { Q_OBJECT public: explicit GpgOLConfigPage(QWidget *parent = nullptr); void save() const; void load(); void defaults(); protected: void setupGUI(); void updateGUI(const QMap &values); + void enableDisableDbgWidgets(); private: QGroupBox *mSMIMEGrp, - *mAutomationGrp; + *mAutomationGrp, + *mDbgGrp; QCheckBox *mPreferSMIMEChk, *mAutoSecureChk, *mAlwaysEncChk, *mAlwaysSigChk, *mInlinePGPChk, *mAutoTrustChk, *mAutoResolveChk, - *mReplyCryptChk; + *mReplyCryptChk, + *mDbgDataChk; + QLineEdit *mDbgLogFileName; + QLabel *mDbgLogFileLabel, + *mDbgComboLabel, + *mDbgVerboseWarningLabel; + QComboBox *mDbgCombo; + QPushButton *mDbgLogFileBtn; }; #endif diff --git a/src/resolver/resolver-options.h b/src/resolver/resolver-options.h index 7dc368a..ffe1bfb 100644 --- a/src/resolver/resolver-options.h +++ b/src/resolver/resolver-options.h @@ -1,53 +1,64 @@ #ifndef RESOLVER_OPTIONS #define RESOLVER_OPTIONS /* Copyright (C) 2018 by Intevation GmbH * * This file is Free Software under the GNU GPL (v>=2) * and comes with ABSOLUTELY NO WARRANTY! * See LICENSE.txt for details. */ #include #include /** @file Commandline options*/ static void options(QCommandLineParser &parser) { QList options; options << QCommandLineOption(QStringList() << QStringLiteral("debug"), QStringLiteral("Print debug output.")) << QCommandLineOption(QStringLiteral("protocol"), QStringLiteral("Specify a forced protocol"), QStringLiteral("pgp or cms")) << QCommandLineOption(QStringLiteral("sender"), QStringLiteral("The sender"), QStringLiteral("sender mailbox")) << QCommandLineOption(QStringLiteral("sign"), QStringLiteral("Should be signed")) << QCommandLineOption(QStringLiteral("encrypt"), QStringLiteral("Should be encrypted")) << QCommandLineOption(QStringLiteral("hwnd"), QStringLiteral("Parent Window"), QStringLiteral("windows window handle")) << QCommandLineOption(QStringLiteral("overlayText"), QStringLiteral("Overlay Text"), QStringLiteral("text to overlay over hwnd")) << QCommandLineOption(QStringLiteral("lang"), QStringLiteral("Language"), QStringLiteral("Language to be used e.g. de_DE")) + << QCommandLineOption(QStringList() << QStringLiteral("override") + << QStringLiteral("o"), + QStringLiteral("Override where format can be:\n" + "InlineOpenPGP\n" + "OpenPGPMIME\n" + "SMIME\n" + "SMIMEOpaque\n" + "AnyOpenPGP\n" + "AnySMIME\n" + "Auto"), + QStringLiteral("mailbox:fpr,fpr,..:format")) << QCommandLineOption(QStringLiteral("alwaysShow"), QStringLiteral("Should always be shown")); for (const auto &opt: options) { parser.addOption(opt); } parser.addVersionOption(); parser.addHelpOption(); parser.addPositionalArgument(QStringLiteral("recipients"), QStringLiteral("Recipient Mailboxes"), QStringLiteral("[recipients]")); } #endif // RESOLVER_OPTIONS diff --git a/src/resolver/resolver.cpp b/src/resolver/resolver.cpp index bd3a84c..cd3c576 100644 --- a/src/resolver/resolver.cpp +++ b/src/resolver/resolver.cpp @@ -1,132 +1,174 @@ /* Copyright (C) 2018 by Intevation GmbH * * This file is Free Software under the GNU GPL (v>=2) * and comes with ABSOLUTELY NO WARRANTY! * See LICENSE.txt for details. */ #include "resolver.h" #include "util/overlay.h" #include #include #include #include #include #include class Resolver::Private { public: Private(Resolver *qq): q(qq) { } void printResolvedKeys(const Kleo::KeyResolver *kr) { const auto sigMap = kr->signingKeys(); for (const auto fmt: sigMap.keys()) { for (const auto key: sigMap[fmt]) { std::string fmtStr; if (fmt & Kleo::AnySMIME) { fmtStr = "smime"; } else { fmtStr = "openpgp"; } if (!key.isNull()) { std::cout << "sig:" << fmtStr << ":" << key.primaryFingerprint() << std::endl; } } } const auto encMap = kr->encryptionKeys(); for (const auto fmt: encMap.keys()) { for (const auto mbox: encMap[fmt].keys()) { for (const auto key: encMap[fmt][mbox]) { std::string fmtStr; if (fmt & Kleo::AnySMIME) { fmtStr = "smime"; } else { fmtStr = "openpgp"; } if (!key.isNull()) { std::cout << "enc:" << fmtStr << ":" << key.primaryFingerprint() << std::endl; } } } } } void newOverlay(WId wid, const QString &text) { m_overlay = std::shared_ptr(new Overlay(wid, text)); } void newResolver(const QCommandLineParser &parser) { const auto proto = parser.value(QStringLiteral("protocol")).toLower(); Kleo::CryptoMessageFormat format; if (proto == QStringLiteral("cms")) { format = Kleo::AnySMIME; } else if (proto == QStringLiteral("pgp")) { format = Kleo::AnyOpenPGP; } else { format = Kleo::AutoFormat; } + QMap > overrides; + + for (const QString &oride: parser.values("override")) { + const QStringList split = oride.split(QLatin1Char(':')); + Kleo::CryptoMessageFormat fmt = Kleo::AutoFormat; + if (split.size() < 2 || split.size() > 3) { + qDebug() << "Invalid override format"; + std::cout << "cancel" << std::endl; + qApp->quit(); + } + + if (split.size() == 3) { + const QString fmtStr = split[2].toLower(); + if (fmtStr == "inlineopenpgp") { + fmt = Kleo::InlineOpenPGPFormat; + } else if (fmtStr == "openpgpmime") { + fmt = Kleo::OpenPGPMIMEFormat; + } else if (fmtStr == "smime") { + fmt = Kleo::SMIMEFormat; + } else if (fmtStr == "smimeopaque") { + fmt = Kleo::SMIMEOpaqueFormat; + } else if (fmtStr == "anyopenpgp") { + fmt = Kleo::AnyOpenPGP; + } else if (fmtStr == "anysmime") { + fmt = Kleo::AnySMIME; + } else if (fmtStr == "auto") { + fmt = Kleo::AutoFormat; + } else { + qDebug() << "Invalid override format string"; + std::cout << "cancel" << std::endl; + qApp->quit(); + } + } + const QStringList fingerprints = split[1].split(QLatin1Char(',')); + + auto map = overrides.value(fmt); + map.insert(split[0], fingerprints); + overrides.insert(fmt, map); + qDebug () << "Passing overrides" << fingerprints << split[0]; + } + const auto recps = parser.positionalArguments(); auto *kr = new Kleo::KeyResolver (!recps.isEmpty() || parser.isSet(QStringLiteral("encrypt")) /* encrypt */, parser.isSet(QStringLiteral("sign")) /*sign*/, format /* CryptoMesssageFormat */, false /* AllowMixed */); kr->setRecipients(recps); kr->setSender(parser.value("sender")); kr->enableNagging(true); + kr->setOverrideKeys(overrides); connect (kr, &Kleo::KeyResolver::keysResolved, q, [this, kr] (bool success, bool sendUnencrypted) { if (!success) { std::cout << "cancel" << std::endl; } else if (sendUnencrypted) { std::cout << "unencrypted" << std::endl; } else { printResolvedKeys(kr); } delete kr; qApp->quit(); }); kr->setDialogWindowFlags(Qt::Window | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowCloseButtonHint); kr->start(parser.isSet(QStringLiteral("alwaysShow")), m_overlay.get()); } Resolver *q; std::shared_ptr m_overlay; }; Resolver::Resolver(int &argc, char *argv[]) : QApplication(argc, argv), d(new Private(this)) { } QString Resolver::newInstance(const QCommandLineParser &parser, const QString &workingDirectry) { const auto hwnd = parser.value(QStringLiteral("hwnd")); if (!hwnd.isEmpty()) { bool ok; WId id = (WId) hwnd.toInt(&ok); if (!ok) { std::cerr << "invalid hwnd value" << std::endl; exit(EXIT_FAILURE); } d->newOverlay(id, parser.value(QStringLiteral("overlayText"))); } d->newResolver(parser); return QString(); }