File indexing completed on 2024-06-02 05:24:11

0001 /* -*- mode: c++; c-basic-offset:4 -*-
0002     commands/dumpcrlcachecommand.cpp
0003 
0004     This file is part of Kleopatra, the KDE keymanager
0005     SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include <config-kleopatra.h>
0011 
0012 #include "dumpcrlcachecommand.h"
0013 
0014 #include "command_p.h"
0015 
0016 #include <Libkleo/GnuPG>
0017 
0018 #include <KConfigGroup>
0019 #include <KLocalizedString>
0020 #include <KMessageBox>
0021 #include <KProcess>
0022 #include <KStandardGuiItem>
0023 #include <QPushButton>
0024 
0025 #include <KSharedConfig>
0026 #include <QByteArray>
0027 #include <QFontDatabase>
0028 #include <QHBoxLayout>
0029 #include <QString>
0030 #include <QTextEdit>
0031 #include <QTimer>
0032 #include <QVBoxLayout>
0033 
0034 static const int PROCESS_TERMINATE_TIMEOUT = 5000; // milliseconds
0035 
0036 namespace
0037 {
0038 class DumpCrlCacheDialog : public QDialog
0039 {
0040     Q_OBJECT
0041 public:
0042     explicit DumpCrlCacheDialog(QWidget *parent = nullptr)
0043         : QDialog(parent)
0044         , ui(this)
0045         , mWithRevocations(false)
0046     {
0047         readConfig();
0048     }
0049     ~DumpCrlCacheDialog() override
0050     {
0051         writeConfig();
0052     }
0053 
0054 Q_SIGNALS:
0055     void updateRequested();
0056 
0057 public Q_SLOTS:
0058     void append(const QString &line)
0059     {
0060         ui.logTextWidget.append(line);
0061         ui.logTextWidget.ensureCursorVisible();
0062     }
0063     void clear()
0064     {
0065         ui.logTextWidget.clear();
0066     }
0067 
0068 public:
0069     void setWithRevocations(bool value)
0070     {
0071         mWithRevocations = value;
0072     }
0073 
0074     [[nodiscard]] bool withRevocations()
0075     {
0076         return mWithRevocations;
0077     }
0078 
0079 private:
0080     void readConfig()
0081     {
0082         KConfigGroup dialog(KSharedConfig::openStateConfig(), QStringLiteral("DumpCrlCacheDialog"));
0083         const QSize size = dialog.readEntry("Size", QSize(600, 400));
0084         if (size.isValid()) {
0085             resize(size);
0086         }
0087     }
0088 
0089     void writeConfig()
0090     {
0091         KConfigGroup dialog(KSharedConfig::openStateConfig(), QStringLiteral("DumpCrlCacheDialog"));
0092         dialog.writeEntry("Size", size());
0093         dialog.sync();
0094     }
0095 
0096     struct Ui {
0097         QTextEdit logTextWidget;
0098         QPushButton updateButton, closeButton, revocationsButton;
0099         QVBoxLayout vlay;
0100         QHBoxLayout hlay;
0101 
0102         explicit Ui(DumpCrlCacheDialog *q)
0103             : logTextWidget(q)
0104             , updateButton(i18nc("@action:button Update the log text widget", "&Update"), q)
0105             , closeButton(q)
0106             , vlay(q)
0107             , hlay()
0108         {
0109             KGuiItem::assign(&closeButton, KStandardGuiItem::close());
0110             KDAB_SET_OBJECT_NAME(logTextWidget);
0111             KDAB_SET_OBJECT_NAME(updateButton);
0112             KDAB_SET_OBJECT_NAME(closeButton);
0113             KDAB_SET_OBJECT_NAME(vlay);
0114             KDAB_SET_OBJECT_NAME(hlay);
0115 
0116             logTextWidget.setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
0117             logTextWidget.setReadOnly(true);
0118 
0119             vlay.addWidget(&logTextWidget, 1);
0120             vlay.addLayout(&hlay);
0121 
0122             revocationsButton.setText(i18n("Show Entries"));
0123 
0124             hlay.addWidget(&updateButton);
0125             hlay.addWidget(&revocationsButton);
0126             hlay.addStretch(1);
0127             hlay.addWidget(&closeButton);
0128 
0129             connect(&updateButton, &QAbstractButton::clicked, q, &DumpCrlCacheDialog::updateRequested);
0130             connect(&closeButton, &QAbstractButton::clicked, q, &QWidget::close);
0131 
0132             connect(&revocationsButton, &QAbstractButton::clicked, q, [q, this]() {
0133                 q->mWithRevocations = true;
0134                 revocationsButton.setEnabled(false);
0135                 q->updateRequested();
0136             });
0137         }
0138     } ui;
0139     bool mWithRevocations;
0140 };
0141 }
0142 
0143 using namespace Kleo;
0144 using namespace Kleo::Commands;
0145 
0146 static QByteArray chomped(QByteArray ba)
0147 {
0148     while (ba.endsWith('\n') || ba.endsWith('\r')) {
0149         ba.chop(1);
0150     }
0151     return ba;
0152 }
0153 
0154 class DumpCrlCacheCommand::Private : Command::Private
0155 {
0156     friend class ::Kleo::Commands::DumpCrlCacheCommand;
0157     DumpCrlCacheCommand *q_func() const
0158     {
0159         return static_cast<DumpCrlCacheCommand *>(q);
0160     }
0161 
0162 public:
0163     explicit Private(DumpCrlCacheCommand *qq, KeyListController *c);
0164     ~Private() override;
0165 
0166     QString errorString() const
0167     {
0168         return QString::fromLocal8Bit(errorBuffer);
0169     }
0170 
0171 private:
0172     void init();
0173     void refreshView();
0174 
0175 private:
0176     void slotProcessFinished(int, QProcess::ExitStatus);
0177 
0178     void slotProcessReadyReadStandardOutput()
0179     {
0180         static int count;
0181         while (process.canReadLine()) {
0182             if (!dialog) {
0183                 break;
0184             }
0185             const QByteArray line = chomped(process.readLine());
0186             if (line.isEmpty()) {
0187                 continue;
0188             }
0189             if (!dialog->withRevocations() && line.contains("reasons")) {
0190                 count++;
0191                 continue;
0192             } else if (count) {
0193                 dialog->append(QLatin1Char(' ') + i18nc("Count of revocations in a CRL", "Entries:") + QStringLiteral("\t\t%1\n").arg(count));
0194                 count = 0;
0195             }
0196             dialog->append(stringFromGpgOutput(line));
0197         }
0198     }
0199 
0200     void slotProcessReadyReadStandardError()
0201     {
0202         errorBuffer += process.readAllStandardError();
0203     }
0204 
0205     void slotUpdateRequested()
0206     {
0207         if (process.state() == QProcess::NotRunning) {
0208             refreshView();
0209         }
0210     }
0211 
0212     void slotDialogDestroyed()
0213     {
0214         dialog = nullptr;
0215         if (process.state() != QProcess::NotRunning) {
0216             q->cancel();
0217         } else {
0218             finished();
0219         }
0220     }
0221 
0222 private:
0223     DumpCrlCacheDialog *dialog;
0224     KProcess process;
0225     QByteArray errorBuffer;
0226     bool canceled;
0227 };
0228 
0229 DumpCrlCacheCommand::Private *DumpCrlCacheCommand::d_func()
0230 {
0231     return static_cast<Private *>(d.get());
0232 }
0233 const DumpCrlCacheCommand::Private *DumpCrlCacheCommand::d_func() const
0234 {
0235     return static_cast<const Private *>(d.get());
0236 }
0237 
0238 #define d d_func()
0239 #define q q_func()
0240 
0241 DumpCrlCacheCommand::Private::Private(DumpCrlCacheCommand *qq, KeyListController *c)
0242     : Command::Private(qq, c)
0243     , dialog(nullptr)
0244     , process()
0245     , errorBuffer()
0246     , canceled(false)
0247 {
0248     process.setOutputChannelMode(KProcess::SeparateChannels);
0249     process.setReadChannel(KProcess::StandardOutput);
0250     process << gpgSmPath() << QStringLiteral("--call-dirmngr") << QStringLiteral("listcrls");
0251 }
0252 
0253 DumpCrlCacheCommand::Private::~Private()
0254 {
0255     if (dialog && !dialog->isVisible()) {
0256         delete dialog;
0257     }
0258 }
0259 
0260 DumpCrlCacheCommand::DumpCrlCacheCommand(KeyListController *c)
0261     : Command(new Private(this, c))
0262 {
0263     d->init();
0264 }
0265 
0266 DumpCrlCacheCommand::DumpCrlCacheCommand(QAbstractItemView *v, KeyListController *c)
0267     : Command(v, new Private(this, c))
0268 {
0269     d->init();
0270 }
0271 
0272 void DumpCrlCacheCommand::Private::init()
0273 {
0274     connect(&process, &QProcess::finished, q, [this](int exitCode, QProcess::ExitStatus status) {
0275         slotProcessFinished(exitCode, status);
0276     });
0277     connect(&process, &QProcess::readyReadStandardError, q, [this]() {
0278         slotProcessReadyReadStandardError();
0279     });
0280     connect(&process, &QProcess::readyReadStandardOutput, q, [this] {
0281         slotProcessReadyReadStandardOutput();
0282     });
0283 }
0284 
0285 DumpCrlCacheCommand::~DumpCrlCacheCommand()
0286 {
0287 }
0288 
0289 void DumpCrlCacheCommand::doStart()
0290 {
0291     d->dialog = new DumpCrlCacheDialog;
0292     d->dialog->setAttribute(Qt::WA_DeleteOnClose);
0293     d->dialog->setWindowTitle(i18nc("@title:window", "CRL Cache Dump"));
0294 
0295     connect(d->dialog, &DumpCrlCacheDialog::updateRequested, this, [this]() {
0296         d->slotUpdateRequested();
0297     });
0298     connect(d->dialog, &QObject::destroyed, this, [this]() {
0299         d->slotDialogDestroyed();
0300     });
0301 
0302     d->refreshView();
0303 }
0304 
0305 void DumpCrlCacheCommand::Private::refreshView()
0306 {
0307     dialog->clear();
0308 
0309     process.start();
0310 
0311     if (process.waitForStarted()) {
0312         dialog->show();
0313     } else {
0314         KMessageBox::error(dialog ? static_cast<QWidget *>(dialog) : parentWidgetOrView(),
0315                            i18n("Unable to start process gpgsm. "
0316                                 "Please check your installation."),
0317                            i18n("Dump CRL Cache Error"));
0318         finished();
0319     }
0320 }
0321 
0322 void DumpCrlCacheCommand::doCancel()
0323 {
0324     d->canceled = true;
0325     if (d->process.state() != QProcess::NotRunning) {
0326         d->process.terminate();
0327         QTimer::singleShot(PROCESS_TERMINATE_TIMEOUT, &d->process, &QProcess::kill);
0328     }
0329     if (d->dialog) {
0330         d->dialog->close();
0331     }
0332     d->dialog = nullptr;
0333 }
0334 
0335 void DumpCrlCacheCommand::Private::slotProcessFinished(int code, QProcess::ExitStatus status)
0336 {
0337     if (!canceled) {
0338         if (status == QProcess::CrashExit)
0339             KMessageBox::error(dialog,
0340                                i18n("The GpgSM process that tried to dump the CRL cache "
0341                                     "ended prematurely because of an unexpected error. "
0342                                     "Please check the output of gpgsm --call-dirmngr listcrls for details."),
0343                                i18nc("@title:window", "Dump CRL Cache Error"));
0344         else if (code)
0345             KMessageBox::error(dialog,
0346                                i18n("An error occurred while trying to dump the CRL cache. "
0347                                     "The output from GpgSM was:\n%1",
0348                                     errorString()),
0349                                i18nc("@title:window", "Dump CRL Cache Error"));
0350     }
0351 }
0352 
0353 #undef d
0354 #undef q
0355 
0356 #include "dumpcrlcachecommand.moc"
0357 #include "moc_dumpcrlcachecommand.cpp"