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"