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

0001 /* -*- mode: c++; c-basic-offset:4 -*-
0002     uiserver/assuanserverconnection.cpp
0003 
0004     This file is part of Kleopatra, the KDE keymanager
0005     SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 #ifndef QT_NO_CAST_TO_ASCII
0010 #define QT_NO_CAST_TO_ASCII
0011 #endif
0012 #ifndef QT_NO_CAST_FROM_ASCII
0013 #define QT_NO_CAST_FROM_ASCII
0014 #endif
0015 
0016 #include <config-kleopatra.h>
0017 #include <version-kleopatra.h>
0018 
0019 #include "assuancommand.h"
0020 #include "assuanserverconnection.h"
0021 #include "sessiondata.h"
0022 
0023 #include <utils/detail_p.h>
0024 #include <utils/input.h>
0025 #include <utils/kleo_assert.h>
0026 #include <utils/log.h>
0027 #include <utils/output.h>
0028 
0029 #include <Libkleo/Formatting>
0030 #include <Libkleo/GnuPG>
0031 #include <Libkleo/Hex>
0032 #include <Libkleo/KeyCache>
0033 #include <Libkleo/KleoException>
0034 #include <Libkleo/Stl_Util>
0035 
0036 #include <gpgme++/data.h>
0037 #include <gpgme++/key.h>
0038 
0039 #include <KMime/HeaderParsing>
0040 
0041 #include "kleopatra_debug.h"
0042 #include <KLocalizedString>
0043 #include <KWindowSystem>
0044 
0045 #include <QCoreApplication>
0046 #include <QFileInfo>
0047 #include <QPointer>
0048 #include <QRegularExpression>
0049 #include <QSocketNotifier>
0050 #include <QStringList>
0051 #include <QTimer>
0052 #include <QVariant>
0053 #include <QWidget>
0054 
0055 #include <algorithm>
0056 #include <functional>
0057 #include <map>
0058 #include <type_traits>
0059 
0060 #include <cerrno>
0061 
0062 #ifdef __GLIBCXX__
0063 #include <ext/algorithm> // for is_sorted
0064 #endif
0065 
0066 #ifdef Q_OS_WIN
0067 #include <io.h>
0068 #include <process.h>
0069 #else
0070 #include <sys/types.h>
0071 #include <unistd.h>
0072 #endif
0073 using namespace Kleo;
0074 
0075 static const unsigned int INIT_SOCKET_FLAGS = 3; // says info assuan...
0076 // static int(*USE_DEFAULT_HANDLER)(assuan_context_t,char*) = 0;
0077 static const int FOR_READING = 0;
0078 static const unsigned int MAX_ACTIVE_FDS = 32;
0079 
0080 static void my_assuan_release(assuan_context_t ctx)
0081 {
0082     if (ctx) {
0083         assuan_release(ctx);
0084     }
0085 }
0086 
0087 // std::shared_ptr for assuan_context_t w/ deleter enforced to assuan_deinit_server:
0088 using AssuanContextBase = std::shared_ptr<std::remove_pointer<assuan_context_t>::type>;
0089 struct AssuanContext : AssuanContextBase {
0090     AssuanContext()
0091         : AssuanContextBase()
0092     {
0093     }
0094     explicit AssuanContext(assuan_context_t ctx)
0095         : AssuanContextBase(ctx, &my_assuan_release)
0096     {
0097     }
0098 
0099     void reset(assuan_context_t ctx = nullptr)
0100     {
0101         AssuanContextBase::reset(ctx, &my_assuan_release);
0102     }
0103 };
0104 
0105 static inline gpg_error_t assuan_process_done_msg(assuan_context_t ctx, gpg_error_t err, const char *err_msg)
0106 {
0107     return assuan_process_done(ctx, assuan_set_error(ctx, err, err_msg));
0108 }
0109 
0110 static inline gpg_error_t assuan_process_done_msg(assuan_context_t ctx, gpg_error_t err, const QString &err_msg)
0111 {
0112     return assuan_process_done_msg(ctx, err, err_msg.toUtf8().constData());
0113 }
0114 
0115 static std::map<std::string, std::string> upcase_option(const char *option, std::map<std::string, std::string> options)
0116 {
0117     std::string value;
0118     bool value_found = false;
0119     auto it = options.begin();
0120     while (it != options.end())
0121         if (qstricmp(it->first.c_str(), option) == 0) {
0122             value = it->second;
0123             options.erase(it++);
0124             value_found = true;
0125         } else {
0126             ++it;
0127         }
0128     if (value_found) {
0129         options[option] = value;
0130     }
0131     return options;
0132 }
0133 
0134 static std::map<std::string, std::string> parse_commandline(const char *line)
0135 {
0136     std::map<std::string, std::string> result;
0137     if (line) {
0138         const char *begin = line;
0139         const char *lastEQ = nullptr;
0140         while (*line) {
0141             if (*line == ' ' || *line == '\t') {
0142                 if (begin != line) {
0143                     if (begin[0] == '-' && begin[1] == '-') {
0144                         begin += 2; // skip initial "--"
0145                     }
0146                     if (lastEQ && lastEQ > begin) {
0147                         result[std::string(begin, lastEQ - begin)] = hexdecode(std::string(lastEQ + 1, line - (lastEQ + 1)));
0148                     } else {
0149                         result[std::string(begin, line - begin)] = std::string();
0150                     }
0151                 }
0152                 begin = line + 1;
0153             } else if (*line == '=') {
0154                 if (line == begin)
0155                     throw Exception(gpg_error(GPG_ERR_ASS_SYNTAX), i18n("No option name given"));
0156                 else {
0157                     lastEQ = line;
0158                 }
0159             }
0160             ++line;
0161         }
0162         if (begin != line) {
0163             if (begin[0] == '-' && begin[1] == '-') {
0164                 begin += 2; // skip initial "--"
0165             }
0166             if (lastEQ && lastEQ > begin) {
0167                 result[std::string(begin, lastEQ - begin)] = hexdecode(std::string(lastEQ + 1, line - (lastEQ + 1)));
0168             } else {
0169                 result[begin] = std::string();
0170             }
0171         }
0172     }
0173 
0174     return result;
0175 }
0176 
0177 static WId wid_from_string(const QString &winIdStr, bool *ok = nullptr)
0178 {
0179     return static_cast<WId>(winIdStr.toULongLong(ok, 16));
0180 }
0181 
0182 static void apply_window_id(QWidget *widget, const QString &winIdStr)
0183 {
0184     if (!widget || winIdStr.isEmpty()) {
0185         return;
0186     }
0187     bool ok = false;
0188     const WId wid = wid_from_string(winIdStr, &ok);
0189     if (!ok) {
0190         qCDebug(KLEOPATRA_LOG) << "window-id value" << wid << "doesn't look like a number";
0191         return;
0192     }
0193     if (QWidget *pw = QWidget::find(wid)) {
0194         widget->setParent(pw, widget->windowFlags());
0195     } else {
0196         widget->setAttribute(Qt::WA_NativeWindow, true);
0197         KWindowSystem::setMainWindow(widget->windowHandle(), wid);
0198     }
0199 }
0200 
0201 //
0202 //
0203 // AssuanServerConnection:
0204 //
0205 //
0206 
0207 class AssuanServerConnection::Private : public QObject
0208 {
0209     Q_OBJECT
0210     friend class ::Kleo::AssuanServerConnection;
0211     friend class ::Kleo::AssuanCommandFactory;
0212     friend class ::Kleo::AssuanCommand;
0213     AssuanServerConnection *const q;
0214 
0215 public:
0216     Private(assuan_fd_t fd_, const std::vector<std::shared_ptr<AssuanCommandFactory>> &factories_, AssuanServerConnection *qq);
0217     ~Private() override;
0218 
0219 Q_SIGNALS:
0220     void startKeyManager();
0221 
0222 public Q_SLOTS:
0223     void slotReadActivity(int)
0224     {
0225         Q_ASSERT(ctx);
0226         int done = false;
0227         if (const int err = assuan_process_next(ctx.get(), &done) || done) {
0228             // if ( err == -1 || gpg_err_code(err) == GPG_ERR_EOF ) {
0229             topHalfDeletion();
0230             if (nohupedCommands.empty()) {
0231                 bottomHalfDeletion();
0232             }
0233             //} else {
0234             // assuan_process_done( ctx.get(), err );
0235             // return;
0236             //}
0237         }
0238     }
0239 
0240     int startCommandBottomHalf();
0241 
0242 private:
0243     void nohupDone(AssuanCommand *cmd)
0244     {
0245         const auto it = std::find_if(nohupedCommands.begin(), nohupedCommands.end(), [cmd](const std::shared_ptr<AssuanCommand> &other) {
0246             return other.get() == cmd;
0247         });
0248         Q_ASSERT(it != nohupedCommands.end());
0249         nohupedCommands.erase(it);
0250         if (nohupedCommands.empty() && closed) {
0251             bottomHalfDeletion();
0252         }
0253     }
0254 
0255     void commandDone(AssuanCommand *cmd)
0256     {
0257         if (!cmd || cmd != currentCommand.get()) {
0258             return;
0259         }
0260         currentCommand.reset();
0261     }
0262 
0263     void topHalfDeletion()
0264     {
0265         if (currentCommand) {
0266             currentCommand->canceled();
0267         }
0268         if (fd != ASSUAN_INVALID_FD) {
0269 #if defined(Q_OS_WIN)
0270             CloseHandle(fd);
0271 #else
0272             ::close(fd);
0273 #endif
0274         }
0275         notifiers.clear();
0276         closed = true;
0277     }
0278 
0279     void bottomHalfDeletion()
0280     {
0281         if (sessionId) {
0282             SessionDataHandler::instance()->exitSession(sessionId);
0283         }
0284         cleanup();
0285         const QPointer<Private> that = this;
0286         Q_EMIT q->closed(q);
0287         if (that) { // still there
0288             q->deleteLater();
0289         }
0290     }
0291 
0292 private:
0293     static gpg_error_t reset_handler(assuan_context_t ctx_, char *)
0294     {
0295         Q_ASSERT(assuan_get_pointer(ctx_));
0296 
0297         AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(ctx_));
0298 
0299         conn.reset();
0300 
0301         return 0;
0302     }
0303 
0304     static gpg_error_t option_handler(assuan_context_t ctx_, const char *key, const char *value)
0305     {
0306         Q_ASSERT(assuan_get_pointer(ctx_));
0307 
0308         AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(ctx_));
0309 
0310         if (key && key[0] == '-' && key[1] == '-') {
0311             key += 2; // skip "--"
0312         }
0313         conn.options[key] = QString::fromUtf8(value);
0314 
0315         return 0;
0316         // return gpg_error( GPG_ERR_UNKNOWN_OPTION );
0317     }
0318 
0319     static gpg_error_t session_handler(assuan_context_t ctx_, char *line)
0320     {
0321         Q_ASSERT(assuan_get_pointer(ctx_));
0322         AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(ctx_));
0323 
0324         const QString str = QString::fromUtf8(line);
0325         static const QRegularExpression rx(QRegularExpression::anchoredPattern(uR"((\d+)(?:\s+(.*))?)"));
0326         const QRegularExpressionMatch match = rx.match(str);
0327         if (!match.hasMatch()) {
0328             static const QString errorString = i18n("Parse error");
0329             return assuan_process_done_msg(ctx_, gpg_error(GPG_ERR_ASS_SYNTAX), errorString);
0330         }
0331         bool ok = false;
0332         if (const qulonglong id = match.captured(1).toULongLong(&ok)) {
0333             if (ok && id <= std::numeric_limits<unsigned int>::max()) {
0334                 SessionDataHandler::instance()->enterSession(id);
0335                 conn.sessionId = id;
0336             } else {
0337                 static const QString errorString = i18n("Parse error: numeric session id too large");
0338                 return assuan_process_done_msg(ctx_, gpg_error(GPG_ERR_ASS_SYNTAX), errorString);
0339             }
0340         }
0341 
0342         const QString cap2 = match.captured(2);
0343         if (!cap2.isEmpty()) {
0344             conn.sessionTitle = cap2;
0345         }
0346         qCDebug(KLEOPATRA_LOG) << "session_handler: "
0347                                << "id=" << static_cast<unsigned long>(conn.sessionId) << ", title=" << qPrintable(conn.sessionTitle);
0348         return assuan_process_done(ctx_, 0);
0349     }
0350 
0351     static gpg_error_t capabilities_handler(assuan_context_t ctx_, char *line)
0352     {
0353         if (!QByteArray(line).trimmed().isEmpty()) {
0354             static const QString errorString = i18n("CAPABILITIES does not take arguments");
0355             return assuan_process_done_msg(ctx_, gpg_error(GPG_ERR_ASS_PARAMETER), errorString);
0356         }
0357         static const char capabilities[] =
0358             "SENDER=info\n"
0359             "RECIPIENT=info\n"
0360             "SESSION\n";
0361         return assuan_process_done(ctx_, assuan_send_data(ctx_, capabilities, sizeof capabilities - 1));
0362     }
0363 
0364     static gpg_error_t getinfo_handler(assuan_context_t ctx_, char *line)
0365     {
0366         Q_ASSERT(assuan_get_pointer(ctx_));
0367         AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(ctx_));
0368 
0369         if (qstrcmp(line, "version") == 0) {
0370             static const char version[] = "Kleopatra " KLEOPATRA_VERSION_STRING;
0371             return assuan_process_done(ctx_, assuan_send_data(ctx_, version, sizeof version - 1));
0372         }
0373 
0374         QByteArray ba;
0375         if (qstrcmp(line, "pid") == 0) {
0376             ba = QByteArray::number(QCoreApplication::applicationPid());
0377         } else if (qstrcmp(line, "options") == 0) {
0378             ba = conn.dumpOptions();
0379         } else if (qstrcmp(line, "x-mementos") == 0) {
0380             ba = conn.dumpMementos();
0381         } else if (qstrcmp(line, "senders") == 0) {
0382             ba = conn.dumpSenders();
0383         } else if (qstrcmp(line, "recipients") == 0) {
0384             ba = conn.dumpRecipients();
0385         } else if (qstrcmp(line, "x-files") == 0) {
0386             ba = conn.dumpFiles();
0387         } else {
0388             static const QString errorString = i18n("Unknown value for WHAT");
0389             return assuan_process_done_msg(ctx_, gpg_error(GPG_ERR_ASS_PARAMETER), errorString);
0390         }
0391         return assuan_process_done(ctx_, assuan_send_data(ctx_, ba.constData(), ba.size()));
0392     }
0393 
0394     static gpg_error_t start_keymanager_handler(assuan_context_t ctx_, char *line)
0395     {
0396         Q_ASSERT(assuan_get_pointer(ctx_));
0397         AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(ctx_));
0398 
0399         if (line && *line) {
0400             static const QString errorString = i18n("START_KEYMANAGER does not take arguments");
0401             return assuan_process_done_msg(ctx_, gpg_error(GPG_ERR_ASS_PARAMETER), errorString);
0402         }
0403 
0404         Q_EMIT conn.q->startKeyManagerRequested();
0405 
0406         return assuan_process_done(ctx_, 0);
0407     }
0408 
0409     static gpg_error_t start_confdialog_handler(assuan_context_t ctx_, char *line)
0410     {
0411         Q_ASSERT(assuan_get_pointer(ctx_));
0412         AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(ctx_));
0413 
0414         if (line && *line) {
0415             static const QString errorString = i18n("START_CONFDIALOG does not take arguments");
0416             return assuan_process_done_msg(ctx_, gpg_error(GPG_ERR_ASS_PARAMETER), errorString);
0417         }
0418 
0419         Q_EMIT conn.q->startConfigDialogRequested();
0420 
0421         return assuan_process_done(ctx_, 0);
0422     }
0423 
0424     template<bool in>
0425     struct Input_or_Output : std::conditional<in, Input, Output> {
0426     };
0427 
0428     // format: TAG (FD|FD=\d+|FILE=...)
0429     template<bool in, typename T_memptr>
0430     static gpg_error_t IO_handler(assuan_context_t ctx_, char *line_, T_memptr which)
0431     {
0432         Q_ASSERT(assuan_get_pointer(ctx_));
0433         AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(ctx_));
0434 
0435         char *binOpt = strstr(line_, "--binary");
0436 
0437         if (binOpt && !in) {
0438             /* Note there is also --armor and --base64 allowed but we don't need
0439              * to parse those because they are default.
0440              * We remove it here so that it is not parsed as an Option.*/
0441             memset(binOpt, ' ', 8);
0442         }
0443 
0444         try {
0445             /*const*/ std::map<std::string, std::string> options = upcase_option("FD", upcase_option("FILE", parse_commandline(line_)));
0446             if (options.size() < 1 || options.size() > 2) {
0447                 throw gpg_error(GPG_ERR_ASS_SYNTAX);
0448             }
0449 
0450             std::shared_ptr<typename Input_or_Output<in>::type> io;
0451 
0452             if (options.count("FD")) {
0453                 if (options.count("FILE")) {
0454                     throw gpg_error(GPG_ERR_CONFLICT);
0455                 }
0456 
0457                 assuan_fd_t fd = ASSUAN_INVALID_FD;
0458 
0459                 const std::string fdstr = options["FD"];
0460 
0461                 if (fdstr.empty()) {
0462                     if (const gpg_error_t err = assuan_receivefd(conn.ctx.get(), &fd)) {
0463                         throw err;
0464                     }
0465                 } else {
0466 #if defined(Q_OS_WIN)
0467                     fd = (assuan_fd_t)std::stoi(fdstr);
0468 #else
0469                     fd = std::stoi(fdstr);
0470 #endif
0471                 }
0472 
0473                 io = Input_or_Output<in>::type::createFromPipeDevice(fd, in ? i18n("Message #%1", (conn.*which).size() + 1) : QString());
0474 
0475                 options.erase("FD");
0476 
0477             } else if (options.count("FILE")) {
0478                 if (options.count("FD")) {
0479                     throw gpg_error(GPG_ERR_CONFLICT);
0480                 }
0481 
0482                 const QString filePath = QFile::decodeName(options["FILE"].c_str());
0483                 if (filePath.isEmpty()) {
0484                     throw Exception(gpg_error(GPG_ERR_ASS_SYNTAX), i18n("Empty file path"));
0485                 }
0486                 const QFileInfo fi(filePath);
0487                 if (!fi.isAbsolute()) {
0488                     throw Exception(gpg_error(GPG_ERR_INV_ARG), i18n("Only absolute file paths are allowed"));
0489                 }
0490                 if (!fi.isFile()) {
0491                     throw Exception(gpg_error(GPG_ERR_INV_ARG), i18n("Only files are allowed in INPUT/OUTPUT FILE"));
0492                 } else {
0493                     io = Input_or_Output<in>::type::createFromFile(fi.absoluteFilePath(), true);
0494                 }
0495 
0496                 options.erase("FILE");
0497 
0498             } else {
0499                 throw gpg_error(GPG_ERR_ASS_PARAMETER);
0500             }
0501 
0502             if (options.size()) {
0503                 throw gpg_error(GPG_ERR_UNKNOWN_OPTION);
0504             }
0505 
0506             (conn.*which).push_back(io);
0507 
0508             if (binOpt && !in) {
0509                 auto out = reinterpret_cast<Output *>(io.get());
0510                 out->setBinaryOpt(true);
0511                 qCDebug(KLEOPATRA_LOG) << "Configured output for binary data";
0512             }
0513 
0514             qCDebug(KLEOPATRA_LOG) << "AssuanServerConnection: added" << io->label();
0515 
0516             return assuan_process_done(conn.ctx.get(), 0);
0517         } catch (const GpgME::Exception &e) {
0518             return assuan_process_done_msg(conn.ctx.get(), e.error().encodedError(), e.message().c_str());
0519         } catch (const std::exception &) {
0520             return assuan_process_done(conn.ctx.get(), gpg_error(GPG_ERR_ASS_SYNTAX));
0521         } catch (const gpg_error_t &e) {
0522             return assuan_process_done(conn.ctx.get(), e);
0523         } catch (...) {
0524             return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_UNEXPECTED), "unknown exception caught");
0525         }
0526     }
0527 
0528     static gpg_error_t input_handler(assuan_context_t ctx, char *line)
0529     {
0530         return IO_handler<true>(ctx, line, &Private::inputs);
0531     }
0532 
0533     static gpg_error_t output_handler(assuan_context_t ctx, char *line)
0534     {
0535         return IO_handler<false>(ctx, line, &Private::outputs);
0536     }
0537 
0538     static gpg_error_t message_handler(assuan_context_t ctx, char *line)
0539     {
0540         return IO_handler<true>(ctx, line, &Private::messages);
0541     }
0542 
0543     static gpg_error_t file_handler(assuan_context_t ctx_, char *line)
0544     {
0545         Q_ASSERT(assuan_get_pointer(ctx_));
0546         AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(ctx_));
0547 
0548         try {
0549             const QFileInfo fi(QFile::decodeName(hexdecode(line).c_str()));
0550             if (!fi.isAbsolute()) {
0551                 throw Exception(gpg_error(GPG_ERR_INV_ARG), i18n("Only absolute file paths are allowed"));
0552             }
0553             if (!fi.exists()) {
0554                 throw gpg_error(GPG_ERR_ENOENT);
0555             }
0556             if (!fi.isReadable() || (fi.isDir() && !fi.isExecutable())) {
0557                 throw gpg_error(GPG_ERR_EPERM);
0558             }
0559 
0560             conn.files.push_back(fi.absoluteFilePath());
0561 
0562             return assuan_process_done(conn.ctx.get(), 0);
0563         } catch (const Exception &e) {
0564             return assuan_process_done_msg(conn.ctx.get(), e.error().encodedError(), e.message().toUtf8().constData());
0565         } catch (const gpg_error_t &e) {
0566             return assuan_process_done(conn.ctx.get(), e);
0567         } catch (...) {
0568             return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_UNEXPECTED), i18n("unknown exception caught").toUtf8().constData());
0569         }
0570     }
0571 
0572     static bool parse_informative(const char *&begin, GpgME::Protocol &protocol)
0573     {
0574         protocol = GpgME::UnknownProtocol;
0575         bool informative = false;
0576         const char *pos = begin;
0577         while (true) {
0578             while (*pos == ' ' || *pos == '\t') {
0579                 ++pos;
0580             }
0581             if (qstrnicmp(pos, "--info", strlen("--info")) == 0) {
0582                 informative = true;
0583                 pos += strlen("--info");
0584                 if (*pos == '=') {
0585                     ++pos;
0586                     break;
0587                 }
0588             } else if (qstrnicmp(pos, "--protocol=", strlen("--protocol=")) == 0) {
0589                 pos += strlen("--protocol=");
0590                 if (qstrnicmp(pos, "OpenPGP", strlen("OpenPGP")) == 0) {
0591                     protocol = GpgME::OpenPGP;
0592                     pos += strlen("OpenPGP");
0593                 } else if (qstrnicmp(pos, "CMS", strlen("CMS")) == 0) {
0594                     protocol = GpgME::CMS;
0595                     pos += strlen("CMS");
0596                 } else {
0597                     ;
0598                 }
0599             } else if (qstrncmp(pos, "-- ", strlen("-- ")) == 0) {
0600                 pos += 3;
0601                 while (*pos == ' ' || *pos == '\t') {
0602                     ++pos;
0603                 }
0604                 break;
0605             } else {
0606                 break;
0607             }
0608         }
0609         begin = pos;
0610         return informative;
0611     }
0612 
0613     template<typename T_memptr, typename T_memptr2>
0614     static gpg_error_t recipient_sender_handler(T_memptr mp, T_memptr2 info, assuan_context_t ctx, char *line, bool sender = false)
0615     {
0616         Q_ASSERT(assuan_get_pointer(ctx));
0617         AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(ctx));
0618 
0619         if (!line || !*line) {
0620             return assuan_process_done(conn.ctx.get(), gpg_error(GPG_ERR_INV_ARG));
0621         }
0622         const char *begin = line;
0623         const char *const end = begin + qstrlen(line);
0624         GpgME::Protocol proto = GpgME::UnknownProtocol;
0625         const bool informative = parse_informative(begin, proto);
0626         if (!(conn.*mp).empty() && informative != (conn.*info))
0627             return assuan_process_done_msg(conn.ctx.get(),
0628                                            gpg_error(GPG_ERR_CONFLICT),
0629                                            i18n("Cannot mix --info with non-info SENDER or RECIPIENT").toUtf8().constData());
0630         KMime::Types::Mailbox mb;
0631         if (!KMime::HeaderParsing::parseMailbox(begin, end, mb))
0632             return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_INV_ARG), i18n("Argument is not a valid RFC-2822 mailbox").toUtf8().constData());
0633         if (begin != end)
0634             return assuan_process_done_msg(conn.ctx.get(),
0635                                            gpg_error(GPG_ERR_INV_ARG),
0636                                            i18n("Garbage after valid RFC-2822 mailbox detected").toUtf8().constData());
0637         (conn.*info) = informative;
0638         (conn.*mp).push_back(mb);
0639 
0640         const QString email = mb.addrSpec().asString();
0641         (void)assuan_write_line(conn.ctx.get(), qPrintable(QString::asprintf("# ok, parsed as \"%s\"", qPrintable(email))));
0642         if (sender && !informative) {
0643             return AssuanCommandFactory::_handle(conn.ctx.get(), line, "PREP_SIGN");
0644         } else {
0645             return assuan_process_done(ctx, 0);
0646         }
0647     }
0648 
0649     static gpg_error_t recipient_handler(assuan_context_t ctx, char *line)
0650     {
0651         return recipient_sender_handler(&Private::recipients, &Private::informativeRecipients, ctx, line);
0652     }
0653 
0654     static gpg_error_t sender_handler(assuan_context_t ctx, char *line)
0655     {
0656         return recipient_sender_handler(&Private::senders, &Private::informativeSenders, ctx, line, true);
0657     }
0658 
0659     QByteArray dumpOptions() const
0660     {
0661         QByteArray result;
0662         for (auto it = options.begin(), end = options.end(); it != end; ++it) {
0663             result += it->first.c_str() + it->second.toString().toUtf8() + '\n';
0664         }
0665         return result;
0666     }
0667 
0668     static QByteArray dumpStringList(const QStringList &sl)
0669     {
0670         return sl.join(QLatin1Char('\n')).toUtf8();
0671     }
0672 
0673     template<typename T_container>
0674     static QByteArray dumpStringList(const T_container &c)
0675     {
0676         QStringList sl;
0677         std::copy(c.begin(), c.end(), std::back_inserter(sl));
0678         return dumpStringList(sl);
0679     }
0680 
0681     template<typename T_container>
0682     static QByteArray dumpMailboxes(const T_container &c)
0683     {
0684         QStringList sl;
0685         std::transform(c.begin(), c.end(), std::back_inserter(sl), [](typename T_container::const_reference val) {
0686             return val.prettyAddress();
0687         });
0688         return dumpStringList(sl);
0689     }
0690 
0691     QByteArray dumpSenders() const
0692     {
0693         return dumpMailboxes(senders);
0694     }
0695 
0696     QByteArray dumpRecipients() const
0697     {
0698         return dumpMailboxes(recipients);
0699     }
0700 
0701     QByteArray dumpMementos() const
0702     {
0703         QByteArray result;
0704         for (auto it = mementos.begin(), end = mementos.end(); it != end; ++it) {
0705             char buf[2 + 2 * sizeof(void *) + 2];
0706             sprintf(buf, "0x%p\n", (void *)it->second.get());
0707             buf[sizeof(buf) - 1] = '\0';
0708             result += it->first + QByteArray::fromRawData(buf, sizeof buf);
0709         }
0710         return result;
0711     }
0712 
0713     QByteArray dumpFiles() const
0714     {
0715         QStringList rv;
0716         rv.reserve(files.size());
0717         std::copy(files.cbegin(), files.cend(), std::back_inserter(rv));
0718         return dumpStringList(rv);
0719     }
0720 
0721     void cleanup();
0722     void reset()
0723     {
0724         options.clear();
0725         senders.clear();
0726         informativeSenders = false;
0727         recipients.clear();
0728         informativeRecipients = false;
0729         sessionTitle.clear();
0730         sessionId = 0;
0731         mementos.clear();
0732         files.clear();
0733         std::for_each(inputs.begin(), inputs.end(), std::mem_fn(&Input::finalize));
0734         inputs.clear();
0735         std::for_each(outputs.begin(), outputs.end(), std::mem_fn(&Output::finalize));
0736         outputs.clear();
0737         std::for_each(messages.begin(), messages.end(), std::mem_fn(&Input::finalize));
0738         messages.clear();
0739         bias = GpgME::UnknownProtocol;
0740     }
0741 
0742     assuan_fd_t fd;
0743     AssuanContext ctx;
0744     bool closed : 1;
0745     bool cryptoCommandsEnabled : 1;
0746     bool commandWaitingForCryptoCommandsEnabled : 1;
0747     bool currentCommandIsNohup : 1;
0748     bool informativeSenders; // address taken, so no : 1
0749     bool informativeRecipients; // address taken, so no : 1
0750     GpgME::Protocol bias;
0751     QString sessionTitle;
0752     unsigned int sessionId;
0753     std::vector<std::shared_ptr<QSocketNotifier>> notifiers;
0754     std::vector<std::shared_ptr<AssuanCommandFactory>> factories; // sorted: _detail::ByName<std::less>
0755     std::shared_ptr<AssuanCommand> currentCommand;
0756     std::vector<std::shared_ptr<AssuanCommand>> nohupedCommands;
0757     std::map<std::string, QVariant> options;
0758     std::vector<KMime::Types::Mailbox> senders, recipients;
0759     std::vector<std::shared_ptr<Input>> inputs, messages;
0760     std::vector<std::shared_ptr<Output>> outputs;
0761     std::vector<QString> files;
0762     std::map<QByteArray, std::shared_ptr<AssuanCommand::Memento>> mementos;
0763 };
0764 
0765 void AssuanServerConnection::Private::cleanup()
0766 {
0767     Q_ASSERT(nohupedCommands.empty());
0768     reset();
0769     currentCommand.reset();
0770     currentCommandIsNohup = false;
0771     commandWaitingForCryptoCommandsEnabled = false;
0772     notifiers.clear();
0773     ctx.reset();
0774     fd = ASSUAN_INVALID_FD;
0775 }
0776 
0777 AssuanServerConnection::Private::Private(assuan_fd_t fd_, const std::vector<std::shared_ptr<AssuanCommandFactory>> &factories_, AssuanServerConnection *qq)
0778     : QObject()
0779     , q(qq)
0780     , fd(fd_)
0781     , closed(false)
0782     , cryptoCommandsEnabled(false)
0783     , commandWaitingForCryptoCommandsEnabled(false)
0784     , currentCommandIsNohup(false)
0785     , informativeSenders(false)
0786     , informativeRecipients(false)
0787     , bias(GpgME::UnknownProtocol)
0788     , sessionId(0)
0789     , factories(factories_)
0790 {
0791 #ifdef __GLIBCXX__
0792     Q_ASSERT(__gnu_cxx::is_sorted(factories_.begin(), factories_.end(), _detail::ByName<std::less>()));
0793 #endif
0794 
0795     if (fd == ASSUAN_INVALID_FD) {
0796         throw Exception(gpg_error(GPG_ERR_INV_ARG), "pre-assuan_init_socket_server_ext");
0797     }
0798 
0799     {
0800         assuan_context_t naked_ctx = nullptr;
0801         if (const gpg_error_t err = assuan_new(&naked_ctx)) {
0802             throw Exception(err, "assuan_new");
0803         }
0804         ctx.reset(naked_ctx);
0805     }
0806     if (const gpg_error_t err = assuan_init_socket_server(ctx.get(), fd, INIT_SOCKET_FLAGS))
0807         throw Exception(err, "assuan_init_socket_server_ext");
0808 
0809     // for callbacks, associate the context with this connection:
0810     assuan_set_pointer(ctx.get(), this);
0811 
0812     FILE *const logFile = Log::instance()->logFile();
0813     assuan_set_log_stream(ctx.get(), logFile ? logFile : stderr);
0814 
0815     // register FDs with the event loop:
0816     assuan_fd_t fds[MAX_ACTIVE_FDS];
0817     const int numFDs = assuan_get_active_fds(ctx.get(), FOR_READING, fds, MAX_ACTIVE_FDS);
0818     Q_ASSERT(numFDs != -1); // == 1
0819 
0820     if (!numFDs || fds[0] != fd) {
0821         const std::shared_ptr<QSocketNotifier> sn(new QSocketNotifier((intptr_t)fd, QSocketNotifier::Read), std::mem_fn(&QObject::deleteLater));
0822         connect(sn.get(), &QSocketNotifier::activated, this, &Private::slotReadActivity);
0823         notifiers.push_back(sn);
0824     }
0825 
0826     notifiers.reserve(notifiers.size() + numFDs);
0827     for (int i = 0; i < numFDs; ++i) {
0828         const std::shared_ptr<QSocketNotifier> sn(new QSocketNotifier((intptr_t)fds[i], QSocketNotifier::Read), std::mem_fn(&QObject::deleteLater));
0829         connect(sn.get(), &QSocketNotifier::activated, this, &Private::slotReadActivity);
0830         notifiers.push_back(sn);
0831     }
0832 
0833     // register our INPUT/OUTPUT/MESSGAE/FILE handlers:
0834     if (const gpg_error_t err = assuan_register_command(ctx.get(), "INPUT", input_handler, ""))
0835         throw Exception(err, "register \"INPUT\" handler");
0836     if (const gpg_error_t err = assuan_register_command(ctx.get(), "MESSAGE", message_handler, ""))
0837         throw Exception(err, "register \"MESSAGE\" handler");
0838     if (const gpg_error_t err = assuan_register_command(ctx.get(), "OUTPUT", output_handler, ""))
0839         throw Exception(err, "register \"OUTPUT\" handler");
0840     if (const gpg_error_t err = assuan_register_command(ctx.get(), "FILE", file_handler, ""))
0841         throw Exception(err, "register \"FILE\" handler");
0842 
0843     // register user-defined commands:
0844     for (std::shared_ptr<AssuanCommandFactory> fac : std::as_const(factories))
0845         if (const gpg_error_t err = assuan_register_command(ctx.get(), fac->name(), fac->_handler(), ""))
0846             throw Exception(err, std::string("register \"") + fac->name() + "\" handler");
0847 
0848     if (const gpg_error_t err = assuan_register_command(ctx.get(), "GETINFO", getinfo_handler, ""))
0849         throw Exception(err, "register \"GETINFO\" handler");
0850     if (const gpg_error_t err = assuan_register_command(ctx.get(), "START_KEYMANAGER", start_keymanager_handler, ""))
0851         throw Exception(err, "register \"START_KEYMANAGER\" handler");
0852     if (const gpg_error_t err = assuan_register_command(ctx.get(), "START_CONFDIALOG", start_confdialog_handler, ""))
0853         throw Exception(err, "register \"START_CONFDIALOG\" handler");
0854     if (const gpg_error_t err = assuan_register_command(ctx.get(), "RECIPIENT", recipient_handler, ""))
0855         throw Exception(err, "register \"RECIPIENT\" handler");
0856     if (const gpg_error_t err = assuan_register_command(ctx.get(), "SENDER", sender_handler, ""))
0857         throw Exception(err, "register \"SENDER\" handler");
0858     if (const gpg_error_t err = assuan_register_command(ctx.get(), "SESSION", session_handler, ""))
0859         throw Exception(err, "register \"SESSION\" handler");
0860     if (const gpg_error_t err = assuan_register_command(ctx.get(), "CAPABILITIES", capabilities_handler, ""))
0861         throw Exception(err, "register \"CAPABILITIES\" handler");
0862 
0863     assuan_set_hello_line(ctx.get(), "GPG UI server (Kleopatra/" KLEOPATRA_VERSION_STRING ") ready to serve");
0864     // assuan_set_hello_line( ctx.get(), GPG UI server (qApp->applicationName() + " v" + kapp->applicationVersion() + "ready to serve" )
0865 
0866     // some notifiers we're interested in:
0867     if (const gpg_error_t err = assuan_register_reset_notify(ctx.get(), reset_handler)) {
0868         throw Exception(err, "register reset notify");
0869     }
0870     if (const gpg_error_t err = assuan_register_option_handler(ctx.get(), option_handler)) {
0871         throw Exception(err, "register option handler");
0872     }
0873 
0874     // and last, we need to call assuan_accept, which doesn't block
0875     // (d/t INIT_SOCKET_FLAGS), but performs vital connection
0876     // establishing handling:
0877     if (const gpg_error_t err = assuan_accept(ctx.get())) {
0878         throw Exception(err, "assuan_accept");
0879     }
0880 }
0881 
0882 AssuanServerConnection::Private::~Private()
0883 {
0884     cleanup();
0885 }
0886 
0887 AssuanServerConnection::AssuanServerConnection(assuan_fd_t fd, const std::vector<std::shared_ptr<AssuanCommandFactory>> &factories, QObject *p)
0888     : QObject(p)
0889     , d(new Private(fd, factories, this))
0890 {
0891 }
0892 
0893 AssuanServerConnection::~AssuanServerConnection()
0894 {
0895 }
0896 
0897 void AssuanServerConnection::enableCryptoCommands(bool on)
0898 {
0899     if (on == d->cryptoCommandsEnabled) {
0900         return;
0901     }
0902     d->cryptoCommandsEnabled = on;
0903     if (d->commandWaitingForCryptoCommandsEnabled) {
0904         QTimer::singleShot(0, d.get(), &Private::startCommandBottomHalf);
0905     }
0906 }
0907 
0908 //
0909 //
0910 // AssuanCommand:
0911 //
0912 //
0913 
0914 namespace Kleo
0915 {
0916 
0917 class InquiryHandler : public QObject
0918 {
0919     Q_OBJECT
0920 public:
0921     explicit InquiryHandler(const char *keyword_, QObject *p = nullptr)
0922         : QObject(p)
0923         , keyword(keyword_)
0924     {
0925     }
0926 
0927     static gpg_error_t handler(void *cb_data, gpg_error_t rc, unsigned char *buffer, size_t buflen)
0928     {
0929         Q_ASSERT(cb_data);
0930         auto this_ = static_cast<InquiryHandler *>(cb_data);
0931         Q_EMIT this_->signal(rc, QByteArray::fromRawData(reinterpret_cast<const char *>(buffer), buflen), this_->keyword);
0932         std::free(buffer);
0933         delete this_;
0934         return 0;
0935     }
0936 
0937 private:
0938     const char *keyword;
0939 
0940 Q_SIGNALS:
0941     void signal(int rc, const QByteArray &data, const QByteArray &keyword);
0942 };
0943 
0944 } // namespace Kleo
0945 
0946 class AssuanCommand::Private
0947 {
0948 public:
0949     Private()
0950         : informativeRecipients(false)
0951         , informativeSenders(false)
0952         , bias(GpgME::UnknownProtocol)
0953         , done(false)
0954         , nohup(false)
0955     {
0956     }
0957 
0958     std::map<std::string, QVariant> options;
0959     std::vector<std::shared_ptr<Input>> inputs, messages;
0960     std::vector<std::shared_ptr<Output>> outputs;
0961     std::vector<QString> files;
0962     std::vector<KMime::Types::Mailbox> recipients, senders;
0963     bool informativeRecipients, informativeSenders;
0964     GpgME::Protocol bias;
0965     QString sessionTitle;
0966     unsigned int sessionId;
0967     QByteArray utf8ErrorKeepAlive;
0968     AssuanContext ctx;
0969     bool done;
0970     bool nohup;
0971 };
0972 
0973 AssuanCommand::AssuanCommand()
0974     : d(new Private)
0975 {
0976 }
0977 
0978 AssuanCommand::~AssuanCommand()
0979 {
0980 }
0981 
0982 int AssuanCommand::start()
0983 {
0984     try {
0985         if (const int err = doStart())
0986             if (!d->done) {
0987                 done(err);
0988             }
0989         return 0;
0990     } catch (const Exception &e) {
0991         if (!d->done) {
0992             done(e.error_code(), e.message());
0993         }
0994         return 0;
0995     } catch (const GpgME::Exception &e) {
0996         if (!d->done) {
0997             done(e.error(), QString::fromLocal8Bit(e.message().c_str()));
0998         }
0999         return 0;
1000     } catch (const std::exception &e) {
1001         if (!d->done) {
1002             done(makeError(GPG_ERR_INTERNAL), i18n("Caught unexpected exception: %1", QString::fromLocal8Bit(e.what())));
1003         }
1004         return 0;
1005     } catch (...) {
1006         if (!d->done) {
1007             done(makeError(GPG_ERR_INTERNAL), i18n("Caught unknown exception - please report this error to the developers."));
1008         }
1009         return 0;
1010     }
1011 }
1012 
1013 void AssuanCommand::canceled()
1014 {
1015     d->done = true;
1016     doCanceled();
1017 }
1018 
1019 // static
1020 int AssuanCommand::makeError(int code)
1021 {
1022     return makeGnuPGError(code);
1023 }
1024 
1025 bool AssuanCommand::hasOption(const char *opt) const
1026 {
1027     return d->options.count(opt);
1028 }
1029 
1030 QVariant AssuanCommand::option(const char *opt) const
1031 {
1032     const auto it = d->options.find(opt);
1033     if (it == d->options.end()) {
1034         return QVariant();
1035     } else {
1036         return it->second;
1037     }
1038 }
1039 
1040 const std::map<std::string, QVariant> &AssuanCommand::options() const
1041 {
1042     return d->options;
1043 }
1044 
1045 namespace
1046 {
1047 template<typename U, typename V>
1048 std::vector<U> keys(const std::map<U, V> &map)
1049 {
1050     std::vector<U> result;
1051     result.resize(map.size());
1052     for (typename std::map<U, V>::const_iterator it = map.begin(), end = map.end(); it != end; ++it) {
1053         result.push_back(it->first);
1054     }
1055     return result;
1056 }
1057 }
1058 
1059 const std::map<QByteArray, std::shared_ptr<AssuanCommand::Memento>> &AssuanCommand::mementos() const
1060 {
1061     // oh, hack :(
1062     Q_ASSERT(assuan_get_pointer(d->ctx.get()));
1063     const AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(d->ctx.get()));
1064     return conn.mementos;
1065 }
1066 
1067 bool AssuanCommand::hasMemento(const QByteArray &tag) const
1068 {
1069     if (const unsigned int id = sessionId()) {
1070         return SessionDataHandler::instance()->sessionData(id)->mementos.count(tag) || mementos().count(tag);
1071     } else {
1072         return mementos().count(tag);
1073     }
1074 }
1075 
1076 std::shared_ptr<AssuanCommand::Memento> AssuanCommand::memento(const QByteArray &tag) const
1077 {
1078     if (const unsigned int id = sessionId()) {
1079         const std::shared_ptr<SessionDataHandler> sdh = SessionDataHandler::instance();
1080         const std::shared_ptr<SessionData> sd = sdh->sessionData(id);
1081         const auto it = sd->mementos.find(tag);
1082         if (it != sd->mementos.end()) {
1083             return it->second;
1084         }
1085     }
1086     const auto it = mementos().find(tag);
1087     if (it == mementos().end()) {
1088         return std::shared_ptr<Memento>();
1089     } else {
1090         return it->second;
1091     }
1092 }
1093 
1094 QByteArray AssuanCommand::registerMemento(const std::shared_ptr<Memento> &mem)
1095 {
1096     const QByteArray tag = QByteArray::number(reinterpret_cast<qulonglong>(mem.get()), 36);
1097     return registerMemento(tag, mem);
1098 }
1099 
1100 QByteArray AssuanCommand::registerMemento(const QByteArray &tag, const std::shared_ptr<Memento> &mem)
1101 {
1102     // oh, hack :(
1103     Q_ASSERT(assuan_get_pointer(d->ctx.get()));
1104     AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(d->ctx.get()));
1105 
1106     if (const unsigned int id = sessionId()) {
1107         SessionDataHandler::instance()->sessionData(id)->mementos[tag] = mem;
1108     } else {
1109         conn.mementos[tag] = mem;
1110     }
1111     return tag;
1112 }
1113 
1114 void AssuanCommand::removeMemento(const QByteArray &tag)
1115 {
1116     // oh, hack :(
1117     Q_ASSERT(assuan_get_pointer(d->ctx.get()));
1118     AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(d->ctx.get()));
1119 
1120     conn.mementos.erase(tag);
1121     if (const unsigned int id = sessionId()) {
1122         SessionDataHandler::instance()->sessionData(id)->mementos.erase(tag);
1123     }
1124 }
1125 
1126 const std::vector<std::shared_ptr<Input>> &AssuanCommand::inputs() const
1127 {
1128     return d->inputs;
1129 }
1130 
1131 const std::vector<std::shared_ptr<Input>> &AssuanCommand::messages() const
1132 {
1133     return d->messages;
1134 }
1135 
1136 const std::vector<std::shared_ptr<Output>> &AssuanCommand::outputs() const
1137 {
1138     return d->outputs;
1139 }
1140 
1141 QStringList AssuanCommand::fileNames() const
1142 {
1143     QStringList rv;
1144     rv.reserve(d->files.size());
1145     std::copy(d->files.cbegin(), d->files.cend(), std::back_inserter(rv));
1146     return rv;
1147 }
1148 
1149 unsigned int AssuanCommand::numFiles() const
1150 {
1151     return d->files.size();
1152 }
1153 
1154 void AssuanCommand::sendStatus(const char *keyword, const QString &text)
1155 {
1156     sendStatusEncoded(keyword, text.toUtf8().constData());
1157 }
1158 
1159 void AssuanCommand::sendStatusEncoded(const char *keyword, const std::string &text)
1160 {
1161     if (d->nohup) {
1162         return;
1163     }
1164     if (const int err = assuan_write_status(d->ctx.get(), keyword, text.c_str())) {
1165         throw Exception(err, i18n("Cannot send \"%1\" status", QString::fromLatin1(keyword)));
1166     }
1167 }
1168 
1169 void AssuanCommand::sendData(const QByteArray &data, bool moreToCome)
1170 {
1171     if (d->nohup) {
1172         return;
1173     }
1174     if (const gpg_error_t err = assuan_send_data(d->ctx.get(), data.constData(), data.size())) {
1175         throw Exception(err, i18n("Cannot send data"));
1176     }
1177     if (!moreToCome)
1178         if (const gpg_error_t err = assuan_send_data(d->ctx.get(), nullptr, 0)) { // flush
1179             throw Exception(err, i18n("Cannot flush data"));
1180         }
1181 }
1182 
1183 int AssuanCommand::inquire(const char *keyword, QObject *receiver, const char *slot, unsigned int maxSize)
1184 {
1185     Q_ASSERT(keyword);
1186     Q_ASSERT(receiver);
1187     Q_ASSERT(slot);
1188 
1189     if (d->nohup) {
1190         return makeError(GPG_ERR_INV_OP);
1191     }
1192 
1193     std::unique_ptr<InquiryHandler> ih(new InquiryHandler(keyword, receiver));
1194     receiver->connect(ih.get(), SIGNAL(signal(int, QByteArray, QByteArray)), slot);
1195     if (const gpg_error_t err = assuan_inquire_ext(d->ctx.get(), keyword, maxSize, InquiryHandler::handler, ih.get())) {
1196         return err;
1197     }
1198     ih.release();
1199     return 0;
1200 }
1201 
1202 void AssuanCommand::done(const GpgME::Error &err, const QString &details)
1203 {
1204     if (d->ctx && !d->done && !details.isEmpty()) {
1205         qCDebug(KLEOPATRA_LOG) << "Error: " << details;
1206         d->utf8ErrorKeepAlive = details.toUtf8();
1207         if (!d->nohup) {
1208             assuan_set_error(d->ctx.get(), err.encodedError(), d->utf8ErrorKeepAlive.constData());
1209         }
1210     }
1211     done(err);
1212 }
1213 
1214 void AssuanCommand::done(const GpgME::Error &err)
1215 {
1216     if (!d->ctx) {
1217         qCDebug(KLEOPATRA_LOG) << Formatting::errorAsString(err) << ": called with NULL ctx.";
1218         return;
1219     }
1220     if (d->done) {
1221         qCDebug(KLEOPATRA_LOG) << Formatting::errorAsString(err) << ": called twice!";
1222         return;
1223     }
1224 
1225     d->done = true;
1226 
1227     std::for_each(d->messages.begin(), d->messages.end(), std::mem_fn(&Input::finalize));
1228     std::for_each(d->inputs.begin(), d->inputs.end(), std::mem_fn(&Input::finalize));
1229     std::for_each(d->outputs.begin(), d->outputs.end(), std::mem_fn(&Output::finalize));
1230     d->messages.clear();
1231     d->inputs.clear();
1232     d->outputs.clear();
1233     d->files.clear();
1234 
1235     // oh, hack :(
1236     Q_ASSERT(assuan_get_pointer(d->ctx.get()));
1237     AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(d->ctx.get()));
1238 
1239     if (d->nohup) {
1240         conn.nohupDone(this);
1241         return;
1242     }
1243 
1244     const gpg_error_t rc = assuan_process_done(d->ctx.get(), err.encodedError());
1245     if (gpg_err_code(rc) != GPG_ERR_NO_ERROR)
1246         qFatal("AssuanCommand::done: assuan_process_done returned error %d (%s)", static_cast<int>(rc), gpg_strerror(rc));
1247 
1248     d->utf8ErrorKeepAlive.clear();
1249 
1250     conn.commandDone(this);
1251 }
1252 
1253 void AssuanCommand::setNohup(bool nohup)
1254 {
1255     d->nohup = nohup;
1256 }
1257 
1258 bool AssuanCommand::isNohup() const
1259 {
1260     return d->nohup;
1261 }
1262 
1263 bool AssuanCommand::isDone() const
1264 {
1265     return d->done;
1266 }
1267 
1268 QString AssuanCommand::sessionTitle() const
1269 {
1270     return d->sessionTitle;
1271 }
1272 
1273 unsigned int AssuanCommand::sessionId() const
1274 {
1275     return d->sessionId;
1276 }
1277 
1278 bool AssuanCommand::informativeSenders() const
1279 {
1280     return d->informativeSenders;
1281 }
1282 
1283 bool AssuanCommand::informativeRecipients() const
1284 {
1285     return d->informativeRecipients;
1286 }
1287 
1288 const std::vector<KMime::Types::Mailbox> &AssuanCommand::recipients() const
1289 {
1290     return d->recipients;
1291 }
1292 
1293 const std::vector<KMime::Types::Mailbox> &AssuanCommand::senders() const
1294 {
1295     return d->senders;
1296 }
1297 
1298 gpg_error_t AssuanCommandFactory::_handle(assuan_context_t ctx, char *line, const char *commandName)
1299 {
1300     Q_ASSERT(assuan_get_pointer(ctx));
1301     AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(ctx));
1302 
1303     try {
1304         const auto it = std::lower_bound(conn.factories.begin(), conn.factories.end(), commandName, _detail::ByName<std::less>());
1305         kleo_assert(it != conn.factories.end());
1306         kleo_assert(*it);
1307         kleo_assert(qstricmp((*it)->name(), commandName) == 0);
1308 
1309         const std::shared_ptr<AssuanCommand> cmd = (*it)->create();
1310         kleo_assert(cmd);
1311 
1312         cmd->d->ctx = conn.ctx;
1313         cmd->d->options = conn.options;
1314         cmd->d->inputs.swap(conn.inputs);
1315         kleo_assert(conn.inputs.empty());
1316         cmd->d->messages.swap(conn.messages);
1317         kleo_assert(conn.messages.empty());
1318         cmd->d->outputs.swap(conn.outputs);
1319         kleo_assert(conn.outputs.empty());
1320         cmd->d->files.swap(conn.files);
1321         kleo_assert(conn.files.empty());
1322         cmd->d->senders.swap(conn.senders);
1323         kleo_assert(conn.senders.empty());
1324         cmd->d->recipients.swap(conn.recipients);
1325         kleo_assert(conn.recipients.empty());
1326         cmd->d->informativeRecipients = conn.informativeRecipients;
1327         cmd->d->informativeSenders = conn.informativeSenders;
1328         cmd->d->bias = conn.bias;
1329         cmd->d->sessionTitle = conn.sessionTitle;
1330         cmd->d->sessionId = conn.sessionId;
1331 
1332         const std::map<std::string, std::string> cmdline_options = parse_commandline(line);
1333         for (auto it = cmdline_options.begin(), end = cmdline_options.end(); it != end; ++it) {
1334             cmd->d->options[it->first] = QString::fromUtf8(it->second.c_str());
1335         }
1336 
1337         bool nohup = false;
1338         if (cmd->d->options.count("nohup")) {
1339             if (!cmd->d->options["nohup"].toString().isEmpty()) {
1340                 return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_ASS_PARAMETER), "--nohup takes no argument");
1341             }
1342             nohup = true;
1343             cmd->d->options.erase("nohup");
1344         }
1345 
1346         conn.currentCommand = cmd;
1347         conn.currentCommandIsNohup = nohup;
1348 
1349         QTimer::singleShot(0, &conn, &AssuanServerConnection::Private::startCommandBottomHalf);
1350 
1351         return 0;
1352 
1353     } catch (const Exception &e) {
1354         return assuan_process_done_msg(conn.ctx.get(), e.error_code(), e.message());
1355     } catch (const std::exception &e) {
1356         return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_UNEXPECTED), e.what());
1357     } catch (...) {
1358         return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_UNEXPECTED), i18n("Caught unknown exception"));
1359     }
1360 }
1361 
1362 int AssuanServerConnection::Private::startCommandBottomHalf()
1363 {
1364     commandWaitingForCryptoCommandsEnabled = currentCommand && !cryptoCommandsEnabled;
1365 
1366     if (!cryptoCommandsEnabled) {
1367         return 0;
1368     }
1369 
1370     const std::shared_ptr<AssuanCommand> cmd = currentCommand;
1371     if (!cmd) {
1372         return 0;
1373     }
1374 
1375     currentCommand.reset();
1376 
1377     const bool nohup = currentCommandIsNohup;
1378     currentCommandIsNohup = false;
1379 
1380     try {
1381         if (const int err = cmd->start()) {
1382             if (cmd->isDone()) {
1383                 return err;
1384             } else {
1385                 return assuan_process_done(ctx.get(), err);
1386             }
1387         }
1388 
1389         if (cmd->isDone()) {
1390             return 0;
1391         }
1392 
1393         if (nohup) {
1394             cmd->setNohup(true);
1395             nohupedCommands.push_back(cmd);
1396             return assuan_process_done_msg(ctx.get(), 0, "Command put in the background to continue executing after connection end.");
1397         } else {
1398             currentCommand = cmd;
1399             return 0;
1400         }
1401 
1402     } catch (const Exception &e) {
1403         return assuan_process_done_msg(ctx.get(), e.error_code(), e.message());
1404     } catch (const std::exception &e) {
1405         return assuan_process_done_msg(ctx.get(), gpg_error(GPG_ERR_UNEXPECTED), e.what());
1406     } catch (...) {
1407         return assuan_process_done_msg(ctx.get(), gpg_error(GPG_ERR_UNEXPECTED), i18n("Caught unknown exception"));
1408     }
1409 }
1410 
1411 //
1412 //
1413 // AssuanCommand convenience methods
1414 //
1415 //
1416 
1417 /*!
1418   Checks the \c --mode parameter.
1419 
1420   \returns The parameter as an AssuanCommand::Mode enum value.
1421 
1422   If no \c --mode was given, or it's value wasn't recognized, throws
1423   an Kleo::Exception.
1424 */
1425 AssuanCommand::Mode AssuanCommand::checkMode() const
1426 {
1427     if (!hasOption("mode")) {
1428         throw Exception(makeError(GPG_ERR_MISSING_VALUE), i18n("Required --mode option missing"));
1429     }
1430 
1431     const QString modeString = option("mode").toString().toLower();
1432     if (modeString == QLatin1StringView("filemanager")) {
1433         return FileManager;
1434     }
1435     if (modeString == QLatin1StringView("email")) {
1436         return EMail;
1437     }
1438     throw Exception(makeError(GPG_ERR_INV_ARG), i18n("invalid mode: \"%1\"", modeString));
1439 }
1440 
1441 /*!
1442   Checks the \c --protocol parameter.
1443 
1444   \returns The parameter as a GpgME::Protocol enum value.
1445 
1446   If \c --protocol was given, but has an invalid value, throws an
1447   Kleo::Exception.
1448 
1449   If no \c --protocol was given, checks the connection bias, if
1450   available, otherwise, in FileManager mode, returns
1451   GpgME::UnknownProtocol, but if \a mode == \c EMail, throws an
1452   Kleo::Exception instead.
1453 */
1454 GpgME::Protocol AssuanCommand::checkProtocol(Mode mode, int options) const
1455 {
1456     if (!hasOption("protocol"))
1457         if (d->bias != GpgME::UnknownProtocol) {
1458             return d->bias;
1459         } else if (mode == AssuanCommand::EMail && (options & AllowProtocolMissing) == 0) {
1460             throw Exception(makeError(GPG_ERR_MISSING_VALUE), i18n("Required --protocol option missing"));
1461         } else {
1462             return GpgME::UnknownProtocol;
1463         }
1464     else if (mode == AssuanCommand::FileManager) {
1465         throw Exception(makeError(GPG_ERR_INV_FLAG), i18n("--protocol is not allowed here"));
1466     }
1467 
1468     const QString protocolString = option("protocol").toString().toLower();
1469     if (protocolString == QLatin1StringView("openpgp")) {
1470         return GpgME::OpenPGP;
1471     }
1472     if (protocolString == QLatin1StringView("cms")) {
1473         return GpgME::CMS;
1474     }
1475     throw Exception(makeError(GPG_ERR_INV_ARG), i18n("invalid protocol \"%1\"", protocolString));
1476 }
1477 
1478 void AssuanCommand::doApplyWindowID(QWidget *widget) const
1479 {
1480     if (!widget || !hasOption("window-id")) {
1481         return;
1482     }
1483     apply_window_id(widget, option("window-id").toString());
1484 }
1485 
1486 WId AssuanCommand::parentWId() const
1487 {
1488     return wid_from_string(option("window-id").toString());
1489 }
1490 
1491 #include "assuanserverconnection.moc"
1492 
1493 #include "moc_assuanserverconnection.cpp"