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"