File indexing completed on 2025-01-05 04:55:51

0001 /*
0002     utils/assuan.cpp
0003 
0004     This file is part of libkleopatra
0005     SPDX-FileCopyrightText: 2021, 2022 g10 Code GmbH
0006     SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #include <config-libkleo.h>
0012 
0013 #include "assuan.h"
0014 
0015 #include <libkleo_debug.h>
0016 
0017 #if __has_include(<QGpgME/Debug>)
0018 #include <QGpgME/Debug>
0019 #endif
0020 
0021 #include <QThread>
0022 
0023 #include <gpgme++/context.h>
0024 #include <gpgme++/defaultassuantransaction.h>
0025 #include <gpgme++/error.h>
0026 
0027 using namespace GpgME;
0028 using namespace Kleo;
0029 using namespace Kleo::Assuan;
0030 using namespace std::chrono_literals;
0031 
0032 static const auto initialRetryDelay = 125ms;
0033 static const auto maxRetryDelay = 1000ms;
0034 static const auto maxConnectionAttempts = 10;
0035 
0036 namespace
0037 {
0038 static QDebug operator<<(QDebug s, const std::vector<std::pair<std::string, std::string>> &v)
0039 {
0040     using pair = std::pair<std::string, std::string>;
0041     s << '(';
0042     for (const pair &p : v) {
0043         s << "status(" << QString::fromStdString(p.first) << ") =" << QString::fromStdString(p.second) << '\n';
0044     }
0045     return s << ')';
0046 }
0047 }
0048 
0049 bool Kleo::Assuan::agentIsRunning()
0050 {
0051     Error err;
0052     const std::unique_ptr<Context> ctx = Context::createForEngine(AssuanEngine, &err);
0053     if (err) {
0054         qCWarning(LIBKLEO_LOG) << __func__ << ": Creating context for Assuan engine failed:" << err;
0055         return false;
0056     }
0057     static const char *command = "GETINFO version";
0058     err = ctx->assuanTransact(command);
0059     if (!err) {
0060         // all good
0061     } else if (err.code() == GPG_ERR_ASS_CONNECT_FAILED) {
0062         qCDebug(LIBKLEO_LOG) << __func__ << ": Connecting to the agent failed.";
0063     } else {
0064         qCWarning(LIBKLEO_LOG) << __func__ << ": Starting Assuan transaction for" << command << "failed:" << err;
0065     }
0066     return !err;
0067 }
0068 
0069 std::unique_ptr<GpgME::AssuanTransaction> Kleo::Assuan::sendCommand(std::shared_ptr<GpgME::Context> &context,
0070                                                                     const std::string &command,
0071                                                                     std::unique_ptr<GpgME::AssuanTransaction> transaction,
0072                                                                     GpgME::Error &err)
0073 {
0074     qCDebug(LIBKLEO_LOG) << __func__ << command;
0075     int connectionAttempts = 1;
0076     err = context->assuanTransact(command.c_str(), std::move(transaction));
0077 
0078     auto retryDelay = initialRetryDelay;
0079     while (err.code() == GPG_ERR_ASS_CONNECT_FAILED && connectionAttempts < maxConnectionAttempts) {
0080         // Esp. on Windows the agent processes may take their time so we try
0081         // in increasing waits for them to start up
0082         qCDebug(LIBKLEO_LOG) << "Connecting to the agent failed. Retrying in" << retryDelay.count() << "ms";
0083         QThread::msleep(retryDelay.count());
0084         retryDelay = std::min(retryDelay * 2, maxRetryDelay);
0085         connectionAttempts++;
0086         err = context->assuanTransact(command.c_str(), context->takeLastAssuanTransaction());
0087     }
0088     if (err.code()) {
0089         qCDebug(LIBKLEO_LOG) << __func__ << command << "failed:" << err;
0090         if (err.code() >= GPG_ERR_ASS_GENERAL && err.code() <= GPG_ERR_ASS_UNKNOWN_INQUIRE) {
0091             qCDebug(LIBKLEO_LOG) << "Assuan problem, killing context";
0092             context.reset();
0093         }
0094         return {};
0095     }
0096     return context->takeLastAssuanTransaction();
0097 }
0098 
0099 std::unique_ptr<DefaultAssuanTransaction> Kleo::Assuan::sendCommand(std::shared_ptr<Context> &context, const std::string &command, Error &err)
0100 {
0101     std::unique_ptr<AssuanTransaction> t = sendCommand(context, command, std::make_unique<DefaultAssuanTransaction>(), err);
0102     return std::unique_ptr<DefaultAssuanTransaction>(dynamic_cast<DefaultAssuanTransaction *>(t.release()));
0103 }
0104 
0105 std::string Kleo::Assuan::sendDataCommand(std::shared_ptr<Context> context, const std::string &command, Error &err)
0106 {
0107     std::string data;
0108     const std::unique_ptr<DefaultAssuanTransaction> t = sendCommand(context, command, err);
0109     if (t.get()) {
0110         data = t->data();
0111         qCDebug(LIBKLEO_LOG) << __func__ << command << ": got" << QString::fromStdString(data);
0112     } else {
0113         qCDebug(LIBKLEO_LOG) << __func__ << command << ": t == NULL";
0114     }
0115     return data;
0116 }
0117 
0118 std::vector<std::pair<std::string, std::string>> Kleo::Assuan::sendStatusLinesCommand(std::shared_ptr<Context> context, const std::string &command, Error &err)
0119 {
0120     std::vector<std::pair<std::string, std::string>> statusLines;
0121     const std::unique_ptr<DefaultAssuanTransaction> t = sendCommand(context, command, err);
0122     if (t.get()) {
0123         statusLines = t->statusLines();
0124         qCDebug(LIBKLEO_LOG) << __func__ << command << ": got" << statusLines;
0125     } else {
0126         qCDebug(LIBKLEO_LOG) << __func__ << command << ": t == NULL";
0127     }
0128     return statusLines;
0129 }
0130 
0131 std::string Kleo::Assuan::sendStatusCommand(const std::shared_ptr<Context> &context, const std::string &command, Error &err)
0132 {
0133     const auto lines = sendStatusLinesCommand(context, command, err);
0134     // The status is only the last attribute
0135     // e.g. for SCD SERIALNO it would only be "SERIALNO" and for SCD GETATTR FOO
0136     // it would only be FOO
0137     const auto lastSpace = command.rfind(' ');
0138     const auto needle = lastSpace == std::string::npos ? command : command.substr(lastSpace + 1);
0139     for (const auto &pair : lines) {
0140         if (pair.first == needle) {
0141             return pair.second;
0142         }
0143     }
0144     return {};
0145 }