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"