File indexing completed on 2024-06-23 05:14:10

0001 /*  smartcard/deviceinfowatcher.cpp
0002 
0003     This file is part of Kleopatra, the KDE keymanager
0004     SPDX-FileCopyrightText: 2020 g10 Code GmbH
0005     SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "deviceinfowatcher.h"
0011 #include "deviceinfowatcher_p.h"
0012 
0013 #include <QGpgME/Debug>
0014 
0015 #include <gpgme++/context.h>
0016 #include <gpgme++/engineinfo.h>
0017 #include <gpgme++/statusconsumerassuantransaction.h>
0018 
0019 #include "kleopatra_debug.h"
0020 
0021 using namespace Kleo;
0022 using namespace GpgME;
0023 using namespace std::chrono_literals;
0024 
0025 static const auto initialRetryDelay = 125ms;
0026 static const auto maxRetryDelay = 1000ms;
0027 static const auto maxConnectionAttempts = 10;
0028 
0029 DeviceInfoWatcher::Worker::Worker()
0030     : mRetryDelay{initialRetryDelay}
0031 {
0032 }
0033 
0034 DeviceInfoWatcher::Worker::~Worker()
0035 {
0036     if (mContext) {
0037         mContext->cancelPendingOperationImmediately();
0038     }
0039 }
0040 
0041 void DeviceInfoWatcher::Worker::start()
0042 {
0043     if (!mContext) {
0044         Error err;
0045         mContext = Context::createForEngine(AssuanEngine, &err);
0046         if (err) {
0047             qCWarning(KLEOPATRA_LOG) << "DeviceInfoWatcher::Worker::start: Creating context failed:" << err;
0048             return;
0049         }
0050     }
0051 
0052     static const char *command = "SCD DEVINFO --watch";
0053     std::unique_ptr<AssuanTransaction> t(new StatusConsumerAssuanTransaction(this));
0054     const Error err = mContext->startAssuanTransaction(command, std::move(t));
0055     if (!err) {
0056         qCDebug(KLEOPATRA_LOG) << "DeviceInfoWatcher::Worker::start: Assuan transaction for" << command << "started";
0057         mRetryDelay = initialRetryDelay;
0058         mFailedConnectionAttempts = 0;
0059         QMetaObject::invokeMethod(this, "poll", Qt::QueuedConnection);
0060         return;
0061     } else if (err.code() == GPG_ERR_ASS_CONNECT_FAILED) {
0062         mFailedConnectionAttempts++;
0063         if (mFailedConnectionAttempts == 1) {
0064             Q_EMIT startOfGpgAgentRequested();
0065         }
0066         if (mFailedConnectionAttempts < maxConnectionAttempts) {
0067             qCInfo(KLEOPATRA_LOG) << "DeviceInfoWatcher::Worker::start: Connecting to the agent failed. Retrying in" << mRetryDelay.count() << "ms";
0068             QThread::msleep(mRetryDelay.count());
0069             mRetryDelay = std::min(mRetryDelay * 2, maxRetryDelay);
0070             QMetaObject::invokeMethod(this, "start", Qt::QueuedConnection);
0071             return;
0072         }
0073         qCWarning(KLEOPATRA_LOG) << "DeviceInfoWatcher::Worker::start: Connecting to the agent failed too often. Giving up.";
0074     } else if (err.code() == GPG_ERR_EPIPE) {
0075         qCDebug(KLEOPATRA_LOG)
0076             << "DeviceInfoWatcher::Worker::start: Assuan transaction failed with broken pipe. The agent seems to have died. Resetting context.";
0077         mContext.reset();
0078         QMetaObject::invokeMethod(this, "start", Qt::QueuedConnection);
0079     } else {
0080         qCWarning(KLEOPATRA_LOG) << "DeviceInfoWatcher::Worker::start: Starting Assuan transaction for" << command << "failed:" << err;
0081     }
0082 }
0083 
0084 void DeviceInfoWatcher::Worker::poll()
0085 {
0086     const bool finished = mContext->poll();
0087     if (finished) {
0088         qCDebug(KLEOPATRA_LOG) << "DeviceInfoWatcher::Worker::poll: context finished with" << mContext->lastError();
0089         QThread::msleep(1000);
0090         QMetaObject::invokeMethod(this, "start", Qt::QueuedConnection);
0091     } else {
0092         QMetaObject::invokeMethod(this, "poll", Qt::QueuedConnection);
0093     }
0094 }
0095 
0096 void DeviceInfoWatcher::Worker::status(const char *status, const char *details)
0097 {
0098     qCDebug(KLEOPATRA_LOG) << "DeviceInfoWatcher::Worker::status:" << status << details;
0099     if (status && std::strcmp(status, "DEVINFO_STATUS") == 0) {
0100         Q_EMIT statusChanged(QByteArray(details));
0101     }
0102 }
0103 
0104 DeviceInfoWatcher::Private::Private(DeviceInfoWatcher *qq)
0105     : q(qq)
0106 {
0107 }
0108 
0109 DeviceInfoWatcher::Private::~Private()
0110 {
0111     workerThread.quit();
0112     workerThread.wait();
0113 }
0114 
0115 void DeviceInfoWatcher::Private::start()
0116 {
0117     auto worker = new DeviceInfoWatcher::Worker;
0118     worker->moveToThread(&workerThread);
0119     connect(&workerThread, &QThread::started, worker, &DeviceInfoWatcher::Worker::start);
0120     connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
0121     connect(worker, &DeviceInfoWatcher::Worker::statusChanged, q, &DeviceInfoWatcher::statusChanged);
0122     connect(worker, &DeviceInfoWatcher::Worker::startOfGpgAgentRequested, q, &DeviceInfoWatcher::startOfGpgAgentRequested);
0123     workerThread.start();
0124 }
0125 
0126 DeviceInfoWatcher::DeviceInfoWatcher(QObject *parent)
0127     : QObject(parent)
0128     , d(new Private(this))
0129 {
0130 }
0131 
0132 DeviceInfoWatcher::~DeviceInfoWatcher()
0133 {
0134     delete d;
0135 }
0136 
0137 // static
0138 bool DeviceInfoWatcher::isSupported()
0139 {
0140 #ifdef Q_OS_WIN
0141     // "SCD DEVINFO --watch" does not seem to work on Windows; see https://dev.gnupg.org/T5359
0142     return false;
0143 #else
0144     return engineInfo(GpgEngine).engineVersion() >= "2.3.0";
0145 #endif
0146 }
0147 
0148 void DeviceInfoWatcher::start()
0149 {
0150     d->start();
0151 }
0152 
0153 #include "moc_deviceinfowatcher_p.cpp"
0154 
0155 #include "moc_deviceinfowatcher.cpp"